pax_global_header00006660000000000000000000000064142624633420014520gustar00rootroot0000000000000052 comment=9e8b7c0264f477f31cdf28dadc783532eb2f2f6f gokit-0.25.9/000077500000000000000000000000001426246334200127325ustar00rootroot00000000000000gokit-0.25.9/.github/000077500000000000000000000000001426246334200142725ustar00rootroot00000000000000gokit-0.25.9/.github/workflows/000077500000000000000000000000001426246334200163275ustar00rootroot00000000000000gokit-0.25.9/.github/workflows/gotest.yaml000066400000000000000000000027061426246334200205250ustar00rootroot00000000000000# gotest.yaml # Maintainer: https://www.likexian.com # Licensed under the Apache License 2.0 name: GoTest on: push: branches: - '**' tags-ignore: - '**' pull_request: types: [opened, synchronize, reopened] jobs: lint: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 - name: Golangci lint uses: golangci/golangci-lint-action@v3 with: version: latest args: -v test: strategy: fail-fast: false matrix: go: [1.13.x, 1.14.x, 1.15.x, 1.16.x, 1.17.x, 1.18.x] os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - name: Set up Go uses: actions/setup-go@v2 with: go-version: ${{ matrix.go }} - name: Checkout code uses: actions/checkout@v2 - name: GoTest code run: | go mod download # make go 1.16.x happy sudo go test -race -coverprofile="coverage.txt" -covermode=atomic ./... coverage=$(go tool cover -func=coverage.txt | grep total | grep -Eo '[0-9]+\.[0-9]+') echo "{\"coverage\": $coverage}" if [[ "${{ matrix.go }}" == "1.18.x" ]]; then repository=$(echo '${{ github.repository }}' | awk -F'/' '{print $2}') curl -A "coverage client/1.0.0" -H "X-Release-Token:${{ secrets.RELEASE_TOKEN }}" -F "coverage=$coverage" https://release.likexian.com/$repository/coverage fi gokit-0.25.9/.gitignore000066400000000000000000000000121426246334200147130ustar00rootroot00000000000000.DS_Store gokit-0.25.9/.golangci.yml000066400000000000000000000016231426246334200153200ustar00rootroot00000000000000# .golangci.yml # Maintainer: https://www.likexian.com # Licensed under the Apache License 2.0 run: timeout: 5m skip-dirs-use-default: true linters: disable-all: true enable: - deadcode - errcheck - gosimple - govet - ineffassign - staticcheck - structcheck - typecheck - unused - varcheck - cyclop - durationcheck - errname - errorlint - exportloopref - gofmt - goimports - lll - misspell - nilerr - nolintlint - prealloc - revive - rowserrcheck - sqlclosecheck - stylecheck - unconvert - wastedassign linters-settings: cyclop: max-complexity: 20 package-average: 10.0 skip-tests: true issues: max-issues-per-linter: 0 max-same-issues: 0 exclude-rules: - path: _test\.go linters: - errcheck - source: "^//go:generate " linters: - lll gokit-0.25.9/LICENSE000066400000000000000000000262361426246334200137500ustar00rootroot00000000000000 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 2012-2022 Li Kexian 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. APPENDIX: Copyright 2012-2022 Li Kexian https://www.likexian.com/ gokit-0.25.9/README.md000066400000000000000000000022201426246334200142050ustar00rootroot00000000000000# GoKit [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE) [![GoDoc](https://pkg.go.dev/badge/github.com/likexian/gokit.svg)](https://pkg.go.dev/github.com/likexian/gokit) [![Go Report Card](https://goreportcard.com/badge/github.com/likexian/gokit)](https://goreportcard.com/report/github.com/likexian/gokit) [![Build Status](https://github.com/likexian/gokit/actions/workflows/gotest.yaml/badge.svg)](https://github.com/likexian/gokit/actions/workflows/gotest.yaml) [![Code Cover](https://release.likexian.com/gokit/coverage.svg)](https://github.com/likexian/gokit/actions/workflows/gotest.yaml) A toolkit for Golang development. ## Installation ```shell go get -u github.com/likexian/gokit ``` ## Importing ```go import ( "github.com/likexian/gokit" ) ``` ## Documentation Visit the docs on [GoDoc](https://pkg.go.dev/github.com/likexian/gokit) ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/assert/000077500000000000000000000000001426246334200142335ustar00rootroot00000000000000gokit-0.25.9/assert/README.md000066400000000000000000000031031426246334200155070ustar00rootroot00000000000000# GoKit - assert Assert kits for Golang development. ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/assert" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/assert) ## Example ### assert panic ```go func willItPanic() { panic("failed") } assert.Panic(t, willItPanic) ``` ### assert err is nil ```go fp, err := os.Open("/data/dev/gokit/LICENSE") assert.Nil(t, err) ``` ### assert equal ```go x := map[string]int{"a": 1, "b": 2} y := map[string]int{"a": 1, "b": 2} assert.Equal(t, x, y, "x shall equal to y") ``` ### check string in array ```go ok := assert.IsContains([]string{"a", "b", "c"}, "b") if ok { fmt.Println("value in array") } else { fmt.Println("value not in array") } ``` ### check string in interface array ```go ok := assert.IsContains([]interface{}{0, "1", 2}, "1") if ok { fmt.Println("value in array") } else { fmt.Println("value not in array") } ``` ### check object in struct array ```go ok := assert.IsContains([]A{A{0, 1}, A{1, 2}, A{1, 3}}, A{1, 2}) if ok { fmt.Println("value in array") } else { fmt.Println("value not in array") } ``` ### a := c ? x : y ```go a := 1 // b := a == 1 ? true : false b := assert.If(a == 1, true, false) ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/assert/assert.go000066400000000000000000000123311426246334200160630ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package assert import ( "fmt" "reflect" "runtime" "testing" ) // Version returns package version func Version() string { return "0.12.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // Equal assert test value to be equal func Equal(t *testing.T, got, exp interface{}, args ...interface{}) { equal(t, got, exp, 1, args...) } // NotEqual assert test value to be not equal func NotEqual(t *testing.T, got, exp interface{}, args ...interface{}) { notEqual(t, got, exp, 1, args...) } // Nil assert test value to be nil func Nil(t *testing.T, got interface{}, args ...interface{}) { equal(t, got, nil, 1, args...) } // NotNil assert test value to be not nil func NotNil(t *testing.T, got interface{}, args ...interface{}) { notEqual(t, got, nil, 1, args...) } // True assert test value to be true func True(t *testing.T, got interface{}, args ...interface{}) { equal(t, got, true, 1, args...) } // False assert test value to be false func False(t *testing.T, got interface{}, args ...interface{}) { notEqual(t, got, true, 1, args...) } // Zero assert test value to be zero value func Zero(t *testing.T, got interface{}, args ...interface{}) { equal(t, IsZero(got), true, 1, args...) } // NotZero assert test value to be not zero value func NotZero(t *testing.T, got interface{}, args ...interface{}) { notEqual(t, IsZero(got), true, 1, args...) } // Len assert length of test vaue to be exp func Len(t *testing.T, got interface{}, exp int, args ...interface{}) { equal(t, Length(got), exp, 1, args...) } // NotLen assert length of test vaue to be not exp func NotLen(t *testing.T, got interface{}, exp int, args ...interface{}) { notEqual(t, Length(got), exp, 1, args...) } // Contains assert test value to be contains func Contains(t *testing.T, got, exp interface{}, args ...interface{}) { equal(t, IsContains(got, exp), true, 1, args...) } // NotContains assert test value to be contains func NotContains(t *testing.T, got, exp interface{}, args ...interface{}) { notEqual(t, IsContains(got, exp), true, 1, args...) } // Match assert test value match exp pattern func Match(t *testing.T, got, exp interface{}, args ...interface{}) { equal(t, IsMatch(got, exp), true, 1, args...) } // NotMatch assert test value not match exp pattern func NotMatch(t *testing.T, got, exp interface{}, args ...interface{}) { notEqual(t, IsMatch(got, exp), true, 1, args...) } // Lt assert test value less than exp func Lt(t *testing.T, got, exp interface{}, args ...interface{}) { equal(t, IsLt(got, exp), true, 1, args...) } // Le assert test value less than exp or equal func Le(t *testing.T, got, exp interface{}, args ...interface{}) { equal(t, IsLe(got, exp), true, 1, args...) } // Gt assert test value greater than exp func Gt(t *testing.T, got, exp interface{}, args ...interface{}) { equal(t, IsGt(got, exp), true, 1, args...) } // Ge assert test value greater than exp or equal func Ge(t *testing.T, got, exp interface{}, args ...interface{}) { equal(t, IsGe(got, exp), true, 1, args...) } // Panic assert testing to be panic func Panic(t *testing.T, fn func(), args ...interface{}) { defer func() { ff := func() { t.Error("! -", "assert expected to be panic") if len(args) > 0 { t.Error("! -", fmt.Sprint(args...)) } } ok := recover() != nil assert(t, ok, ff, 2) }() fn() } // NotPanic assert testing to be panic func NotPanic(t *testing.T, fn func(), args ...interface{}) { defer func() { ff := func() { t.Error("! -", "assert expected to be not panic") if len(args) > 0 { t.Error("! -", fmt.Sprint(args...)) } } ok := recover() == nil assert(t, ok, ff, 3) }() fn() } func equal(t *testing.T, got, exp interface{}, step int, args ...interface{}) { fn := func() { switch got.(type) { case error: t.Errorf("! unexpected error: \"%s\"", got) default: t.Errorf("! expected %#v, but got %#v", exp, got) } if len(args) > 0 { t.Error("! -", fmt.Sprint(args...)) } } ok := reflect.DeepEqual(exp, got) assert(t, ok, fn, step+1) } func notEqual(t *testing.T, got, exp interface{}, step int, args ...interface{}) { fn := func() { t.Errorf("! unexpected: %#v", got) if len(args) > 0 { t.Error("! -", fmt.Sprint(args...)) } } ok := !reflect.DeepEqual(exp, got) assert(t, ok, fn, step+1) } func assert(t *testing.T, pass bool, fn func(), step int) { if !pass { _, file, line, ok := runtime.Caller(step + 1) if ok { t.Errorf("%s:%d", file, line) } fn() t.FailNow() } } gokit-0.25.9/assert/assert_test.go000066400000000000000000000107141426246334200171250ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package assert import ( "testing" ) type a struct { x, y int } type b struct { x, y int } func TestVersion(t *testing.T) { Contains(t, Version(), ".") Contains(t, Author(), "likexian") Contains(t, License(), "Apache License") } func TestEqual(t *testing.T) { Equal(t, nil, nil, "testing Equal failed") Equal(t, true, true, "testing Equal failed") Equal(t, "string", "string", "testing Equal failed") Equal(t, int(1.0), int(1.0), "testing Equal failed") Equal(t, int64(1.0), int64(1.0), "testing Equal failed") Equal(t, uint64(1.0), uint64(1.0), "testing Equal failed") Equal(t, float64(1.0), float64(1.0), "testing Equal failed") Equal(t, []string{"a", "b", "c"}, []string{"a", "b", "c"}, "testing Equal failed") Equal(t, []int{1, 2, 3}, []int{1, 2, 3}, "testing Equal failed") Equal(t, []float64{1.0, 2.0, 3.0}, []float64{1.0, 2.0, 3.0}, "testing Equal failed") Equal(t, map[string]int{"a": 1, "b": 2}, map[string]int{"a": 1, "b": 2}, "testing Equal failed") Equal(t, map[string]interface{}{"a": 1, "b": "2"}, map[string]interface{}{"a": 1, "b": "2"}, "testing Equal failed") Equal(t, map[string]interface{}{"a": []int{1, 2}}, map[string]interface{}{"a": []int{1, 2}}, "testing Equal failed") Equal(t, a{1, 2}, a{1, 2}, "testing Equal failed") } func TestNotEqual(t *testing.T) { NotEqual(t, nil, "", "testing NotEqual failed") NotEqual(t, true, false, "testing NotEqual failed") NotEqual(t, "string", "strings", "testing NotEqual failed") NotEqual(t, int(1.0), int(2.0), "testing NotEqual failed") NotEqual(t, int64(1.0), int64(2.0), "testing NotEqual failed") NotEqual(t, uint64(1.0), uint64(2.0), "testing NotEqual failed") NotEqual(t, float64(1.0), float64(2.0), "testing NotEqual failed") NotEqual(t, []string{"a", "b", "c"}, []string{"a", "b", "d"}, "testing NotEqual failed") NotEqual(t, []int{1, 2, 3}, []int{1, 2, 4}, "testing NotEqual failed") NotEqual(t, []float64{1.0, 2.0, 3.0}, []float64{1.0, 2.0, 4.0}, "testing NotEqual failed") NotEqual(t, map[string]int{"a": 1, "b": 2}, map[string]int{"a": 1, "b": 3}, "testing NotEqual failed") NotEqual(t, map[string]interface{}{"a": 1, "b": "2"}, map[string]interface{}{"a": 1, "b": "3"}, "testing NotEqual failed") NotEqual(t, map[string]interface{}{"a": []int{1, 2}}, map[string]interface{}{"a": []int{1, 3}}, "testing NotEqual failed") NotEqual(t, a{1, 1}, a{1, 2}, "testing NotEqual failed") NotEqual(t, a{1, 2}, b{1, 2}, "testing NotEqual failed") } func TestNil(t *testing.T) { Nil(t, nil, "testing expect to be nil") NotNil(t, true, "testing expect to be not nil") } func TestTrue(t *testing.T) { True(t, true, "testing expect to be true") False(t, false, "testing expect to be false") } func TestZero(t *testing.T) { Zero(t, []interface{}{}, "testing expect to be zero") NotZero(t, true, "testing expect to be not zero") } func TestContains(t *testing.T) { Contains(t, []int{1, 2, 3}, 2, "testing expect to be contains") NotContains(t, []string{"a", "b", "c"}, "d", "testing expect to be not contains") } func TestMatch(t *testing.T) { Match(t, "li*", "likexian", "testing expect to be match") NotMatch(t, "li.kexian", "likexian", "testing expect to be not match") } func TestLen(t *testing.T) { Len(t, []int{0, 1, 2}, 3, "length expect to be 3") NotLen(t, []int{0, 1, 2}, 1, "length expect to be not 1") } func TestLtGt(t *testing.T) { Lt(t, 1, 2, "testing expect to be less") Le(t, 1, 2, "testing expect to be less or equal") Le(t, 1, 1, "testing expect to be less or equal") Gt(t, 2, 1, "testing expect to be greater") Ge(t, 2, 1, "testing expect to be greater or equal") Ge(t, 1, 1, "testing expect to be greater or equal") } func TestPanic(t *testing.T) { Panic(t, func() { panic("failed") }) Panic(t, func() { panic("failed") }, "why not panic") } func TestNotPanic(t *testing.T) { NotPanic(t, func() {}) NotPanic(t, func() {}, "why panic") } gokit-0.25.9/assert/values.go000066400000000000000000000201171426246334200160620ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package assert import ( "errors" "fmt" "reflect" "regexp" "strconv" "strings" ) var ( // ErrInvalid is value invalid for operation ErrInvalid = errors.New("assert: value if invalid") // ErrLess is expect to be greater error ErrLess = errors.New("assert: left is less the right") // ErrGreater is expect to be less error ErrGreater = errors.New("assert: left is greater then right") ) // Comparer is compare operator var Comparer = struct { LT string LE string GT string GE string }{ "<", "<=", ">", ">=", } // IsZero returns value is zero value func IsZero(v interface{}) bool { vv := reflect.ValueOf(v) switch vv.Kind() { case reflect.Invalid: return true case reflect.Bool: return !vv.Bool() case reflect.Ptr, reflect.Interface: return vv.IsNil() case reflect.Array, reflect.Slice, reflect.Map, reflect.String: return vv.Len() == 0 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return vv.Int() == 0 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return vv.Uint() == 0 case reflect.Float32, reflect.Float64: return vv.Float() == 0 default: return false } } // IsContains returns whether value is within array func IsContains(array interface{}, value interface{}) bool { vv := reflect.ValueOf(array) if vv.Kind() == reflect.Ptr || vv.Kind() == reflect.Interface { if vv.IsNil() { return false } vv = vv.Elem() } switch vv.Kind() { case reflect.Invalid: return false case reflect.Slice: for i := 0; i < vv.Len(); i++ { if reflect.DeepEqual(value, vv.Index(i).Interface()) { return true } } return false case reflect.Map: s := vv.MapKeys() for i := 0; i < len(s); i++ { if reflect.DeepEqual(value, s[i].Interface()) { return true } } return false case reflect.String: ss := reflect.ValueOf(value) switch ss.Kind() { case reflect.String: return strings.Contains(vv.String(), ss.String()) } return false default: return reflect.DeepEqual(array, value) } } // IsMatch returns if value v contains any match of pattern r // IsMatch(regexp.MustCompile("v\d+"), "v100") // IsMatch("v\d+", "v100") // IsMatch("\d+\.\d+", 100.1) func IsMatch(r interface{}, v interface{}) bool { var re *regexp.Regexp if v, ok := r.(*regexp.Regexp); ok { re = v } else { re = regexp.MustCompile(fmt.Sprint(r)) } return re.MatchString(fmt.Sprint(v)) } // Length returns length of value func Length(v interface{}) int { vv := reflect.ValueOf(v) if vv.Kind() == reflect.Ptr || vv.Kind() == reflect.Interface { if vv.IsNil() { return 0 } vv = vv.Elem() } switch vv.Kind() { case reflect.Invalid: return 0 case reflect.Ptr, reflect.Interface: return 0 case reflect.Array, reflect.Slice, reflect.Map, reflect.String: return vv.Len() default: return len(fmt.Sprintf("%#v", v)) } } // IsLt returns if x less than y, value invalid will returns false func IsLt(x, y interface{}) bool { return Compare(x, y, Comparer.LT) == nil } // IsLe returns if x less than or equal to y, value invalid will returns false func IsLe(x, y interface{}) bool { return Compare(x, y, Comparer.LE) == nil } // IsGt returns if x greater than y, value invalid will returns false func IsGt(x, y interface{}) bool { return Compare(x, y, Comparer.GT) == nil } // IsGe returns if x greater than or equal to y, value invalid will returns false func IsGe(x, y interface{}) bool { return Compare(x, y, Comparer.GE) == nil } // Compare compare x and y, by operation // It returns nil for true, ErrInvalid for invalid operation, err for false // Compare(1, 2, ">") // number compare -> true // Compare("a", "a", ">=") // string compare -> true // Compare([]string{"a", "b"}, []string{"a"}, "<") // slice len compare -> false func Compare(x, y interface{}, op string) error { //nolint:cyclop if !IsContains([]string{Comparer.LT, Comparer.LE, Comparer.GT, Comparer.GE}, op) { return ErrInvalid } vv := reflect.ValueOf(x) if vv.Kind() == reflect.Ptr || vv.Kind() == reflect.Interface { if vv.IsNil() { return ErrInvalid } vv = vv.Elem() } var c float64 switch vv.Kind() { case reflect.Invalid: return ErrInvalid case reflect.String: yy := reflect.ValueOf(y) switch yy.Kind() { case reflect.String: c = float64(strings.Compare(vv.String(), yy.String())) default: return ErrInvalid } case reflect.Slice, reflect.Map, reflect.Array: yy := reflect.ValueOf(y) switch yy.Kind() { case reflect.Slice, reflect.Map, reflect.Array: c = float64(vv.Len() - yy.Len()) default: return ErrInvalid } case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: yy, err := ToInt64(y) if err != nil { return ErrInvalid } c = float64(vv.Int() - yy) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: yy, err := ToUint64(y) if err != nil { return ErrInvalid } c = float64(vv.Uint()) - float64(yy) case reflect.Float32, reflect.Float64: yy, err := ToFloat64(y) if err != nil { return ErrInvalid } c = vv.Float() - yy default: return ErrInvalid } switch { case c < 0: switch op { case Comparer.LT, Comparer.LE: return nil default: return ErrLess } case c > 0: switch op { case Comparer.GT, Comparer.GE: return nil default: return ErrGreater } default: switch op { case Comparer.LT: return ErrGreater case Comparer.GT: return ErrLess default: return nil } } } // ToInt64 returns int value for int or uint or float func ToInt64(v interface{}) (int64, error) { vv := reflect.ValueOf(v) switch vv.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return vv.Int(), nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return int64(vv.Uint()), nil case reflect.Float32, reflect.Float64: return int64(vv.Float()), nil case reflect.String: r, err := strconv.ParseInt(vv.String(), 10, 64) if err != nil { return 0, ErrInvalid } return r, nil default: return 0, ErrInvalid } } // ToUint64 returns uint value for int or uint or float func ToUint64(v interface{}) (uint64, error) { vv := reflect.ValueOf(v) switch vv.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return uint64(vv.Int()), nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return vv.Uint(), nil case reflect.Float32, reflect.Float64: return uint64(vv.Float()), nil case reflect.String: r, err := strconv.ParseUint(vv.String(), 10, 64) if err != nil { return 0, ErrInvalid } return r, nil default: return 0, ErrInvalid } } // ToFloat64 returns float64 value for int or uint or float func ToFloat64(v interface{}) (float64, error) { vv := reflect.ValueOf(v) switch vv.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return float64(vv.Int()), nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return float64(vv.Uint()), nil case reflect.Float32, reflect.Float64: return vv.Float(), nil case reflect.String: r, err := strconv.ParseFloat(vv.String(), 64) if err != nil { return 0, ErrInvalid } return r, nil default: return 0, ErrInvalid } } // If returns x if c is true, else y // z = If(c, x, y) // equal to: // z = c ? x : y func If(c bool, x, y interface{}) interface{} { if c { return x } return y } gokit-0.25.9/assert/values_test.go000066400000000000000000000277221426246334200171320ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package assert import ( "regexp" "testing" ) func TestIsZero(t *testing.T) { var i interface{} tests := []interface{}{ i, "", false, []byte{}, []int{}, []string{}, map[string]int{}, map[string]string{}, map[string]interface{}{}, 0, int(0), int8(0), int32(0), int64(0), uint(0), uint8(0), uint32(0), uint64(0), float32(0), float64(0), } for _, v := range tests { True(t, IsZero(v)) } i = "a" tests = []interface{}{ i, &i, "a", true, []byte{0}, []int{0}, []string{"a"}, map[string]int{"a": 1}, map[string]string{"a": ""}, map[string]interface{}{"a": "b"}, 1, int(1), int8(1), int32(1), int64(1), uint(1), uint8(1), uint32(1), uint64(1), float32(0.1), float64(0.1), struct{ x int }{1}, } for _, v := range tests { False(t, IsZero(v)) } } func TestIsContains(t *testing.T) { var s *string var i interface{} = s tests := []struct { x interface{} y interface{} z bool }{ {nil, nil, false}, {s, s, false}, {&i, &i, true}, {[]int{0, 1, 2}, 1, true}, {[]int{0, 1, 2}, 3, false}, {[]int{0, 1, 2}, int64(1), false}, {[]int{0, 1, 2}, "1", false}, {[]int{0, 1, 2}, true, false}, {[]int64{0, 1, 2}, int64(1), true}, {[]int64{0, 1, 2}, int64(3), false}, {[]int64{0, 1, 2}, 1, false}, {[]int64{0, 1, 2}, "1", false}, {[]int64{0, 1, 2}, true, false}, {[]float64{0.0, 1.0, 2.0}, 1.0, true}, {[]float64{0.0, 1.0, 2.0}, float64(1), true}, {[]float64{0.0, 1.0, 2.0}, 3.0, false}, {[]float64{0.0, 1.0, 2.0}, 1, false}, {[]string{"a", "b", "c"}, "a", true}, {[]string{"a", "b", "c"}, "d", false}, {[]string{"a", "b", "c"}, 1, false}, {[]string{"a", "b", "c"}, true, false}, {[]interface{}{0, "1", 2}, "1", true}, {[]interface{}{0, "1", 2}, 1, false}, {[]interface{}{0, 1, 2}, true, false}, {[]interface{}{0, true, 2}, true, true}, {[]interface{}{0, false, 2}, true, false}, {[]interface{}{[]int{0, 1}, []int{1, 2}}, []int{1, 2}, true}, {[]interface{}{[]int{0, 1}, []int{1, 2, 3}}, []int{1, 2}, false}, {[]a{{0, 1}, {1, 2}, {1, 3}}, a{1, 2}, true}, {[]interface{}{a{0, 1}, b{1, 2}, a{1, 3}}, b{1, 2}, true}, {[]interface{}{a{0, 1}, b{1, 2}, a{1, 3}}, a{1, 2}, false}, {map[string]int{"a": 1}, "a", true}, {map[string]int{"a": 1}, "d", false}, {map[string]int{"a": 1}, 1, false}, {map[string]int{"a": 1}, true, false}, {"abc", "a", true}, {"abc", "d", false}, {"abc", 1, false}, {"abc", true, false}, {"a", "a", true}, {1, 1, true}, {-1, -1, true}, {1.0, 1.0, true}, {true, true, true}, {false, false, true}, } for _, v := range tests { Equal(t, IsContains(v.x, v.y), v.z) } } func TestIsMatch(t *testing.T) { var i interface{} tests := []struct { x interface{} y interface{} z bool }{ {regexp.MustCompile(`v\d+`), "v100", true}, {`v\d+`, "v100", true}, {`\d+\.\d+`, 100.1, true}, {regexp.MustCompile(`v\d+`), "x100", false}, {`v\d+`, "x100", false}, {`\d+\.\d+`, "x100", false}, {`v\d+`, i, false}, {i, 100.1, false}, } for _, v := range tests { vv := IsMatch(v.x, v.y) Equal(t, vv, v.z, v) } } func TestLength(t *testing.T) { var s *string var i interface{} = s tests := []struct { in interface{} out int }{ {nil, 0}, {s, 0}, {&i, 0}, {"", 0}, {"1", 1}, {true, 4}, {false, 5}, {[]byte{}, 0}, {[]byte{0}, 1}, {[]int{}, 0}, {[]int{0}, 1}, {[]string{}, 0}, {[]string{"a"}, 1}, {map[string]int{}, 0}, {map[string]int{"a": 1}, 1}, {map[string]string{}, 0}, {map[string]string{"a": "b"}, 1}, {map[string]interface{}{}, 0}, {map[string]interface{}{"a": i}, 1}, {0, 1}, {1, 1}, {int(0), 1}, {int(1), 1}, {int8(0), 1}, {int8(1), 1}, {int32(0), 1}, {int32(1), 1}, {int64(0), 1}, {int64(1), 1}, {uint(0), 3}, {uint(1), 3}, {uint8(0), 3}, {uint8(1), 3}, {uint32(0), 3}, {uint32(1), 3}, {uint64(0), 3}, {uint64(1), 3}, {float32(0), 1}, {float32(1), 1}, {float32(0.1), 3}, {float64(0), 1}, {float64(1), 1}, {float64(0.1), 3}, } for _, v := range tests { Equal(t, Length(v.in), v.out) } } func TestCompare(t *testing.T) { var s *string var i interface{} = s tests := []struct { x interface{} y interface{} op string err error }{ {nil, nil, "", ErrInvalid}, {nil, nil, Comparer.LT, ErrInvalid}, {s, s, Comparer.LT, ErrInvalid}, {&i, &i, Comparer.LT, ErrInvalid}, {"a", "b", Comparer.LT, nil}, {"b", "a", Comparer.LT, ErrGreater}, {"a", "a", Comparer.LT, ErrGreater}, {"a", 1, Comparer.LT, ErrInvalid}, {"a", "b", Comparer.LE, nil}, {"b", "a", Comparer.LE, ErrGreater}, {"a", "a", Comparer.LE, nil}, {"a", 1, Comparer.LE, ErrInvalid}, {"b", "a", Comparer.GT, nil}, {"a", "b", Comparer.GT, ErrLess}, {"a", "a", Comparer.GT, ErrLess}, {"a", 1, Comparer.GT, ErrInvalid}, {"b", "a", Comparer.GE, nil}, {"a", "b", Comparer.GE, ErrLess}, {"a", "a", Comparer.GE, nil}, {"a", 1, Comparer.GE, ErrInvalid}, {int(1), int(2), Comparer.LT, nil}, {int(2), int(1), Comparer.LT, ErrGreater}, {int(1), int(1), Comparer.LT, ErrGreater}, {int(1), "1", Comparer.LT, ErrGreater}, {int(1), "a", Comparer.LT, ErrInvalid}, {int(1), int(2), Comparer.LE, nil}, {int(2), int(1), Comparer.LE, ErrGreater}, {int(1), int(1), Comparer.LE, nil}, {int(1), "1", Comparer.LE, nil}, {int(1), "a", Comparer.LE, ErrInvalid}, {int(2), int(1), Comparer.GT, nil}, {int(1), int(2), Comparer.GT, ErrLess}, {int(1), int(1), Comparer.GT, ErrLess}, {int(1), "1", Comparer.GT, ErrLess}, {int(1), "a", Comparer.GT, ErrInvalid}, {int(2), int(1), Comparer.GE, nil}, {int(1), int(2), Comparer.GE, ErrLess}, {int(1), int(1), Comparer.GE, nil}, {int(1), "1", Comparer.GE, nil}, {int(1), "a", Comparer.GE, ErrInvalid}, {uint(1), uint(2), Comparer.LT, nil}, {uint(2), uint(1), Comparer.LT, ErrGreater}, {uint(1), uint(1), Comparer.LT, ErrGreater}, {uint(1), "1", Comparer.LT, ErrGreater}, {uint(1), "a", Comparer.LT, ErrInvalid}, {uint(1), uint(2), Comparer.LE, nil}, {uint(2), uint(1), Comparer.LE, ErrGreater}, {uint(1), uint(1), Comparer.LE, nil}, {uint(1), "1", Comparer.LE, nil}, {uint(1), "a", Comparer.LE, ErrInvalid}, {uint(2), uint(1), Comparer.GT, nil}, {uint(1), uint(2), Comparer.GT, ErrLess}, {uint(1), uint(1), Comparer.GT, ErrLess}, {uint(1), "1", Comparer.GT, ErrLess}, {uint(1), "a", Comparer.GT, ErrInvalid}, {uint(2), uint(1), Comparer.GE, nil}, {uint(1), uint(2), Comparer.GE, ErrLess}, {uint(1), uint(1), Comparer.GE, nil}, {uint(1), "1", Comparer.GE, nil}, {uint(1), "a", Comparer.GE, ErrInvalid}, {float64(1), float64(2), Comparer.LT, nil}, {float64(2), float64(1), Comparer.LT, ErrGreater}, {float64(1), float64(1), Comparer.LT, ErrGreater}, {float64(1), "1", Comparer.LT, ErrGreater}, {float64(1), "a", Comparer.LT, ErrInvalid}, {float64(1), float64(2), Comparer.LE, nil}, {float64(2), float64(1), Comparer.LE, ErrGreater}, {float64(1), float64(1), Comparer.LE, nil}, {float64(1), "1", Comparer.LE, nil}, {float64(1), "a", Comparer.LE, ErrInvalid}, {float64(2), float64(1), Comparer.GT, nil}, {float64(1), float64(2), Comparer.GT, ErrLess}, {float64(1), float64(1), Comparer.GT, ErrLess}, {float64(1), "1", Comparer.GT, ErrLess}, {float64(1), "a", Comparer.GT, ErrInvalid}, {float64(2), float64(1), Comparer.GE, nil}, {float64(1), float64(2), Comparer.GE, ErrLess}, {float64(1), float64(1), Comparer.GE, nil}, {float64(1), "1", Comparer.GE, nil}, {float64(1), "a", Comparer.GE, ErrInvalid}, {[]int{1}, []int{1, 2}, Comparer.LT, nil}, {[]int{1, 2}, []int{1}, Comparer.LT, ErrGreater}, {[]int{1}, []int{1}, Comparer.LT, ErrGreater}, {[]int{1}, "1", Comparer.LT, ErrInvalid}, {[]int{1}, []int{1, 2}, Comparer.LE, nil}, {[]int{1, 2}, []int{1}, Comparer.LE, ErrGreater}, {[]int{1}, []int{1}, Comparer.LE, nil}, {[]int{1}, "1", Comparer.LE, ErrInvalid}, {[]int{1, 2}, []int{1}, Comparer.GT, nil}, {[]int{1}, []int{1, 2}, Comparer.GT, ErrLess}, {[]int{1}, []int{1}, Comparer.GT, ErrLess}, {[]int{1}, "1", Comparer.GT, ErrInvalid}, {[]int{1, 2}, []int{1}, Comparer.GE, nil}, {[]int{1}, []int{1, 2}, Comparer.GE, ErrLess}, {[]int{1}, []int{1}, Comparer.GE, nil}, {[]int{1}, "1", Comparer.GE, ErrInvalid}, {map[string]int{"a": 1}, map[string]int{"a": 1, "b": 2}, Comparer.LT, nil}, {map[string]int{"a": 1, "b": 2}, map[string]int{"a": 1}, Comparer.LT, ErrGreater}, {map[string]int{"a": 1}, map[string]int{"a": 1}, Comparer.LT, ErrGreater}, {map[string]int{"a": 1}, "1", Comparer.LT, ErrInvalid}, {map[string]int{"a": 1}, map[string]int{"a": 1, "b": 2}, Comparer.LE, nil}, {map[string]int{"a": 1, "b": 2}, map[string]int{"a": 1}, Comparer.LE, ErrGreater}, {map[string]int{"a": 1}, map[string]int{"a": 1}, Comparer.LE, nil}, {map[string]int{"a": 1}, "1", Comparer.LE, ErrInvalid}, {map[string]int{"a": 1, "b": 2}, map[string]int{"a": 1}, Comparer.GT, nil}, {map[string]int{"a": 1}, map[string]int{"a": 1, "b": 2}, Comparer.GT, ErrLess}, {map[string]int{"a": 1}, map[string]int{"a": 1}, Comparer.GT, ErrLess}, {map[string]int{"a": 1}, "1", Comparer.GT, ErrInvalid}, {map[string]int{"a": 1, "b": 2}, map[string]int{"a": 1}, Comparer.GE, nil}, {map[string]int{"a": 1}, map[string]int{"a": 1, "b": 2}, Comparer.GE, ErrLess}, {map[string]int{"a": 1}, map[string]int{"a": 1}, Comparer.GE, nil}, {map[string]int{"a": 1}, "1", Comparer.GE, ErrInvalid}, } for _, v := range tests { vv := Compare(v.x, v.y, v.op) Equal(t, vv, v.err) if v.op == Comparer.LT { Equal(t, IsLt(v.x, v.y), v.err == nil) } if v.op == Comparer.LE { Equal(t, IsLe(v.x, v.y), v.err == nil) } if v.op == Comparer.GT { Equal(t, IsGt(v.x, v.y), v.err == nil) } if v.op == Comparer.GE { Equal(t, IsGe(v.x, v.y), v.err == nil) } } } func TestToInt64(t *testing.T) { tests := []struct { in interface{} out interface{} err error }{ {int64(1), int64(1), nil}, {uint64(1), int64(1), nil}, {float64(1), int64(1), nil}, {"1", int64(1), nil}, {"1a", int64(0), ErrInvalid}, {"aa", int64(0), ErrInvalid}, {true, int64(0), ErrInvalid}, {[]int{1}, int64(0), ErrInvalid}, {map[string]int{"a": 1}, int64(0), ErrInvalid}, } for _, v := range tests { vv, err := ToInt64(v.in) Equal(t, err, v.err) Equal(t, vv, v.out) } } func TestToUint64(t *testing.T) { tests := []struct { in interface{} out interface{} err error }{ {int64(1), uint64(1), nil}, {uint64(1), uint64(1), nil}, {float64(1), uint64(1), nil}, {"1", uint64(1), nil}, {"1a", uint64(0), ErrInvalid}, {"aa", uint64(0), ErrInvalid}, {true, uint64(0), ErrInvalid}, {[]int{1}, uint64(0), ErrInvalid}, {map[string]int{"a": 1}, uint64(0), ErrInvalid}, } for _, v := range tests { vv, err := ToUint64(v.in) Equal(t, err, v.err) Equal(t, vv, v.out) } } func TestToFloat64(t *testing.T) { tests := []struct { in interface{} out interface{} err error }{ {int64(1), float64(1), nil}, {uint64(1), float64(1), nil}, {float64(1), float64(1), nil}, {"1", float64(1), nil}, {"1a", float64(0), ErrInvalid}, {"aa", float64(0), ErrInvalid}, {true, float64(0), ErrInvalid}, {[]int{1}, float64(0), ErrInvalid}, {map[string]int{"a": 1}, float64(0), ErrInvalid}, } for _, v := range tests { vv, err := ToFloat64(v.in) Equal(t, err, v.err) Equal(t, vv, v.out) } } func TestIf(t *testing.T) { n := 50 z := If(n >= 60, "pass", "fail") Equal(t, z, "fail") n = 80 z = If(n >= 60, "pass", "fail") Equal(t, z, "pass") } gokit-0.25.9/go.mod000066400000000000000000000001141426246334200140340ustar00rootroot00000000000000module github.com/likexian/gokit go 1.18 require golang.org/x/text v0.3.7 gokit-0.25.9/go.sum000066400000000000000000000002311426246334200140610ustar00rootroot00000000000000golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= gokit-0.25.9/xaes/000077500000000000000000000000001426246334200136725ustar00rootroot00000000000000gokit-0.25.9/xaes/README.md000066400000000000000000000015231426246334200151520ustar00rootroot00000000000000# GoKit - xaes AES kits for Golang development. ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/xaes" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xaes) ## Example ```go // Encrypt ciphertext, err := xaes.CBCEncrypt(plaintext, key, iv) if err != nil { panic(err) } fmt.Printf("Encrypted: %x", ciphertext) // Decrypt plaintext, err := CBCDecrypt(ciphertext, key, iv) if err != nil { panic(err) } fmt.Printf("Decrypted: %s", plaintext) ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/xaes/cbc.go000066400000000000000000000120061426246334200147470ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xaes import ( "bytes" "crypto/aes" "crypto/cipher" "crypto/sha256" "errors" "fmt" "github.com/likexian/gokit/xhash" "github.com/likexian/gokit/xrand" ) var ( // ErrMissingEncryptKey is missing encrypt key error ErrMissingEncryptKey = errors.New("xaes: key for encrypting is missing") // ErrMissingDecryptKey is missing decrypt key error ErrMissingDecryptKey = errors.New("xaes: key for decrypting is missing") // ErrInvalidIVSize is invalid IV size error ErrInvalidIVSize = fmt.Errorf("xaes: length of iv must be %d", aes.BlockSize) // ErrInvalidCiphertextSize is invalid ciphertext size error ErrInvalidCiphertextSize = fmt.Errorf("xaes: length of ciphertext must be greater than %d", aes.BlockSize+sha256.Size) // ErrInvalidHmac is invalid hmac error ErrInvalidHmac = fmt.Errorf("xaes: hmac is invalid") // ErrInvalidPKCS7Padding is invalid pkcs7 padding ErrInvalidPKCS7Padding = fmt.Errorf("xaes: invalid PKCS7 padding") ) // CBCEncryptWithHmac do AES CBC encrypt then HMAC func CBCEncryptWithHmac(plaintext, key, iv []byte) ([]byte, error) { if plaintext == nil { return nil, nil } if key == nil { return nil, ErrMissingEncryptKey } if iv == nil { var err error iv, err = xrand.Bytes(aes.BlockSize) if err != nil { return nil, err } } hashKey := xhash.Sha256(key).Bytes() ciphertext, err := CBCEncrypt(plaintext, hashKey[:sha256.Size/2], iv) if err != nil { return nil, err } hmactext := xhash.HmacSha256(string(hashKey[sha256.Size/2:]), iv).Bytes() hmactext = xhash.HmacSha256(string(hmactext), ciphertext).Bytes() ciphertext = append(ciphertext, iv...) ciphertext = append(ciphertext, hmactext...) return ciphertext, nil } // CBCDecryptWithHmac do AES CBC decrypt with HMAC func CBCDecryptWithHmac(ciphertext, key []byte) ([]byte, error) { if ciphertext == nil { return nil, nil } if len(ciphertext) <= aes.BlockSize+sha256.Size { return nil, ErrInvalidCiphertextSize } if key == nil { return nil, ErrMissingDecryptKey } hashKey := xhash.Sha256(key).Bytes() hmactextIn := ciphertext[len(ciphertext)-sha256.Size:] iv := ciphertext[len(ciphertext)-sha256.Size-aes.BlockSize : len(ciphertext)-sha256.Size] ciphertext = ciphertext[:len(ciphertext)-sha256.Size-aes.BlockSize] hmactext := xhash.HmacSha256(string(hashKey[sha256.Size/2:]), iv).Bytes() hmactext = xhash.HmacSha256(string(hmactext), ciphertext).Bytes() if !bytes.Equal(hmactext, hmactextIn) { return nil, ErrInvalidHmac } return CBCDecrypt(ciphertext, hashKey[:sha256.Size/2], iv) } // CBCEncrypt do AES CBC encrypt func CBCEncrypt(plaintext, key, iv []byte) ([]byte, error) { if plaintext == nil { return nil, nil } if key == nil { return nil, ErrMissingEncryptKey } if iv == nil { iv = make([]byte, aes.BlockSize) } if len(iv) != aes.BlockSize { return nil, ErrInvalidIVSize } block, err := aes.NewCipher(key) if err != nil { return nil, err } blockSize := block.BlockSize() plaintext, err = PKCS7Padding(plaintext, blockSize) if err != nil { return nil, err } ciphertext := make([]byte, len(plaintext)) blockMode := cipher.NewCBCEncrypter(block, iv) blockMode.CryptBlocks(ciphertext, plaintext) return ciphertext, nil } // CBCDecrypt do AES CBC decrypt func CBCDecrypt(ciphertext, key, iv []byte) ([]byte, error) { if ciphertext == nil { return nil, nil } if key == nil { return nil, ErrMissingDecryptKey } if iv == nil { iv = make([]byte, aes.BlockSize) } if len(iv) != aes.BlockSize { return nil, ErrInvalidIVSize } block, err := aes.NewCipher(key) if err != nil { return nil, err } plaintext := make([]byte, len(ciphertext)) blockMode := cipher.NewCBCDecrypter(block, iv) blockMode.CryptBlocks(plaintext, ciphertext) plaintext, err = PKCS7Unpadding(plaintext) if err != nil { return nil, err } return plaintext, nil } // PKCS7Padding do PKCS7 padding func PKCS7Padding(data []byte, blockSize int) ([]byte, error) { if len(data) == 0 { return nil, ErrInvalidPKCS7Padding } paddingSize := blockSize - len(data)%blockSize paddingText := bytes.Repeat([]byte{byte(paddingSize)}, paddingSize) return append(data, paddingText...), nil } // PKCS7Unpadding do PKCS7 unpadding func PKCS7Unpadding(data []byte) ([]byte, error) { dataSize := len(data) paddingSize := int(data[dataSize-1]) if dataSize < paddingSize { return nil, ErrInvalidPKCS7Padding } return data[:(dataSize - paddingSize)], nil } gokit-0.25.9/xaes/cbc_test.go000066400000000000000000000104041426246334200160060ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xaes import ( "crypto/aes" "strconv" "testing" "github.com/likexian/gokit/assert" ) var ( cbcAESKey = []byte("1234567812345678") cbcPlaintext = []byte("hello xaes!") cbcCiphertext = []byte{32, 73, 238, 61, 249, 194, 179, 122, 136, 105, 227, 59, 55, 89, 10, 97} cbcHmacCiphertext = []byte{56, 91, 201, 6, 75, 116, 161, 43, 218, 129, 203, 149, 23, 197, 144, 175, 49, 50, 51, 52, 53, 54, 55, 56, 49, 50, 51, 52, 53, 54, 55, 56, 183, 182, 120, 158, 253, 33, 84, 16, 240, 33, 44, 163, 38, 26, 103, 57, 96, 131, 128, 47, 251, 7, 180, 234, 107, 134, 0, 126, 1, 1, 227, 15, } ) func TestCBCEncrypt(t *testing.T) { ciphertext, err := CBCEncrypt(nil, cbcAESKey, nil) assert.Nil(t, err) assert.Equal(t, ciphertext, []byte(nil)) _, err = CBCEncrypt(cbcPlaintext, nil, nil) assert.NotNil(t, err) _, err = CBCEncrypt(cbcPlaintext, cbcAESKey[:1], nil) assert.NotNil(t, err) _, err = CBCEncrypt(cbcPlaintext, cbcAESKey, cbcAESKey[:1]) assert.NotNil(t, err) ciphertext, err = CBCEncrypt(cbcPlaintext, cbcAESKey, nil) assert.Nil(t, err) assert.Equal(t, ciphertext, cbcCiphertext) } func TestCBCDecrypt(t *testing.T) { plaintext, err := CBCDecrypt(nil, cbcAESKey, nil) assert.Nil(t, err) assert.Equal(t, plaintext, []byte(nil)) _, err = CBCDecrypt(cbcCiphertext, nil, nil) assert.NotNil(t, err) _, err = CBCDecrypt(cbcCiphertext, cbcAESKey[:1], nil) assert.NotNil(t, err) _, err = CBCDecrypt(cbcCiphertext, cbcAESKey, cbcAESKey[:1]) assert.NotNil(t, err) _, err = CBCDecrypt(cbcCiphertext, []byte("1234567812345677"), nil) assert.NotNil(t, err) plaintext, err = CBCDecrypt(cbcCiphertext, cbcAESKey, nil) assert.Nil(t, err) assert.Equal(t, plaintext, cbcPlaintext) } func TestCBC(t *testing.T) { data := "" for i := 0; i < 1000; i++ { data += strconv.Itoa(i) ciphertext, err := CBCEncrypt([]byte(data), cbcAESKey, nil) assert.Nil(t, err) plaintext, err := CBCDecrypt(ciphertext, cbcAESKey, nil) assert.Nil(t, err) assert.Equal(t, plaintext, []byte(data)) } } func TestCBCEncryptWithHmacWithHmac(t *testing.T) { ciphertext, err := CBCEncryptWithHmac(nil, cbcAESKey, nil) assert.Nil(t, err) assert.Equal(t, ciphertext, []byte(nil)) _, err = CBCEncryptWithHmac(cbcPlaintext, nil, nil) assert.NotNil(t, err) _, err = CBCEncryptWithHmac(cbcPlaintext, cbcAESKey[:1], nil) assert.Nil(t, err) _, err = CBCEncryptWithHmac(cbcPlaintext, cbcAESKey, cbcAESKey[:1]) assert.NotNil(t, err) ciphertext, err = CBCEncryptWithHmac(cbcPlaintext, cbcAESKey, cbcAESKey) assert.Nil(t, err) assert.Equal(t, ciphertext, cbcHmacCiphertext) } func TestCBCDecryptWithHmac(t *testing.T) { plaintext, err := CBCDecryptWithHmac(nil, cbcAESKey) assert.Nil(t, err) assert.Equal(t, plaintext, []byte(nil)) _, err = CBCDecryptWithHmac(cbcHmacCiphertext, nil) assert.NotNil(t, err) _, err = CBCDecryptWithHmac(cbcHmacCiphertext, cbcAESKey[:1]) assert.NotNil(t, err) _, err = CBCDecryptWithHmac(cbcHmacCiphertext[:1], cbcAESKey) assert.NotNil(t, err) _, err = CBCDecryptWithHmac(append(cbcHmacCiphertext, '1'), cbcAESKey) assert.NotNil(t, err) plaintext, err = CBCDecryptWithHmac(cbcHmacCiphertext, cbcAESKey) assert.Nil(t, err) assert.Equal(t, plaintext, cbcPlaintext) } func TestCBCWithHmac(t *testing.T) { data := "" for i := 0; i < 1000; i++ { data += strconv.Itoa(i) ciphertext, err := CBCEncryptWithHmac([]byte(data), cbcAESKey, nil) assert.Nil(t, err) plaintext, err := CBCDecryptWithHmac(ciphertext, cbcAESKey) assert.Nil(t, err) assert.Equal(t, plaintext, []byte(data)) } } func TestPKCS7Padding(t *testing.T) { _, err := PKCS7Padding(nil, aes.BlockSize) assert.NotNil(t, err) } gokit-0.25.9/xaes/xaes.go000066400000000000000000000017201426246334200151610ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xaes // Version returns package version func Version() string { return "0.3.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } gokit-0.25.9/xaes/xaes_test.go000066400000000000000000000016071426246334200162240ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xaes import ( "testing" "github.com/likexian/gokit/assert" ) func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } gokit-0.25.9/xcache/000077500000000000000000000000001426246334200141655ustar00rootroot00000000000000gokit-0.25.9/xcache/README.md000066400000000000000000000020371426246334200154460ustar00rootroot00000000000000# GoKit - xcache Cache kits for Golang development. ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/xcache" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xcache) ## Example ### Use memory cache ```go // init memory cache c := xcache.New(xcache.MemoryCache) // set gc param, gc every 60s, once clean max 100 c.SetGC(60, 100) // set key value cache with no expire c.Set("key", "value", 0) // set key value cache with ttl, expire after 30s c.Set("key", "value", 30) // check key exists c.Has("key") // get value c.Get("key") // remove key c.Del("key") // get multiple once c.MGet("k1", "k2", "k3") // do not forget stop the service c.Close() ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/xcache/memory/000077500000000000000000000000001426246334200154755ustar00rootroot00000000000000gokit-0.25.9/xcache/memory/memory.go000066400000000000000000000115341426246334200173400ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package memory import ( "errors" "sync" "time" ) var ( // ErrKeyNotExists is key not exists error ErrKeyNotExists = errors.New("xcache: the key is not exists") // ErrDataTypeNotSupported is data type not supported error ErrDataTypeNotSupported = errors.New("xcache: data type is not supported") // ErrValueLessThanZero is value less than zero error ErrValueLessThanZero = errors.New("xcache: object value is less than zero") ) // Object is storing single object type Object struct { value interface{} expire int64 } // Objects is storing all object type Objects struct { values map[string]*Object gcInterval int gcMaxOnce int gcExit chan int sync.RWMutex } // Version returns package version func Version() string { return "0.2.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // New init a new cache func New() *Objects { o := &Objects{ values: map[string]*Object{}, gcInterval: 60, gcMaxOnce: 100, gcExit: make(chan int), } go o.gc() return o } // Set set key value to cache func (o *Objects) Set(key string, val interface{}, ttl int64) error { if ttl > 0 { ttl = time.Now().Add(time.Duration(ttl) * time.Second).Unix() } o.Lock() defer o.Unlock() o.values[key] = &Object{val, ttl} return nil } // Get get value from cache func (o *Objects) Get(key string) interface{} { o.RLock() defer o.RUnlock() v, ok := o.values[key] if !ok { return nil } if v.expired() { return nil } return v.value } // MGet get multiple value from cache func (o *Objects) MGet(key ...string) []interface{} { r := []interface{}{} for _, k := range key { r = append(r, o.Get(k)) } return r } // Has returns key is exists func (o *Objects) Has(key string) bool { o.RLock() defer o.RUnlock() v, ok := o.values[key] if !ok { return false } return !v.expired() } // Del remove key from cache func (o *Objects) Del(key string) error { o.Lock() defer o.Unlock() delete(o.values, key) return nil } // Incr increase cache counter func (o *Objects) Incr(key string) error { o.Lock() defer o.Unlock() v, ok := o.values[key] if !ok { return ErrKeyNotExists } switch vv := v.value.(type) { case int: v.value = vv + 1 case int32: v.value = vv + 1 case int64: v.value = vv + 1 case uint: v.value = vv + 1 case uint32: v.value = vv + 1 case uint64: v.value = vv + 1 default: return ErrDataTypeNotSupported } return nil } // Decr decrease cache counter func (o *Objects) Decr(key string) error { o.Lock() defer o.Unlock() v, ok := o.values[key] if !ok { return ErrKeyNotExists } switch vv := v.value.(type) { case int: v.value = vv - 1 case int32: v.value = vv - 1 case int64: v.value = vv - 1 case uint: if vv <= 0 { return ErrValueLessThanZero } v.value = vv - 1 case uint32: if vv <= 0 { return ErrValueLessThanZero } v.value = vv - 1 case uint64: if vv <= 0 { return ErrValueLessThanZero } v.value = vv - 1 default: return ErrDataTypeNotSupported } return nil } // Flush empty the cache func (o *Objects) Flush() error { o.Lock() defer o.Unlock() o.values = map[string]*Object{} return nil } // Close stop the cache service func (o *Objects) Close() error { o.gcExit <- 1 o.Flush() return nil } // SetGC set gc interval and max once func (o *Objects) SetGC(gcInterval, gcMaxOnce int) { o.Lock() o.gcInterval = gcInterval o.gcMaxOnce = gcMaxOnce o.Unlock() o.gcExit <- 1 go o.gc() } // gc do gc check func (o *Objects) gc() { o.RLock() gcInterval := o.gcInterval o.RUnlock() t := time.NewTicker(time.Duration(gcInterval) * time.Second) for { select { case <-o.gcExit: t.Stop() return case <-t.C: o.RLock() e := []string{} for k, v := range o.values { if v.expired() { e = append(e, k) if len(e) >= o.gcMaxOnce { break } } } o.RUnlock() o.Lock() for _, k := range e { delete(o.values, k) } o.Unlock() } } } // expired returns object is expired func (b *Object) expired() bool { if b.expire <= 0 { return false } return time.Now().Unix() >= b.expire } gokit-0.25.9/xcache/memory/memory_test.go000066400000000000000000000062331426246334200203770ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package memory import ( "fmt" "testing" "time" "github.com/likexian/gokit/assert" ) func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } func TestBase(t *testing.T) { c := New() defer c.Close() // has nx := c.Has("x") assert.False(t, nx) // set err := c.Set("x", 1, -1) assert.Nil(t, err) // check set nx = c.Has("x") assert.True(t, nx) // get v := c.Get("x") assert.Equal(t, v, 1) // del err = c.Del("x") assert.Nil(t, err) // check del nx = c.Has("x") assert.False(t, nx) v = c.Get("x") assert.Equal(t, v, nil) for i := 0; i < 10; i++ { k := fmt.Sprintf("%d", i) err = c.Set(k, i, 0) assert.Nil(t, err) assert.True(t, c.Has(k)) } // get multiple key vs := c.MGet("1", "2", "3") assert.Len(t, vs, 3) assert.Equal(t, vs[0], 1) assert.Equal(t, vs[1], 2) assert.Equal(t, vs[2], 3) // flush cache c.Flush() v = c.Get("1") assert.Equal(t, v, nil) // get on expired key err = c.Set("xx", 1, 1) assert.Nil(t, err) time.Sleep(1 * time.Second) v = c.Get("xx") assert.Equal(t, v, nil) } func TestGC(t *testing.T) { c := New() defer c.Close() c.SetGC(1, 1) err := c.Set("x", 1, 1) assert.Nil(t, err) nx := c.Has("x") assert.True(t, nx) time.Sleep(1 * time.Second) nx = c.Has("x") assert.False(t, nx) } func TestIncr(t *testing.T) { c := New() defer c.Close() tests := []struct { in interface{} out interface{} }{ {int(0), int(1)}, {int32(0), int32(1)}, {int64(0), int64(1)}, {uint(0), uint(1)}, {uint32(0), uint32(1)}, {uint64(0), uint64(1)}, } for _, v := range tests { _ = c.Set("k", v.in, 0) _ = c.Incr("k") assert.Equal(t, c.Get("k"), v.out) } err := c.Incr("x") assert.NotNil(t, err) err = c.Set("x", "o", 0) assert.Nil(t, err) err = c.Incr("x") assert.NotNil(t, err) } func TestDecr(t *testing.T) { c := New() defer c.Close() tests := []struct { in interface{} out interface{} }{ {int(1), int(0)}, {int32(1), int32(0)}, {int64(1), int64(0)}, {uint(1), uint(0)}, {uint32(1), uint32(0)}, {uint64(1), uint64(0)}, } for _, v := range tests { _ = c.Set("k", v.in, 0) _ = c.Decr("k") assert.Equal(t, c.Get("k"), v.out) } err := c.Decr("x") assert.NotNil(t, err) err = c.Set("x", "o", 0) assert.Nil(t, err) err = c.Decr("x") assert.NotNil(t, err) for _, v := range []interface{}{ uint(0), uint32(0), uint64(0), } { _ = c.Set("k", v, 0) err = c.Decr("k") assert.NotNil(t, err) } } gokit-0.25.9/xcache/xcache.go000066400000000000000000000027601426246334200157540ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xcache import ( "github.com/likexian/gokit/xcache/memory" ) // Cacher list const ( MemoryCache = iota ) // Cachex is cache interface type Cachex interface { Get(key string) interface{} MGet(key ...string) []interface{} Set(key string, val interface{}, ttl int64) error Has(key string) bool Del(key string) error Incr(key string) error Decr(key string) error SetGC(gcInterval, gcMaxOnce int) Flush() error Close() error } // Version returns package version func Version() string { return "0.2.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // New returns a new cacher func New(cacher int) Cachex { switch cacher { default: return memory.New() } } gokit-0.25.9/xcache/xcache_test.go000066400000000000000000000024221426246334200170060ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xcache import ( "testing" "github.com/likexian/gokit/assert" ) func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } func TestNew(t *testing.T) { c := New(MemoryCache) defer c.Close() b := c.Has("x") assert.False(t, b) v := c.Get("x") assert.Equal(t, v, nil) err := c.Set("x", 1, 0) assert.Nil(t, err) b = c.Has("x") assert.True(t, b) v = c.Get("x") assert.Equal(t, v, 1) err = c.Del("x") assert.Nil(t, err) b = c.Has("x") assert.False(t, b) v = c.Get("x") assert.Equal(t, v, nil) } gokit-0.25.9/xcron/000077500000000000000000000000001426246334200140635ustar00rootroot00000000000000gokit-0.25.9/xcron/README.md000066400000000000000000000054431426246334200153500ustar00rootroot00000000000000# GoKit - xcron Cron kits for Golang development. ## Features - Thread safe and Easy to use - Fractional precision to Seconds - Compatible with Standard cron expression - Support Nonstandard macros definitions - Extense Support @every N duration - Dynamic addã€updateã€removeã€empty cron job ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/xcron" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xcron) ## Field of rule ``` Field name | Mandatory | Allowed values | Allowed special characters ------------ | ---------- | --------------- | -------------------------- Seconds | No | 0-59 | * / , - Minutes | Yes | 0-59 | * / , - Hours | Yes | 0-23 | * / , - Day of month | Yes | 1-31 | * / , - Month | Yes | 1–12 or JAN–DEC | * / , - Day of week | Yes | 0–6 or SUN–SAT | * / , - ``` ## Predefined rule ``` Entry | Description | Equivalent To ---------------------- | ------------------------------------------ | ------------- @yearly (or @annually) | Run once a year, midnight, Jan. 1st | 0 0 0 1 1 * @monthly | Run once a month, midnight, first of month | 0 0 0 1 * * @weekly | Run once a week, midnight between Sat/Sun | 0 0 0 * * 0 @daily (or @midnight) | Run once a day, midnight | 0 0 0 * * * @hourly | Run once an hour, beginning of hour | 0 0 * * * * ``` ## Example ### Cron service ```go // start a cron service service := xcron.New() // add a job to service, specify the rule and loop func id, err := service.Add("@every second", func(){fmt.Println("add a echo")}) // update exists job by job id err = service.Set(id, "@every second", func(){fmt.Println("set a echo")}) // delete exists job from service, job will stop service.Del(id) // clear all jobs, jobs will stop service.Empty() // wait for all job exit service.Wait() ``` ### Parse cron rule ```go // standard cron rule, every hour rule, err := xcron.Parse("0 * * * *") // standard cron rule, every half hour rule, err := xcron.Parse("0,30 * * * *") // standard cron rule, every half hour rule, err := xcron.Parse("0/30 * * * *") // every second, six fields rule, err := xcron.Parse("* * * * * *") // every hour rule, err := xcron.Parse("@every hour") // every 6 hour rule, err := xcron.Parse("@every 6 hour") ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/xcron/xcron.go000066400000000000000000000234731426246334200155540ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xcron import ( "context" "fmt" "strconv" "strings" "sync" "time" "github.com/likexian/gokit/assert" "github.com/likexian/gokit/xhash" "github.com/likexian/gokit/xtime" ) // Field type of rule const ( Second = iota Minute Hour DayOfMonth Month DayOfWeek ) var ( // MonthsMap is month string to int map MonthsMap = map[string]int{ "jan": 1, "feb": 2, "mar": 3, "apr": 4, "may": 5, "jun": 6, "jul": 7, "aug": 8, "sep": 9, "oct": 10, "nov": 11, "dec": 12, } // DayOfWeekMap is day of week string to int map DayOfWeekMap = map[string]int{ "sun": 0, "mon": 1, "tue": 2, "wed": 3, "thu": 4, "fri": 5, "sat": 6, } ) // Rule is parsed cron rule type Rule struct { Second []int Minute []int Hour []int DayOfMonth []int Month []int DayOfWeek []int } // Job is a cron job type Job struct { rule string loop func() tidy func() stop chan bool } // Service is cron service type Service struct { jobs map[string]Job ctx context.Context cancel context.CancelFunc wg *sync.WaitGroup sync.RWMutex } // Version returns package version func Version() string { return "0.7.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // MustParse do parse and returns rule, panic if error func MustParse(s string) Rule { r, err := Parse(s) if err != nil { panic(err) } return r } // Parse parse single cron rule // Base on https://en.wikipedia.org/wiki/Cron and extensed // Fields: second minute hour dayOfMonth month dayOfWeek // * * * * * * func Parse(s string) (r Rule, err error) { r = Rule{ []int{}, []int{}, []int{}, []int{}, []int{}, []int{}, } s = strings.TrimSpace(s) if s == "" || s == "*" { return } s = strings.ToLower(s) if s[0] == '@' { s, err = parseMacros(s) if err != nil { return } } fs := strings.Fields(s) if len(fs) < 6 { fs = append([]string{"0"}, fs...) } if len(fs) != 6 { return r, fmt.Errorf("xcron: unrecognized rule: %s", s) } for i := 0; i < len(fs); i++ { err := r.parseField(fs[i], i) if err != nil { return r, err } } return } // New returns new cron service func New() *Service { ctx, cancel := context.WithCancel(context.Background()) return &Service{ jobs: map[string]Job{}, ctx: ctx, cancel: cancel, wg: &sync.WaitGroup{}, } } // Add add new cron job to service func (s *Service) Add(rule string, loop func(), tidy ...func()) (string, error) { id := xhash.Sha1("xcron", rule, xtime.Ns()).Hex() return id, s.Set(id, rule, loop, tidy...) } // Set update service cron job func (s *Service) Set(id, rule string, loop func(), tidy ...func()) error { rules, err := Parse(rule) if err != nil { return err } if s.Has(id) { s.Del(id) } done := func() {} if len(tidy) > 0 { done = tidy[0] } j := Job{ rule: rule, loop: loop, tidy: done, stop: make(chan bool, 1), } s.Lock() s.jobs[id] = j s.Unlock() s.wg.Add(1) go func() { t := time.NewTicker(time.Second) for { select { case <-j.stop: t.Stop() j.tidy() s.wg.Done() return case <-s.ctx.Done(): s.Del(id) case v := <-t.C: if isDue(v, rules) { j.loop() } } } }() return nil } // Del del cron job from service by id func (s *Service) Del(id string) { s.Lock() defer s.Unlock() if j, ok := s.jobs[id]; ok { delete(s.jobs, id) close(j.stop) } } // Has returns if cron job is running func (s *Service) Has(id string) bool { s.RLock() defer s.RUnlock() _, ok := s.jobs[id] return ok } // Len returns running cron job number func (s *Service) Len() int { s.RLock() defer s.RUnlock() return len(s.jobs) } // Empty empty the cron job service func (s *Service) Empty() { s.cancel() } // Wait wait for all cron job exit func (s *Service) Wait() { s.wg.Wait() } // isDue check if is due with rule func isDue(now time.Time, rule Rule) bool { rules := [][]int{ rule.Second, rule.Minute, rule.Hour, rule.DayOfMonth, rule.Month, rule.DayOfWeek, } toCheck := []int{} for k, v := range rules { if len(v) > 0 { toCheck = append(toCheck, k) } } if len(toCheck) == 0 { return true } _, m, d := now.Date() h, i, s := now.Clock() w := now.Weekday() nows := []int{s, i, h, d, int(m), int(w)} for _, k := range toCheck { if !assert.IsContains(rules[k], nows[k]) { return false } } return true } // parseField parse every fields func (r *Rule) parseField(s string, t int) (err error) { switch t { case Second: if strings.Contains(s, ",") { r.Second, err = getField(s, t, 0, 59) } else { r.Second, err = getRange(s, t, 0, 59) } case Minute: if strings.Contains(s, ",") { r.Minute, err = getField(s, t, 0, 59) } else { r.Minute, err = getRange(s, t, 0, 59) } case Hour: if strings.Contains(s, ",") { r.Hour, err = getField(s, t, 0, 23) } else { r.Hour, err = getRange(s, t, 0, 23) } case DayOfMonth: if strings.Contains(s, ",") { r.DayOfMonth, err = getField(s, t, 1, 31) } else { r.DayOfMonth, err = getRange(s, t, 1, 31) } case Month: if strings.Contains(s, ",") { r.Month, err = getField(s, t, 1, 12) } else { r.Month, err = getRange(s, t, 1, 12) } case DayOfWeek: if strings.Contains(s, ",") { r.DayOfWeek, err = getField(s, t, 0, 6) } else { r.DayOfWeek, err = getRange(s, t, 0, 6) } } return err } // getRange get int array from string range, for example 3, 0-23, */3 func getRange(s string, t, min, max int) ([]int, error) { r := []int{} if s == "*" { return r, nil } if strings.Contains(s, "-") { ss := strings.Split(s, "-") sl, err := fieldToi(ss[0], t) if err != nil { return r, fmt.Errorf("xcron: unrecognized charset: %s", ss[0]) } sr, err := fieldToi(ss[1], t) if err != nil { return r, fmt.Errorf("xcron: unrecognized charset: %s", ss[1]) } if sl > sr { st := sr sr = sl sl = st } if sl < min || sr > max { return r, fmt.Errorf("xcron: %d is not in [%d, %d]", sr, min, max) } for i := sl; i <= sr; i++ { r = append(r, i) } } else if strings.Contains(s, "/") { ss := strings.Split(s, "/") sr, err := fieldToi(ss[1], t) if err != nil { return r, fmt.Errorf("xcron: unrecognized charset: %s", ss[1]) } if sr < min || sr > max { return r, fmt.Errorf("xcron: %d is not in [%d, %d]", sr, min, max) } for i := min; i <= max; i++ { if i%sr == 0 { r = append(r, i) } } } else { sr, err := fieldToi(s, t) if err != nil { return r, fmt.Errorf("xcron: unrecognized charset: %s", s) } if sr < min || sr > max { return r, fmt.Errorf("xcron: %d is not in [%d, %d]", sr, min, max) } r = append(r, sr) } return r, nil } // getField get int array from string fields, for example 0,1,2 func getField(s string, t, min, max int) ([]int, error) { r := []int{} for _, v := range strings.Split(s, ",") { v = strings.TrimSpace(v) if v != "" { vv, err := fieldToi(v, t) if err != nil { return r, fmt.Errorf("xcron: unrecognized charset: %s", v) } if vv < min || vv > max { return r, fmt.Errorf("xcron: %d is not in [%d, %d]", vv, min, max) } r = append(r, vv) } } return r, nil } // fieldToi get field value as int func fieldToi(s string, t int) (int, error) { vv, err := strconv.Atoi(s) if err == nil { return vv, nil } if t == Month { if v, ok := MonthsMap[s]; ok { return v, nil } } if t == DayOfWeek { if v, ok := DayOfWeekMap[s]; ok { return v, nil } } return 0, fmt.Errorf("xcron: unrecognized charset: %s", s) } // parseMacros parse nonstandard predefined scheduling definitions // returns as standard scheduling definitions func parseMacros(s string) (string, error) { //nolint:cyclop switch s { case "@yearly", "@annually": return "0 0 1 1 *", nil case "@monthly": return "0 0 1 * *", nil case "@daily", "@midnight": return "0 0 * * *", nil case "@hourly": return "0 * * * *", nil case "@weekly": return "0 0 * * 0", nil default: every := "@every " if strings.HasPrefix(s, every) { s = strings.TrimSpace(s[len(every):]) ss := strings.Fields(s) // @every hour ev := "*" vv := 0 if len(ss) > 1 { vv, err := strconv.Atoi(ss[0]) if err == nil && vv > 0 { // @every 2 hour -> */2 ev = "*/" + ss[0] ss[0] = ss[1] } else { return "", fmt.Errorf("xcron: unrecognized macros: %s", s) } } switch ss[0] { case "year": if ev == "*" { return "0 0 1 1 *", nil } case "month": if vv <= 12 { return fmt.Sprintf("0 0 1 %s *", ev), nil } case "day": if vv <= 31 { return fmt.Sprintf("0 0 %s * *", ev), nil } case "hour": if vv < 24 { return fmt.Sprintf("0 %s * * *", ev), nil } case "minute": if vv < 60 { return fmt.Sprintf("%s * * * *", ev), nil } case "second": if vv < 60 { return fmt.Sprintf("%s * * * * *", ev), nil } case "week": if ev == "*" { return "0 0 0 * * 0", nil } case "dayofweek": if vv < 7 { return fmt.Sprintf("0 0 0 * * %s", ev), nil } } } return "", fmt.Errorf("xcron: unrecognized macros: %s", s) } } gokit-0.25.9/xcron/xcron_test.go000066400000000000000000000207211426246334200166040ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xcron import ( "testing" "time" "github.com/likexian/gokit/assert" "github.com/likexian/gokit/xtime" ) func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } func TestAddSet(t *testing.T) { c := New() _, err := c.Add("@error", func() {}) assert.NotNil(t, err) id, err := c.Add("@every second", func() { t.Log("add a echo") }) assert.Nil(t, err) assert.NotEqual(t, id, "") assert.Equal(t, c.Len(), 1) time.Sleep(1 * time.Second) err = c.Set(id, "@every second", func() { t.Log("set a echo") }) assert.Nil(t, err) assert.NotEqual(t, id, "") assert.Equal(t, c.Len(), 1) time.AfterFunc(3*time.Second, func() { c.Del(id) }) c.Wait() assert.Equal(t, c.Len(), 0) } func TestEmpty(t *testing.T) { c := New() for i := 0; i < 3; i++ { id, err := c.Add("@every second", func() { t.Log("add 3 echo") }) assert.Nil(t, err) assert.NotEqual(t, id, "") } assert.Equal(t, c.Len(), 3) time.AfterFunc(3*time.Second, func() { c.Empty() }) c.Wait() assert.Equal(t, c.Len(), 0) } func TestAddEchoChannel(t *testing.T) { r := make(chan interface{}, 10) echo := func(i int) int { return i } c := New() id, err := c.Add("@every 3 second", func() { r <- echo(int(xtime.S())) }, func() { close(r) }) assert.Nil(t, err) assert.NotEqual(t, id, "") time.AfterFunc(3*time.Second, func() { c.Del(id) }) d := []interface{}{} for { i, ok := <-r if !ok { break } d = append(d, i) } assert.Len(t, d, 1) } func TestParse(t *testing.T) { tests := []struct { in string out Rule err error }{ {"", Rule{[]int{}, []int{}, []int{}, []int{}, []int{}, []int{}}, nil}, {"*", Rule{[]int{}, []int{}, []int{}, []int{}, []int{}, []int{}}, nil}, {"* * * * *", Rule{[]int{0}, []int{}, []int{}, []int{}, []int{}, []int{}}, nil}, {"* * * * * *", Rule{[]int{}, []int{}, []int{}, []int{}, []int{}, []int{}}, nil}, {"* * * * jan *", Rule{[]int{}, []int{}, []int{}, []int{}, []int{1}, []int{}}, nil}, {"* * * * jan-mar *", Rule{[]int{}, []int{}, []int{}, []int{}, []int{1, 2, 3}, []int{}}, nil}, {"* * * * jan,feb,mar *", Rule{[]int{}, []int{}, []int{}, []int{}, []int{1, 2, 3}, []int{}}, nil}, {"* * * * * sun", Rule{[]int{}, []int{}, []int{}, []int{}, []int{}, []int{0}}, nil}, {"* * * * * sun-tue", Rule{[]int{}, []int{}, []int{}, []int{}, []int{}, []int{0, 1, 2}}, nil}, {"* * * * * sun,mon,tue", Rule{[]int{}, []int{}, []int{}, []int{}, []int{}, []int{0, 1, 2}}, nil}, {"1 * * * * *", Rule{[]int{1}, []int{}, []int{}, []int{}, []int{}, []int{}}, nil}, {"* 1 * * * *", Rule{[]int{}, []int{1}, []int{}, []int{}, []int{}, []int{}}, nil}, {"* * 1 * * *", Rule{[]int{}, []int{}, []int{1}, []int{}, []int{}, []int{}}, nil}, {"* * * 1 * *", Rule{[]int{}, []int{}, []int{}, []int{1}, []int{}, []int{}}, nil}, {"* * * * 1 *", Rule{[]int{}, []int{}, []int{}, []int{}, []int{1}, []int{}}, nil}, {"* * * * * 1", Rule{[]int{}, []int{}, []int{}, []int{}, []int{}, []int{1}}, nil}, {"1,2,3 * * * * *", Rule{[]int{1, 2, 3}, []int{}, []int{}, []int{}, []int{}, []int{}}, nil}, {"* 1,2,3 * * * *", Rule{[]int{}, []int{1, 2, 3}, []int{}, []int{}, []int{}, []int{}}, nil}, {"* * 1,2,3 * * *", Rule{[]int{}, []int{}, []int{1, 2, 3}, []int{}, []int{}, []int{}}, nil}, {"* * * 1,2,3 * *", Rule{[]int{}, []int{}, []int{}, []int{1, 2, 3}, []int{}, []int{}}, nil}, {"* * * * 1,2,3 *", Rule{[]int{}, []int{}, []int{}, []int{}, []int{1, 2, 3}, []int{}}, nil}, {"* * * * * 1,2,3", Rule{[]int{}, []int{}, []int{}, []int{}, []int{}, []int{1, 2, 3}}, nil}, {"1-3 * * * * *", Rule{[]int{1, 2, 3}, []int{}, []int{}, []int{}, []int{}, []int{}}, nil}, {"* 1-3 * * * *", Rule{[]int{}, []int{1, 2, 3}, []int{}, []int{}, []int{}, []int{}}, nil}, {"* * 1-3 * * *", Rule{[]int{}, []int{}, []int{1, 2, 3}, []int{}, []int{}, []int{}}, nil}, {"* * * 1-3 * *", Rule{[]int{}, []int{}, []int{}, []int{1, 2, 3}, []int{}, []int{}}, nil}, {"* * * * 1-3 *", Rule{[]int{}, []int{}, []int{}, []int{}, []int{1, 2, 3}, []int{}}, nil}, {"* * * * * 1-3", Rule{[]int{}, []int{}, []int{}, []int{}, []int{}, []int{1, 2, 3}}, nil}, {"*/20 * * * * *", Rule{[]int{0, 20, 40}, []int{}, []int{}, []int{}, []int{}, []int{}}, nil}, {"* */30 * * * *", Rule{[]int{}, []int{0, 30}, []int{}, []int{}, []int{}, []int{}}, nil}, {"* * */6 * * *", Rule{[]int{}, []int{}, []int{0, 6, 12, 18}, []int{}, []int{}, []int{}}, nil}, {"* * * */10 * *", Rule{[]int{}, []int{}, []int{}, []int{10, 20, 30}, []int{}, []int{}}, nil}, {"* * * * */4 *", Rule{[]int{}, []int{}, []int{}, []int{}, []int{4, 8, 12}, []int{}}, nil}, {"* * * * * */2", Rule{[]int{}, []int{}, []int{}, []int{}, []int{}, []int{0, 2, 4, 6}}, nil}, {"@weekly", Rule{[]int{0}, []int{0}, []int{0}, []int{}, []int{}, []int{0}}, nil}, {"@hourly", Rule{[]int{0}, []int{0}, []int{}, []int{}, []int{}, []int{}}, nil}, {"@daily", Rule{[]int{0}, []int{0}, []int{0}, []int{}, []int{}, []int{}}, nil}, {"@monthly", Rule{[]int{0}, []int{0}, []int{0}, []int{1}, []int{}, []int{}}, nil}, {"@yearly", Rule{[]int{0}, []int{0}, []int{0}, []int{1}, []int{1}, []int{}}, nil}, {"@every second", Rule{[]int{}, []int{}, []int{}, []int{}, []int{}, []int{}}, nil}, {"@every minute", Rule{[]int{0}, []int{}, []int{}, []int{}, []int{}, []int{}}, nil}, {"@every hour", Rule{[]int{0}, []int{0}, []int{}, []int{}, []int{}, []int{}}, nil}, {"@every day", Rule{[]int{0}, []int{0}, []int{0}, []int{}, []int{}, []int{}}, nil}, {"@every month", Rule{[]int{0}, []int{0}, []int{0}, []int{1}, []int{}, []int{}}, nil}, {"@every week", Rule{[]int{0}, []int{0}, []int{0}, []int{}, []int{}, []int{0}}, nil}, {"@every year", Rule{[]int{0}, []int{0}, []int{0}, []int{1}, []int{1}, []int{}}, nil}, {"@every 20 second", Rule{[]int{0, 20, 40}, []int{}, []int{}, []int{}, []int{}, []int{}}, nil}, {"@every 30 minute", Rule{[]int{0}, []int{0, 30}, []int{}, []int{}, []int{}, []int{}}, nil}, {"@every 6 hour", Rule{[]int{0}, []int{0}, []int{0, 6, 12, 18}, []int{}, []int{}, []int{}}, nil}, {"@every 10 day", Rule{[]int{0}, []int{0}, []int{0}, []int{10, 20, 30}, []int{}, []int{}}, nil}, {"@every 4 month", Rule{[]int{0}, []int{0}, []int{0}, []int{1}, []int{4, 8, 12}, []int{}}, nil}, {"@every 2 dayofweek", Rule{[]int{0}, []int{0}, []int{0}, []int{}, []int{}, []int{0, 2, 4, 6}}, nil}, } for _, v := range tests { vv, err := Parse(v.in) assert.Equal(t, err, v.err, v) assert.Equal(t, vv, v.out, v) } fails := []string{ "@likexian", "0 0", "-3 * * * *", "3- * * * *", "x-3 * * * *", "3-x * * * *", "100-1000 * * * *", "1000-100 * * * *", "1/x * * * *", "100/x * * * *", "x/100 * * * *", "x * * * *", "1000 * * * *", "1,1000 * * * *", "1,x,3 * * * *", "@every x", "@every x second", "* * * * janx *", "* * * * janx-mar *", "* * * * janx,feb,mar *", "* * * * * sunx", "* * * * * sunx-tue", "* * * * * sunx,mon,tue", } for _, v := range fails { _, err := Parse(v) assert.NotNil(t, err, v) } } func TestMustParse(t *testing.T) { assert.Panic(t, func() { MustParse("@every night") }) assert.NotPanic(t, func() { MustParse("@every second") }) } func TestIsDue(t *testing.T) { tests := []struct { now time.Time rule Rule out bool }{ {time.Date(2019, 04, 10, 0, 0, 0, 0, time.UTC), Rule{[]int{}, []int{}, []int{}, []int{}, []int{}, []int{}}, true}, {time.Date(2019, 04, 10, 0, 0, 0, 0, time.UTC), Rule{[]int{0}, []int{}, []int{}, []int{}, []int{}, []int{}}, true}, {time.Date(2019, 04, 10, 0, 0, 0, 0, time.UTC), Rule{[]int{0, 1}, []int{}, []int{}, []int{}, []int{}, []int{}}, true}, {time.Date(2019, 04, 10, 0, 0, 0, 0, time.UTC), Rule{[]int{1}, []int{}, []int{}, []int{}, []int{}, []int{}}, false}, {time.Date(2019, 04, 10, 0, 0, 0, 0, time.UTC), Rule{[]int{1, 2}, []int{}, []int{}, []int{}, []int{}, []int{}}, false}, } for _, v := range tests { assert.Equal(t, isDue(v.now, v.rule), v.out) } } gokit-0.25.9/xdaemon/000077500000000000000000000000001426246334200143655ustar00rootroot00000000000000gokit-0.25.9/xdaemon/README.md000066400000000000000000000016111426246334200156430ustar00rootroot00000000000000# GoKit - xdaemon Daemon kits for Golang development. ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/xdaemon" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xdaemon) ## Example ### Do deamon ```go c := xdaemon.Config { Pid: "/tmp/test.pid", // the pid file name Log: "/tmp/test.log", // the log file name User: "nobody", // run daemon as user, if set, ROOT is required Chdir: "/", // change working dir } err := c.Daemon() if err != nil { panic(err) } ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/xdaemon/xdaemon.go000066400000000000000000000045541426246334200163570ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xdaemon import ( "os" "syscall" "github.com/likexian/gokit/xfile" "github.com/likexian/gokit/xos" ) // Config storing config for daemon type Config struct { Pid string Log string User string Chdir string } // Version returns package version func Version() string { return "0.7.2" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // Daemon start to daemon func (c *Config) Daemon() (err error) { var pid *xos.Pidx if c.Pid != "" { pid = xos.Pid(c.Pid) _, err := pid.Alive() if err == nil { return xos.ErrPidExists } } err = c.doDaemon() if err != nil { return } if c.Pid != "" { _, err = pid.Create() if err != nil { return } } if c.User != "" { err = xos.SetUser(c.User) if err != nil { return } } return } // Doing the daemon func (c *Config) doDaemon() (err error) { syscall.Umask(0) if c.Chdir != "" { err = os.Chdir(c.Chdir) if err != nil { return } } if syscall.Getppid() == 1 { return } files := make([]*os.File, 3, 6) if c.Log != "" { fp, err := xfile.NewFile(c.Log, true) if err != nil { return err } files[0], files[1], files[2] = fp, fp, fp } else { files[0], files[1], files[2] = os.Stdin, os.Stdout, os.Stderr } dir, _ := os.Getwd() sysattrs := syscall.SysProcAttr{Setsid: true} prcattrs := os.ProcAttr{ Dir: dir, Env: os.Environ(), Files: files, Sys: &sysattrs, } proc, err := os.StartProcess(os.Args[0], os.Args, &prcattrs) if err != nil { return } err = proc.Release() if err != nil { return } os.Exit(0) return } gokit-0.25.9/xdaemon/xdaemon_fail_test.go000066400000000000000000000026121426246334200204020ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xdaemon import ( "os" "testing" "github.com/likexian/gokit/assert" "github.com/likexian/gokit/xfile" ) func TestFailDaemon(t *testing.T) { v := os.Args[0] os.Args[0] = "xx" c := Config{ Pid: "", Log: "", User: "", Chdir: "", } err := c.Daemon() assert.NotNil(t, err) c = Config{ Pid: "/tmp/test.pid", Log: "/tmp/test.log", User: "nobody", Chdir: "/", } err = c.Daemon() assert.NotNil(t, err) os.Args[0] = v } func TestIsRunning(t *testing.T) { pidFile := "/tmp/test.pid" defer os.Remove(pidFile) err := xfile.WriteText(pidFile, "1") assert.Nil(t, err) c := Config{ Pid: pidFile, Log: "/tmp/test.log", User: "nobody", Chdir: "/", } err = c.Daemon() assert.NotNil(t, err) } gokit-0.25.9/xdaemon/xdaemon_hasuser_test.go000066400000000000000000000024571426246334200211500ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xdaemon import ( "os" "os/exec" "testing" "github.com/likexian/gokit/assert" ) func testHasUser(t *testing.T) { c := Config{ Pid: "/tmp/test.pid", Log: "/tmp/test.log", User: "nobody", Chdir: "/", } err := c.Daemon() assert.Nil(t, err) } func TestHasUser(t *testing.T) { if os.Getenv("TestCase") != "" && os.Getenv("TestCase") != "TestHasUser" { return } if os.Getenv("TestCase") != "" { testHasUser(t) return } cmd := exec.Command(os.Args[0], "-test.run=TestHasUser") cmd.Env = append(os.Environ(), "TestCase=TestHasUser") err := cmd.Run() if err == nil { return } t.Errorf("Test expect to be daemon") } gokit-0.25.9/xdaemon/xdaemon_nouser_test.go000066400000000000000000000024101426246334200207760ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xdaemon import ( "os" "os/exec" "testing" "github.com/likexian/gokit/assert" ) func testNoUser(t *testing.T) { c := Config{ Pid: "", Log: "", User: "", Chdir: "", } err := c.Daemon() assert.Nil(t, err) } func TestNoUser(t *testing.T) { if os.Getenv("TestCase") != "" && os.Getenv("TestCase") != "TestNoUser" { return } if os.Getenv("TestCase") != "" { testNoUser(t) return } cmd := exec.Command(os.Args[0], "-test.run=TestNoUser") cmd.Env = append(os.Environ(), "TestCase=TestNoUser") err := cmd.Run() if err == nil { return } t.Errorf("Test expect to be daemon") } gokit-0.25.9/xdaemon/xdaemon_test.go000066400000000000000000000016121426246334200174060ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xdaemon import ( "testing" "github.com/likexian/gokit/assert" ) func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } gokit-0.25.9/xfile/000077500000000000000000000000001426246334200140415ustar00rootroot00000000000000gokit-0.25.9/xfile/README.md000066400000000000000000000021411426246334200153160ustar00rootroot00000000000000# GoKit - xfile File kits for Golang development. ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/xfile" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xfile) ## Example ### check file is exists ```go exists := xfile.Exists("/data/dev/gokit/LICENSE") if exists { fmt.Println("file is exists") } else { fmt.Println("file not exists") } ``` ### get file size ```go size, err := xfile.Size("/data/dev/gokit/LICENSE") if err != nil { panic(err) } else { fmt.Println("file size is", size) } ``` ### write text to file ```go err := xfile.WriteText("/tmp/not-exists-dir/LICENSE", "Copyright 2012-2022 Li Kexian\n") if err != nil { panic(err) } else { fmt.Println("write to file successful") } ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/xfile/xfile.go000066400000000000000000000227311426246334200155040ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xfile import ( "bufio" "errors" "io" "io/ioutil" "os" "path/filepath" "strings" ) const ( // TypeAll list dir and file TypeAll int = iota // TypeDir list only dir TypeDir // TypeFile list only file TypeFile ) var ( // ErrNotExists file is exists error ErrNotExists = errors.New("xfile: file is not exists") // ErrHasExists file is exists error ErrHasExists = errors.New("xfile: the file is exists") ) // LsFile is list file info type LsFile struct { Type int Path string Name string } // Version returns package version func Version() string { return "0.15.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // Exists returns path is exists, symbolic link will check the target func Exists(fpath string) bool { _, err := os.Stat(fpath) return !os.IsNotExist(err) } // Lexists returns path is exists, symbolic link will not follow func Lexists(fpath string) bool { _, err := os.Lstat(fpath) return !os.IsNotExist(err) } // IsFile returns path is a file, symbolic link will check the target func IsFile(fpath string) bool { f, err := os.Stat(fpath) return err == nil && f.Mode().IsRegular() } // IsDir returns path is a dir, symbolic link will check the target func IsDir(fpath string) bool { f, err := os.Stat(fpath) return err == nil && f.Mode().IsDir() } // IsSymlink returns path is a symbolic link func IsSymlink(fpath string) bool { f, err := os.Lstat(fpath) return err == nil && f.Mode()&os.ModeSymlink != 0 } // Size returns the file size of path, symbolic link will check the target func Size(fpath string) (int64, error) { f, err := os.Stat(fpath) if err != nil { return -1, err } return f.Size(), nil } // MTime returns the file mtime of path, symbolic link will check the target func MTime(fpath string) (int64, error) { f, err := os.Stat(fpath) if err != nil { return -1, err } return f.ModTime().Unix(), nil } // Copy copys file and folder from src to dst func Copy(src, dst string) error { if src == "" { src = "." } if dst == "" { dst = "." } if strings.TrimRight(src, "/") == strings.TrimRight(dst, "/") { return ErrHasExists } if Exists(dst) { return ErrHasExists } f, err := os.Lstat(src) if err != nil { return err } if f.Mode()&os.ModeSymlink != 0 { target, err := os.Readlink(src) if err != nil { return err } return os.Symlink(target, dst) } else if f.Mode().IsDir() { if !strings.HasSuffix(src, "/") { src += "/" } if !strings.HasSuffix(dst, "/") { dst += "/" } err = os.MkdirAll(dst, 0755) if err != nil { return err } ls, err := ListDir(src, TypeAll, -1) if err != nil { return err } for _, v := range ls { err = Copy(src+v.Name, dst+v.Name) if err != nil { return err } } if err = os.Chtimes(dst, f.ModTime(), f.ModTime()); err != nil { return err } return os.Chmod(dst, f.Mode()) } else { fd, err := os.Open(src) if err != nil { return err } defer fd.Close() td, err := New(dst) if err != nil { return err } defer td.Close() if _, err = io.Copy(td, fd); err != nil { return err } } if err = os.Chtimes(dst, f.ModTime(), f.ModTime()); err != nil { return err } return os.Chmod(dst, f.Mode()) } // New opens a file for new and return fd func New(fpath string) (*os.File, error) { return NewFile(fpath, false) } // NewFile opens a file and return fd func NewFile(fpath string, isAppend bool) (*os.File, error) { dir, _ := filepath.Split(fpath) if dir != "" && !IsDir(dir) { err := os.MkdirAll(dir, 0755) if err != nil { return nil, err } } if isAppend { return os.OpenFile(fpath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) } return os.OpenFile(fpath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) } // WriteText writes string data to file func WriteText(fpath, text string) (err error) { return Write(fpath, []byte(text)) } // AppendText appends string data to file func AppendText(fpath, text string) (err error) { return Append(fpath, []byte(text)) } // Write writes bytes data to file func Write(fpath string, data []byte) (err error) { return writeFile(fpath, data, false) } // Append appends bytes data to file func Append(fpath string, data []byte) (err error) { return writeFile(fpath, data, true) } // writeFile writes bytes data to file func writeFile(fpath string, data []byte, isAppend bool) (err error) { fd, err := NewFile(fpath, isAppend) if err != nil { return } n, err := fd.Write(data) if err == nil && n < len(data) { err = io.ErrShortWrite } if e := fd.Close(); err == nil { err = e } return } // Read returns bytes of file func Read(fpath string) ([]byte, error) { return ioutil.ReadFile(fpath) } // ReadText returns text of file func ReadText(fpath string) (string, error) { b, err := Read(fpath) if err != nil { return "", err } return string(b), nil } // ReadLines returns N lines of file func ReadLines(fpath string, n int) (lines []string, err error) { fd, err := os.Open(fpath) if err != nil { return } defer fd.Close() nRead := 0 scanner := bufio.NewScanner(fd) for scanner.Scan() { lines = append(lines, scanner.Text()) nRead++ if n > 0 && nRead >= n { break } } err = scanner.Err() return } // ReadFirstLine returns first NOT empty line func ReadFirstLine(fpath string) (line string, err error) { fd, err := os.Open(fpath) if err != nil { return } defer fd.Close() scanner := bufio.NewScanner(fd) for scanner.Scan() { line = strings.TrimSpace(scanner.Text()) if line != "" { return } } err = scanner.Err() return } // ReadLastLine returns last NOT empty line func ReadLastLine(fpath string) (line string, err error) { fd, err := os.Open(fpath) if err != nil { return } defer fd.Close() stat, err := fd.Stat() if err != nil { return } size := stat.Size() if size == 0 { return } var cursor int64 data := make([]byte, 0) for { cursor-- _, err = fd.Seek(cursor, io.SeekEnd) if err != nil { return } buf := make([]byte, 1) _, err = fd.Read(buf) if err != nil { return } if buf[0] != '\r' && buf[0] != '\n' { data = append([]byte{buf[0]}, data...) } else { if cursor != -1 && strings.TrimSpace(string(data)) != "" { break } data = make([]byte, 0) } if cursor == -size { break } } return string(data), nil } // ListDir lists dir without recursion func ListDir(fpath string, ftype, n int) (ls []LsFile, err error) { if fpath == "" { fpath = "." } if !strings.HasSuffix(fpath, "/") { fpath += "/" } fd, err := os.Open(fpath) if err != nil { return } defer fd.Close() fs, err := fd.Readdir(-1) if err != nil { return } for _, f := range fs { tpath := fpath + f.Name() if f.IsDir() { if ftype == TypeAll || ftype == TypeDir { ls = append(ls, LsFile{TypeDir, tpath, f.Name()}) if n > 0 && len(ls) >= n { return } } } else { if ftype == TypeAll || ftype == TypeFile { ls = append(ls, LsFile{TypeFile, tpath, f.Name()}) if n > 0 && len(ls) >= n { return } } } } return } // ListDirAll lists dir and children, filter by type, returns up to n func ListDirAll(fpath string, ftype, n int) (ls []LsFile, err error) { if fpath == "" { fpath = "." } if !strings.HasSuffix(fpath, "/") { fpath += "/" } fd, err := os.Open(fpath) if err != nil { return } defer fd.Close() fs, err := fd.Readdir(-1) if err != nil { return } for _, f := range fs { tpath := fpath + f.Name() if f.IsDir() { if ftype == TypeAll || ftype == TypeDir { ls = append(ls, LsFile{TypeDir, tpath, f.Name()}) if n > 0 && len(ls) >= n { return } } tls, err := ListDirAll(tpath, ftype, n-len(ls)) if err != nil { return ls, err } ls = append(ls, tls...) if n > 0 && len(ls) >= n { return ls, nil } } else { if ftype == TypeAll || ftype == TypeFile { ls = append(ls, LsFile{TypeFile, tpath, f.Name()}) if n > 0 && len(ls) >= n { return } } } } return } // Chmod chmods to path without recursion func Chmod(fpath string, mode os.FileMode) error { return os.Chmod(fpath, mode) } // ChmodAll chmods to path and children, returns the first error it encounters func ChmodAll(root string, mode os.FileMode) error { return filepath.Walk(root, func(fpath string, info os.FileInfo, err error) error { if err != nil { return err } return Chmod(fpath, mode) }) } // Chown chowns to path without recursion func Chown(fpath string, uid, gid int) error { return os.Chown(fpath, uid, gid) } // ChownAll chowns to path and children, returns the first error it encounters func ChownAll(root string, uid, gid int) error { return filepath.Walk(root, func(fpath string, info os.FileInfo, err error) error { if err != nil { return err } return Chown(fpath, uid, gid) }) } gokit-0.25.9/xfile/xfile_test.go000066400000000000000000000211211426246334200165330ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xfile import ( "fmt" "os" "testing" "github.com/likexian/gokit/assert" ) const ( testDir = "tmp" testFile = "tmp/file" ) func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } func TestFile(t *testing.T) { defer os.RemoveAll(testDir) err := os.Mkdir(testDir, 0755) assert.Nil(t, err) ok := Exists(testDir + "/dir") assert.False(t, ok, "file expect to be not exists") err = os.Mkdir(testDir+"/dir", 0755) assert.Nil(t, err) ok = Exists(testDir + "/dir") assert.True(t, ok, "file expect to be exists") ok = IsDir(testDir + "/dir") assert.True(t, ok, "file expect to be dir") ok = IsFile(testDir + "/dir") assert.False(t, ok, "file expect to be not file") ok = IsFile(testDir + "/file") assert.False(t, ok, "file expect to be not file") _, err = New(testDir + "/dir/test/") assert.NotNil(t, err) fd, err := New(testFile) assert.Nil(t, err) err = fd.Close() assert.Nil(t, err) _, err = New(testFile + "/test") assert.NotNil(t, err) ok = IsFile(testFile) assert.True(t, ok, "file expect to be file") err = Write(testFile, []byte("likexian")) assert.Nil(t, err) err = Write(testFile+"/test", []byte("likexian")) assert.NotNil(t, err) text, err := ReadText(testFile) assert.Nil(t, err) assert.Equal(t, text, "likexian") err = WriteText(testFile, "1\n2\n3\n4\n5") assert.Nil(t, err) lines, err := ReadLines(testFile, 0) assert.Nil(t, err) assert.Equal(t, len(lines), 5) lines, err = ReadLines(testFile, 1) assert.Nil(t, err) assert.Equal(t, len(lines), 1) _, err = ReadText(testDir + "/not-exists") assert.NotNil(t, err) _, err = ReadLines(testDir+"/not-exists", 0) assert.NotNil(t, err) err = WriteText(testFile, "likexian") assert.Nil(t, err) text, err = ReadText(testFile) assert.Nil(t, err) assert.Equal(t, text, "likexian") ok = IsFile(testFile) assert.True(t, ok, "file expect to be file") ok = IsDir(testFile) assert.False(t, ok, "file expect to be not dir") n, err := Size(testFile) assert.Nil(t, err) assert.Equal(t, n, int64(8)) m, err := MTime(testFile) assert.Nil(t, err) assert.True(t, m > 0) _, err = Size(testDir + "/not-exists") assert.NotNil(t, err) _, err = MTime(testDir + "/not-exists") assert.NotNil(t, err) ok = IsSymlink(testDir + "/link") assert.False(t, ok, "file expect to be not expect") err = os.Symlink("file", testDir+"/link") assert.Nil(t, err) ok = IsSymlink(testDir + "/link") assert.True(t, ok, "file expect to be symbolic link") ok = IsFile(testDir + "/link") assert.True(t, ok, "file expect to be file") ok = IsDir(testDir + "/link") assert.False(t, ok, "file expect to be not dir") err = Chmod(testDir, 0777) assert.Nil(t, err) err = ChmodAll(testDir, 0777) assert.Nil(t, err) err = Chown(testDir, 0, 0) assert.Nil(t, err) err = ChownAll(testDir, 0, 0) assert.Nil(t, err) err = ChmodAll(testDir+"/not-exists", 0777) assert.NotNil(t, err) err = ChownAll(testDir+"/not-exists", 0, 0) assert.NotNil(t, err) } func TestNewAndAppend(t *testing.T) { defer os.RemoveAll(testDir) // init test file fd, err := New(testFile) assert.Nil(t, err) _, _ = fd.Write([]byte("1")) text, err := ReadText(testFile) assert.Nil(t, err) assert.Equal(t, text, "1") // test new mode fd, err = New(testFile) assert.Nil(t, err) _, _ = fd.Write([]byte("1")) text, err = ReadText(testFile) assert.Nil(t, err) assert.Equal(t, text, "1") // test append mode fd, err = NewFile(testFile, true) assert.Nil(t, err) _, _ = fd.Write([]byte("1")) text, err = ReadText(testFile) assert.Nil(t, err) assert.Equal(t, text, "11") // test write text err = WriteText(testFile, "1") assert.Nil(t, err) text, err = ReadText(testFile) assert.Nil(t, err) assert.Equal(t, text, "1") // test append text err = AppendText(testFile, "1") assert.Nil(t, err) text, err = ReadText(testFile) assert.Nil(t, err) assert.Equal(t, text, "11") } func TestReadFirstLine(t *testing.T) { defer os.RemoveAll(testDir) err := os.Mkdir(testDir, 0755) assert.Nil(t, err) _, err = ReadFirstLine(testFile) assert.NotNil(t, err) tests := []struct { in string out string }{ {"", ""}, {"\n", ""}, {"\n\n", ""}, {"\n\n\n", ""}, {"abc\ndef\nghi", "abc"}, {"\nabc\ndef\nghi", "abc"}, {"\n\nabc\ndef\nghi", "abc"}, {"\n\n\nabc\ndef\nghi", "abc"}, } for _, v := range tests { err = WriteText(testFile, v.in) assert.Nil(t, err) line, err := ReadFirstLine(testFile) assert.Nil(t, err) assert.Equal(t, line, v.out) } } func TestReadLastLine(t *testing.T) { defer os.RemoveAll(testDir) err := os.Mkdir(testDir, 0755) assert.Nil(t, err) _, err = ReadLastLine(testFile) assert.NotNil(t, err) tests := []struct { in string out string }{ {"", ""}, {"\n", ""}, {"\n\n", ""}, {"\n\n\n", ""}, {"abc\ndef\nghi", "ghi"}, {"abc\ndef\nghi\n", "ghi"}, {"abc\ndef\nghi\n\n", "ghi"}, {"abc\ndef\nghi\n\n\n", "ghi"}, } for _, v := range tests { err = WriteText(testFile, v.in) assert.Nil(t, err) line, err := ReadLastLine(testFile) assert.Nil(t, err) assert.Equal(t, line, v.out) } } func TestListDir(t *testing.T) { defer os.RemoveAll(testDir) ls, err := ListDir("", TypeAll, -1) assert.Nil(t, err) assert.Equal(t, len(ls), 3) ls, err = ListDir(testDir, TypeAll, -1) assert.NotNil(t, err) assert.Equal(t, len(ls), 0) for i := 0; i < 10; i++ { _ = WriteText(fmt.Sprintf("%s/%d.txt", testDir, i), ".") for j := 0; j < 10; j++ { _ = WriteText(fmt.Sprintf("%s/%d/%d.txt", testDir, i, j), ".") } } ls, err = ListDir(testDir, TypeAll, -1) assert.Nil(t, err) assert.Equal(t, len(ls), 20) ls, err = ListDir(testDir, TypeDir, -1) assert.Nil(t, err) assert.Equal(t, len(ls), 10) ls, err = ListDir(testDir, TypeFile, -1) assert.Nil(t, err) assert.Equal(t, len(ls), 10) ls, err = ListDir(testDir, TypeAll, 5) assert.Nil(t, err) assert.Equal(t, len(ls), 5) ls, err = ListDir(testDir, TypeDir, 5) assert.Nil(t, err) assert.Equal(t, len(ls), 5) ls, err = ListDir(testDir, TypeFile, 5) assert.Nil(t, err) assert.Equal(t, len(ls), 5) } func TestListDirAll(t *testing.T) { defer os.RemoveAll(testDir) ls, err := ListDirAll("", TypeAll, -1) assert.Nil(t, err) assert.Equal(t, len(ls), 3) ls, err = ListDirAll(testDir, TypeAll, -1) assert.NotNil(t, err) assert.Equal(t, len(ls), 0) for i := 0; i < 10; i++ { _ = WriteText(fmt.Sprintf("%s/%d.txt", testDir, i), ".") for j := 0; j < 10; j++ { _ = WriteText(fmt.Sprintf("%s/%d/%d.txt", testDir, i, j), ".") } } ls, err = ListDirAll(testDir, TypeAll, -1) assert.Nil(t, err) assert.Equal(t, len(ls), 120) ls, err = ListDirAll(testDir, TypeDir, -1) assert.Nil(t, err) assert.Equal(t, len(ls), 10) ls, err = ListDirAll(testDir, TypeFile, -1) assert.Nil(t, err) assert.Equal(t, len(ls), 110) ls, err = ListDirAll(testDir, TypeAll, 5) assert.Nil(t, err) assert.Equal(t, len(ls), 5) ls, err = ListDirAll(testDir, TypeDir, 5) assert.Nil(t, err) assert.Equal(t, len(ls), 5) ls, err = ListDirAll(testDir, TypeFile, 5) assert.Nil(t, err) assert.Equal(t, len(ls), 5) } func TestCopy(t *testing.T) { defer os.RemoveAll(testDir) for i := 0; i < 10; i++ { for j := 0; j < 10; j++ { _ = WriteText(fmt.Sprintf("%s/%d/%d.txt", testDir, i, j), fmt.Sprintf("%d", i+j)) } } _ = os.Symlink(testDir+"/0", testDir+"/100") err := Copy("", "") assert.Equal(t, err, ErrHasExists) err = Copy(testDir+"/0", testDir+"/1") assert.Equal(t, err, ErrHasExists) err = Copy(testDir+"/10", testDir+"/11") assert.NotNil(t, err) err = Copy(testDir+"/100", testDir+"/101") assert.Nil(t, err) assert.True(t, Lexists(testDir+"/101")) err = Copy(testDir+"/0/0.txt", testDir+"/0/10.txt") assert.Nil(t, err) assert.True(t, Exists(testDir+"/0/10.txt")) err = Copy(testDir+"/0", testDir+"/102") assert.Nil(t, err) assert.True(t, Exists(testDir+"/102")) ls, err := ListDir(testDir+"/0", TypeAll, -1) assert.Nil(t, err) for _, v := range ls { assert.True(t, Exists(testDir+"/102/"+v.Name)) } } gokit-0.25.9/xhash/000077500000000000000000000000001426246334200140455ustar00rootroot00000000000000gokit-0.25.9/xhash/README.md000066400000000000000000000016471426246334200153340ustar00rootroot00000000000000# GoKit - xhash Hash kits for Golang development. ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/xhash" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xhash) ## Example ### Get md5 of string ```go h := xhash.Md5("12345678") fmt.Println(h.Hex()) fmt.Println(h.B64()) ``` ## Get Hmac Md5 of string ```go h := xhash.HmacMd5("key", "12345678") fmt.Println(h.Hex()) fmt.Println(h.B64()) ``` ### Get md5 of file ```go h, err := xhash.FileMd5("xhash.go") if err != nil { panic(err) } fmt.Println(h.Hex()) fmt.Println(h.B64()) ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/xhash/xhash.go000066400000000000000000000100531426246334200155060ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xhash import ( "crypto/hmac" "crypto/md5" "crypto/sha1" "crypto/sha256" "crypto/sha512" "encoding/base64" "encoding/hex" "hash" "io" "os" "github.com/likexian/gokit/xstring" ) // Hashx storing hash object type Hashx struct { Hash hash.Hash } // Version returns package version func Version() string { return "0.9.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // Md5 returns md5 hash of string func Md5(s ...interface{}) (h Hashx) { h.Hash = md5.New() h.writeString(s...) return } // Sha1 returns sha1 hash of string func Sha1(s ...interface{}) (h Hashx) { h.Hash = sha1.New() h.writeString(s...) return } // Sha256 returns sha256 hash of string func Sha256(s ...interface{}) (h Hashx) { h.Hash = sha256.New() h.writeString(s...) return } // Sha512 returns sha512 hash of string func Sha512(s ...interface{}) (h Hashx) { h.Hash = sha512.New() h.writeString(s...) return } // HmacMd5 returns hmac md5 hash of string with key func HmacMd5(key string, s ...interface{}) (h Hashx) { h.Hash = hmac.New(md5.New, []byte(key)) h.writeString(s...) return } // HmacSha1 returns hmac sha1 hash of string with key func HmacSha1(key string, s ...interface{}) (h Hashx) { h.Hash = hmac.New(sha1.New, []byte(key)) h.writeString(s...) return } // HmacSha256 returns hmac sha256 hash of string with key func HmacSha256(key string, s ...interface{}) (h Hashx) { h.Hash = hmac.New(sha256.New, []byte(key)) h.writeString(s...) return } // HmacSha512 returns hmac sha512 hash of string with key func HmacSha512(key string, s ...interface{}) (h Hashx) { h.Hash = hmac.New(sha512.New, []byte(key)) h.writeString(s...) return } // FileMd5 returns md5 hash of file func FileMd5(f interface{}) (h Hashx, err error) { h.Hash = md5.New() err = h.writeFile(f) return } // FileSha1 returns sha1 hash of file func FileSha1(f interface{}) (h Hashx, err error) { h.Hash = sha1.New() err = h.writeFile(f) return } // FileSha256 returns sha256 hash of file func FileSha256(f interface{}) (h Hashx, err error) { h.Hash = sha256.New() err = h.writeFile(f) return } // FileSha512 returns sha512 hash of file func FileSha512(f interface{}) (h Hashx, err error) { h.Hash = sha512.New() err = h.writeFile(f) return } // Bytes returns hash sum as bytes func (h Hashx) Bytes() []byte { return h.Hash.Sum(nil) } // Hex encoding hash sum as hex string func (h Hashx) Hex() string { return hex.EncodeToString(h.Hash.Sum(nil)) } // Base64 encoding hash sum as base64 string func (h Hashx) Base64() string { return base64.StdEncoding.EncodeToString(h.Hash.Sum(nil)) } // writeString write string content to hash func (h Hashx) writeString(s ...interface{}) { length := len(s) for _, v := range s { switch v := v.(type) { case []byte: _, _ = h.Hash.Write(v) default: _, _ = h.Hash.Write([]byte(xstring.ToString(v))) } if length > 1 { _, _ = h.Hash.Write([]byte("\n")) } } } // writeFile write file content to hash func (h Hashx) writeFile(f interface{}) error { switch f := f.(type) { case string: fd, err := os.Open(f) if err != nil { return err } defer fd.Close() _, err = io.Copy(h.Hash, fd) return err case *os.File: _, err := io.Copy(h.Hash, f) return err default: panic("xhash: not supported file type") } } gokit-0.25.9/xhash/xhash_test.go000066400000000000000000000325211426246334200165510ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xhash import ( "encoding/base64" "os" "testing" "github.com/likexian/gokit/assert" ) func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } func TestMd5(t *testing.T) { tests := []struct { in []interface{} hex string b64 string }{ { []interface{}{[]byte("12345678")}, "25d55ad283aa400af464c76d713c07ad", "JdVa0oOqQAr0ZMdtcTwHrQ==", }, { []interface{}{"12345678"}, "25d55ad283aa400af464c76d713c07ad", "JdVa0oOqQAr0ZMdtcTwHrQ==", }, { []interface{}{"1234", "5678"}, "9053253e972cf40443a4083f452f24d4", "kFMlPpcs9ARDpAg/RS8k1A==", }, { []interface{}{1234, 5678}, "9053253e972cf40443a4083f452f24d4", "kFMlPpcs9ARDpAg/RS8k1A==", }, { []interface{}{123, 456, 78}, "37b07f6264727bdd5818a3093e91e6bd", "N7B/YmRye91YGKMJPpHmvQ==", }, } for _, v := range tests { h := Md5(v.in...) bs, _ := base64.StdEncoding.DecodeString(v.b64) assert.Equal(t, h.Bytes(), bs) assert.Equal(t, h.Hex(), v.hex) assert.Equal(t, h.Base64(), v.b64) } } func TestSha1(t *testing.T) { tests := []struct { in []interface{} hex string b64 string }{ { []interface{}{[]byte("12345678")}, "7c222fb2927d828af22f592134e8932480637c0d", "fCIvspJ9goryL1khNOiTJIBjfA0=", }, { []interface{}{"12345678"}, "7c222fb2927d828af22f592134e8932480637c0d", "fCIvspJ9goryL1khNOiTJIBjfA0=", }, { []interface{}{"1234", "5678"}, "495b10744ee2c4d5423d7e25e7632d31db08fc00", "SVsQdE7ixNVCPX4l52MtMdsI/AA=", }, { []interface{}{1234, 5678}, "495b10744ee2c4d5423d7e25e7632d31db08fc00", "SVsQdE7ixNVCPX4l52MtMdsI/AA=", }, { []interface{}{123, 456, 78}, "d00e00aca9d085d6b53a363c7a95e112b3064270", "0A4ArKnQhda1OjY8epXhErMGQnA=", }, } for _, v := range tests { h := Sha1(v.in...) bs, _ := base64.StdEncoding.DecodeString(v.b64) assert.Equal(t, h.Bytes(), bs) assert.Equal(t, h.Hex(), v.hex) assert.Equal(t, h.Base64(), v.b64) } } func TestSha256(t *testing.T) { tests := []struct { in []interface{} hex string b64 string }{ { []interface{}{[]byte("12345678")}, "ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f", "73l8gRjwLftklgfdXT+MdiMEjJwGPVMsyVxe16iYpk8=", }, { []interface{}{"12345678"}, "ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f", "73l8gRjwLftklgfdXT+MdiMEjJwGPVMsyVxe16iYpk8=", }, { []interface{}{"1234", "5678"}, "15b4d8e3c2d7987b16be2cfba411d5fbd980340d3736ad61913aba0ffaf3608c", "FbTY48LXmHsWviz7pBHV+9mANA03Nq1hkTq6D/rzYIw=", }, { []interface{}{1234, 5678}, "15b4d8e3c2d7987b16be2cfba411d5fbd980340d3736ad61913aba0ffaf3608c", "FbTY48LXmHsWviz7pBHV+9mANA03Nq1hkTq6D/rzYIw=", }, { []interface{}{123, 456, 78}, "ac817a17155f86736d8389b3943149f54da90ff16f6376c00b5aef47ed868ea9", "rIF6FxVfhnNtg4mzlDFJ9U2pD/FvY3bAC1rvR+2Gjqk=", }, } for _, v := range tests { h := Sha256(v.in...) bs, _ := base64.StdEncoding.DecodeString(v.b64) assert.Equal(t, h.Bytes(), bs) assert.Equal(t, h.Hex(), v.hex) assert.Equal(t, h.Base64(), v.b64) } } func TestSha512(t *testing.T) { tests := []struct { in []interface{} hex string b64 string }{ { []interface{}{[]byte("12345678")}, "fa585d89c851dd338a70dcf535aa2a92fee7836dd6aff1226583e88e0996293f16bc009c652826e0fc5c70669" + "5a03cddce372f139eff4d13959da6f1f5d3eabe", "+lhdichR3TOKcNz1Naoqkv7ng23Wr/EiZYPojgmWKT8WvACcZSgm4PxccGaVoDzdzjcvE57/TROVnabx9dPqvg==", }, { []interface{}{"12345678"}, "fa585d89c851dd338a70dcf535aa2a92fee7836dd6aff1226583e88e0996293f16bc009c652826e0fc5c70669" + "5a03cddce372f139eff4d13959da6f1f5d3eabe", "+lhdichR3TOKcNz1Naoqkv7ng23Wr/EiZYPojgmWKT8WvACcZSgm4PxccGaVoDzdzjcvE57/TROVnabx9dPqvg==", }, { []interface{}{"1234", "5678"}, "5a3d898311876b67bffacdd911908301a6ceb0f1256ddc1bbdbf130beed31f3db726c9cc7eac9dc7f70911df3" + "19c30a4509db302a271e72bf8d64c0d73718c7e", "Wj2JgxGHa2e/+s3ZEZCDAabOsPElbdwbvb8TC+7THz23JsnMfqydx/cJEd8xnDCkUJ2zAqJx5yv41kwNc3GMfg==", }, { []interface{}{1234, 5678}, "5a3d898311876b67bffacdd911908301a6ceb0f1256ddc1bbdbf130beed31f3db726c9cc7eac9dc7f70911df3" + "19c30a4509db302a271e72bf8d64c0d73718c7e", "Wj2JgxGHa2e/+s3ZEZCDAabOsPElbdwbvb8TC+7THz23JsnMfqydx/cJEd8xnDCkUJ2zAqJx5yv41kwNc3GMfg==", }, { []interface{}{123, 456, 78}, "778475d192b6effb468014c31863e98fdb7b18be556f79940c4a0430866246beaf52e4075f7484a1b15cf5c10" + "16d98e244b07293b0cd314cecf08a8d5ae0383e", "d4R10ZK27/tGgBTDGGPpj9t7GL5Vb3mUDEoEMIZiRr6vUuQHX3SEobFc9cEBbZjiRLByk7DNMUzs8IqNWuA4Pg==", }, } for _, v := range tests { h := Sha512(v.in...) bs, _ := base64.StdEncoding.DecodeString(v.b64) assert.Equal(t, h.Bytes(), bs) assert.Equal(t, h.Hex(), v.hex) assert.Equal(t, h.Base64(), v.b64) } } func TestHmacMd5(t *testing.T) { key := "87654321" tests := []struct { in []interface{} hex string b64 string }{ { []interface{}{[]byte("12345678")}, "2589a5e790d014bf42e049126624cbdd", "JYml55DQFL9C4EkSZiTL3Q==", }, { []interface{}{"12345678"}, "2589a5e790d014bf42e049126624cbdd", "JYml55DQFL9C4EkSZiTL3Q==", }, { []interface{}{"1234", "5678"}, "b13909276f8ff40ee0acfc845ebe2b70", "sTkJJ2+P9A7grPyEXr4rcA==", }, { []interface{}{1234, 5678}, "b13909276f8ff40ee0acfc845ebe2b70", "sTkJJ2+P9A7grPyEXr4rcA==", }, { []interface{}{123, 456, 78}, "b3dc572ce8fd87c48d51c303ae315307", "s9xXLOj9h8SNUcMDrjFTBw==", }, } for _, v := range tests { h := HmacMd5(key, v.in...) bs, _ := base64.StdEncoding.DecodeString(v.b64) assert.Equal(t, h.Bytes(), bs) assert.Equal(t, h.Hex(), v.hex) assert.Equal(t, h.Base64(), v.b64) } } func TestHmacSha1(t *testing.T) { key := "87654321" tests := []struct { in []interface{} hex string b64 string }{ { []interface{}{[]byte("12345678")}, "3f271885b5503055cf2b93facc5cde88f94f7708", "PycYhbVQMFXPK5P6zFzeiPlPdwg=", }, { []interface{}{"12345678"}, "3f271885b5503055cf2b93facc5cde88f94f7708", "PycYhbVQMFXPK5P6zFzeiPlPdwg=", }, { []interface{}{"1234", "5678"}, "49498ad44ae78c3da4ce326648c7c2bb6e7838cc", "SUmK1ErnjD2kzjJmSMfCu254OMw=", }, { []interface{}{1234, 5678}, "49498ad44ae78c3da4ce326648c7c2bb6e7838cc", "SUmK1ErnjD2kzjJmSMfCu254OMw=", }, { []interface{}{123, 456, 78}, "74c84407f881334f889a0a0ec8ba43a1ae9d9509", "dMhEB/iBM0+ImgoOyLpDoa6dlQk=", }, } for _, v := range tests { h := HmacSha1(key, v.in...) bs, _ := base64.StdEncoding.DecodeString(v.b64) assert.Equal(t, h.Bytes(), bs) assert.Equal(t, h.Hex(), v.hex) assert.Equal(t, h.Base64(), v.b64) } } func TestHmacSha256(t *testing.T) { key := "87654321" tests := []struct { in []interface{} hex string b64 string }{ { []interface{}{[]byte("12345678")}, "18cef3462f052e9fad5f4198f4ef397783189c6e25ab9dafbc7071401065ac76", "GM7zRi8FLp+tX0GY9O85d4MYnG4lq52vvHBxQBBlrHY=", }, { []interface{}{"12345678"}, "18cef3462f052e9fad5f4198f4ef397783189c6e25ab9dafbc7071401065ac76", "GM7zRi8FLp+tX0GY9O85d4MYnG4lq52vvHBxQBBlrHY=", }, { []interface{}{"1234", "5678"}, "fb8c94cc6c4e7a2c3651160db8f96fd26cb05a230a204610a5a0498bc5e2a6f9", "+4yUzGxOeiw2URYNuPlv0mywWiMKIEYQpaBJi8Xipvk=", }, { []interface{}{1234, 5678}, "fb8c94cc6c4e7a2c3651160db8f96fd26cb05a230a204610a5a0498bc5e2a6f9", "+4yUzGxOeiw2URYNuPlv0mywWiMKIEYQpaBJi8Xipvk=", }, { []interface{}{123, 456, 78}, "821ab013ab6e38ac94d1b9ed996f019e2180f66d7c61be22660711ec29a34c6b", "ghqwE6tuOKyU0bntmW8BniGA9m18Yb4iZgcR7CmjTGs=", }, } for _, v := range tests { h := HmacSha256(key, v.in...) bs, _ := base64.StdEncoding.DecodeString(v.b64) assert.Equal(t, h.Bytes(), bs) assert.Equal(t, h.Hex(), v.hex) assert.Equal(t, h.Base64(), v.b64) } } func TestHmacSha512(t *testing.T) { key := "87654321" tests := []struct { in []interface{} hex string b64 string }{ { []interface{}{[]byte("12345678")}, "defdfafdbdbd488d40691246cffca688c75255ce9bbc7260f63b6e00f5fc4453aff465e6430cb7c7303fb523d" + "bf80b99e1f8ea890fe8ab1de19a33d3da497dce", "3v36/b29SI1AaRJGz/ymiMdSVc6bvHJg9jtuAPX8RFOv9GXmQwy3xzA/tSPb+AuZ4fjqiQ/oqx3hmjPT2kl9zg==", }, { []interface{}{"12345678"}, "defdfafdbdbd488d40691246cffca688c75255ce9bbc7260f63b6e00f5fc4453aff465e6430cb7c7303fb523d" + "bf80b99e1f8ea890fe8ab1de19a33d3da497dce", "3v36/b29SI1AaRJGz/ymiMdSVc6bvHJg9jtuAPX8RFOv9GXmQwy3xzA/tSPb+AuZ4fjqiQ/oqx3hmjPT2kl9zg==", }, { []interface{}{"1234", "5678"}, "d5f6dcb8107a7f1059185380e5205b206fb22b5e3996c2999e01ae2bda3a1a1d017d4eb6e0be799b6a8d97e68" + "d1516e15ab3a9da323af561edd5b635d98a5df6", "1fbcuBB6fxBZGFOA5SBbIG+yK145lsKZngGuK9o6Gh0BfU624L55m2qNl+aNFRbhWrOp2jI69WHt1bY12Ypd9g==", }, { []interface{}{1234, 5678}, "d5f6dcb8107a7f1059185380e5205b206fb22b5e3996c2999e01ae2bda3a1a1d017d4eb6e0be799b6a8d97e68" + "d1516e15ab3a9da323af561edd5b635d98a5df6", "1fbcuBB6fxBZGFOA5SBbIG+yK145lsKZngGuK9o6Gh0BfU624L55m2qNl+aNFRbhWrOp2jI69WHt1bY12Ypd9g==", }, { []interface{}{123, 456, 78}, "4c859bcf0ff932b34a4dc0d8b5f1aa537d67ad2be26f67c18de4626a503d28cb69525538efd0f3c363b2b6f9d" + "9a7ac121bfd64389ca2d94abf68867acf1b2c45", "TIWbzw/5MrNKTcDYtfGqU31nrSvib2fBjeRialA9KMtpUlU479Dzw2OytvnZp6wSG/1kOJyi2Uq/aIZ6zxssRQ==", }, } for _, v := range tests { h := HmacSha512(key, v.in...) bs, _ := base64.StdEncoding.DecodeString(v.b64) assert.Equal(t, h.Bytes(), bs) assert.Equal(t, h.Hex(), v.hex) assert.Equal(t, h.Base64(), v.b64) } } func TestFileMd5(t *testing.T) { fd, err := os.Open("/dev/null") assert.Nil(t, err) defer fd.Close() tests := []struct { in interface{} hex string b64 string }{ {"/dev/null", "d41d8cd98f00b204e9800998ecf8427e", "1B2M2Y8AsgTpgAmY7PhCfg=="}, {fd, "d41d8cd98f00b204e9800998ecf8427e", "1B2M2Y8AsgTpgAmY7PhCfg=="}, } for _, v := range tests { h, err := FileMd5(v.in) assert.Nil(t, err) bs, _ := base64.StdEncoding.DecodeString(v.b64) assert.Equal(t, h.Bytes(), bs) assert.Equal(t, h.Hex(), v.hex) assert.Equal(t, h.Base64(), v.b64) } _, err = FileMd5("/i-am-not-exists") assert.NotNil(t, err) assert.Panic(t, func() { _, _ = FileMd5(true) }) } func TestFileSha1(t *testing.T) { fd, err := os.Open("/dev/null") assert.Nil(t, err) defer fd.Close() tests := []struct { in interface{} hex string b64 string }{ {"/dev/null", "da39a3ee5e6b4b0d3255bfef95601890afd80709", "2jmj7l5rSw0yVb/vlWAYkK/YBwk="}, {fd, "da39a3ee5e6b4b0d3255bfef95601890afd80709", "2jmj7l5rSw0yVb/vlWAYkK/YBwk="}, } for _, v := range tests { h, err := FileSha1(v.in) assert.Nil(t, err) bs, _ := base64.StdEncoding.DecodeString(v.b64) assert.Equal(t, h.Bytes(), bs) assert.Equal(t, h.Hex(), v.hex) assert.Equal(t, h.Base64(), v.b64) } _, err = FileSha1("/i-am-not-exists") assert.NotNil(t, err) assert.Panic(t, func() { _, _ = FileSha1(true) }) } func TestFileSha256(t *testing.T) { fd, err := os.Open("/dev/null") assert.Nil(t, err) defer fd.Close() tests := []struct { in interface{} hex string b64 string }{ { "/dev/null", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", }, { fd, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", }, } for _, v := range tests { h, err := FileSha256(v.in) assert.Nil(t, err) bs, _ := base64.StdEncoding.DecodeString(v.b64) assert.Equal(t, h.Bytes(), bs) assert.Equal(t, h.Hex(), v.hex) assert.Equal(t, h.Base64(), v.b64) } _, err = FileSha256("/i-am-not-exists") assert.NotNil(t, err) assert.Panic(t, func() { _, _ = FileSha256(true) }) } func TestFileSha512(t *testing.T) { fd, err := os.Open("/dev/null") assert.Nil(t, err) defer fd.Close() tests := []struct { in interface{} hex string b64 string }{ { "/dev/null", "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d28" + "77eec2f63b931bd47417a81a538327af927da3e", "z4PhNX7vuL3xVChQ1m2AB9Yg5AULVxXcg/SpIdNs6c5H0NE8XYXysP+DGNKHfuwvY7kxvUdBeoGlODJ6+SfaPg==", }, { fd, "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d28" + "77eec2f63b931bd47417a81a538327af927da3e", "z4PhNX7vuL3xVChQ1m2AB9Yg5AULVxXcg/SpIdNs6c5H0NE8XYXysP+DGNKHfuwvY7kxvUdBeoGlODJ6+SfaPg==", }, } for _, v := range tests { h, err := FileSha512(v.in) assert.Nil(t, err) bs, _ := base64.StdEncoding.DecodeString(v.b64) assert.Equal(t, h.Bytes(), bs) assert.Equal(t, h.Hex(), v.hex) assert.Equal(t, h.Base64(), v.b64) } _, err = FileSha512("/i-am-not-exists") assert.NotNil(t, err) assert.Panic(t, func() { _, _ = FileSha512(true) }) } gokit-0.25.9/xhttp/000077500000000000000000000000001426246334200141015ustar00rootroot00000000000000gokit-0.25.9/xhttp/README.md000066400000000000000000000056201426246334200153630ustar00rootroot00000000000000# GoKit - xhttp HTTP kits for Golang development. ## Features - Light weight and Easy to use - Cookies and Proxy are support - Easy use with friendly JSON api - Upload and Download file support - Debug and Trace info are open - Retry request is possible - Cache request by method ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/xhttp" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xhttp) ## Example ### The Most easy way ```go rsp, err := xhttp.Get(context.Background(), "https://www.likexian.com/") if err != nil { panic(err) } defer rsp.Close() text, err := rsp.String() if err == nil { fmt.Println("http status code:", rsp.StatusCode) fmt.Println("http response body:", text) } ``` ### Do a Post with form and files ```go // xhttp.FormParam is form, xhttp.FormFile is file rsp, err := xhttp.Post(context.Background(), "https://www.likexian.com/", xhttp.FormParam{"name": "likexian", "age": 18}, xhttp.FormFile{"file": "README.md"}) if err != nil { panic(err) } defer rsp.Close() json, err := rsp.JSON() if err == nil { // http response {"status": {"code": 1, "message": "ok"}} code, _ := json.Get("status.code").Int() fmt.Println("json status code:", code) } ``` ### Use as Interactive mode ```go req := xhttp.New() // set ua and referer req.SetUA("the new ua") req.SetReferer("http://the-referer-url.com") // set tcp connect timeout and client total timeout req.SetConnectTimeout(3) req.SetClientTimeout(30) // not follow 302 and use cookies req.FollowRedirect(false) req.EnableCookie(true) // will send get to https://www.likexian.com/?v=1.0.0 rsp, err := req.Get(context.Background(), "https://www.likexian.com/", xhttp.QueryParam{"v", "1.0.0"}) if err != nil { panic(err) } // save file as index.html defer rsp.Close() _, err := rsp.File("index.html") if err == nil { fmt.Println("Url download as index.html") } // use the request param as above rsp, err := req.Get(context.Background(), "https://www.likexian.com/page/") if err != nil { panic(err) } defer rsp.Close() ... ``` ### xhttp.Request not thread-safe This version of xhttp.Request is not thread-safe, please New every thread when doing concurrent ```go for i := 0; i < 100; i++ { go func() { // always New one req := New() rsp, err := req.Do(context.Background(), "GET", LOCALURL) if err != nil { fmt.Println(err) return } defer rsp.Close() str, err := rsp.String() if err == nil { fmt.Println(str) } }() } ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/xhttp/wrap.go000066400000000000000000000043451426246334200154070ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xhttp import ( "compress/gzip" "io" "io/ioutil" "net/http" "strings" "sync" ) // gzPool is gzip writer pool var gzPool = sync.Pool{ New: func() interface{} { return gzip.NewWriter(ioutil.Discard) }, } // gzResponseWriter is gzip ResponseWriter type gzResponseWriter struct { io.Writer http.ResponseWriter } // Write write body byte func (w *gzResponseWriter) Write(b []byte) (int, error) { return w.Writer.Write(b) } // WriteString write body string func (w *gzResponseWriter) WriteString(s string) (int, error) { return w.Writer.Write([]byte(s)) } // WriteHeader write http status code header func (w *gzResponseWriter) WriteHeader(status int) { w.Header().Del("Content-Length") w.ResponseWriter.WriteHeader(status) } // GzWrap is http gzip transparent compression middleware func GzWrap(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Vary", "Accept-Encoding") if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { next.ServeHTTP(w, r) return } gz := gzPool.Get().(*gzip.Writer) defer func() { gz.Reset(ioutil.Discard) gzPool.Put(gz) }() gz.Reset(w) defer gz.Close() w.Header().Set("Content-Encoding", "gzip") next.ServeHTTP(&gzResponseWriter{Writer: gz, ResponseWriter: w}, r) }) } // SetHeaderWrap is http set header middleware func SetHeaderWrap(next http.Handler, header Header) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { for k, v := range header { w.Header().Set(k, v) } next.ServeHTTP(w, r) }) } gokit-0.25.9/xhttp/xhttp.go000066400000000000000000000504731426246334200156100ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xhttp import ( "bytes" "context" "crypto/tls" "fmt" "io" "io/ioutil" "mime/multipart" "net" "net/http" "net/http/cookiejar" "net/http/httputil" "net/url" "os" "path/filepath" "strings" "time" "github.com/likexian/gokit/assert" "github.com/likexian/gokit/xcache" "github.com/likexian/gokit/xfile" "github.com/likexian/gokit/xhash" "github.com/likexian/gokit/xjson" "github.com/likexian/gokit/xrand" "github.com/likexian/gokit/xtime" ) var ( // DefaultRequest is default request DefaultRequest = New() // Caching is http request cache caching = xcache.New(xcache.MemoryCache) // supportMethod list all supported http method supportMethod = []string{ "GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", } ) // Timeout storing timeout setting type Timeout struct { ConnectTimeout int TLSHandshakeTimeout int ResponseHeaderTimeout int ExpectContinueTimeout int ClientTimeout int KeepAliveTimeout int } // Retries storing retry setting type Retries struct { Times int Sleep time.Duration } // Dumping storing http dump setting type Dumping struct { DumpHTTP bool DumpBody bool } // Caching storing cache method and ttl type Caching struct { Method map[string]int64 } // Request storing request data type Request struct { ClientID string Request *http.Request Client *http.Client ClientKey string Timeout Timeout Caching Caching Retries Retries Dumping Dumping } // Tracing storing tracing data type Tracing struct { ClientID string RequestID string Timestamp string Nonce string SendTime int64 RecvTime int64 Retries int } // Response storing response data type Response struct { Method string URL *url.URL Response *http.Response StatusCode int ContentLength int64 CacheKey string Tracing Tracing Dumping [][]byte } // Host is http host type Host string // Header is http request header type Header map[string]string // QueryParam is query param map pass to xhttp type QueryParam map[string]interface{} // FormParam is form param map pass to xhttp type FormParam map[string]interface{} // JSONParam is json param map pass to xhttp type JSONParam map[string]interface{} // FormFile is form file for upload, formfield: filename type FormFile map[string]string // param storing QueryParam and FormParam data set by Do type param struct { url.Values } // getValues return values pointer func (p *param) getValues() url.Values { if p.Values == nil { p.Values = make(url.Values) } return p.Values } func (p *param) Update(m param) { if m.Values == nil { return } vv := p.getValues() for m, n := range m.Values { for _, nn := range n { vv.Set(m, nn) } } } // Adds add map data to param func (p *param) Adds(m map[string]interface{}) { if len(m) == 0 { return } vv := p.getValues() for k, v := range m { vv.Add(k, fmt.Sprint(v)) } } // IsEmpty returns param is empty func (p *param) IsEmpty() bool { return p.Values == nil } // Version returns package version func Version() string { return "0.19.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // New init a new xhttp client func New() (r *Request) { timeout := Timeout{ ConnectTimeout: 15, TLSHandshakeTimeout: 5, ResponseHeaderTimeout: 30, ExpectContinueTimeout: 5, ClientTimeout: 120, KeepAliveTimeout: 60, } request := &http.Request{ Header: http.Header{ "User-Agent": []string{fmt.Sprintf("GoKit XHTTP Client/%s", Version())}, }, } client := &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: false}, DisableCompression: false, }, } cache := Caching{ Method: map[string]int64{}, } r = &Request{ ClientID: xhash.Sha1("xhttp", xtime.Ns()).Hex(), Request: request, Client: client, ClientKey: "", Timeout: timeout, Caching: cache, Retries: Retries{}, Dumping: Dumping{}, } return } // Get do http GET request and returns response func Get(ctx context.Context, surl string, args ...interface{}) (s *Response, err error) { return DefaultRequest.Do(ctx, "GET", surl, args...) } // Head do http HEAD request and returns response func Head(ctx context.Context, surl string, args ...interface{}) (s *Response, err error) { return DefaultRequest.Do(ctx, "HEAD", surl, args...) } // Post do http POST request and returns response func Post(ctx context.Context, surl string, args ...interface{}) (s *Response, err error) { return DefaultRequest.Do(ctx, "POST", surl, args...) } // Put do http PUT request and returns response func Put(ctx context.Context, surl string, args ...interface{}) (s *Response, err error) { return DefaultRequest.Do(ctx, "PUT", surl, args...) } // Patch do http PATCH request and returns response func Patch(ctx context.Context, surl string, args ...interface{}) (s *Response, err error) { return DefaultRequest.Do(ctx, "PATCH", surl, args...) } // Delete do http DELETE request and returns response func Delete(ctx context.Context, surl string, args ...interface{}) (s *Response, err error) { return DefaultRequest.Do(ctx, "DELETE", surl, args...) } // Options do http OPTIONS request and returns response func Options(ctx context.Context, surl string, args ...interface{}) (s *Response, err error) { return DefaultRequest.Do(ctx, "OPTIONS", surl, args...) } // Get do http GET request and returns response func (r *Request) Get(ctx context.Context, surl string, args ...interface{}) (s *Response, err error) { return r.Do(ctx, "GET", surl, args...) } // Head do http HEAD request and returns response func (r *Request) Head(ctx context.Context, surl string, args ...interface{}) (s *Response, err error) { return r.Do(ctx, "HEAD", surl, args...) } // Post do http POST request and returns response func (r *Request) Post(ctx context.Context, surl string, args ...interface{}) (s *Response, err error) { return r.Do(ctx, "POST", surl, args...) } // Put do http PUT request and returns response func (r *Request) Put(ctx context.Context, surl string, args ...interface{}) (s *Response, err error) { return r.Do(ctx, "PUT", surl, args...) } // Patch do http PATCH request and returns response func (r *Request) Patch(ctx context.Context, surl string, args ...interface{}) (s *Response, err error) { return r.Do(ctx, "PATCH", surl, args...) } // Delete do http DELETE request and returns response func (r *Request) Delete(ctx context.Context, surl string, args ...interface{}) (s *Response, err error) { return r.Do(ctx, "DELETE", surl, args...) } // Options do http OPTIONS request and returns response func (r *Request) Options(ctx context.Context, surl string, args ...interface{}) (s *Response, err error) { return r.Do(ctx, "OPTIONS", surl, args...) } // GetHeader return request header value by name func (r *Request) GetHeader(name string) string { return r.Request.Header.Get(name) } // SetClientKey set key for signing requestid func (r *Request) SetClientKey(key string) *Request { r.ClientKey = key return r } // SetHost set http request host func (r *Request) SetHost(host string) *Request { r.Request.Host = host return r } // SetHeader set http request header func (r *Request) SetHeader(key, value string) *Request { r.Request.Header.Set(key, value) return r } // SetUA set http request user-agent func (r *Request) SetUA(ua string) *Request { r.SetHeader("User-Agent", ua) return r } // SetReferer set http request referer func (r *Request) SetReferer(referer string) *Request { r.SetHeader("Referer", referer) return r } // SetGzip set http request gzip func (r *Request) SetGzip(gzip bool) *Request { r.Client.Transport.(*http.Transport).DisableCompression = !gzip return r } // SetVerifyTLS set http request tls verify func (r *Request) SetVerifyTLS(verify bool) *Request { r.Client.Transport.(*http.Transport).TLSClientConfig.InsecureSkipVerify = !verify return r } // SetKeepAliveTimeout set http keepalive timeout func (r *Request) SetKeepAliveTimeout(timeout int) *Request { r.Timeout.KeepAliveTimeout = timeout r.SetTimeout(r.Timeout) return r } // SetConnectTimeout set http connect timeout func (r *Request) SetConnectTimeout(timeout int) *Request { r.Timeout.ConnectTimeout = timeout r.SetTimeout(r.Timeout) return r } // SetClientTimeout set http client timeout func (r *Request) SetClientTimeout(timeout int) *Request { r.Timeout.ClientTimeout = timeout r.SetTimeout(r.Timeout) return r } // SetTimeout set http request timeout func (r *Request) SetTimeout(timeout Timeout) *Request { r.Timeout = timeout if r.Timeout.KeepAliveTimeout <= 0 { r.Client.Transport.(*http.Transport).DisableKeepAlives = true } else { r.Client.Transport.(*http.Transport).DisableKeepAlives = false } r.Client.Transport.(*http.Transport).DialContext = (&net.Dialer{ Timeout: time.Duration(r.Timeout.ConnectTimeout) * time.Second, KeepAlive: time.Duration(r.Timeout.KeepAliveTimeout) * time.Second, }).DialContext r.Client.Transport.(*http.Transport).TLSHandshakeTimeout = time.Duration(r.Timeout.TLSHandshakeTimeout) * time.Second r.Client.Transport.(*http.Transport).ResponseHeaderTimeout = time.Duration(r.Timeout.ResponseHeaderTimeout) * time.Second r.Client.Transport.(*http.Transport).ExpectContinueTimeout = time.Duration(r.Timeout.ExpectContinueTimeout) * time.Second r.Client.Timeout = time.Duration(r.Timeout.ClientTimeout) * time.Second return r } // GetTimeout get http request timeout func (r *Request) GetTimeout() Timeout { return r.Timeout } // SetProxy set http request proxy func (r *Request) SetProxy(proxy func(*http.Request) (*url.URL, error)) *Request { r.Client.Transport.(*http.Transport).Proxy = proxy return r } // SetProxyURL set http request proxy url func (r *Request) SetProxyURL(proxy string) *Request { if !strings.HasPrefix(proxy, "http://") && !strings.HasPrefix(proxy, "https://") && !strings.HasPrefix(proxy, "socks5://") { proxy = "http://" + proxy } r.SetProxy(func(req *http.Request) (*url.URL, error) { return url.ParseRequestURI(proxy) }) return r } // FollowRedirect set http request follow redirect func (r *Request) FollowRedirect(follow bool) *Request { if follow { r.Client.CheckRedirect = nil } else { r.Client.CheckRedirect = func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse } } return r } // EnableCookie set http request enable cookie func (r *Request) EnableCookie(enable bool) *Request { if enable { if r.Client.Jar == nil { r.Client.Jar, _ = cookiejar.New(nil) } } else { if r.Client.Jar != nil { r.Client.Jar = nil } } return r } // EnableCache enable http client cache func (r *Request) EnableCache(method string, ttl int64) *Request { method = strings.ToUpper(strings.TrimSpace(method)) if assert.IsContains(supportMethod, method) { r.Caching.Method[method] = ttl } return r } // SetRetries set retry param // int arg is setting retry times, time.Duration is setting retry sleep duration // 0: no retry (default), -1: retry until success, > 1: retry x times func (r *Request) SetRetries(args ...interface{}) *Request { if len(args) == 0 { panic("xhttp: the arguments is empty") } for i := 0; i < len(args); i++ { switch args[i].(type) { case int: r.Retries.Times = args[i].(int) case time.Duration: r.Retries.Sleep = args[i].(time.Duration) } } return r } // SetDump set http dump func (r *Request) SetDump(dumpHTTP, dumpBody bool) *Request { r.Dumping.DumpHTTP = dumpHTTP r.Dumping.DumpBody = dumpBody return r } // Do send http request and return response func (r *Request) Do(ctx context.Context, //nolint:cyclop method, surl string, args ...interface{}) (s *Response, err error) { r.Request.Host = "" r.Request.Header.Del("Cookie") r.Request.Header.Del("Content-Type") method = strings.ToUpper(strings.TrimSpace(method)) if !assert.IsContains(supportMethod, method) { return nil, fmt.Errorf("xhttp: not supported method: %s", method) } r.Request.Method = method surl = strings.TrimSpace(surl) if surl == "" { return nil, fmt.Errorf("xhttp: no request url specify") } var formBody string var formParam param var queryParam param formFile := FormFile{} for _, v := range args { switch vv := v.(type) { case Host: r.SetHost(string(vv)) case Header: for k, v := range vv { r.SetHeader(k, v) } case http.Header: for k, v := range vv { for _, vv := range v { r.SetHeader(k, vv) } } case *http.Client: r.Client = vv case *http.Cookie: r.Request.AddCookie(vv) case FormParam: formParam.Adds(vv) case QueryParam: queryParam.Adds(vv) case url.Values: if assert.IsContains([]string{"POST", "PUT", "PATCH"}, method) { formParam.Update(param{vv}) } else { queryParam.Update(param{vv}) } case JSONParam: formBody, err = xjson.Dumps(vv) if err != nil { return nil, fmt.Errorf("xhttp: encode json param failed: %w", err) } r.Request.Header.Set("Content-Type", "application/json") case string: formBody = vv case []byte: formBody = string(vv) case bytes.Buffer: formBody = vv.String() case FormFile: for k, v := range vv { formFile[k] = v } } } r.Request = r.Request.WithContext(ctx) r.Request.Body = nil r.Request.ContentLength = 0 if assert.IsContains([]string{"POST", "PUT", "PATCH"}, method) { if len(formFile) > 0 { pr, pw := io.Pipe() bw := multipart.NewWriter(pw) go func() { for k, v := range formFile { fw, err := bw.CreateFormFile(k, v) if err != nil { continue } fd, err := os.Open(v) if err != nil { continue } _, err = io.Copy(fw, fd) fd.Close() if err != nil { continue } } for k, v := range formParam.Values { for _, vv := range v { _ = bw.WriteField(k, vv) } } bw.Close() pw.Close() }() r.SetHeader("Content-Type", bw.FormDataContentType()) r.Request.Body = ioutil.NopCloser(pr) } else { if !formParam.IsEmpty() { formBody += formParam.Encode() } if formBody != "" { r.Request.Body = ioutil.NopCloser(bytes.NewReader([]byte(formBody))) r.Request.ContentLength = int64(len(formBody)) if r.Request.Header.Get("Content-Type") == "" { r.Request.Header.Set("Content-Type", "application/x-www-form-urlencoded") } } } } if !queryParam.IsEmpty() { q := queryParam.Encode() if strings.Contains(surl, "?") { surl += "&" + q } else { surl += "?" + q } } u, err := url.Parse(surl) if err != nil { return nil, fmt.Errorf("xhttp: parse url failed: %w", err) } r.Request.URL = u s = &Response{ Method: r.Request.Method, URL: r.Request.URL, Tracing: Tracing{ Timestamp: fmt.Sprintf("%d", xtime.S()), Nonce: fmt.Sprintf("%d", xrand.IntRange(1000000, 9999999)), ClientID: r.ClientID, Retries: -1, }, } startAt := xtime.Ms() defer func() { s.Tracing.SendTime = xtime.Ms() - startAt }() s.Tracing.RequestID = xhash.Sha1("xhttp", s.Tracing.Timestamp, s.Tracing.Nonce, s.Method, s.URL.Path, s.URL.RawQuery, r.ClientKey).Hex() r.Request.Header.Set("X-HTTP-GoKit-RequestId", fmt.Sprintf("%s-%s-%s", s.Tracing.Timestamp, s.Tracing.Nonce, s.Tracing.RequestID)) cacheTTL, cacheEnabled := r.Caching.Method[s.Method] if cacheEnabled || r.Dumping.DumpHTTP { dumpBody := r.Dumping.DumpBody if cacheEnabled { dumpBody = true } d, err := httputil.DumpRequestOut(r.Request, dumpBody) if err == nil { s.Dumping = append(s.Dumping, d) } } if cacheEnabled { body := "" if len(s.Dumping) > 0 { d := strings.Split(string(s.Dumping[0]), "\r\n\r\n") if len(d) > 1 { body = d[1] } s.CacheKey = xhash.Sha1(s.Method, s.URL.String(), body).Hex() cacheVal := caching.Get(s.CacheKey) if cacheVal != nil { s = cacheVal.(*Response) return } } } for i := 0; r.Retries.Times == -1 || i <= r.Retries.Times; i++ { s.Tracing.Retries++ s.Response, err = r.Client.Do(r.Request) if err == nil { break } if r.Retries.Sleep > 0 { time.Sleep(r.Retries.Sleep) } } if err == nil { s.StatusCode = s.Response.StatusCode s.ContentLength = s.Response.ContentLength } if r.Dumping.DumpHTTP { d, err := httputil.DumpResponse(s.Response, r.Dumping.DumpBody) if err == nil { s.Dumping = append(s.Dumping, d) } } if s.CacheKey != "" { _ = caching.Set(s.CacheKey, s, cacheTTL) } return } // Close close response body func (r *Response) Close() { r.Response.Body.Close() } // GetHeader return response header value by name func (r *Response) GetHeader(name string) string { return r.Response.Header.Get(name) } // File save response body to file func (r *Response) File(paths ...string) (size int64, err error) { fpath := "" if len(paths) > 0 { fpath = paths[0] } fpath = strings.TrimSpace(fpath) if fpath == "" { _, fpath = filepath.Split(r.URL.String()) if fpath == "" { fpath = "index.html" } } else { dir, name := filepath.Split(fpath) if name == "" { fpath = dir + "index.html" } if dir != "" && !xfile.Exists(dir) { err = os.MkdirAll(dir, 0755) if err != nil { return } } } if xfile.Exists(fpath) { return 0, fmt.Errorf("xhttp: file %s is exists", fpath) } defer r.Response.Body.Close() if r.Response.StatusCode != http.StatusOK { return 0, fmt.Errorf("xhttp: bad status code: %d", r.Response.StatusCode) } startAt := xtime.Ms() defer func() { r.Tracing.RecvTime = xtime.Ms() - startAt }() fd, err := xfile.New(fpath) if err != nil { return } defer fd.Close() size, err = io.Copy(fd, r.Response.Body) return } // Bytes returns response body as bytes func (r *Response) Bytes() (b []byte, err error) { startAt := xtime.Ms() defer func() { r.Tracing.RecvTime = xtime.Ms() - startAt }() b, err = ioutil.ReadAll(r.Response.Body) r.Response.Body.Close() if r.CacheKey != "" { r.Response.Body = ioutil.NopCloser(bytes.NewBuffer(b)) } return } // String returns response body as string func (r *Response) String() (s string, err error) { b, err := r.Bytes() if err != nil { return } return string(b), nil } // JSON returns response body as *xjson.JSON // For more please refer to gokit/xjson func (r *Response) JSON() (*xjson.JSON, error) { s, err := r.String() if err != nil { return &xjson.JSON{}, err } return xjson.Loads(s) } // Dump returns http dump of request and response // [bytes[request], bytes[response]] func (r *Response) Dump() [][]byte { return r.Dumping } // CheckClient returns is a valid client request // used by http server, it will check the requestId func CheckClient(r *http.Request, ClientKey string) error { id := r.Header.Get("X-Http-Gokit-Requestid") if id == "" { return fmt.Errorf("xhttp: missing request id") } ids := strings.Split(id, "-") if len(ids) != 3 { return fmt.Errorf("xhttp: request id invalid") } tm, err := assert.ToInt64(ids[0]) if err != nil { return fmt.Errorf("xhttp: time stamp invalid") } now := xtime.S() if tm-now > 300 || tm-now < -300 { return fmt.Errorf("xhttp: time stamp expired") } nonce, err := assert.ToInt64(ids[1]) if err != nil { return fmt.Errorf("xhttp: request nonce invalid") } sum := xhash.Sha1("xhttp", tm, nonce, r.Method, r.URL.Path, r.URL.RawQuery, ClientKey).Hex() if sum != ids[2] { return fmt.Errorf("xhttp: hash value not matched") } return nil } // GetClientIPs returns all ips from http client func GetClientIPs(r *http.Request) []string { ips := []string{} for _, h := range []string{"X-Real-Ip", "X-Forwarded-For"} { ip := r.Header.Get(h) if ip != "" { for _, v := range strings.Split(ip, ",") { v = strings.TrimSpace(v) if v != "" { ips = append(ips, v) } } } } ips = append(ips, strings.Split(r.RemoteAddr, ":")[0]) return ips } gokit-0.25.9/xhttp/xhttp_test.go000066400000000000000000000770741426246334200166550ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xhttp import ( "bytes" "context" "fmt" "io/ioutil" "net/http" "net/url" "os" "strings" "sync" "testing" "time" "github.com/likexian/gokit/assert" "github.com/likexian/gokit/xfile" "github.com/likexian/gokit/xjson" "github.com/likexian/gokit/xtime" ) var ( LOCALURL = ServerForTesting("6666") ) func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } func TestNew(t *testing.T) { req := New() ctx := context.Background() _, err := req.Do(ctx, "GET", LOCALURL) assert.Nil(t, err) assert.Equal(t, req.Request.Method, "GET") assert.Equal(t, req.Request.URL.String(), LOCALURL) _, err = req.Do(ctx, "CODE", LOCALURL) assert.NotNil(t, err) _, err = req.Do(ctx, "GET", "") assert.NotNil(t, err) _, err = req.Do(ctx, "GET", "::") assert.NotNil(t, err) _, err = req.Do(ctx, "get", LOCALURL) assert.Nil(t, err) assert.Equal(t, req.Request.Method, "GET") assert.Equal(t, req.Request.URL.String(), LOCALURL) _, err = req.Do(ctx, "POST", LOCALURL+"post") assert.Nil(t, err) assert.Equal(t, req.Request.Method, "POST") assert.Equal(t, req.Request.URL.String(), LOCALURL+"post") clientID := req.ClientID req = New() _, err = req.Do(ctx, "GET", LOCALURL) assert.Nil(t, err) assert.NotEqual(t, req.ClientID, clientID) } func TestMethod(t *testing.T) { ctx := context.Background() rsp, err := Get(ctx, LOCALURL+"get") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) rsp, err = Head(ctx, LOCALURL+"get") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) rsp, err = Post(ctx, LOCALURL+"post") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) rsp, err = Put(ctx, LOCALURL+"put") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) rsp, err = Patch(ctx, LOCALURL+"patch") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) rsp, err = Delete(ctx, LOCALURL+"delete") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) rsp, err = Options(ctx, LOCALURL+"get") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) req := New() rsp, err = req.Get(ctx, LOCALURL+"get") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) rsp, err = req.Head(ctx, LOCALURL+"get") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) rsp, err = req.Post(ctx, LOCALURL+"post") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) rsp, err = req.Put(ctx, LOCALURL+"put") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) rsp, err = req.Patch(ctx, LOCALURL+"patch") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) rsp, err = req.Delete(ctx, LOCALURL+"delete") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) rsp, err = req.Options(ctx, LOCALURL+"get") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) } func TestSetClientKey(t *testing.T) { req := New() assert.Equal(t, req.ClientKey, "") req.SetClientKey(LOCALURL) assert.Equal(t, req.ClientKey, LOCALURL) } func TestSetHost(t *testing.T) { req := New() ctx := context.Background() host := req.Request.Host req.SetHost("likexian.com") assert.Equal(t, req.Request.Host, "likexian.com") assert.NotEqual(t, req.Request.Host, host) var h Host = "likexian.com" _, _ = req.Do(ctx, "GET", LOCALURL, h) assert.Equal(t, req.Request.Host, "likexian.com") } func TestSetHeader(t *testing.T) { req := New() ctx := context.Background() author := req.GetHeader("X-Author") assert.Equal(t, author, "") req.SetHeader("X-Author", "likexian") assert.Equal(t, req.GetHeader("X-Author"), "likexian") h1 := Header{ "X-Version": Version(), } _, _ = req.Do(ctx, "GET", LOCALURL, h1) assert.Equal(t, req.GetHeader("X-Author"), "likexian") assert.Equal(t, req.GetHeader("X-Version"), Version()) h2 := http.Header{ "X-License": []string{License()}, } _, _ = req.Do(ctx, "GET", LOCALURL, h2) assert.Equal(t, req.GetHeader("X-Author"), "likexian") assert.Equal(t, req.GetHeader("X-Version"), Version()) assert.Equal(t, req.GetHeader("X-License"), License()) } func TestSetUA(t *testing.T) { req := New() ua := req.GetHeader("User-Agent") assert.Equal(t, ua, fmt.Sprintf("GoKit XHTTP Client/%s", Version())) req.SetUA("HTTP Client by likexian") assert.Equal(t, req.GetHeader("User-Agent"), "HTTP Client by likexian") } func TestSetReferer(t *testing.T) { req := New() referer := req.GetHeader("referer") assert.Equal(t, referer, "") req.SetHeader("referer", LOCALURL) assert.Equal(t, req.GetHeader("referer"), LOCALURL) req.SetReferer(LOCALURL + "test") assert.Equal(t, req.GetHeader("referer"), LOCALURL+"test") } func TestGetHeader(t *testing.T) { req := New() ctx := context.Background() rsp, err := req.Do(ctx, "GET", LOCALURL) assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) for _, k := range []string{"Content-Type", "Date", "Server"} { h := rsp.GetHeader(k) assert.NotEqual(t, h, "") } } func TestSetClient(t *testing.T) { req := New() ctx := context.Background() rsp, err := req.Do(ctx, "GET", LOCALURL, &http.Client{}) assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) } func TestBytes(t *testing.T) { req := New() ctx := context.Background() rsp, err := req.Do(ctx, "GET", LOCALURL) assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) b, err := rsp.Bytes() assert.Nil(t, err) assert.NotEqual(t, len(b), 0) assert.Equal(t, string(b[0:1]), "<") tracing := rsp.Tracing assert.NotEqual(t, tracing.Timestamp, "") assert.NotEqual(t, tracing.Nonce, "") rsp, err = req.Do(ctx, "GET", LOCALURL+"get") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) b, err = rsp.Bytes() assert.Nil(t, err) assert.NotEqual(t, len(b), 0) assert.Equal(t, string(b[0:1]), "{") assert.NotEqual(t, rsp.Tracing.Timestamp, "") assert.NotEqual(t, rsp.Tracing.Nonce, tracing.Nonce) assert.Equal(t, rsp.Tracing.ClientID, tracing.ClientID) assert.NotEqual(t, rsp.Tracing.RequestID, tracing.RequestID) tracing = rsp.Tracing req = New() rsp, err = req.Do(ctx, "GET", LOCALURL+"status/404") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 404) assert.NotEqual(t, rsp.Tracing.ClientID, tracing.ClientID) assert.NotEqual(t, rsp.Tracing.RequestID, tracing.RequestID) } func TestString(t *testing.T) { req := New() ctx := context.Background() rsp, err := req.Do(ctx, "GET", LOCALURL) assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) s, err := rsp.String() assert.Nil(t, err) assert.NotEqual(t, len(s), 0) assert.Equal(t, s[0:1], "<") rsp, err = req.Do(ctx, "GET", LOCALURL+"get") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) s, err = rsp.String() assert.Nil(t, err) assert.NotEqual(t, len(s), 0) assert.Equal(t, s[0:1], "{") } func TestJSON(t *testing.T) { req := New() ctx := context.Background() rsp, err := req.Do(ctx, "GET", LOCALURL) assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) _, err = rsp.JSON() assert.NotNil(t, err) rsp, err = req.Do(ctx, "GET", LOCALURL+"get") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) s, err := rsp.JSON() assert.Nil(t, err) assert.Equal(t, s.Get("url").MustString(""), LOCALURL+"get") } func TestFile(t *testing.T) { defer func() { os.Remove("index.html") os.Remove("get.html") os.RemoveAll("tmp") }() req := New() ctx := context.Background() rsp, err := req.Do(ctx, "GET", LOCALURL) assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) ss, err := rsp.File() assert.Nil(t, err) fs, err := xfile.Size("index.html") assert.Nil(t, err) assert.Equal(t, fs, ss) rsp, err = req.Do(ctx, "GET", LOCALURL+"get") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) ss, err = rsp.File("tmp/") assert.Nil(t, err) fs, err = xfile.Size("tmp/index.html") assert.Nil(t, err) assert.Equal(t, fs, ss) rsp, err = req.Do(ctx, "GET", LOCALURL+"get") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) ss, err = rsp.File("get.html") assert.Nil(t, err) fs, err = xfile.Size("get.html") assert.Nil(t, err) assert.Equal(t, fs, ss) rsp, err = req.Do(ctx, "GET", LOCALURL+"get") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) _, err = rsp.File("get.html") assert.NotNil(t, err) rsp, err = req.Do(ctx, "GET", LOCALURL+"status/404") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 404) _, err = rsp.File("404.html") assert.NotNil(t, err) } func TestFollowRedirect(t *testing.T) { req := New() ctx := context.Background() rsp, err := req.Do(ctx, "GET", LOCALURL+"redirect/3") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) loc := rsp.GetHeader("Location") assert.Equal(t, loc, "") req.FollowRedirect(false) rsp, err = req.Do(ctx, "GET", LOCALURL+"redirect/3") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 302) loc = rsp.GetHeader("Location") assert.Equal(t, loc, "/redirect/2") req.FollowRedirect(true) rsp, err = req.Do(ctx, "GET", LOCALURL+"redirect/3") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) loc = rsp.GetHeader("Location") assert.Equal(t, loc, "") } func TestSetGzip(t *testing.T) { req := New() ctx := context.Background() rsp, err := req.Do(ctx, "GET", LOCALURL) assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) s, err := rsp.String() assert.Nil(t, err) assert.NotEqual(t, len(s), 0) assert.Equal(t, s[0:1], "<") req.SetGzip(false) rsp, err = req.Do(ctx, "GET", LOCALURL) assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) s, err = rsp.String() assert.Nil(t, err) assert.NotEqual(t, len(s), 0) assert.Equal(t, s[0:1], "<") req.SetGzip(true) rsp, err = req.Do(ctx, "GET", LOCALURL) assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) s, err = rsp.String() assert.Nil(t, err) assert.NotEqual(t, len(s), 0) assert.Equal(t, s[0:1], "<") } func TestSetVerifyTls(t *testing.T) { req := New() assert.False(t, req.Client.Transport.(*http.Transport).TLSClientConfig.InsecureSkipVerify) req.SetVerifyTLS(false) assert.True(t, req.Client.Transport.(*http.Transport).TLSClientConfig.InsecureSkipVerify) req.SetVerifyTLS(true) assert.False(t, req.Client.Transport.(*http.Transport).TLSClientConfig.InsecureSkipVerify) } func TestSetKeepAliveTimeout(t *testing.T) { req := New() assert.False(t, req.Client.Transport.(*http.Transport).DisableKeepAlives) req.SetKeepAliveTimeout(0) assert.True(t, req.Client.Transport.(*http.Transport).DisableKeepAlives) req.SetKeepAliveTimeout(30) assert.False(t, req.Client.Transport.(*http.Transport).DisableKeepAlives) } func TestSetConnectTimeout(t *testing.T) { req := New() req.SetConnectTimeout(3) assert.Equal(t, req.GetTimeout().ConnectTimeout, 3) } func TestSetClientTimeout(t *testing.T) { req := New() req.SetClientTimeout(30) assert.Equal(t, req.Client.Timeout, time.Duration(30)*time.Second) } func TestSetTimeout(t *testing.T) { req := New() timeout := req.GetTimeout() timeout.ClientTimeout = 10 timeout.ResponseHeaderTimeout = 3 req.SetTimeout(timeout) assert.Equal(t, req.Client.Timeout, time.Duration(10)*time.Second) assert.Equal(t, req.Client.Transport.(*http.Transport).ResponseHeaderTimeout, time.Duration(3)*time.Second) } func TestSetProxy(t *testing.T) { req := New().SetProxy(func(req *http.Request) (*url.URL, error) { return url.ParseRequestURI("http://127.0.0.1:8080") }) ctx := context.Background() _, err := req.Do(ctx, "GET", LOCALURL) assert.NotNil(t, err) } func TestSetProxyUrl(t *testing.T) { req := New().SetProxyURL("127.0.0.1:8080") ctx := context.Background() _, err := req.Do(ctx, "GET", LOCALURL) assert.NotNil(t, err) } func TestEnableCookie(t *testing.T) { // not enable cookies req := New() ctx := context.Background() req.FollowRedirect(false) rsp, err := req.Do(ctx, "GET", LOCALURL+"cookies/set/k/v") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, len(req.Request.Cookies()), 0) rsp, err = req.Do(ctx, "GET", LOCALURL+"cookies") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, len(req.Request.Cookies()), 0) // enable cookies req.EnableCookie(true) rsp, err = req.Do(ctx, "GET", LOCALURL+"cookies/set/k/v") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, len(req.Request.Cookies()), 0) rsp, err = req.Do(ctx, "GET", LOCALURL+"cookies") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, len(req.Request.Cookies()), 1) // delete cookies rsp, err = req.Do(ctx, "GET", LOCALURL+"cookies/delete?k=") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, len(req.Request.Cookies()), 1) rsp, err = req.Do(ctx, "GET", LOCALURL+"cookies") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, len(req.Request.Cookies()), 0) // set cookie again req.EnableCookie(true) rsp, err = req.Do(ctx, "GET", LOCALURL+"cookies/set/k/v") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, len(req.Request.Cookies()), 0) rsp, err = req.Do(ctx, "GET", LOCALURL+"cookies") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, len(req.Request.Cookies()), 1) // disable cookies req.EnableCookie(false) rsp, err = req.Do(ctx, "GET", LOCALURL+"cookies/set/k/v") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, len(req.Request.Cookies()), 0) rsp, err = req.Do(ctx, "GET", LOCALURL+"cookies") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, len(req.Request.Cookies()), 0) // set cookies by args cookie := &http.Cookie{Name: "k", Value: "likexian"} req.EnableCookie(true) rsp, err = req.Do(ctx, "GET", LOCALURL, cookie) assert.Nil(t, err) defer rsp.Close() assert.Equal(t, len(req.Request.Cookies()), 1) } func TestQueryParam(t *testing.T) { req := New() ctx := context.Background() query := QueryParam{"k": "v"} _, err := req.Do(ctx, "GET", LOCALURL+"get", query) assert.Nil(t, err) assert.Equal(t, req.Request.URL.String(), LOCALURL+"get?k=v") query = QueryParam{"a": "1", "b": 2, "c": 3} _, err = req.Do(ctx, "GET", req.Request.URL.String(), query) assert.Nil(t, err) assert.Equal(t, req.Request.URL.String(), LOCALURL+"get?k=v&a=1&b=2&c=3") query = QueryParam{} _, err = req.Do(ctx, "GET", LOCALURL+"get", query) assert.Nil(t, err) assert.Equal(t, req.Request.URL.String(), LOCALURL+"get") } func TestFormParam(t *testing.T) { req := New() ctx := context.Background() form := FormParam{"k": "v"} rsp, err := req.Do(ctx, "POST", LOCALURL+"post", form) assert.Nil(t, err) assert.Equal(t, req.Request.URL.String(), LOCALURL+"post") json, err := rsp.JSON() assert.Nil(t, err) assert.Equal(t, json.Get("form").Get("k.0").MustString(""), "v") form = FormParam{"a": "1", "b": 2, "c": 3} rsp, err = req.Do(ctx, "POST", req.Request.URL.String(), form) assert.Nil(t, err) assert.Equal(t, req.Request.URL.String(), LOCALURL+"post") json, err = rsp.JSON() assert.Nil(t, err) assert.Equal(t, json.Get("form").Get("a.0").MustString(""), "1") assert.Equal(t, json.Get("form").Get("b.0").MustString(""), "2") assert.Equal(t, json.Get("form").Get("c.0").MustString(""), "3") form = FormParam{} rsp, err = req.Do(ctx, "POST", LOCALURL+"post", form) assert.Nil(t, err) assert.Equal(t, req.Request.URL.String(), LOCALURL+"post") json, err = rsp.JSON() assert.Nil(t, err) m, _ := json.Get("form").Map() assert.Equal(t, m, map[string]interface{}{}) data := map[string]interface{}{"a": "1", "b": 2, "c": 3} rsp, err = req.Do(ctx, "POST", LOCALURL+"post", FormParam(data)) assert.Nil(t, err) assert.Equal(t, req.Request.URL.String(), LOCALURL+"post") json, err = rsp.JSON() assert.Nil(t, err) assert.Equal(t, json.Get("form").Get("a.0").MustString(""), "1") assert.Equal(t, json.Get("form").Get("b.0").MustString(""), "2") assert.Equal(t, json.Get("form").Get("c.0").MustString(""), "3") } func TestValuesParam(t *testing.T) { req := New() ctx := context.Background() values := url.Values{"k": []string{"v"}} // url.Values as query string _, err := req.Do(ctx, "GET", LOCALURL+"get", values) assert.Nil(t, err) assert.Equal(t, req.Request.URL.String(), LOCALURL+"get?k=v") // url.Values as form data rsp, err := req.Do(ctx, "POST", LOCALURL+"post", values) assert.Nil(t, err) assert.Equal(t, req.Request.URL.String(), LOCALURL+"post") json, err := rsp.JSON() assert.Nil(t, err) assert.Equal(t, json.Get("form").Get("k.0").MustString(""), "v") } func TestPostBody(t *testing.T) { req := New() ctx := context.Background() // Post string rsp, err := req.Do(ctx, "POST", LOCALURL+"post", "k=v") assert.Nil(t, err) assert.Equal(t, req.Request.URL.String(), LOCALURL+"post") json, err := rsp.JSON() assert.Nil(t, err) assert.Equal(t, json.Get("form").Get("k.0").MustString(""), "v") // Post []byte rsp, err = req.Do(ctx, "POST", req.Request.URL.String(), []byte("a=1&b=2&c=3")) assert.Nil(t, err) assert.Equal(t, req.Request.URL.String(), LOCALURL+"post") json, err = rsp.JSON() assert.Nil(t, err) assert.Equal(t, json.Get("form").Get("a.0").MustString(""), "1") assert.Equal(t, json.Get("form").Get("b.0").MustString(""), "2") assert.Equal(t, json.Get("form").Get("c.0").MustString(""), "3") // Post bytes.Buffer var b bytes.Buffer b.Write([]byte("k=v")) rsp, err = req.Do(ctx, "POST", LOCALURL+"post", b) assert.Nil(t, err) assert.Equal(t, req.Request.URL.String(), LOCALURL+"post") json, err = rsp.JSON() assert.Nil(t, err) assert.Equal(t, json.Get("form").Get("k.0").MustString(""), "v") // Post json string rsp, err = req.Do(ctx, "POST", LOCALURL+"post", `{"k": "v"}`, Header{"Content-Type": "application/json"}) assert.Nil(t, err) assert.Equal(t, req.Request.URL.String(), LOCALURL+"post") json, err = rsp.JSON() assert.Nil(t, err) assert.Equal(t, json.Get("json").Get("k").MustString(""), "v") // Post map as json data := map[string]interface{}{"a": "1", "b": 2, "c": 3} rsp, err = req.Do(ctx, "POST", LOCALURL+"post", JSONParam(data)) assert.Nil(t, err) assert.Equal(t, req.Request.URL.String(), LOCALURL+"post") j, err := rsp.JSON() assert.Nil(t, err) assert.Equal(t, j.Get("url").MustString(""), LOCALURL+"post") assert.Equal(t, j.Get("json.a").MustString(""), "1") assert.Equal(t, j.Get("json.b").MustInt(0), 2) assert.Equal(t, j.Get("json.c").MustInt(0), 3) } func TestPostFile(t *testing.T) { req := New() ctx := context.Background() // Test post one file rsp, err := req.Do(ctx, "POST", LOCALURL+"post", FormFile{"file": "../go.mod"}) assert.Nil(t, err) defer rsp.Close() json, err := rsp.JSON() assert.Nil(t, err) assert.Contains(t, json.Get("headers.Content-Type.0").MustString(""), "multipart/form-data") assert.Contains(t, json.Get("file").Get("file").MustString(""), "module github.com/likexian/gokit") // Test post more files rsp, err = req.Do(ctx, "POST", LOCALURL+"post", FormFile{"file_0": "../go.mod"}, FormFile{"file_1": "../go.sum"}) assert.Nil(t, err) defer rsp.Close() json, err = rsp.JSON() assert.Nil(t, err) assert.Contains(t, json.Get("headers.Content-Type.0").MustString(""), "multipart/form-data") assert.Contains(t, json.Get("file").Get("file_0").MustString(""), "module github.com/likexian/gokit") assert.Contains(t, json.Get("file").Get("file_1").MustString(""), "") // Test post file and form rsp, err = req.Do(ctx, "POST", LOCALURL+"post", FormParam{"k": "v"}, FormFile{"file": "../go.mod", "404": "404.md"}) assert.Nil(t, err) defer rsp.Close() json, err = rsp.JSON() assert.Nil(t, err) assert.Contains(t, json.Get("headers.Content-Type.0").MustString(""), "multipart/form-data") assert.Contains(t, json.Get("file").Get("file").MustString(""), "module github.com/likexian/gokit") assert.Equal(t, json.Get("form").Get("k.0").MustString(""), "v") } func TestWithCancel(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) go func() { time.AfterFunc(100*time.Millisecond, cancel) }() req := New() _, err := req.Do(ctx, "GET", LOCALURL+"sleep") assert.NotNil(t, err) } func TestSetRetries(t *testing.T) { req := New() ctx := context.Background() assert.Panic(t, func() { req.SetRetries() }) // no retry (default) rsp, err := req.Do(ctx, "Get", "http://127.0.0.1:5555/") assert.NotNil(t, err) assert.Equal(t, rsp.Tracing.Retries, 0) // retry 3 times req.SetRetries(3) rsp, err = req.Do(ctx, "Get", "http://127.0.0.1:5555/") assert.NotNil(t, err) assert.Equal(t, rsp.Tracing.Retries, 3) // start http server after 3 second, then request shall success go func() { time.AfterFunc(100*time.Millisecond, func() { http.HandleFunc("/after3/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello!") }) _ = http.ListenAndServe("127.0.0.1:5555", nil) }) }() // retry until success, sleep 1 second per request req.SetRetries(-1, 100*time.Millisecond) rsp, err = req.Do(ctx, "Get", "http://127.0.0.1:5555/after3/") assert.Nil(t, err) defer rsp.Close() text, err := rsp.String() assert.Nil(t, err) assert.Equal(t, text, "Hello!") assert.Gt(t, rsp.Tracing.Retries, 0) } func TestDump(t *testing.T) { req := New() ctx := context.Background() req.SetDump(true, false) rsp, err := req.Do(ctx, "POST", LOCALURL+"post", "k=v") assert.Nil(t, err) defer rsp.Close() dump := rsp.Dump() assert.NotContains(t, string(dump[0]), "k=v") req.SetDump(true, true) rsp, err = req.Do(ctx, "POST", LOCALURL+"post", "k=v") assert.Nil(t, err) defer rsp.Close() dump = rsp.Dump() assert.Contains(t, string(dump[0]), "k=v") } func TestEnableCache(t *testing.T) { req := New() ctx := context.Background() rsp, err := req.Do(ctx, "GET", LOCALURL+"time") assert.Nil(t, err) defer rsp.Close() text, err := rsp.String() assert.Nil(t, err) assert.NotEqual(t, text, "") newRsp, err := req.Do(ctx, "GET", LOCALURL+"time") assert.Nil(t, err) defer newRsp.Close() newText, err := newRsp.String() assert.Nil(t, err) assert.NotEqual(t, newText, text) assert.NotEqual(t, newRsp.Tracing.RequestID, rsp.Tracing.RequestID) // enable get cache req.EnableCache("GET", 300) rsp, err = req.Do(ctx, "GET", LOCALURL+"time") assert.Nil(t, err) defer rsp.Close() text, err = rsp.String() assert.Nil(t, err) assert.NotEqual(t, text, "") newRsp, err = req.Do(ctx, "GET", LOCALURL+"time") assert.Nil(t, err) defer newRsp.Close() newText, err = newRsp.String() assert.Nil(t, err) assert.Equal(t, newText, text) assert.Equal(t, newRsp.Tracing.RequestID, rsp.Tracing.RequestID) newRsp, err = req.Do(ctx, "GET", LOCALURL+"time", QueryParam{"q": "a"}) assert.Nil(t, err) defer newRsp.Close() newText, err = newRsp.String() assert.Nil(t, err) assert.NotEqual(t, newText, text) assert.NotEqual(t, newRsp.Tracing.RequestID, rsp.Tracing.RequestID) // enable post cache req.EnableCache("post", 300) rsp, err = req.Do(ctx, "POST", LOCALURL+"time", QueryParam{"q": "a"}, FormParam{"d": "v", "x": "likexian"}) assert.Nil(t, err) defer rsp.Close() text, err = rsp.String() assert.Nil(t, err) assert.NotEqual(t, text, "") newRsp, err = req.Do(ctx, "POST", LOCALURL+"time", QueryParam{"q": "a"}, FormParam{"d": "v", "x": "likexian"}) assert.Nil(t, err) defer newRsp.Close() newText, err = newRsp.String() assert.Nil(t, err) assert.Equal(t, newText, text) assert.Equal(t, newRsp.Tracing.RequestID, rsp.Tracing.RequestID) newRsp, err = req.Do(ctx, "POST", LOCALURL+"time", QueryParam{"q": "a"}, FormParam{"d": "v", "x": "likexian", "q": "a"}) assert.Nil(t, err) defer newRsp.Close() newText, err = newRsp.String() assert.Nil(t, err) assert.NotEqual(t, newText, text) assert.NotEqual(t, newRsp.Tracing.RequestID, rsp.Tracing.RequestID) } func TestCheckClient(t *testing.T) { u, _ := url.Parse(LOCALURL) r := &http.Request{ Header: http.Header{}, Method: "POST", URL: u, } err := CheckClient(r, "") assert.NotNil(t, err) r.Header.Set("X-Http-Gokit-Requestid", "test") err = CheckClient(r, "") assert.NotNil(t, err) r.Header.Set("X-Http-Gokit-Requestid", "test-test-test") err = CheckClient(r, "") assert.NotNil(t, err) r.Header.Set("X-Http-Gokit-Requestid", "1234-test-test") err = CheckClient(r, "") assert.NotNil(t, err) r.Header.Set("X-Http-Gokit-Requestid", fmt.Sprintf("%d-test-test", xtime.S())) err = CheckClient(r, "") assert.NotNil(t, err) r.Header.Set("X-Http-Gokit-Requestid", fmt.Sprintf("%d-1234-test", xtime.S())) err = CheckClient(r, "") assert.NotNil(t, err) req := New() ctx := context.Background() rsp, err := req.Do(ctx, "GET", LOCALURL) assert.Nil(t, err) defer rsp.Close() err = CheckClient(req.Request, "") assert.Nil(t, err) } func TestConcurrent(t *testing.T) { var wg sync.WaitGroup ctx := context.Background() for i := 0; i < 100; i++ { wg.Add(1) go func() { defer wg.Done() req := New() req.SetHeader("X-Test-Value", "Test") rsp, err := req.Do(ctx, "GET", LOCALURL) assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) str, err := rsp.String() assert.Nil(t, err) assert.Equal(t, len(str), 128) }() } wg.Wait() } func TestGetClientIPs(t *testing.T) { u, _ := url.Parse(LOCALURL) r := &http.Request{ RemoteAddr: "127.0.0.1:1234", Header: http.Header{}, Method: "POST", URL: u, } ips := GetClientIPs(r) assert.Equal(t, ips, []string{"127.0.0.1"}) r.Header.Set("X-Real-Ip", "1.1.1.1") ips = GetClientIPs(r) assert.Equal(t, ips, []string{"1.1.1.1", "127.0.0.1"}) r.Header.Set("X-Forwarded-For", "2.2.2.2") ips = GetClientIPs(r) assert.Equal(t, ips, []string{"1.1.1.1", "2.2.2.2", "127.0.0.1"}) r.Header.Set("X-Forwarded-For", "2.2.2.2, 3.3.3.3") ips = GetClientIPs(r) assert.Equal(t, ips, []string{"1.1.1.1", "2.2.2.2", "3.3.3.3", "127.0.0.1"}) } func ServerForTesting(listen string) string { defaultListenIP := "127.0.0.1" defaultListenPort := "8080" listen = strings.TrimSpace(strings.Replace(listen, " ", "", -1)) listen = strings.Trim(listen, ":") if !strings.Contains(listen, ":") { if len(listen) == 0 { listen = fmt.Sprintf("%s:%s", defaultListenIP, defaultListenPort) } else if len(listen) < 5 { listen = fmt.Sprintf("%s:%s", defaultListenIP, listen) } else { listen = fmt.Sprintf("%s:%s", listen, defaultListenPort) } } go func() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html") fmt.Fprintf(w, ``+ `HTTP Server For TestingHello Testing!`) }) http.HandleFunc("/get", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") type Result struct { Args url.Values `json:"args"` Headers http.Header `json:"headers"` Origin string `json:"origin"` URL string `json:"url"` } result := Result{ Args: r.URL.Query(), Headers: r.Header, Origin: strings.Split(r.RemoteAddr, ":")[0], URL: fmt.Sprintf("http://%s%s", r.Host, r.URL.String()), } text, _ := xjson.Dumps(result) fmt.Fprint(w, text) }) http.HandleFunc("/post", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") type Result struct { Args url.Values `json:"args"` Form url.Values `json:"form"` JSON map[string]interface{} `json:"json"` File map[string]string `json:"file"` Headers http.Header `json:"headers"` Origin string `json:"origin"` URL string `json:"url"` } result := Result{ Args: r.URL.Query(), Headers: r.Header, Form: url.Values{}, JSON: map[string]interface{}{}, File: map[string]string{}, Origin: strings.Split(r.RemoteAddr, ":")[0], URL: fmt.Sprintf("http://%s%s", r.Host, r.URL.String()), } if r.Header.Get("Content-Type") == "application/json" { body, _ := ioutil.ReadAll(r.Body) json, _ := xjson.Loads(string(body)) result.JSON, _ = json.Map() } else { err := r.ParseMultipartForm(32 << 20) if err != nil { result.Form = r.PostForm } else { result.Form = r.MultipartForm.Value for k, v := range r.MultipartForm.File { for _, f := range v { fd, err := f.Open() if err == nil { ss, _ := ioutil.ReadAll(fd) result.File[k] = string(ss) } } } } } text, _ := xjson.Dumps(result) fmt.Fprint(w, text) }) http.HandleFunc("/put", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html") }) http.HandleFunc("/patch", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html") }) http.HandleFunc("/delete", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html") }) http.HandleFunc("/cookies", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") type Result struct { Cookies map[string]string `json:"cookies"` } result := Result{ Cookies: map[string]string{}, } for _, v := range r.Cookies() { result.Cookies[v.Name] = v.Value } text, _ := xjson.Dumps(result) fmt.Fprint(w, text) }) http.HandleFunc("/cookies/set/", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html") l := r.URL.String()[13:] ls := strings.Split(l, "/") if len(ls) > 1 { cookie := http.Cookie{Name: ls[0], Value: ls[1], Path: "/"} http.SetCookie(w, &cookie) } http.Redirect(w, r, "/cookies", http.StatusFound) }) http.HandleFunc("/cookies/delete", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html") for k := range r.URL.Query() { cookie := http.Cookie{Name: k, Value: "", Path: "/", MaxAge: -1} http.SetCookie(w, &cookie) } http.Redirect(w, r, "/cookies", http.StatusFound) }) http.HandleFunc("/redirect/", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html") s := "/get" l := r.URL.String()[10:] if len(l) > 0 { n, err := assert.ToInt64(l) if err == nil && n > 1 { s = fmt.Sprintf("/redirect/%d", n-1) } } http.Redirect(w, r, s, http.StatusFound) }) http.HandleFunc("/status/", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html") s := 200 l := r.URL.String()[8:] if len(l) > 0 { n, err := assert.ToInt64(l) if err == nil && n > 0 { s = int(n) } } w.WriteHeader(s) }) http.HandleFunc("/time", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "%d", time.Now().UnixNano()) }) http.HandleFunc("/sleep", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html") time.Sleep(1 * time.Second) }) _ = http.ListenAndServe(listen, GzWrap(SetHeaderWrap(http.DefaultServeMux, Header{"Server": "Testing"}))) }() req := New() for { _, err := req.Do(context.Background(), "GET", fmt.Sprintf("http://%s/", listen)) if err == nil { break } } return fmt.Sprintf("http://%s/", listen) } gokit-0.25.9/xhuman/000077500000000000000000000000001426246334200142325ustar00rootroot00000000000000gokit-0.25.9/xhuman/README.md000066400000000000000000000021711426246334200155120ustar00rootroot00000000000000# GoKit - xhuman Human kits for Golang development. ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/xhuman" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xhuman) ## Example ### Get human string for bytes size ```go // print 1024 * 1024 as 1MB stringSize := xhuman.FormatByteSize(1024 * 1024) fmt.Println("formated bytes size is:", stringSize) ``` ### Get bytes size from human string ```go // get 1024 * 1024 from 1MB byteSize, err := xhuman.ParseByteSize("1MB") if err != nil { fmt.Println("original bytes size is:", byteSize) } ``` ### Get comma split string for number ```go // print 123456789123456 as "123,456,789,123,456" s := xhuman.Comma(float64(123456789123456), 0) if err != nil { fmt.Println("comma number:", s) } ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/xhuman/xhuman.go000066400000000000000000000067411426246334200160710ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xhuman import ( "errors" "math" "strconv" "strings" "unicode" ) // Bytes unit convert const ( B = 1 << (10 * iota) KB MB GB TB PB EB ) var ( // ErrStringByteSizeInvalid is string of byte size invalid error ErrStringByteSizeInvalid = errors.New("xhuman: string of byte size is invalid") ) // Version returns package version func Version() string { return "0.2.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // FormatByteSize returns human string of byte size func FormatByteSize(n int64, precision int) string { value, unit := float64(n), "B" switch { case value >= EB: value, unit = value/EB, "EB" case value >= PB: value, unit = value/PB, "PB" case value >= TB: value, unit = value/TB, "TB" case value >= GB: value, unit = value/GB, "GB" case value >= MB: value, unit = value/MB, "MB" case value >= KB: value, unit = value/KB, "KB" } r := strconv.FormatFloat(value, 'f', precision, 64) r += unit return r } // ParseByteSize returns int size of string size func ParseByteSize(s string) (int64, error) { s = strings.TrimSpace(strings.ToUpper(s)) i := strings.IndexFunc(s, unicode.IsLetter) if i == -1 { i = len(s) s += "B" } value, unit := strings.TrimSpace(s[:i]), strings.TrimSpace(s[i:]) bytes, err := strconv.ParseFloat(value, 64) if err != nil { return 0, err } if bytes < 0 { return 0, ErrStringByteSizeInvalid } switch unit { case "E", "EB": return int64(bytes * EB), nil case "P", "PB": return int64(bytes * PB), nil case "T", "TB": return int64(bytes * TB), nil case "G", "GB": return int64(bytes * GB), nil case "M", "MB": return int64(bytes * MB), nil case "K", "KB": return int64(bytes * KB), nil case "B": return int64(bytes * B), nil default: return 0, ErrStringByteSizeInvalid } } // Round returns round number with precision func Round(n float64, precision int) (r float64) { pow := math.Pow(10, float64(precision)) num := n * pow _, div := math.Modf(num) if n >= 0 && div >= 0.5 { r = math.Ceil(num) } else if n < 0 && div > -0.5 { r = math.Ceil(num) } else { r = math.Floor(num) } return r / pow } // Comma returns number string with comma func Comma(n float64, precision int) string { s := strconv.FormatFloat(n, 'f', precision, 64) sc := "" if s[0] == '-' { sc = "-" s = s[1:] } si, sf := s, "" if strings.Contains(s, ".") { ss := strings.Split(s, ".") si, sf = ss[0], ss[1] } ss := []string{} for { if len(si) == 0 { break } else { start := len(si) - 3 if start < 0 { start = 0 } ss = append([]string{si[start:]}, ss...) si = si[:start] } } s = sc + strings.Join(ss, ",") if sf != "" { s += "." + sf } return s } gokit-0.25.9/xhuman/xhuman_test.go000066400000000000000000000114601426246334200171220ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xhuman import ( "testing" "github.com/likexian/gokit/assert" ) func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } func TestFormatByteSize(t *testing.T) { tests := []struct { in int64 p int out string }{ {0, 2, "0.00B"}, {B, 2, "1.00B"}, {KB, 2, "1.00KB"}, {MB, 2, "1.00MB"}, {GB, 2, "1.00GB"}, {TB, 2, "1.00TB"}, {PB, 2, "1.00PB"}, {EB, 2, "1.00EB"}, {100 * TB, 2, "100.00TB"}, {1024 * GB, 2, "1.00TB"}, {GB + MB, 2, "1.00GB"}, {GB + 10*MB, 2, "1.01GB"}, {GB + 100*MB, 2, "1.10GB"}, {GB + 1000*MB, 2, "1.98GB"}, {GB + 1024*MB, 2, "2.00GB"}, {TB + 1000*GB, 0, "2TB"}, {TB + 1000*GB, 1, "2.0TB"}, {TB + 1000*GB, 2, "1.98TB"}, {TB + 1000*GB, 3, "1.977TB"}, {TB + 1000*GB, 4, "1.9766TB"}, {TB + 1000*GB, 5, "1.97656TB"}, {TB + 1000*GB, 6, "1.976562TB"}, {TB + 1000*GB, 7, "1.9765625TB"}, {TB + 1000*GB, 8, "1.97656250TB"}, {TB + 1000*GB, 9, "1.976562500TB"}, {TB + 1000*GB, 10, "1.9765625000TB"}, {1024 * 1024 * 1024 * 1024 * 1024, 0, "1PB"}, } for _, v := range tests { vv := FormatByteSize(v.in, v.p) assert.Equal(t, vv, v.out) } } func TestParseByteSize(t *testing.T) { tests := []struct { in string out int64 }{ {"0", 0}, {"10", 10}, {"0B", 0}, {"1B", B}, {"1KB", KB}, {"1MB", MB}, {"1GB", GB}, {"1TB", TB}, {"1PB", PB}, {"1EB", EB}, {"1 EB", EB}, {"1.0EB", EB}, {"1.00EB", EB}, {"1 G", GB}, {"1 GB", GB}, {"1.0 G", GB}, {"1.00 GB", GB}, {"0.1 KB", 102}, {"0.10 KB", 102}, {"100GB", 100 * GB}, {"100 GB", 100 * GB}, } for _, v := range tests { vv, err := ParseByteSize(v.in) assert.Nil(t, err) assert.Equal(t, vv, v.out) } for _, v := range []string{"K", "KB", "-1", "-1K", "-1KB", "1AB", "1 K B"} { _, err := ParseByteSize(v) assert.NotNil(t, err) } } func TestRound(t *testing.T) { tests := []struct { in float64 p int out float64 }{ {0, 0, 0}, {1, 0, 1}, {0, 1, 0}, {1, 1, 1}, {0, 2, 0}, {1, 2, 1}, {1.0, 0, 1}, {1.4, 0, 1}, {1.5, 0, 2}, {1.6, 0, 2}, {1.0, 1, 1.0}, {1.4, 1, 1.4}, {1.5, 1, 1.5}, {1.6, 1, 1.6}, {1.0, 2, 1.00}, {1.4, 2, 1.40}, {1.5, 2, 1.50}, {1.6, 2, 1.60}, {-1, 0, -1}, {-1.4, 0, -1}, {-1.5, 0, -2}, {-1.6, 0, -2}, {-1, 1, -1.0}, {-1.4, 1, -1.4}, {-1.5, 1, -1.5}, {-1.6, 1, -1.6}, {-1, 2, -1.00}, {-1.4, 2, -1.40}, {-1.5, 2, -1.50}, {-1.6, 2, -1.60}, } for _, v := range tests { vv := Round(v.in, v.p) assert.Equal(t, vv, v.out) } } func TestComma(t *testing.T) { tests := []struct { in float64 p int out string }{ {0, 0, "0"}, {10, 0, "10"}, {100, 0, "100"}, {1000, 0, "1,000"}, {10000, 0, "10,000"}, {100000, 0, "100,000"}, {1000000, 0, "1,000,000"}, {10000000, 0, "10,000,000"}, {100000000, 0, "100,000,000"}, {1000000000, 0, "1,000,000,000"}, {10000000000, 0, "10,000,000,000"}, {100000000000, 0, "100,000,000,000"}, {1000000000000, 0, "1,000,000,000,000"}, {1000000000000.1, 0, "1,000,000,000,000"}, {1000000000000.1, 1, "1,000,000,000,000.1"}, {1000000000000.1, 2, "1,000,000,000,000.10"}, {1000000000000.1, 3, "1,000,000,000,000.100"}, {1000000000000.1, 4, "1,000,000,000,000.1000"}, {123456789123456.1, 0, "123,456,789,123,456"}, {123456789123456.1, 1, "123,456,789,123,456.1"}, {0, 0, "0"}, {-10, 0, "-10"}, {-100, 0, "-100"}, {-1000, 0, "-1,000"}, {-10000, 0, "-10,000"}, {-100000, 0, "-100,000"}, {-1000000, 0, "-1,000,000"}, {-10000000, 0, "-10,000,000"}, {-100000000, 0, "-100,000,000"}, {-1000000000, 0, "-1,000,000,000"}, {-10000000000, 0, "-10,000,000,000"}, {-100000000000, 0, "-100,000,000,000"}, {-1000000000000, 0, "-1,000,000,000,000"}, {-1000000000000.1, 0, "-1,000,000,000,000"}, {-1000000000000.1, 1, "-1,000,000,000,000.1"}, {-1000000000000.1, 2, "-1,000,000,000,000.10"}, {-1000000000000.1, 3, "-1,000,000,000,000.100"}, {-1000000000000.1, 4, "-1,000,000,000,000.1000"}, {-123456789123456.1, 0, "-123,456,789,123,456"}, {-123456789123456.1, 1, "-123,456,789,123,456.1"}, } for _, v := range tests { vv := Comma(v.in, v.p) assert.Equal(t, vv, v.out) } } gokit-0.25.9/xip/000077500000000000000000000000001426246334200135325ustar00rootroot00000000000000gokit-0.25.9/xip/README.md000066400000000000000000000015651426246334200150200ustar00rootroot00000000000000# GoKit - xip IP kits for Golang development. ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/xip" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xip) ## Example ### Check string is a valid ip ```go ok := xip.IsIP("1.1.1.1") fmt.Println("1.1.1.1 is a ip:", ok) ``` ### IPv4 ip2long ```go i, err := IPv4ToLong("1.1.1.1") if err == nil { fmt.Println("1.1.1.1 ip2long is:", i) } ``` ### IPv4 long2ip ```go ip := LongToIPv4(16843009) fmt.Println("16843009 long2ip is:", ip) ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/xip/xip.go000066400000000000000000000127441426246334200146710ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xip import ( "encoding/binary" "encoding/hex" "errors" "fmt" "net" "strconv" "strings" ) var ( // ErrInvalidIP ip value is invalid ErrInvalidIP = errors.New("xip: not valid ip string") // ErrInvalidMask ip mask value is invalid ErrInvalidMask = errors.New("xip: not valid ip mask") // ErrInvalidHex hex string is invalid ErrInvalidHex = errors.New("xip: not valid hex string") ) // PrivateIPs is private ip var PrivateIPs = []string{ "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "100.64.0.0/10", "fc00::/7", } // Version returns package version func Version() string { return "0.5.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // IsIP returns if string is a ip func IsIP(ip string) bool { return net.ParseIP(ip) != nil } // IsIPv4 returns if string is a ipv4 func IsIPv4(ip string) bool { if !strings.Contains(ip, ".") { return false } return IsIP(ip) } // IsIPv6 returns if string is a ipv6 func IsIPv6(ip string) bool { if !strings.Contains(ip, ":") { return false } return IsIP(ip) } // IPv4ToLong returns uint32 of ip, -1 for error func IPv4ToLong(ip string) (uint32, error) { if !IsIPv4(ip) { return 0, ErrInvalidIP } return binary.BigEndian.Uint32(net.ParseIP(ip).To4()), nil } // LongToIPv4 returns string from uint32 of ip func LongToIPv4(ip uint32) string { buf := make([]byte, 4) binary.BigEndian.PutUint32(buf, ip) s := net.IP(buf) return s.String() } // Uint32ToHex returns hex from uint32 func Uint32ToHex(i uint32) string { buf := make([]byte, 4) binary.BigEndian.PutUint32(buf, i) return hex.EncodeToString(buf) } // HexToUint32 returns uint32 from hex string func HexToUint32(s string) (uint32, error) { buf, err := hex.DecodeString(s) if err != nil { return 0, ErrInvalidHex } return binary.BigEndian.Uint32(buf), nil } // GetEthIPv4 returns all interface ipv4 without loopback func GetEthIPv4() (ips []string, err error) { addrs, err := net.InterfaceAddrs() if err != nil { return } for _, v := range addrs { if ipnet, ok := v.(*net.IPNet); ok && !ipnet.IP.IsLoopback() && ipnet.IP.To4() != nil { if IsIPv4(ipnet.IP.String()) { ips = append(ips, ipnet.IP.String()) } } } return } // GetEthIPv4ByInterface returns interface ipv4 by name func GetEthIPv4ByInterface(name string) (ips []string, err error) { iface, err := net.InterfaceByName(name) if err != nil { return } addrs, err := iface.Addrs() if err != nil { return } for _, v := range addrs { if ipnet, ok := v.(*net.IPNet); ok && ipnet.IP.To4() != nil { if IsIPv4(ipnet.IP.String()) { ips = append(ips, ipnet.IP.String()) } } } return } // GetEthIPv6 returns all interface ipv6 without loopback func GetEthIPv6() (ips []string, err error) { addrs, err := net.InterfaceAddrs() if err != nil { return } for _, v := range addrs { if ipnet, ok := v.(*net.IPNet); ok && !ipnet.IP.IsLoopback() && ipnet.IP.To16() != nil { if IsIPv6(ipnet.IP.String()) { ips = append(ips, ipnet.IP.String()) } } } return } // GetEthIPv6ByInterface returns interface ipv6 by name func GetEthIPv6ByInterface(name string) (ips []string, err error) { iface, err := net.InterfaceByName(name) if err != nil { return } addrs, err := iface.Addrs() if err != nil { return } for _, v := range addrs { if ipnet, ok := v.(*net.IPNet); ok && ipnet.IP.To16() != nil { if IsIPv6(ipnet.IP.String()) { ips = append(ips, ipnet.IP.String()) } } } return } // IsContains returns if ip is in cidr func IsContains(cidr, ip string) bool { _, ipnet, err := net.ParseCIDR(cidr) if err != nil { return false } ipaddr := net.ParseIP(ip) if ipaddr == nil { return false } return ipnet.Contains(ipaddr) } // IsPrivate return ip is private func IsPrivate(ip string) bool { ipaddr := net.ParseIP(ip) if ipaddr == nil { return false } if ipaddr.IsLoopback() || ipaddr.IsLinkLocalMulticast() || ipaddr.IsLinkLocalUnicast() { return true } for _, v := range PrivateIPs { if IsContains(v, ip) { return true } } return false } // FixSubnet fix ip with a subnet mask, for example: 1.2.3.4/24, 2001:700:300::/48 func FixSubnet(ip string) (string, error) { ips := strings.Split(ip, "/") if len(ips) == 1 { ips = append(ips, "-1") } ips[1] = strings.TrimSpace(ips[1]) if ips[1] == "" { ips[1] = "-1" } mask, err := strconv.Atoi(ips[1]) if err != nil { return "", ErrInvalidMask } ip = strings.TrimSpace(ips[0]) if IsIPv4(ip) { if mask > 32 { return "", ErrInvalidMask } if mask < 0 { mask = 24 } } else if IsIPv6(ip) { if mask > 128 { return "", ErrInvalidMask } if mask < 0 { mask = 56 } } else { return "", ErrInvalidIP } return fmt.Sprintf("%s/%d", ip, mask), nil } gokit-0.25.9/xip/xip_test.go000066400000000000000000000153311426246334200157230ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xip import ( "testing" "github.com/likexian/gokit/assert" ) func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } func TestIsIP(t *testing.T) { tests := []struct { in string out bool }{ {"", false}, {"1.1.1.256", false}, {"1.1.1.1:80", false}, {"1.1.1.s", false}, {"i.a.m.s", false}, {"0.0.0.0", true}, {"1.1.1.1", true}, {"127.0.0.1", true}, {"255.255.255.255", true}, {"::1", true}, {"2404:6800:4005:806::2004", true}, {"2001:db8:0:1:1:1:1:1", true}, {"::FFFF:1:1", true}, {"::FFFF:1.1.1.1", true}, {"2001:db8:0:0:0:0:2:1", true}, {"2001:db8::2:1", true}, {"2001:db8::2:1:12345", false}, {"2001:db8::2:1::1", false}, {"2001:db8::2:1:ss", false}, {"1:1:1:1:1:1:1:1:80", false}, } for _, v := range tests { assert.Equal(t, IsIP(v.in), v.out) } } func TestIsIPv4(t *testing.T) { tests := []struct { in string out bool }{ {"", false}, {"1.1.1.256", false}, {"1.1.1.1:80", false}, {"1.1.1.s", false}, {"i.a.m.s", false}, {"0.0.0.0", true}, {"1.1.1.1", true}, {"127.0.0.1", true}, {"255.255.255.255", true}, {"::1", false}, {"2404:6800:4005:806::2004", false}, {"2001:db8:0:1:1:1:1:1", false}, {"::FFFF:1:1", false}, {"::FFFF:1.1.1.1", true}, {"2001:db8:0:0:0:0:2:1", false}, {"2001:db8::2:1", false}, {"2001:db8::2:1:12345", false}, {"2001:db8::2:1::1", false}, {"2001:db8::2:1:ss", false}, {"1:1:1:1:1:1:1:1:80", false}, } for _, v := range tests { assert.Equal(t, IsIPv4(v.in), v.out) } } func TestIsIPv6(t *testing.T) { tests := []struct { in string out bool }{ {"", false}, {"1.1.1.256", false}, {"1.1.1.1:80", false}, {"1.1.1.s", false}, {"i.a.m.s", false}, {"0.0.0.0", false}, {"1.1.1.1", false}, {"127.0.0.1", false}, {"255.255.255.255", false}, {"::1", true}, {"2404:6800:4005:806::2004", true}, {"2001:db8:0:1:1:1:1:1", true}, {"::FFFF:1:1", true}, {"::FFFF:1.1.1.1", true}, {"2001:db8:0:0:0:0:2:1", true}, {"2001:db8::2:1", true}, {"2001:db8::2:1:12345", false}, {"2001:db8::2:1::1", false}, {"2001:db8::2:1:ss", false}, {"1:1:1:1:1:1:1:1:80", false}, } for _, v := range tests { assert.Equal(t, IsIPv6(v.in), v.out) } } func TestIPv4ToLong(t *testing.T) { tests := []struct { in string out uint32 err error }{ {"", uint32(0), ErrInvalidIP}, {"1.1.1.256", uint32(0), ErrInvalidIP}, {"1.1.1.1:80", uint32(0), ErrInvalidIP}, {"1.1.1.s", uint32(0), ErrInvalidIP}, {"i.a.m.s", uint32(0), ErrInvalidIP}, {"0.0.0.0", 0, nil}, {"1.1.1.1", 16843009, nil}, {"127.0.0.1", 2130706433, nil}, {"255.255.255.255", 4294967295, nil}, {"::1", uint32(0), ErrInvalidIP}, {"2404:6800:4005:806::2004", uint32(0), ErrInvalidIP}, } for _, v := range tests { vv, err := IPv4ToLong(v.in) assert.Equal(t, err, v.err) assert.Equal(t, vv, v.out) } } func TestLongToIPv4(t *testing.T) { tests := []struct { in uint32 out string }{ {0, "0.0.0.0"}, {16843009, "1.1.1.1"}, {2130706433, "127.0.0.1"}, {4294967295, "255.255.255.255"}, } for _, v := range tests { vv := LongToIPv4(v.in) assert.Equal(t, vv, v.out) } } func TestUint32ToHex(t *testing.T) { tests := []struct { in uint32 out string }{ {0, "00000000"}, {16843009, "01010101"}, {2130706433, "7f000001"}, {4294967295, "ffffffff"}, } for _, v := range tests { vv := Uint32ToHex(v.in) assert.Equal(t, vv, v.out) } } func TestHexToUint32(t *testing.T) { tests := []struct { in string out uint32 err error }{ {"00000000", 0, nil}, {"01010101", 16843009, nil}, {"7f000001", 2130706433, nil}, {"ffffffff", 4294967295, nil}, {"s", 0, ErrInvalidHex}, } for _, v := range tests { vv, err := HexToUint32(v.in) assert.Equal(t, err, v.err) assert.Equal(t, vv, v.out) } } func TestGetEthIPv4(t *testing.T) { ips, err := GetEthIPv4() assert.Nil(t, err) assert.Gt(t, len(ips), 0) } func TestGetEthIPv4ByInterface(t *testing.T) { ips, err := GetEthIPv4ByInterface("lo") if err != nil { ips, err = GetEthIPv4ByInterface("lo0") } assert.Nil(t, err) assert.Gt(t, len(ips), 0) } func TestGetEthIPv6(t *testing.T) { _, err := GetEthIPv6() assert.Nil(t, err) // assert.Gt(t, len(ips), 0) } func TestGetEthIPv6ByInterface(t *testing.T) { _, err := GetEthIPv6ByInterface("lo") if err != nil { _, err = GetEthIPv6ByInterface("lo0") } assert.Nil(t, err) // assert.Gt(t, len(ips), 0) } func TestIsContains(t *testing.T) { tests := []struct { cidr string ip string out bool }{ {"1", "1", false}, {"1.1.1.1", "1", false}, {"1.1.1.1", "1.1.1.1", false}, {"1.1.1.0/24", "1.1.1", false}, {"1.1.1.0/24", "1.1.1.1", true}, {"1.1.1.0/24", "1.1.2.1", false}, {"2404:6800:4005:806::0", "2404:6800:4005:806::0", false}, {"2404:6800:4005:806::0/64", "2404:6800:4005:806::0", true}, {"2404:6800:4005:806::0/64", "2404:6800:4005:807::0", false}, } for _, v := range tests { assert.Equal(t, IsContains(v.cidr, v.ip), v.out) } } func TestIsPrivate(t *testing.T) { tests := []struct { ip string out bool }{ {"1", false}, {"127.0.0.1", true}, {"10.0.0.0", true}, {"192.168.1.1", true}, {"100.64.1.1", true}, {"0.0.0.0", false}, {"1.1.1.1", false}, {"fc00::1", true}, {"2404:6800:4005:806::0", false}, } for _, v := range tests { assert.Equal(t, IsPrivate(v.ip), v.out, v) } } func TestFixSubnet(t *testing.T) { tests := []struct { ip string out string err error }{ {"", "", ErrInvalidIP}, {"1.1.1", "", ErrInvalidIP}, {"1.1.1.1", "1.1.1.1/24", nil}, {"1.1.1.1/", "1.1.1.1/24", nil}, {"1.1.1.1/25", "1.1.1.1/25", nil}, {"1.1.1.1/-1", "1.1.1.1/24", nil}, {"1.1.1.1/33", "", ErrInvalidMask}, {"1.1.1.1/x", "", ErrInvalidMask}, {"fc00", "", ErrInvalidIP}, {"fc00::1", "fc00::1/56", nil}, {"fc00::1/", "fc00::1/56", nil}, {"fc00::1/57", "fc00::1/57", nil}, {"fc00::1/-1", "fc00::1/56", nil}, {"fc00::1/129", "", ErrInvalidMask}, {"fc00::1/x", "", ErrInvalidMask}, } for _, v := range tests { vv, err := FixSubnet(v.ip) assert.Equal(t, err, v.err) assert.Equal(t, vv, v.out) } } gokit-0.25.9/xjson/000077500000000000000000000000001426246334200140735ustar00rootroot00000000000000gokit-0.25.9/xjson/README.md000066400000000000000000000036461426246334200153630ustar00rootroot00000000000000# GoKit - xjson JSON kits for Golang development. ## Features - Easy load to json and dump to string - Load and dump with file is supported - Modify the json data is simple - One line retrieval with MustXXX - Get by dot notation key is supported ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/xjson" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xjson) ## Example ### Dump the struct data to JSON string ```go // Define Status struct type Status struct { Code int64 `json:"code"` Message string `json:"message"` } // Init status status := Status{1, "Success"} // Dump status to json string j := xjson.New(status) s, err := j.Dumps() if err == nil { fmt.Println("JSON text is:", s) } // OR dumps using the easy way s, err := xjson.Dumps(status) if err == nil { fmt.Println("JSON text is:", s) } ``` ### Dump the map data to JSON string ```go // Init a map data data := map[string]interface{}{ "code": 1, "message": "success", "result": { "Name": "Li Kexian" } } // Dump to string in the easy way s, err := xjson.Dumps(status) if err == nil { fmt.Println("JSON text is:", s) } ``` ### Load the JSON string ```go // JSON strig text := `{"Code": 1, "Message": "Success", "Result": {"Student": [{"Name": "Li Kexian"}]}}` // Load json string j, err := xjson.Loads(text) if err == nil { fmt.Println("Code is:", j.Get("Code").MustInt(0)) fmt.Println("Message is:", j.Get("Message").MustString("")) fmt.Println("First Student name is:", j.Get("Result.Student.0.Name").MustString("-")) } ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/xjson/xjson.go000066400000000000000000000375331426246334200155760ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xjson import ( "bytes" "encoding/json" "errors" "reflect" "strconv" "strings" "time" "github.com/likexian/gokit/xfile" ) var ( // ErrTooManyArguments is too many arguments error ErrTooManyArguments = errors.New("xjson: too many arguments") // ErrInvalidArgumentType is invalid argument type error ErrInvalidArgumentType = errors.New("xjson: argument type is invalid") // ErrInvalidValueType is invalid value type error ErrInvalidValueType = errors.New("xjson: valud type is invalid") // ErrAssertToMap is assert to map error ErrAssertToMap = errors.New("xjson: assert to map failed") // ErrAssertToArray is assert to array error ErrAssertToArray = errors.New("xjson: assert to array failed") // ErrAssertToBool is assert to bool error ErrAssertToBool = errors.New("xjson: assert to bool failed") // ErrAssertToString is assert to string error ErrAssertToString = errors.New("xjson: assert to string failed") // ErrAssertToStringArray is assert to string array error ErrAssertToStringArray = errors.New("xjson: assert to string array failed") ) // JSON storing json data type JSON struct { data interface{} escapeHTML bool } // Version returns package version func Version() string { return "0.15.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // New returns a pointer to a new JSON object // data_json := New() // data_json := New(type Data struct{data string}{"zzz"}) // data_json := New(map[string]interface{}{"iam": "Li Kexian"}) func New(args ...interface{}) *JSON { switch len(args) { case 1: return &JSON{ data: args[0], } default: return &JSON{ data: make(map[string]interface{}), } } } // Load loads data from file, returns a json object func Load(path string) (*JSON, error) { j := New() err := j.Load(path) return j, err } // Loads unmarshal json from string, returns json object func Loads(text string) (*JSON, error) { j := New() err := j.Loads(text) return j, err } // Dump dumps json object to a file func Dump(path string, data interface{}) error { return New(data).Dump(path) } // Dumps marshal json object to string func Dumps(data interface{}) (string, error) { return New(data).Dumps() } // PrettyDumps marshal json object to string, with identation func PrettyDumps(data interface{}) (string, error) { return New(data).PrettyDumps() } // Load loads data from file, returns a json object func (j *JSON) Load(path string) error { text, err := xfile.ReadText(path) if err != nil { return err } return j.Loads(text) } // Loads unmarshal json from string, returns json object func (j *JSON) Loads(text string) error { dec := json.NewDecoder(bytes.NewBuffer([]byte(text))) dec.UseNumber() err := dec.Decode(&j.data) return err } // Dump dumps json object to a file func (j *JSON) Dump(path string) (err error) { result, err := j.PrettyDumps() if err != nil { return } return xfile.WriteText(path, result) } // Dumps marshal json object to string func (j *JSON) Dumps() (result string, err error) { return j.doDumps("") } // PrettyDumps marshal json object to string, with identation func (j *JSON) PrettyDumps() (result string, err error) { return j.doDumps(strings.Repeat(" ", 4)) } // do marshal json to string func (j *JSON) doDumps(indent string) (result string, err error) { var buf bytes.Buffer enc := json.NewEncoder(&buf) enc.SetEscapeHTML(j.escapeHTML) enc.SetIndent("", indent) err = enc.Encode(j.data) if err != nil { return } result = buf.String() result = strings.TrimSpace(result) return } // SetHTMLEscape set html escape for escaping of <, >, and & in JSON strings func (j *JSON) SetHTMLEscape(escape bool) { j.escapeHTML = escape } // Set set key-value to json object, dot(.) separated key is supported // json.Set("status", 1) // json.Set("status.code", 1) // ! NOT SUPPORTED json.Set("result.intlist.3", 666) func (j *JSON) Set(key string, value interface{}) { key = strings.TrimSpace(key) if key == "" { j.data = value return } result, err := j.Map() if err != nil { return } keys := strings.Split(key, ".") for i := 0; i < len(keys)-1; i++ { v := strings.TrimSpace(keys[i]) if v != "" { if _, ok := result[v]; !ok { result[v] = make(map[string]interface{}) } result = result[v].(map[string]interface{}) } } result[keys[len(keys)-1]] = value } // Del delete key-value from json object, dot(.) separated key is supported // json.Del("status") // json.Del("status.code") // ! NOT SUPPORTED json.Del("result.intlist.3") func (j *JSON) Del(key string) { result, err := j.Map() if err != nil { return } var ok bool keys := strings.Split(key, ".") for i := 0; i < len(keys)-1; i++ { v := strings.TrimSpace(keys[i]) if v != "" { if _, ok = result[v]; !ok { return } result, ok = result[v].(map[string]interface{}) if !ok { return } } } delete(result, keys[len(keys)-1]) } // Has check json object has key, dot(.) separated key is supported // json.Has("status") // json.Has("status.code") // json.Has("result.intlist.3") func (j *JSON) Has(key string) bool { result := j keys := strings.Split(key, ".") for i := 0; i < len(keys); i++ { v := strings.TrimSpace(keys[i]) if v != "" { tmp, err := result.Map() if err == nil { if _, ok := tmp[v]; !ok { return false } if i == len(keys)-1 { return true } result = result.Get(v) } else { tmp, err := result.Array() if err == nil { n, err := strconv.Atoi(v) if err != nil { return false } if n >= len(tmp) { return false } if i == len(keys)-1 { return true } result = result.Index(n) } else { return false } } } } return false } // Get returns the pointer to json object by key, dot(.) separated key is supported // json.Get("status").Int() // json.Get("status.code").Int() // json.Get("result.intlist.3").Int() func (j *JSON) Get(key string) *JSON { result := j for _, v := range strings.Split(key, ".") { v = strings.TrimSpace(v) if v != "" { tmp, err := result.Map() if err == nil { if _, ok := tmp[v]; ok { result = &JSON{tmp[v], j.escapeHTML} } else { return &JSON{nil, j.escapeHTML} } } else { _, err := result.Array() if err == nil { i, err := strconv.Atoi(v) if err != nil { return &JSON{nil, j.escapeHTML} } result = result.Index(i) } else { return &JSON{nil, j.escapeHTML} } } } } return result } // Index returns a pointer to the index of json object // json.Get("int_list").Index(1).Int() func (j *JSON) Index(i int) *JSON { data, err := j.Array() if err == nil { if len(data) > i { return &JSON{data[i], j.escapeHTML} } } return &JSON{nil, j.escapeHTML} } // Len returns len of json object, -1 if type invalid or error func (j *JSON) Len() int { if v, err := j.Map(); err == nil { return len(v) } if v, err := j.Array(); err == nil { return len(v) } if v, err := j.String(); err == nil { return len(v) } return -1 } // IsMap returns json object is a map func (j *JSON) IsMap() bool { switch j.data.(type) { case map[string]interface{}: return true default: return false } } // IsArray returns json object is an array func (j *JSON) IsArray() bool { switch j.data.(type) { case []interface{}: return true default: return false } } // Map returns as map from json object func (j *JSON) Map() (result map[string]interface{}, err error) { result, ok := (j.data).(map[string]interface{}) if !ok { err = ErrAssertToMap } return } // Array returns as array from json object func (j *JSON) Array() (result []interface{}, err error) { result, ok := (j.data).([]interface{}) if !ok { err = ErrAssertToArray } return } // Bool returns as bool from json object func (j *JSON) Bool() (result bool, err error) { result, ok := (j.data).(bool) if !ok { err = ErrAssertToBool } return } // String returns as string from json object func (j *JSON) String() (result string, err error) { result, ok := (j.data).(string) if !ok { err = ErrAssertToString } return } // StringArray returns as string array from json object func (j *JSON) StringArray() (result []string, err error) { data, err := j.Array() if err != nil { return } for _, v := range data { if v == nil { result = append(result, "") } else { r, ok := v.(string) if !ok { err = ErrAssertToStringArray return } result = append(result, r) } } return } // Time returns as time.Time from json object // optional args is to set the time string parsing format, time.RFC3339 by default // if the time is of int, optional args must not set // json.Time() // json.Time("2006-01-02 15:04:05") func (j *JSON) Time(args ...string) (result time.Time, err error) { switch j.data.(type) { case string: if len(args) > 1 { return result, ErrTooManyArguments } format := time.RFC3339 if len(args) == 1 && strings.TrimSpace(args[0]) != "" { format = strings.TrimSpace(args[0]) } return time.ParseInLocation(format, j.data.(string), time.Local) default: if len(args) > 0 { return result, ErrTooManyArguments } r, e := j.Int64() if e != nil { return result, e } return time.Unix(r, 0), nil } } // Float64 returns as float64 from json object func (j *JSON) Float64() (result float64, err error) { switch j.data.(type) { case json.Number: return j.data.(json.Number).Float64() case float32, float64: return reflect.ValueOf(j.data).Float(), nil case int, int8, int16, int32, int64: return float64(reflect.ValueOf(j.data).Int()), nil case uint, uint8, uint16, uint32, uint64: return float64(reflect.ValueOf(j.data).Uint()), nil default: return 0, ErrInvalidValueType } } // Int returns as int from json object func (j *JSON) Int() (result int, err error) { switch j.data.(type) { case json.Number: r, err := j.data.(json.Number).Int64() return int(r), err case float32, float64: return int(reflect.ValueOf(j.data).Float()), nil case int, int8, int16, int32, int64: return int(reflect.ValueOf(j.data).Int()), nil case uint, uint8, uint16, uint32, uint64: return int(reflect.ValueOf(j.data).Uint()), nil default: return 0, ErrInvalidValueType } } // Int64 returns as int64 from json object func (j *JSON) Int64() (result int64, err error) { switch j.data.(type) { case json.Number: return j.data.(json.Number).Int64() case float32, float64: return int64(reflect.ValueOf(j.data).Float()), nil case int, int8, int16, int32, int64: return reflect.ValueOf(j.data).Int(), nil case uint, uint8, uint16, uint32, uint64: return int64(reflect.ValueOf(j.data).Uint()), nil default: return 0, ErrInvalidValueType } } // Uint64 returns as uint64 from json object func (j *JSON) Uint64() (result uint64, err error) { switch j.data.(type) { case json.Number: return strconv.ParseUint(j.data.(json.Number).String(), 10, 64) case float32, float64: return uint64(reflect.ValueOf(j.data).Float()), nil case int, int8, int16, int32, int64: return uint64(reflect.ValueOf(j.data).Int()), nil case uint, uint8, uint16, uint32, uint64: return reflect.ValueOf(j.data).Uint(), nil default: return 0, ErrInvalidValueType } } // MustMap returns as map from json object with optional default value // if error return default(if set) or panic func (j *JSON) MustMap(args ...map[string]interface{}) map[string]interface{} { if len(args) > 1 { panic(ErrTooManyArguments) } r, err := j.Map() if err == nil { return r } if len(args) == 1 { return args[0] } panic(err) } // MustArray returns as array from json object with optional default value // if error return default(if set) or panic func (j *JSON) MustArray(args ...[]interface{}) []interface{} { if len(args) > 1 { panic(ErrTooManyArguments) } r, err := j.Array() if err == nil { return r } if len(args) == 1 { return args[0] } panic(err) } // MustBool returns as bool from json object with optional default value // if error return default(if set) or panic func (j *JSON) MustBool(args ...bool) bool { if len(args) > 1 { panic(ErrTooManyArguments) } r, err := j.Bool() if err == nil { return r } if len(args) == 1 { return args[0] } panic(err) } // MustString returns as string from json object with optional default value // if error return default(if set) or panic func (j *JSON) MustString(args ...string) string { if len(args) > 1 { panic(ErrTooManyArguments) } r, err := j.String() if err == nil { return r } if len(args) == 1 { return args[0] } panic(err) } // MustStringArray returns as string from json object with optional default value // if error return default(if set) or panic func (j *JSON) MustStringArray(args ...[]string) []string { if len(args) > 1 { panic(ErrTooManyArguments) } r, err := j.StringArray() if err == nil { return r } if len(args) == 1 { return args[0] } panic(err) } // MustTime returns as time.Time from json object // if error return default(if set) or panic // json.Time() // No format, No default // json.Time("2006-01-02 15:04:05") // Has format, No default // json.Time(time.Unix(1548907870, 0)) // No format, Has default // json.Time("2006-01-02 15:04:05", time.Unix(1548907870, 0)) // Has format, Has default func (j *JSON) MustTime(args ...interface{}) time.Time { if len(args) > 2 { panic(ErrTooManyArguments) } format := "" defset := false var defbak time.Time for i := 0; i < len(args); i++ { switch args[i].(type) { case string: format = args[i].(string) case time.Time: defbak = args[i].(time.Time) defset = true default: panic(ErrInvalidArgumentType) } } var r time.Time var err error if format != "" { r, err = j.Time(format) } else { r, err = j.Time() } if err == nil { return r } if defset { return defbak } panic(err) } // MustFloat64 returns as float64 from json object with optional default value // if error return default(if set) or panic func (j *JSON) MustFloat64(args ...float64) float64 { if len(args) > 1 { panic(ErrTooManyArguments) } r, err := j.Float64() if err == nil { return r } if len(args) == 1 { return args[0] } panic(err) } // MustInt returns as int from json object with optional default value // if error return default(if set) or panic func (j *JSON) MustInt(args ...int) int { if len(args) > 1 { panic(ErrTooManyArguments) } r, err := j.Int() if err == nil { return r } if len(args) == 1 { return args[0] } panic(err) } // MustInt64 returns as int64 from json object with optional default value // if error return default(if set) or panic func (j *JSON) MustInt64(args ...int64) int64 { if len(args) > 1 { panic(ErrTooManyArguments) } r, err := j.Int64() if err == nil { return r } if len(args) == 1 { return args[0] } panic(err) } // MustUint64 returns as uint64 from json object with optional default value // if error return default(if set) or panic func (j *JSON) MustUint64(args ...uint64) uint64 { if len(args) > 1 { panic(ErrTooManyArguments) } r, err := j.Uint64() if err == nil { return r } if len(args) == 1 { return args[0] } panic(err) } gokit-0.25.9/xjson/xjson_test.go000066400000000000000000000657551426246334200166440ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xjson import ( "encoding/json" "fmt" "os" "testing" "time" "github.com/likexian/gokit/assert" ) type JSONResult struct { Result Result `json:"result"` Status Status `json:"status"` } type Result struct { IntList []int64 `json:"intlist"` Online bool `json:"online"` Rate float64 `json:"rate"` } type Status struct { Code int64 `json:"code"` Message string `json:"message"` } var ( jsonResult = JSONResult{} textResult = `{"result":{"intlist":[0,1,2,3,4],"online":true,"rate":0.8},"status":{"code":1,"message":"success"}}` textFile = "xjson.json" jsonName = "Li Kexian" jsonLink = "https://www.likexian.com/" ) func init() { os.Remove(textFile) dataResult := Result{} dataResult.IntList = []int64{0, 1, 2, 3, 4} dataResult.Online = true dataResult.Rate = 0.8 dataStatus := Status{} dataStatus.Code = 1 dataStatus.Message = "success" jsonResult.Result = dataResult jsonResult.Status = dataStatus } func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } func Test_New(t *testing.T) { // no init value to New jsonData := New() jsonText, err := jsonData.Dumps() assert.Nil(t, err) assert.Equal(t, jsonText, `{}`) // pass init value to New jsonData = New(jsonResult) jsonText, err = jsonData.Dumps() assert.Nil(t, err) assert.Equal(t, jsonText, textResult) // pass init map to New jsonMap := map[string]interface{}{"i": map[string]interface{}{"am": "Li Kexian", "age": 18}} jsonData = New(jsonMap) jsonText, err = jsonData.Dumps() assert.Nil(t, err) assert.Equal(t, jsonText, `{"i":{"age":18,"am":"Li Kexian"}}`) name, err := jsonData.Get("i").Get("am").String() assert.Nil(t, err) assert.Equal(t, name, "Li Kexian") } func Test_Load_Dump(t *testing.T) { defer os.Remove(textFile) // Loads json from text j, err := Loads(textResult) assert.Nil(t, err) code, err := j.Get("status.code").Int() assert.Nil(t, err) assert.Equal(t, code, 1) // Dumps json to text s, err := Dumps(jsonResult) assert.Nil(t, err) assert.Equal(t, s, textResult) // PrettyDumps json to text s, err = PrettyDumps(jsonResult) assert.Nil(t, err) assert.NotEqual(t, s, textResult) // Dump json to file err = Dump(textFile, jsonResult) assert.Nil(t, err) // Load json from file j, err = Load(textFile) assert.Nil(t, err) code, err = j.Get("status.code").Int() assert.Nil(t, err) assert.Equal(t, code, 1) _, err = Load("not-exists") assert.NotNil(t, err) err = New(make(chan int)).Dump(textFile) assert.NotNil(t, err) } func Test_J_Load_Dump(t *testing.T) { defer os.Remove(textFile) j := New() err := j.Loads(textResult) assert.Nil(t, err) code, err := j.Get("status.code").Int() assert.Nil(t, err) assert.Equal(t, code, 1) s, err := j.Dumps() assert.Nil(t, err) assert.Equal(t, s, textResult) err = j.Dump(textFile) assert.Nil(t, err) err = j.Load(textFile) assert.Nil(t, err) code, err = j.Get("status.code").Int() assert.Nil(t, err) assert.Equal(t, code, 1) } func Test_Set_Has_Get_Del(t *testing.T) { defer os.Remove(textFile) // Loads json for Set jsonData, err := Loads(textResult) assert.Nil(t, err) // Test key exists exists := jsonData.Has("name") assert.False(t, exists) // Set key-value jsonData.Set("name", jsonName) jsonData.Set("link", jsonLink) // Test dumpable err = jsonData.Dump(textFile) assert.Nil(t, err) // Test Set key-value exists = jsonData.Has("name") assert.True(t, exists) // Get the Set name value rName, err := jsonData.Get("name").String() assert.Nil(t, err) assert.Equal(t, jsonName, rName) // Get the Set link value rLink, err := jsonData.Get("link").String() assert.Nil(t, err) assert.Equal(t, jsonLink, rLink) // Get the not-exists key _, err = jsonData.Get("not-exists").String() assert.NotNil(t, err) // Del key-value jsonData.Del("name") exists = jsonData.Has("name") assert.False(t, exists) // Del not-exists key jsonData.Del("not-exists") exists = jsonData.Has("not-exists") assert.False(t, exists) // Del on Array jsonData.Del("not-exists.name") jsonData.Set("", map[string]interface{}{"status": []int{1, 2, 3}}) jsonData.Del("status.name") // Del on Nil var i interface{} jsonData.Set("", i) jsonData.Set("name", "kexian.li") jsonData.Del("name") // Replace all data jsonData.Set("", map[string]interface{}{"name": "kexian.li"}) assert.Equal(t, jsonData.Get("name").MustString(""), "kexian.li") } func Test_Set_Has_Get_Del_W_Dot(t *testing.T) { defer os.Remove(textFile) // Loads json for Set jsonData, err := Loads(textResult) assert.Nil(t, err) // Test key exists exists := jsonData.Has("i.am.that.who") assert.False(t, exists) // Set key-value jsonData.Set("i.am.that.who", jsonName) jsonData.Set("name", jsonName) jsonData.Set("link", jsonLink) // Test dumpable err = jsonData.Dump(textFile) assert.Nil(t, err) // Test Set key-value exists = jsonData.Has("i.am.that.who") assert.True(t, exists) // Get the Set name value rName, err := jsonData.Get("i.am.that.who").String() assert.Nil(t, err) assert.Equal(t, jsonName, rName) // Get the not exists key _, err = jsonData.Get("i.am.that.what").String() assert.NotNil(t, err) _, err = jsonData.Get("i.am.this.who").String() assert.NotNil(t, err) // Get the Set name value with origin way rName, err = jsonData.Get("i").Get("am").Get("that").Get("who").String() assert.Nil(t, err) assert.Equal(t, jsonName, rName) // Get the not exists key _, err = jsonData.Get("i").Get("am").Get("that").Get("what").String() assert.NotNil(t, err) // Del key-value jsonData.Del("i.am.that.who") exists = jsonData.Has("i.am.that.who") assert.False(t, exists) // Del not-exists key jsonData.Del("i.am.that.what") exists = jsonData.Has("i.am.that.what") assert.False(t, exists) } func Test_Set_Has_Get_Del_W_List(t *testing.T) { // Loads json for Set jsonData, err := Loads(textResult) assert.Nil(t, err) // Test key exists exists := jsonData.Has("that.is.a.list") assert.False(t, exists) exists = jsonData.Has("") assert.False(t, exists) // Set key-value jsonData.Set("that.is.a.list", []interface{}{0, 1, 2, 3, 4}) exists = jsonData.Has("that.is.a.list") assert.True(t, exists) exists = jsonData.Has("that.is.a.list.not-exists") assert.False(t, exists) exists = jsonData.Has("that.is.a.list.0.a") assert.False(t, exists) // Test N key exists exists = jsonData.Has("that.is.a.list.3") assert.True(t, exists) // Test N key not exists exists = jsonData.Has("that.is.a.list.666") assert.False(t, exists) // Test set dict in list jsonData.Set("that.is.a.dict.in.list", []interface{}{map[string]interface{}{"a": 1, "b": 2, "c": 3}}) exists = jsonData.Has("that.is.a.dict.in.list") assert.True(t, exists) // Test dict in list exists exists = jsonData.Has("that.is.a.dict.in.list.0") assert.True(t, exists) exists = jsonData.Has("that.is.a.dict.in.list.0.a") assert.True(t, exists) exists = jsonData.Has("that.is.a.dict.in.list.1.a") assert.False(t, exists) exists = jsonData.Has("that.is.a.dict.in.list.0.z") assert.False(t, exists) // Test get dict in list intData, err := jsonData.Get("that.is.a.dict.in.list.0.b").Int() assert.Nil(t, err) assert.Equal(t, intData, 2) _, err = jsonData.Get("that.is.a.dict.in.list.1.b").Int() assert.NotNil(t, err) _, err = jsonData.Get("that.is.a.dict.in.list.0.z").Int() assert.NotNil(t, err) // Get the list value rNumber, err := jsonData.Get("that.is.a.list.3").Int() assert.Nil(t, err) assert.Equal(t, rNumber, 3) // Get not-exists N _, err = jsonData.Get("that.is.a.list.666").Int() assert.NotNil(t, err) // Get the list value with origin way rNumber, err = jsonData.Get("that").Get("is").Get("a").Get("list").Get("4").Int() assert.Nil(t, err) assert.Equal(t, rNumber, 4) // Get the list value with Index rNumber, err = jsonData.Get("that.is.a.list").Index(1).Int() assert.Nil(t, err) assert.Equal(t, rNumber, 1) // Get the list value with origin way Index rNumber, err = jsonData.Get("that").Get("is").Get("a").Get("list").Index(2).Int() assert.Nil(t, err) assert.Equal(t, rNumber, 2) // Get not-exists N with origin way Index _, err = jsonData.Get("that").Get("is").Get("a").Get("list").Index(666).Int() assert.NotNil(t, err) } func Test_Set_Has_Get_Del_Type(t *testing.T) { // Loads json for Set jsonData, err := Loads(textResult) assert.Nil(t, err) // Set bool value jsonData.Set("bool", true) boolData, err := jsonData.Get("bool").Bool() assert.Nil(t, err) assert.Equal(t, boolData, true) assert.Equal(t, "bool", fmt.Sprintf("%T", boolData)) assert.Equal(t, jsonData.Has("bool"), true) jsonData.Del("bool") assert.Equal(t, jsonData.Has("bool"), false) // Set string value jsonData.Set("string", "string") stringData, err := jsonData.Get("string").String() assert.Nil(t, err) assert.Equal(t, stringData, "string") assert.Equal(t, "string", fmt.Sprintf("%T", stringData)) assert.Equal(t, jsonData.Has("string"), true) jsonData.Del("string") assert.Equal(t, jsonData.Has("string"), false) // Set float64 value jsonData.Set("float64", float64(999)) float64Data, err := jsonData.Get("float64").Float64() assert.Nil(t, err) assert.Equal(t, float64Data, float64(999)) assert.Equal(t, "float64", fmt.Sprintf("%T", float64Data)) assert.Equal(t, jsonData.Has("float64"), true) jsonData.Del("float64") assert.Equal(t, jsonData.Has("float64"), false) jsonData.Set("float64", int(999)) float64Data, err = jsonData.Get("float64").Float64() assert.Nil(t, err) assert.Equal(t, float64Data, float64(999)) jsonData.Set("float64", uint(999)) float64Data, err = jsonData.Get("float64").Float64() assert.Nil(t, err) assert.Equal(t, float64Data, float64(999)) // Set int value jsonData.Set("int", int(666)) intData, err := jsonData.Get("int").Int() assert.Nil(t, err) assert.Equal(t, intData, int(666)) assert.Equal(t, "int", fmt.Sprintf("%T", intData)) assert.Equal(t, jsonData.Has("int"), true) jsonData.Del("int") assert.Equal(t, jsonData.Has("int"), false) jsonData.Set("int", float64(666)) intData, err = jsonData.Get("int").Int() assert.Nil(t, err) assert.Equal(t, intData, int(666)) jsonData.Set("int", uint(666)) intData, err = jsonData.Get("int").Int() assert.Nil(t, err) assert.Equal(t, intData, int(666)) // Set int64 value jsonData.Set("int64", int64(666)) int64Data, err := jsonData.Get("int64").Int64() assert.Nil(t, err) assert.Equal(t, int64Data, int64(666)) assert.Equal(t, "int64", fmt.Sprintf("%T", int64Data)) assert.Equal(t, jsonData.Has("int64"), true) jsonData.Del("int64") assert.Equal(t, jsonData.Has("int64"), false) jsonData.Set("int64", float64(666)) int64Data, err = jsonData.Get("int64").Int64() assert.Nil(t, err) assert.Equal(t, int64Data, int64(666)) jsonData.Set("int64", uint(666)) int64Data, err = jsonData.Get("int64").Int64() assert.Nil(t, err) assert.Equal(t, int64Data, int64(666)) // Set uint64 value jsonData.Set("uint64", uint64(666)) uint64Data, err := jsonData.Get("uint64").Uint64() assert.Nil(t, err) assert.Equal(t, uint64Data, uint64(666)) assert.Equal(t, "uint64", fmt.Sprintf("%T", uint64Data)) assert.Equal(t, jsonData.Has("uint64"), true) jsonData.Del("uint64") assert.Equal(t, jsonData.Has("uint64"), false) jsonData.Set("uint64", float64(666)) uint64Data, err = jsonData.Get("uint64").Uint64() assert.Nil(t, err) assert.Equal(t, uint64Data, uint64(666)) jsonData.Set("uint64", int(666)) uint64Data, err = jsonData.Get("uint64").Uint64() assert.Nil(t, err) assert.Equal(t, uint64Data, uint64(666)) // Set string array value jsonData.Set("string_array", []interface{}{"a", "b", "c"}) stringArrayData, err := jsonData.Get("string_array").StringArray() assert.Nil(t, err) assert.Equal(t, stringArrayData, []string{"a", "b", "c"}) assert.Equal(t, "[]string", fmt.Sprintf("%T", stringArrayData)) assert.Equal(t, jsonData.Has("string_array"), true) jsonData.Del("string_array") assert.Equal(t, jsonData.Has("string_array"), false) } func Test_Get_Assert_Data(t *testing.T) { // Loads json for Set jsonData, err := Loads(textResult) assert.Nil(t, err) // Get data as map mapData, err := jsonData.Get("status").Map() assert.Nil(t, err) assert.Equal(t, "map[string]interface {}", fmt.Sprintf("%T", mapData)) // Get data as array ArrayData, err := jsonData.Get("result").Get("intlist").Array() assert.Nil(t, err) assert.Equal(t, "[]interface {}", fmt.Sprintf("%T", ArrayData)) for k, v := range ArrayData { r, _ := v.(json.Number).Int64() assert.Equal(t, k, int(r)) } // Get data as bool boolData, err := jsonData.Get("result").Get("online").Bool() assert.Nil(t, err) assert.Equal(t, "bool", fmt.Sprintf("%T", boolData)) assert.Equal(t, boolData, true) // Get data as string stringData, err := jsonData.Get("status").Get("message").String() assert.Nil(t, err) assert.Equal(t, "string", fmt.Sprintf("%T", stringData)) assert.Equal(t, stringData, "success") // Get data as float64 float64Data, err := jsonData.Get("result").Get("rate").Float64() assert.Nil(t, err) assert.Equal(t, "float64", fmt.Sprintf("%T", float64Data)) assert.Equal(t, float64Data, float64(0.8)) // Get data as int intData, err := jsonData.Get("status").Get("code").Int() assert.Nil(t, err) assert.Equal(t, "int", fmt.Sprintf("%T", intData)) assert.Equal(t, intData, int(1)) // Get data as int64 int64Data, err := jsonData.Get("status").Get("code").Int64() assert.Nil(t, err) assert.Equal(t, "int64", fmt.Sprintf("%T", int64Data)) assert.Equal(t, int64Data, int64(1)) // Get data as uint64 uint64Data, err := jsonData.Get("status").Get("code").Uint64() assert.Nil(t, err) assert.Equal(t, "uint64", fmt.Sprintf("%T", uint64Data)) assert.Equal(t, uint64Data, uint64(1)) // Get data as string array jsonData.Set("that.is.a.list", []interface{}{"a", "b", "c", "d", "e", nil}) stringArrayData, err := jsonData.Get("that.is.a.list").StringArray() assert.Nil(t, err) assert.Equal(t, "[]string", fmt.Sprintf("%T", stringArrayData)) assert.Equal(t, stringArrayData, []string{"a", "b", "c", "d", "e", ""}) jsonData.Set("that.is.a.list", []interface{}{"a", "b", "c", "d", "e", 1}) _, err = jsonData.Get("that.is.a.list").StringArray() assert.NotNil(t, err) } func Test_Get_Must_Assert_Data(t *testing.T) { // Loads json for Set jsonData, err := Loads(textResult) assert.Nil(t, err) // Get data as map mapData := jsonData.Get("status").MustMap() assert.Equal(t, "map[string]interface {}", fmt.Sprintf("%T", mapData)) assert.Equal(t, len(mapData), 2) // Get data as array arrayData := jsonData.Get("result").Get("intlist").MustArray() assert.Equal(t, "[]interface {}", fmt.Sprintf("%T", arrayData)) assert.Equal(t, len(arrayData), 5) // Get data as bool boolData := jsonData.Get("result").Get("online").MustBool() assert.Equal(t, "bool", fmt.Sprintf("%T", boolData)) assert.Equal(t, boolData, true) // Get data as string stringData := jsonData.Get("status").Get("message").MustString() assert.Equal(t, "string", fmt.Sprintf("%T", stringData)) assert.Equal(t, stringData, "success") // Get data as float64 float64Data := jsonData.Get("result").Get("rate").MustFloat64() assert.Equal(t, "float64", fmt.Sprintf("%T", float64Data)) assert.Equal(t, float64Data, float64(0.8)) // Get data as int intData := jsonData.Get("status").Get("code").MustInt() assert.Equal(t, "int", fmt.Sprintf("%T", intData)) assert.Equal(t, intData, int(1)) // Get data as int64 int64Data := jsonData.Get("status").Get("code").MustInt64() assert.Equal(t, "int64", fmt.Sprintf("%T", int64Data)) assert.Equal(t, int64Data, int64(1)) // Get data as uint64 uint64Data := jsonData.Get("status").Get("code").MustUint64() assert.Equal(t, "uint64", fmt.Sprintf("%T", uint64Data)) assert.Equal(t, uint64Data, uint64(1)) // Get data as string array jsonData.Set("that.is.a.list", []interface{}{"a", "b", "c", "d", "e"}) stringArrayData := jsonData.Get("that.is.a.list").MustStringArray() assert.Equal(t, "[]string", fmt.Sprintf("%T", stringArrayData)) assert.Equal(t, stringArrayData, []string{"a", "b", "c", "d", "e"}) } func Test_Get_Must_Assert_Data_N_Default(t *testing.T) { // Loads json for Set jsonData, err := Loads(textResult) assert.Nil(t, err) // Get data as map assert.Panic(t, func() { jsonData.Get("not-exists").MustMap() }) assert.Panic(t, func() { jsonData.Get("not-exists").MustMap(map[string]interface{}{}, map[string]interface{}{}) }) // Get data as array assert.Panic(t, func() { jsonData.Get("not-exists").MustArray() }) assert.Panic(t, func() { jsonData.Get("not-exists").MustArray([]interface{}{}, []interface{}{}) }) // Get data as bool assert.Panic(t, func() { jsonData.Get("not-exists").MustBool() }) assert.Panic(t, func() { jsonData.Get("not-exists").MustBool(false, false) }) // Get data as string assert.Panic(t, func() { jsonData.Get("not-exists").MustString() }) assert.Panic(t, func() { jsonData.Get("not-exists").MustString("", "") }) // Get data as float64 assert.Panic(t, func() { jsonData.Get("not-exists").MustFloat64() }) assert.Panic(t, func() { jsonData.Get("not-exists").MustFloat64(0, 0) }) // Get data as int assert.Panic(t, func() { jsonData.Get("not-exists").MustInt() }) assert.Panic(t, func() { jsonData.Get("not-exists").MustInt(0, 0) }) // Get data as int64 assert.Panic(t, func() { jsonData.Get("not-exists").MustInt64() }) assert.Panic(t, func() { jsonData.Get("not-exists").MustInt64(0, 0) }) // Get data as uint64 assert.Panic(t, func() { jsonData.Get("not-exists").MustUint64() }) assert.Panic(t, func() { jsonData.Get("not-exists").MustUint64(0, 0) }) // Get data as string array assert.Panic(t, func() { jsonData.Get("not-exists").MustStringArray() }) assert.Panic(t, func() { jsonData.Get("not-exists").MustStringArray([]string{}, []string{}) }) } func Test_Get_Must_Assert_Data_W_Default(t *testing.T) { // Loads json for Set jsonData, err := Loads(textResult) assert.Nil(t, err) // Get data as map mapData := jsonData.Get("not-exists").MustMap(map[string]interface{}{}) assert.Equal(t, "map[string]interface {}", fmt.Sprintf("%T", mapData)) assert.Equal(t, len(mapData), 0) // Get data as array arrayData := jsonData.Get("not-exists").MustArray([]interface{}{}) assert.Equal(t, "[]interface {}", fmt.Sprintf("%T", arrayData)) assert.Equal(t, len(arrayData), 0) // Get data as bool boolData := jsonData.Get("not-exists").MustBool(true) assert.Equal(t, "bool", fmt.Sprintf("%T", boolData)) assert.Equal(t, boolData, true) // Get data as string stringData := jsonData.Get("not-exists").MustString("ok") assert.Equal(t, "string", fmt.Sprintf("%T", stringData)) assert.Equal(t, stringData, "ok") // Get data as float64 float64Data := jsonData.Get("not-exists").MustFloat64(float64(999)) assert.Equal(t, "float64", fmt.Sprintf("%T", float64Data)) assert.Equal(t, float64Data, float64(999)) // Get data as int intData := jsonData.Get("not-exists").MustInt(int(666)) assert.Equal(t, "int", fmt.Sprintf("%T", intData)) assert.Equal(t, intData, int(666)) // Get data as int64 int64Data := jsonData.Get("not-exists").MustInt64(int64(666)) assert.Equal(t, "int64", fmt.Sprintf("%T", int64Data)) assert.Equal(t, int64Data, int64(666)) // Get data as uint64 uint64Data := jsonData.Get("not-exists").MustUint64(uint64(666)) assert.Equal(t, "uint64", fmt.Sprintf("%T", uint64Data)) assert.Equal(t, uint64Data, uint64(666)) // Get data as string array stringArrayData := jsonData.Get("not-exists").MustStringArray([]string{"i", "am", "ok"}) assert.Equal(t, "[]string", fmt.Sprintf("%T", stringArrayData)) assert.Equal(t, stringArrayData, []string{"i", "am", "ok"}) } func Test_HTML_Escape(t *testing.T) { // Init json and set html jsonData := New() jsonData.Set("param", "a=1&b=2&c=3") jsonData.Set("title", "test escape") // dumps not escaped html jsonText, err := jsonData.Dumps() assert.Nil(t, err) assert.Equal(t, jsonText, `{"param":"a=1&b=2&c=3","title":"test escape"}`) // dumps escaped html jsonData.SetHTMLEscape(true) jsonText, err = jsonData.Dumps() assert.Nil(t, err) assert.Equal(t, jsonText, `{"param":"a=1\u0026b=2\u0026c=3","title":"\u003ctitle\u003etest escape\u003c/title\u003e"}`) } func Test_isMap(t *testing.T) { // Loads json for Set jsonData, err := Loads(textResult) assert.Nil(t, err) // Test the top level json isMap := jsonData.IsMap() assert.True(t, isMap) // Test after get isMap = jsonData.Get("status").IsMap() assert.True(t, isMap) // Test after twice get isMap = jsonData.Get("result").Get("online").IsMap() assert.False(t, isMap) // Test after magic get isMap = jsonData.Get("result.online").IsMap() assert.False(t, isMap) // Test the array isMap = jsonData.Get("result.intlist").IsMap() assert.False(t, isMap) // Test not exists key isMap = jsonData.Get("result.not-exists").IsMap() assert.False(t, isMap) } func Test_Is_Array(t *testing.T) { // Loads json for Set jsonData, err := Loads(textResult) assert.Nil(t, err) // Test the top level json isMap := jsonData.IsArray() assert.False(t, isMap) // Test after get isMap = jsonData.Get("status").IsArray() assert.False(t, isMap) // Test after twice get isMap = jsonData.Get("result").Get("intlist").IsArray() assert.True(t, isMap) // Test the array isMap = jsonData.Get("result.intlist").IsArray() assert.True(t, isMap) // Test the array element isMap = jsonData.Get("result.intlist.0").IsArray() assert.False(t, isMap) // Test not exists key isMap = jsonData.Get("result.not-exists").IsArray() assert.False(t, isMap) } func Test_Len(t *testing.T) { // Loads json for Set jsonData, err := Loads(textResult) assert.Nil(t, err) // Get len of top level json n := jsonData.Len() assert.Equal(t, n, 2) // Get len of map n = jsonData.Get("status").Len() assert.Equal(t, n, 2) // Get len of not exists map n = jsonData.Get("status.not-exists").Len() assert.Equal(t, n, -1) // Get len of int n = jsonData.Get("status.code").Len() assert.Equal(t, n, -1) // Get len of string n = jsonData.Get("status.message").Len() assert.Equal(t, n, 7) // Get len of not exists string n = jsonData.Get("status.message.not-exists").Len() assert.Equal(t, n, -1) // Get len of array n = jsonData.Get("result.intlist").Len() assert.Equal(t, n, 5) // Get len of not exists array n = jsonData.Get("result.intlist.not-exists").Len() assert.Equal(t, n, -1) } func Test_Time_Assert_Data(t *testing.T) { // Loads json for Set jsonData, err := Loads(textResult) assert.Nil(t, err) // Time for comparing testTime, err := time.Parse(time.RFC3339, "2019-01-31T12:11:10+08:00") assert.Nil(t, err) assert.NotEqual(t, testTime.Unix(), int64(0)) // Test get rfc3339 time jsonData.Set("time", "2019-01-31T12:11:10+08:00") timeData, err := jsonData.Get("time").Time() assert.Nil(t, err) assert.Equal(t, timeData, testTime) // Test get format time jsonData.Set("time", "2019-01-31 12:11:10") timeData, err = jsonData.Get("time").Time("2006-01-02 15:04:05") assert.Nil(t, err) assert.NotEqual(t, timeData.Unix(), int64(0)) // Test get rfc3339 time with not exists key timeData, err = jsonData.Get("not-exists").Time() assert.NotNil(t, err) assert.Equal(t, timeData.Unix(), int64(-62135596800)) // Test get time from int testTime = time.Unix(1548907870, 0) jsonData.Set("time", int(1548907870)) timeData, err = jsonData.Get("time").Time() assert.Nil(t, err) assert.Equal(t, timeData, testTime) // Test get time from int64 jsonData.Set("time", int64(1548907870)) timeData, err = jsonData.Get("time").Time() assert.Nil(t, err) assert.Equal(t, timeData, testTime) // Test get time from uint64 jsonData.Set("time", uint64(1548907870)) timeData, err = jsonData.Get("time").Time() assert.Nil(t, err) assert.Equal(t, timeData, testTime) // Test get time from float64 jsonData.Set("time", float64(1548907870)) timeData, err = jsonData.Get("time").Time() assert.Nil(t, err) assert.Equal(t, timeData, testTime) // Date for comparing testTime, err = time.ParseInLocation("2006-01-02", "2019-01-31", time.Local) assert.Nil(t, err) assert.NotEqual(t, testTime.Unix(), int64(0)) // Test get format date jsonData.Set("time", "2019-01-31") timeData, err = jsonData.Get("time").Time("2006-01-02") assert.Nil(t, err) assert.Equal(t, timeData, testTime) // Invalid args _, err = jsonData.Get("time").Time("2006-01-02", "2006-01-02") assert.NotNil(t, err) jsonData.Set("time", int64(1548907870)) _, err = jsonData.Get("time").Time("2006-01-02", "2006-01-02") assert.NotNil(t, err) } func Test_Time_Must_Assert_Data(t *testing.T) { // Loads json for Set jsonData, err := Loads(textResult) assert.Nil(t, err) // Time for comparing testTime, err := time.Parse(time.RFC3339, "2019-01-31T12:11:10+08:00") assert.Nil(t, err) assert.NotEqual(t, testTime.Unix(), int64(0)) // Test get rfc3339 time jsonData.Set("time", "2019-01-31T12:11:10+08:00") timeData := jsonData.Get("time").MustTime() assert.Nil(t, err) assert.Equal(t, timeData, testTime) // Test get format time jsonData.Set("time", "2019-01-31 12:11:10") timeData = jsonData.Get("time").MustTime("2006-01-02 15:04:05") assert.Nil(t, err) assert.NotEqual(t, timeData.Unix(), int64(0)) // No format, no default jsonData.Set("time", "i-am-not-the-time") assert.Panic(t, func() { jsonData.Get("time").MustTime() }) // Has format, no default assert.Panic(t, func() { jsonData.Get("time").MustTime("2006-01-02 15:04:05") }) // No format, has default testTime = time.Unix(1548907870, 0) timeData = jsonData.Get("time").MustTime(time.Unix(1548907870, 0)) assert.Nil(t, err) assert.Equal(t, timeData, testTime) // Has format, has default timeData = jsonData.Get("time").MustTime("2006-01-02 15:04:05", time.Unix(1548907870, 0)) assert.Nil(t, err) assert.Equal(t, timeData, testTime) // No format, no default, key not exists assert.Panic(t, func() { jsonData.Get("not-exists").MustTime() }) assert.Panic(t, func() { jsonData.Get("not-exists").MustTime(1) }) assert.Panic(t, func() { jsonData.Get("not-exists").MustTime(1, 1, 1) }) // No format, has default, key not exists timeData = jsonData.Get("not-exists").MustTime(time.Unix(1548907870, 0)) assert.Nil(t, err) assert.Equal(t, timeData, testTime) // Test must get time from int jsonData.Set("time", int(1548907870)) timeData = jsonData.Get("time").MustTime() assert.Nil(t, err) assert.Equal(t, timeData, testTime) // Get from int, No default jsonData.Set("time", true) assert.Panic(t, func() { jsonData.Get("time").MustTime() }) // Get from int, Has default timeData = jsonData.Get("time").MustTime(time.Unix(1548907870, 0)) assert.Nil(t, err) assert.Equal(t, timeData, testTime) } gokit-0.25.9/xlog/000077500000000000000000000000001426246334200137035ustar00rootroot00000000000000gokit-0.25.9/xlog/README.md000066400000000000000000000016721426246334200151700ustar00rootroot00000000000000# GoKit - xlog Log kits for Golang development. ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/xlog" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xlog) ## Example ### Do logging to stderr ```go log := xlog.New(os.Stderr, xlog.INFO) log.Info("This is Info") log.SetLevel(xlog.DEBUG) log.Debug("This is Debug") log.Close() ``` ### Do logging to a file ```go log, err := xlog.File("test.log", xlog.DEBUG) if err != nil { panic(err) } log.SetFlag(xlog.LstdFlags|xlog.Llongfile) log.Debug("This is Debug") log.Info("This is Info") log.Close() ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/xlog/example/000077500000000000000000000000001426246334200153365ustar00rootroot00000000000000gokit-0.25.9/xlog/example/.gitignore000066400000000000000000000000101426246334200173150ustar00rootroot00000000000000example gokit-0.25.9/xlog/example/main.go000066400000000000000000000024531426246334200166150ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package main import ( "time" "github.com/likexian/gokit/xlog" ) func main() { sizeLog() dailyLog() } // dailyLog do log rotate daily func dailyLog() { log, err := xlog.File("test.daily.log", xlog.DEBUG) if err != nil { panic(err) } _ = log.SetDailyRotate(3) for { log.Info("This is a test log") time.Sleep(1 * time.Second) } } // sizeLog do log rotate by size func sizeLog() { log, err := xlog.File("test.size.log", xlog.DEBUG) if err != nil { panic(err) } _ = log.SetSizeRotate(10, 1000000) for i := 0; i < 1000; i++ { go func() { for { log.Info("This is a test log") time.Sleep(1 * time.Second) } }() } } gokit-0.25.9/xlog/xlog.go000066400000000000000000000222201426246334200152010ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xlog import ( "errors" "fmt" "io" "os" "path/filepath" "runtime" "strconv" "strings" "sync" "time" "github.com/likexian/gokit/xcache" "github.com/likexian/gokit/xfile" "github.com/likexian/gokit/xhash" ) // Log level const const ( DEBUG LogLevel = iota INFO WARN ERROR FATAL ) // Log prefix flag, similar to golang log package const ( Ldate = 1 << iota Ltime Lmicroseconds Llongfile Lshortfile LUTC LstdFlags = Ldate | Ltime ) // log level mapper var levelMap = map[LogLevel]string{ DEBUG: "DEBUG", INFO: "INFO", WARN: "WARN", ERROR: "ERROR", FATAL: "FATAL", } // log once cache var onceCache = xcache.New(xcache.MemoryCache) // LogLevel storing log level type LogLevel int // LogFlag storing log flag type LogFlag int // Logger storing logger type Logger struct { logFile logFile logLevel LogLevel logFlag LogFlag logQueue chan string logExit chan bool logClosed bool sync.RWMutex } // logFile storing log file info type logFile struct { name string fd *os.File writer io.Writer rotateType string rotateNum int64 rotateSize int64 rotateNowDate string rotateNowSize int64 rotateNextNum int64 } // Version returns package version func Version() string { return "0.7.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // New returns a new logger func New(w io.Writer, level LogLevel) *Logger { return newLog(logFile{writer: w}, level, LstdFlags) } // File returns a new file logger func File(fname string, level LogLevel) (*Logger, error) { fd, err := openFile(fname) if err != nil { return nil, err } return newLog(logFile{name: fname, writer: fd, fd: fd}, level, LstdFlags), nil } // openFile open file with flags func openFile(fname string) (*os.File, error) { return os.OpenFile(fname, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) } // newLogger returns a new file logger func newLog(lf logFile, level LogLevel, flag LogFlag) *Logger { l := &Logger{ logFile: lf, logLevel: level, logFlag: flag, logQueue: make(chan string, 10000), logExit: make(chan bool), logClosed: false, } go l.writeLog() return l } // Close close the logger func (l *Logger) Close() { l.Lock() l.logClosed = true close(l.logQueue) l.Unlock() <-l.logExit } // SetLevel set the log level by int level func (l *Logger) SetLevel(level LogLevel) { l.Lock() l.logLevel = level l.Unlock() } // SetFlag set the log flag func (l *Logger) SetFlag(flag LogFlag) { l.Lock() l.logFlag = flag l.Unlock() } // SetDailyRotate set daily log rotate func (l *Logger) SetDailyRotate(rotateNum int64) error { return l.SetRotate("date", rotateNum, 0) } // SetSizeRotate set filesize log rotate func (l *Logger) SetSizeRotate(rotateNum int64, rotateSize int64) error { return l.SetRotate("size", rotateNum, rotateSize) } // SetRotate set log rotate // rotateType: date: daily rotate, size: filesize rotate func (l *Logger) SetRotate(rotateType string, rotateNum int64, rotateSize int64) error { l.Lock() defer l.Unlock() if l.logFile.name == "" { return errors.New("xlog: rotate require log to file") } if rotateType != "date" && rotateType != "size" { return fmt.Errorf("xlog: not supported rotate type: %s", rotateType) } l.logFile.rotateType = rotateType l.logFile.rotateNum = rotateNum l.logFile.rotateSize = rotateSize l.logFile.rotateNowDate = time.Now().Format("2006-01-02") size, err := xfile.Size(l.logFile.name) if err != nil { l.logFile.rotateNowSize = 0 } else { l.logFile.rotateNowSize = size } if l.logFile.rotateNum < 2 { return nil } list, err := getFileList(l.logFile.name) if err != nil { l.logFile.rotateNextNum = 1 } else { if int64(len(list)) < l.logFile.rotateNum { l.logFile.rotateNextNum = int64(len(list)) } else { maxf := list[0] for _, v := range list { if v[0].(string) != l.logFile.name { if v[1].(int64) < maxf[1].(int64) { maxf = v } } } fs := strings.Split(maxf[0].(string), ".") num, _ := strconv.Atoi(fs[len(fs)-1]) l.logFile.rotateNextNum = int64(num) } } return nil } // writeLog get log from queue and write func (l *Logger) writeLog() { t := time.NewTicker(1 * time.Second) for { select { case <-t.C: l.RLock() lf := l.logFile l.RUnlock() if lf.rotateType == "" { continue } if lf.rotateNum < 2 { continue } today := time.Now().Format("2006-01-02") if lf.rotateType == "date" { if today != lf.rotateNowDate { l.Lock() l.logFile.rotateNowDate = today l.logFile.rotateNowSize = 0 l.Unlock() _ = l.rotateFile() } } if lf.rotateSize > 0 { if lf.rotateNowSize >= lf.rotateSize { l.Lock() l.logFile.rotateNowDate = today l.logFile.rotateNowSize = 0 l.Unlock() _ = l.rotateFile() } } case s, ok := <-l.logQueue: l.Lock() if !ok { l.logExit <- true l.logFile.fd.Close() l.Unlock() return } _, err := fmt.Fprint(l.logFile.writer, s) if err == nil { l.logFile.rotateNowSize += int64(len(s)) } l.Unlock() } } } // rotateFile do rotate log file func (l *Logger) rotateFile() (err error) { l.Lock() defer l.Unlock() l.logFile.fd.Close() err = os.Rename(l.logFile.name, fmt.Sprintf("%s.%d", l.logFile.name, l.logFile.rotateNextNum)) if err != nil { return } l.logFile.rotateNextNum++ if l.logFile.rotateNextNum >= l.logFile.rotateNum { l.logFile.rotateNextNum = 1 } fd, err := openFile(l.logFile.name) if err != nil { return err } l.logFile.fd = fd l.logFile.writer = fd return } // Log do log a msg func (l *Logger) Log(level LogLevel, msg string, args ...interface{}) { if l.logClosed { return } if l.logLevel > level { return } if _, ok := levelMap[level]; !ok { return } logTime := "" now := time.Now() if l.logFlag&(Ldate|Ltime|Lmicroseconds) != 0 { if l.logFlag&LUTC != 0 { now = now.UTC() } if l.logFlag&Ldate != 0 { logTime += fmt.Sprintf("%s ", now.Format("2006-01-02")) } if l.logFlag&Ltime != 0 { logTime += fmt.Sprintf("%s ", now.Format("15:04:05")) } if l.logFlag&Lmicroseconds != 0 { logTime = fmt.Sprintf("%s.%d ", strings.TrimSpace(logTime), now.Nanosecond()/1e3) } } logFile := "" if l.logFlag&(Llongfile|Lshortfile) != 0 { _, file, line, ok := runtime.Caller(2) if !ok { logFile = "???:? " } else { logFile = fmt.Sprintf("%s:%d ", file, line) } if l.logFlag&Lshortfile != 0 { ls := strings.Split(logFile, "/") logFile = ls[len(ls)-1] } } str := fmt.Sprintf("%s%s[%s] %s\n", logTime, logFile, levelMap[level], msg) l.logQueue <- fmt.Sprintf(str, args...) } // LogOnce do log a msg only one times within one hour func (l *Logger) LogOnce(level LogLevel, msg string, args ...interface{}) { str := fmt.Sprintf("%d-%s", level, msg) key := xhash.Md5(fmt.Sprintf(str, args...)).Hex() v := onceCache.Get(key) if v != nil { return } _ = onceCache.Set(key, 1, 3600) l.Log(level, msg, args...) } // Debug level msg logging func (l *Logger) Debug(msg string, args ...interface{}) { l.Log(DEBUG, msg, args...) } // Info level msg logging func (l *Logger) Info(msg string, args ...interface{}) { l.Log(INFO, msg, args...) } // Warn level msg logging func (l *Logger) Warn(msg string, args ...interface{}) { l.Log(WARN, msg, args...) } // Error level msg logging func (l *Logger) Error(msg string, args ...interface{}) { l.Log(ERROR, msg, args...) } // Fatal level msg logging, followed by a call to os.Exit(1) func (l *Logger) Fatal(msg string, args ...interface{}) { l.Log(FATAL, msg, args...) l.Close() os.Exit(1) } // DebugOnce level msg logging func (l *Logger) DebugOnce(msg string, args ...interface{}) { l.LogOnce(DEBUG, msg, args...) } // InfoOnce level msg logging func (l *Logger) InfoOnce(msg string, args ...interface{}) { l.LogOnce(INFO, msg, args...) } // WarnOnce level msg logging func (l *Logger) WarnOnce(msg string, args ...interface{}) { l.LogOnce(WARN, msg, args...) } // ErrorOnce level msg logging func (l *Logger) ErrorOnce(msg string, args ...interface{}) { l.LogOnce(ERROR, msg, args...) } // getFileList returns file list func getFileList(fname string) (result [][]interface{}, err error) { result = [][]interface{}{} fs, err := filepath.Glob(fname + "*") if err != nil { return } for _, f := range fs { fd, e := os.Stat(f) if e != nil { err = e return } result = append(result, []interface{}{f, fd.ModTime().Unix()}) } return } gokit-0.25.9/xlog/xlog_fatal_test.go000066400000000000000000000026641426246334200174210ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xlog import ( "bytes" "errors" "os" "os/exec" "strings" "testing" ) const LogLine = "This line will show before exit" func LogFatal() { log := New(os.Stderr, DEBUG) log.Fatal(LogLine) } func TestFatal(t *testing.T) { if os.Getenv("TestFatal") == "1" { LogFatal() return } var stderr bytes.Buffer cmd := exec.Command(os.Args[0], "-test.run=TestFatal") cmd.Env = append(os.Environ(), "TestFatal=1") cmd.Stderr = &stderr err := cmd.Run() var exitError *exec.ExitError if ok := errors.As(err, &exitError); ok && !exitError.Success() { output := strings.TrimSpace(stderr.String()) if !strings.Contains(output, LogLine) { t.Errorf("Test got %s, expect %s", output, LogLine) } return } t.Errorf("Test got err %s, expect exit status 1", err.Error()) } gokit-0.25.9/xlog/xlog_test.go000066400000000000000000000110011426246334200162330ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xlog import ( "os" "sync" "testing" "time" "github.com/likexian/gokit/assert" ) func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } func TestLogger(t *testing.T) { // log to stderr log := New(os.Stderr, DEBUG) log.Info("Now setting level to Debug") log.Log(99, "this is Not supported") log.Debug("This is Debug") log.Info("This is Info") log.Warn("This is Warn") log.Error("This is Error") log.Error("This is %s", "Args") log.Error("") // test SetLevel log.Info("Now setting level to Info") log.SetLevel(INFO) log.Debug("This is Debug shall NOT! shown") log.Info("This is Info") log.Warn("This is Warn") log.Error("This is Error") log.Error("This is %s", "Args") log.Error("") // log to file defer os.Remove("test.log") log, err := File("test.log", DEBUG) if err != nil { panic(err) } log.Debug("This is Debug") log.Info("This is Info") log.Warn("This is Warn") log.Error("This is Error") log.Error("This is %s", "Args") log.Close() // will be ignore log.Info("Log after closed") } func TestLogOnce(t *testing.T) { // log to stderr log := New(os.Stderr, DEBUG) log.LogOnce(INFO, "This only log once") log.LogOnce(INFO, "This only log once") log.LogOnce(INFO, "This only log once, %d", 1) log.LogOnce(INFO, "This only log once, %d", 1) log.LogOnce(INFO, "This only log once, %d", 2) log.DebugOnce("This only log once") log.DebugOnce("This only log once") log.InfoOnce("This only log once") log.InfoOnce("This only log once") log.WarnOnce("This only log once") log.WarnOnce("This only log once") log.ErrorOnce("This only log once") log.ErrorOnce("This only log once") log.Close() } func TestFlag(t *testing.T) { // log to stderr log := New(os.Stderr, DEBUG) log.SetFlag(Ldate) log.Info("Log with flag LDate") log.SetFlag(Ltime) log.Info("Log with flag Ltime") log.SetFlag(Lmicroseconds) log.Info("Log with flag Lmicroseconds") log.SetFlag(Ldate | Ltime) log.Info("Log with flag LDate|Ltime") log.SetFlag(Ldate | Ltime | Lmicroseconds) log.Info("Log with flag LDate|Ltime|Lmicroseconds") log.SetFlag(Ldate | Ltime | Lmicroseconds | LUTC) log.Info("Log with flag LDate|Ltime|Lmicroseconds|LUTC") log.SetFlag(Ldate | Ltime | Llongfile) log.Info("Log with flag LDate|Ltime|Llongfile") log.SetFlag(Ldate | Ltime | Lshortfile) log.Info("Log with flag LDate|Ltime|Lshortfile") log.SetFlag(LstdFlags) log.Info("Log with flag LstdFlags") log.SetFlag(LstdFlags | Lshortfile) log.Info("Log with flag LstdFlags|Lshortfile") log.Close() } func TestConcurrency(t *testing.T) { // log to stderr log := New(os.Stderr, DEBUG) for i := 0; i < 10; i++ { go func(i int) { log.Info("This is %d", i) }(i) } // log to file defer os.Remove("test.log") flog, err := File("test.log", DEBUG) if err != nil { panic(err) } for i := 0; i < 10000; i++ { go func(i int) { flog.Info("This is %d", i) }(i) } // wait for queue empty time.Sleep(3 * time.Second) log.Close() flog.Close() } func TestLogRotate(t *testing.T) { defer os.Remove("test.log") defer os.Remove("test.log.1") defer os.Remove("test.log.2") // log to file log, err := File("test.log", DEBUG) if err != nil { panic(err) } // not support rotateType err = log.SetRotate("lkx", 0, 0) assert.NotNil(t, err) // set rotate by filesize var wg sync.WaitGroup _ = log.SetSizeRotate(3, 10000) for i := 0; i < 10000; i++ { wg.Add(1) go func(i int) { log.Info("This is a log line of log file by log thread: %d", i) wg.Done() }(i) } // wait for log end time.Sleep(3 * time.Second) wg.Wait() _ = log.SetSizeRotate(3, 10000) log.Close() // only file log support rotate log = New(os.Stderr, DEBUG) err = log.SetDailyRotate(10) assert.NotNil(t, err) // wait for log end time.Sleep(3 * time.Second) wg.Wait() log.Close() } gokit-0.25.9/xlump/000077500000000000000000000000001426246334200140775ustar00rootroot00000000000000gokit-0.25.9/xlump/README.md000066400000000000000000000033711426246334200153620ustar00rootroot00000000000000# GoKit - xlump Lump kits for Golang development. ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/xlump" ) ## How it work // .---------. .---------. .---------. // Task.Add -> | | | | | | // Task.Add -> | | Worker.Work | | | | // Task.Add -> | TaskIn | -> Worker.Work -> | TaskOut | -> Merger.Merge -> | TaskSum | // Task.Add -> | | Worker.Work | | | | // Task.Add -> | | | | | | // '---------' '---------' '---------' ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xlump) ## Example ### Plus 1 to every number and sum to total ```go // Worker: Plus 1 to every number mathPlus := func(t xlump.Task) xlump.Task { return t.(int) + 1 } // Merger: Sum to total mathSum := func(r xlump.Task, t xlump.Task) xlump.Task { return r.(int) + t.(int) } // New a work queue wq := xlump.New(100) // Set Worker func wq.SetWorker(mathPlus, 10) // Set Merger func wq.SetMerger(mathSum, 0) // Add number to queue for i := 0; i < 1000; i++ { wq.Add(i) } // Wait for result and print result := wq.Wait() fmt.Println("sum is:", result) ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/xlump/xlump.go000066400000000000000000000060421426246334200155750ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xlump import ( "runtime" "sync" ) // .---------. .---------. .---------. // Task.Add -> | | | | | | // Task.Add -> | | Worker.Work | | | | // Task.Add -> | TaskIn | -> Worker.Work -> | TaskOut | -> Merger.Merge -> | TaskSum | // Task.Add -> | | Worker.Work | | | | // Task.Add -> | | | | | | // '---------' '---------' '---------' // Task is task put to queue type Task interface{} // Work work with the task type Work func(Task) Task // Merge merge worker result type Merge func(Task, Task) Task // Queue is the work queue type Queue struct { TaskIn chan Task TaskOut chan Task TaskSum chan Task WorkWait sync.WaitGroup } // Version returns package version func Version() string { return "0.3.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // New returns new work queue func New(bufferSize int) *Queue { if bufferSize <= 0 { bufferSize = 0 } return &Queue{ TaskIn: make(chan Task, bufferSize), TaskOut: make(chan Task, bufferSize), TaskSum: make(chan Task), WorkWait: sync.WaitGroup{}, } } // SetWorker start worker to do work func (q *Queue) SetWorker(work Work, number int) *Queue { if number <= 0 { number = runtime.NumCPU() } for i := 0; i < number; i++ { go q.worker(work) q.WorkWait.Add(1) } return q } // worker get task and work func (q *Queue) worker(work Work) { for { t, ok := <-q.TaskIn if !ok { q.WorkWait.Done() return } q.TaskOut <- work(t) } } // SetMerger start merger to do merge func (q *Queue) SetMerger(merge Merge, result Task) *Queue { go func() { for { t, ok := <-q.TaskOut if !ok { q.TaskSum <- result return } result = merge(result, t) } }() return q } // Add add task to queue func (q *Queue) Add(task Task) { q.TaskIn <- task } // Wait close in queue and wait for result func (q *Queue) Wait() Task { close(q.TaskIn) q.WorkWait.Wait() close(q.TaskOut) r := <-q.TaskSum close(q.TaskSum) return r } gokit-0.25.9/xlump/xlump_test.go000066400000000000000000000060751426246334200166420ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xlump import ( "context" "fmt" "net/http" "os" "testing" "github.com/likexian/gokit/assert" "github.com/likexian/gokit/xfile" "github.com/likexian/gokit/xhttp" ) func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } func TestMathSum(t *testing.T) { mathPlus := func(t Task) Task { return t.(int) + 1 } mathSum := func(r Task, t Task) Task { return r.(int) + t.(int) } wq := New(100) wq.SetWorker(mathPlus, 10) wq.SetMerger(mathSum, 0) for i := 0; i < 1000; i++ { wq.Add(i) } result := wq.Wait() assert.Equal(t, result, 500500) } func TestFileLine(t *testing.T) { defer os.RemoveAll("tmp") lineCount := func(t Task) Task { ls, _ := xfile.ReadLines(t.(string), 0) return len(ls) } lineSum := func(r Task, t Task) Task { return r.(int) + t.(int) } wq := New(0) wq.SetWorker(lineCount, 0) wq.SetMerger(lineSum, 0) for i := 0; i < 100; i++ { _ = xfile.WriteText(fmt.Sprintf("tmp/%d.txt", i), "0\n1\n2\n3\n4\n5\n6\n7\n8\n9") } files, err := xfile.ListDir("tmp", xfile.TypeFile, -1) assert.Nil(t, err) for _, v := range files { wq.Add(v.Path) } result := wq.Wait() assert.Equal(t, result, 1000) } func TestHTTPStatus(t *testing.T) { go func() { http.HandleFunc("/status/", func(w http.ResponseWriter, r *http.Request) { s := 200 l := r.URL.String()[8:] if len(l) > 0 { n, err := assert.ToInt64(l) if err == nil && n > 0 { s = int(n) } } w.WriteHeader(s) }) _ = http.ListenAndServe("127.0.0.1:6666", nil) }() req := xhttp.New() for { _, err := req.Do(context.Background(), "GET", fmt.Sprintf("http://127.0.0.1:6666/%s", "")) if err == nil { break } } getStatus := func(t Task) Task { ctx := context.Background() rsp, err := xhttp.New().Get(ctx, fmt.Sprintf("http://127.0.0.1:6666/status/%d", t.(int))) if err != nil { return 0 } defer rsp.Close() return rsp.Response.StatusCode } sumStatus := func(r Task, t Task) Task { tt := t.(int) rr := r.(map[int]int) if _, ok := rr[tt]; !ok { rr[tt] = 0 } rr[tt]++ return r } wq := New(0) wq.SetWorker(getStatus, 100) wq.SetMerger(sumStatus, map[int]int{}) tasks := map[int]int{200: 50, 206: 40, 401: 30, 403: 20, 405: 10} for k, v := range tasks { for i := 0; i < v; i++ { wq.Add(k) } } result := wq.Wait() assert.Equal(t, result, tasks) } gokit-0.25.9/xmail/000077500000000000000000000000001426246334200140445ustar00rootroot00000000000000gokit-0.25.9/xmail/README.md000066400000000000000000000025371426246334200153320ustar00rootroot00000000000000# GoKit - xmail Mail kits for Golang development. ## Features - Light weight and Easy to use - Attachment sending support - Plain text sending support - TLS sending support ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/xmail" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xmail) ## Example ### Send mail ```go // Set the smtp info // New("smtp server:port", "smtp username", "smtp password", isTLS) m := New("smtp.likexian.com:465", "i@likexian.com", "8Bd0a7681333214", true) // Set email from m.From("i@likexian.com") // Set send to m.To("to@likexian.com") // Set send cc m.Cc("cc@likexian.com") // Set send bcc m.BCc("bcc@likexian.com") // set mail content type m.ContentType("text/html") // Set mail subject m.Content("Mailer Test", "xmail via github.com/likexian/gokit/xmail.
") // Add attachment err := m.Attach("xmail_test.jpg") if err != nil { panic(err) } err = m.Send() if err != nil { panic(err) } ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/xmail/xmail.go000066400000000000000000000134601426246334200155110ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xmail import ( "bytes" "crypto/tls" "encoding/base64" "io/ioutil" "net/smtp" "path/filepath" "strings" "time" "github.com/likexian/gokit/xhash" ) // attachment storing mail attachment type attachment struct { name string content []byte } // auth storing mail auth type auth struct { tls bool server string host string auth smtp.Auth } // Message storing mail message type Message struct { from string to []string cc []string bcc []string subject string body string contentType string attachments map[string]*attachment auth *auth } // Version returns package version func Version() string { return "0.8.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // New returns a new xmail func New(server, username, password string, tls bool) (m *Message) { m = &Message{ from: username, to: []string{username}, cc: []string{}, bcc: []string{}, subject: "Mailer Test", body: "Hello World. This is xmail via github.com/likexian/gokit/xmail.", contentType: "text/plain", attachments: map[string]*attachment{}, } servers := strings.Split(server, ":") m.auth = &auth{ tls: tls, server: server, host: servers[0], auth: smtp.PlainAuth("", username, password, servers[0]), } return } // From set mail from func (m *Message) From(s string) error { m.from = s return nil } // To set mail to func (m *Message) To(s ...string) error { m.to = s return nil } // Cc set mail cc func (m *Message) Cc(s ...string) error { m.cc = s return nil } // BCc set mail bcc func (m *Message) BCc(s ...string) error { m.bcc = s return nil } // ContentType set mail content type func (m *Message) ContentType(t string) error { m.contentType = t return nil } // Content set mail content func (m *Message) Content(subject, body string) error { m.subject = subject m.body = body return nil } // Attach add a attachment func (m *Message) Attach(fname string) (err error) { data, err := ioutil.ReadFile(fname) if err != nil { return } _, name := filepath.Split(fname) m.attachments[name] = &attachment{ name: name, content: data, } return } // Send do the sending func (m *Message) Send() (err error) { if !m.auth.tls { return smtp.SendMail(m.auth.server, m.auth.auth, m.from, m.innerTo(), m.innerBody()) } return m.tlsSendMail() } // tlsSendMail send using tls func (m *Message) tlsSendMail() (err error) { conn, err := tls.Dial("tcp", m.auth.server, nil) if err != nil { return } defer conn.Close() client, err := smtp.NewClient(conn, m.auth.host) if err != nil { return } defer client.Close() if ok, _ := client.Extension("AUTH"); ok { err = client.Auth(m.auth.auth) if err != nil { return } } err = client.Mail(m.from) if err != nil { return } for _, v := range m.innerTo() { err = client.Rcpt(v) if err != nil { return } } fd, err := client.Data() if err != nil { return } _, err = fd.Write(m.innerBody()) if err != nil { return } err = fd.Close() if err != nil { return } err = client.Quit() return } // innerTo returns mail receipt func (m *Message) innerTo() (to []string) { to = m.to for _, v := range m.cc { if v == "" { continue } to = append(to, v) } for _, v := range m.bcc { if v == "" { continue } to = append(to, v) } return } // innerBody returns mail body func (m *Message) innerBody() (body []byte) { now := time.Now() date := now.Format(time.RFC822) buf := bytes.NewBuffer(nil) boundary := "----=_NextPart_" + xhash.Md5(now.UnixNano()).Hex()[:16] buf.WriteString("Date: " + date + "\r\n") buf.WriteString("From: " + m.from + "\r\n") buf.WriteString("To: " + strings.Join(m.to, ",") + "\r\n") if len(m.cc) > 0 { buf.WriteString("Cc: " + strings.Join(m.cc, ",") + "\r\n") } buf.WriteString("Subject: " + m.subject + "\r\n") buf.WriteString("X-Priority: 3\r\n") buf.WriteString("MIME-Version: 1.0\r\n") buf.WriteString("X-Mailer: github.com/likexian/gokit/xmail\r\n") buf.WriteString("Content-Type: multipart/mixed; boundary=" + boundary + "\r\n\r\n") buf.WriteString("This is a multi-part message in MIME format.\r\n") buf.WriteString("\r\n--" + boundary + "\r\n") buf.WriteString("Content-Type: " + m.contentType + "; charset=utf-8\r\n\r\n") buf.WriteString(m.body) buf.WriteString("\r\n") if len(m.attachments) > 0 { for _, attachment := range m.attachments { buf.WriteString("\r\n--" + boundary + "\r\n") buf.WriteString("Content-Type: application/octet-stream\r\n") buf.WriteString("Content-Transfer-Encoding: base64\r\n") buf.WriteString("Content-ID: <" + attachment.name + ">\r\n") buf.WriteString("Content-Disposition: attachment; filename=\"" + attachment.name + "\"\r\n\r\n") data := make([]byte, base64.StdEncoding.EncodedLen(len(attachment.content))) base64.StdEncoding.Encode(data, attachment.content) buf.Write(data) buf.WriteString("\r\n") } } buf.WriteString("\r\n--" + boundary + "--\r\n") body = buf.Bytes() return } gokit-0.25.9/xmail/xmail_test.go000066400000000000000000000057431426246334200165550ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xmail import ( "testing" "github.com/likexian/gokit/assert" ) func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } func TestSend(t *testing.T) { // Set the smtp info m := New("smtp.likexian.com:25", "i@likexian.com", "8Bd0a7681333214", false) // Set email from _ = m.From("i@likexian.com") // Set send to _ = m.To("i@likexian.com") // Set send cc _ = m.Cc("cc@likexian.com") // Set send bcc _ = m.BCc("bcc@likexian.com") // set mail content type _ = m.ContentType("text/html") // Set mail subject _ = m.Content("Mailer Test", "xmail via github.com/likexian/gokit/xmail.
") // Add attachment err := m.Attach("xmail_test.jpg") assert.Nil(t, err) // Add attachment err = m.Attach("not-exists.jpg") assert.NotNil(t, err) err = m.Send() // The smtp auth info is fake, sending will never success. // Change below line to // assert.Nil(t, err) // If specify the valid smtp auth info assert.NotNil(t, err) } func TestTlsSend(t *testing.T) { // Set the smtp info m := New("smtp.likexian.com:465", "i@likexian.com", "8Bd0a7681333214", true) // Set email from _ = m.From("i@likexian.com") // Set send to _ = m.To("i@likexian.com") // Set send cc _ = m.Cc("cc@likexian.com") // Set send bcc _ = m.BCc("bcc@likexian.com") // set mail content type _ = m.ContentType("text/html") // Set mail subject _ = m.Content("Mailer Test", "xmail via github.com/likexian/gokit/xmail.
") // Add attachment err := m.Attach("xmail_test.jpg") assert.Nil(t, err) // Add attachment err = m.Attach("not-exists.jpg") assert.NotNil(t, err) err = m.Send() // The smtp auth info is fake, sending will never success. // Change below line to // assert.Nil(t, err) // If specify the valid smtp auth info assert.NotNil(t, err) } func TestAuthFail(t *testing.T) { // Set the smtp info m := New("smtp.126.com:465", "i@likexian.com", "8Bd0a7681333214", true) // Set email from _ = m.From("i@likexian.com") // Set send to _ = m.To("i@likexian.com") err := m.Send() // The smtp auth info is fake, sending will never success. // Change below line to // assert.Nil(t, err) // If specify the valid smtp auth info assert.NotNil(t, err) } gokit-0.25.9/xmail/xmail_test.jpg000066400000000000000000000764101426246334200167270ustar00rootroot00000000000000ÿØÿàJFIFÿþ;CREATOR: gd-jpeg v1.0 (using IJG JPEG v80), quality = 90 ÿÛC     ÿÛC   ÿÀÌÌ"ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?ùo‘O‚í €qŠbiz’A'uCydSÀ8#¦0[9¦=$üÜŠ€Û4l <j %˜+‘p 9|”]™9™;HÒî€zSà¶wä–=轊F€Ž6ßHc~uX6ÞqO[wÎìÐÑÍpdѤg¸«‘ÆŠF5V-9ƒdµX[·€sÛš¤"W$ }iŒÊ:õ¨ä¶’OvªÒ[Êçp$sV„[Èoâ{ŒÓ%ÈoJ¡öYÓqÉ«1–Ç<šL ³+³näý*&¸h±ÀzgŠÓò²3ùÓd±YS×5H ÙnÕv¸qóu”…‘—åëV¿±Ô’Å)ÒÔ”â‚H @ΜŽO¦ñ2㿪µ½)ì:V¡‚DAÆï¥/˜Ëœüª¥ˆ’K’Wrí9'Ú´§Ý»çÒ¤’5±Òe#Y=:ÔrؤՋV×é2†Ft©ãœœdz×5_ìÛr£†Ï­t×âHw €h ѽ%ÆAÀÅpã½|õ®ÂIZa•n«ÖI{Ù[ÉÅ\w9qÐe÷¹â­^ªñ‚MT²ù9<оÊOº LËqÁ5Öø@iÎyÀ®f{rœü¾•½áY ¨wÁÁ#8©–Ç~ÞGoå}標7 ä}(‚í$ŒqžÔøÐ1>•ÎÏL©"(Î;T|J§¶*ûÙoÉ­UkC^((¤É†ôªÓª¯NZRZógðõªóZ£å¤2§ôˆwñ«R<ƒçPÙç b«InÂ0crWÐðj æîNMØïíM!ˆÊœCÖ¢V|`ðZz“Ÿz LF2¤7Ò‚ÄœÓþݬ “Þ¢#iÆh(°5)…â–-MÕŽMQÞ¯½4œd“]Iœ÷f·ö¯'Ÿ¥E& ¯…ÎO|V[ÈÓDª1Æy¨qE›V²G3 €t«áa—vµsV²2 $óš·æ:9ËTò¡]›È±ãÏÖ¦Œ§¨®vžV ¹ýì j(.ÍŸ—z§[”Â85ÏIs$ci‰,²EU»÷:=Fõ/HP§ª¢¸aÆj¼ ï‚FÓWT“ÄØqõ£ÉôãÔö¦¹º“NĹ`#c¨¦« ½9¥À<óIA&˜\†cºšü·)¬Ùã¦j=øïÒ˜\V%¬Á¨HŒvêj²#ÏÂ!fö«VºkÇ)RNMH¤\¹XFX:UÑ¡Äñ¯™'Éêj½¸kllP¾¾ôKzÌÝ:u©{ ønÎÞF“isœ’Ni*!T£3£‚ #éY3ýòö5™BI àãœu®WP#í/’HÜzÖûÍósÇjæîØ=Ìž…i ÎLK\„ÖÜž:U؈ù†yª¶…væ¥YÜØ•ÕÐù÷¹+€âµô-Ë „pç5‡æŒsŽMnè²n‚NÀšÉ­K ¹¤¼á‘Š0þÕ¡g¨‘•—­fåNzTŽá±ò“Æ7 Äõ.t0N´ƒÅI:,ƒŽkÎ9° ç•­¼ŽrÍ·ŒŸj æ eHÎig³U#¦*xlrì9=÷ÅXþÌi{švAt`\YïcØzÔuŽÙ¹«§] s’j€èg† g ¤ W3Z@«Œàô⢠҅UüI­_ìM‡ò{Ó#Ò2Ç'Ö¦Åómw b íúsP˳‘¶CW³V»iȇ¡È¨eƒÚ„¬&ûeI<Ž˜ùiDeÀÝ)Ó=*w„¢ät¨<½Äïªm“4œƒ*qߊcZÆÀ*²}ê„’ÔRˆ·Cë@ö.œ«ž™œÕK=ÃŒ©Ž0*ø·ù¨éŽ´2N9Ïßc6;`8ÈÏ¡ëI%™†Éô5¨ÊíV+ 'ø—š´±Ä¡#‘ü“Ð2*XÓ1 »´`éØÕv„ÄÙ~ ¯s·Èr»Aá”ñTf'e}EA\Åu†)‰™s¢æ<£~T³[H‡to€GLqE¥Ä¥-€?åŸoΦÅò(Œˆy¨ÚŒ®E]¿ŒÈ¥×»ƒT º%vËÈ{R±WàšcDXä žO‘‡yÍ?í(¼ÏåNÁqÏ¥…Y÷v™_/o~µÐÎx'¬Õ`f9äÓæd™ãJÂŒç¥C5žöU\“ô­Ye;Nzf¥ÓâÜÙG'Š|Àg-£ /J>Êå²Fk¡òWŽãNû:ô bÀç㉣PqƒéR‹—S‚+^Hv¨þÌ®Ëò zÕ¡3:4’àŽ0 ëZv¶øÿ*·ªÆ  qÂô­·aœ/S<Ìœb–WÆH¨·ŽôÅ{Ž ÏZFlšýi…ó@ùø^•Ƀ׭C# šä ŠÅÔôíûˆiy¦q÷,ÑÀçÚ±î¥g|ýÖõ=k£Ô´ùcû¼ã¨5ÏÝE´’AÒ‘W)½ÓØø9çw¥aÝàJÛNy­9ξ dË0œÕÃs‡¢-@@\ °»yZ©lG<Ôñ¾Õnù9É®†xorFm­ßŠÙÒîÚÏç$Ö %ŽâF\´fœ¤e‰\ä OCÑÃnuv ¤U«hiı¨Æy5_G‹(˜ƒŠßQµÓc<æ±hîr×AÙ›xvªåˆàzÕ¤´—ì¤3Œž*Cs•ŸpÂ)W†pcˆù‡╊SvØ»k(êFpF*ôHª*3Tóaac€3U®|O ½Ï”ÑHéÅ!^æÑU`Ò0Pƒ©æ²[Zù—‘ž´É|Eœ@ËrhY¦ÐnR1ô>•Û( 7wâ³WÄ+:yîÇqP¢©wFŒgaÍÜЖĿ$Ф-•¥(@'Ö˜ºÜRÀ³jbëPE&æÂV¥pØŽ{3’xê´Ö¤¯L:UØu(¥,èwrHçò¢âò"wtwÆ™‘*•Qž8éQ†ØTšÒ–(¤ù‚–©T™àBC\tϘïr”éÇz@à’=³‘D›6†®*&ŽÝTŒûP„J³84ËÆ2€CòÒ Aò±Gu!fóÒ†®.6º“ϱ$3 fu@'±8ZÍšY£êúUT½“ ÏCëI"®trÄ»q·qY:…¹çñ¥–|tëPI&zÕ¸†f<¶šyïLy1Å@ò2ûR¸X²e÷ÍDÒà5WÏØXõ¢K€OLQq“F9ëL3n ƪ™‡5O‡Æp?*çg¢ù ±qƒ×ô¯§~ ü²ñŽÚ½Ô‚h¤à&9ñí®¿öG@ÁËû»öYñ:k_ŒHw&ÔàÛ’?•rú¸`JñžÕô_Ž~ _iÂw¶â(ù\W…kÚdÖS²MŒŽ ×4¢Ó'Sˆ¼µ`:t®nàbVóÅv—jFòF8®2äëšpZœX©]!ÈÆ¥Y8ã‘P  IɤwàWKµú“<šÑÒ$jØÖ”ìÉ5oG¿ò¦F~PMfüÏK¹éºsJÖÌPc¸sF»iÔ­ÁÁêǹëO…÷>XÇw8â©êzʬ™ŽE“<¶™è¥tzPÔ,¢]¾bð@¬_¶¥•Ü…xçà®6LÔÚ v QÝk³_>^AœcÒ¦år3Ó-õá®­oóN2Ý!%jšk"• m§î±Z‚f·’qøÔ\[…9$žÀš›‹Ù¡§TAq»nåÇz¨×Ë%Ãa@ÿ6б$|àÏÉþeP=è¹vp²g8U5¸Sœ·9õ¡¢Üxl ì ëHD„¬iÇZI$Î#“ÍPÓî »˜ôõ«–ø’R~÷<P;Ö*Lc°•t˜€2qIeH¹üª|§P" „·z|´ÑáxÇ¥?8° 0+E©-Ù*›OZc¿ãíK+à“UL§&µ¹›Ô{¾å5 `£®M1Ü’ÜqéPù˜&‹……i9äãÞ¢2dŸ›4Ö`sP»m¸?Z9äÂÔO.@¦9yô¨†ìz÷¤;³ñP±.xíQ—m̸Ï=é7cÏÖ€±ò¸#=y¯©¿bO5KEpÊ.#Þ‡Ö³#4˜pà ~•ó‘4r“4’¹åâýÝe¨%û¢­º9 Ap¤ àÖBµÊÉòKer‚#¸á‚ÿÒ¢¹ÊezYÌèÛ!IèkÝÖ# ‚ìÍ?ÊÙNÄÔsåãséXë?ÙãÈá»SÌÄ;0äú×5Ùé¤m&¤ã–÷©â¼màžñTa’5l`êjÌrƤ0Æ}¨»*Æ´-'eëëI6¢Ð>ߺjƒj*¬w0L Ž:Õ9n¼éU éLfÊê8ô«¶W[ŸçlñÒ¹ø®” ¤qOûNÉB;R±hº•°:S.%1 îËÕ„º‹¨$ÓÖõ†pjTUö)ÉØÝ†ë¨bª}1JÓ°û¿(=ëÎK ¤”³^H… Öœ¤¦[M^I¨9¸«ðàØb+.N1üTÉ'tRO_J\£¹Ò­Óm%Y~Uç'Yµ'Èc…QÜÂ7NØVæ¥iö¨ œvÇj9InæÀÔZOùh1O}FUd”Œw®xÊQ²Ÿ¯Zw<–Á§a]£¡kòÝ \ú‚âñ‘ I lõˆ×ƒ`å‡zíÛñNù²/<Å&F'޵×*@ëYtÈË–Èô¤[‚íÏJr+ø³)ã*xªMl%\«VÁe•0[Rkqצ( Lµ·Ã˜TjÒùIàu¬L|·‘ŒÓ,“##|VZ—s5^E ç¥+O"7Ÿu,itÀ0·5f;A2#v{Ži¥qtùVÜV¦”¸˜Æk)îªÏzÐÓ&òÓsc?JVÎŒ¸-Ç‘Üý=«9nÉè)Æé˜ýÞžô¬K4Q†ÀqHò qÐU¿Ú1·ó¦‹µ*pqõ­bŒÞ£îäÈëÅUß‚*›œð W3ò:ÕnI‡cUØòMBnW<ÓZPI bXã¨ÙHQ“ȤóFàsMi7t4àõ=©Žrzb—z–Ï9·zâLÅHÅG»æ4Hàõ5‚<qeo3ðíWt >Í«Ú:ðUà  Gz[iŠÝ£gzSNÎã?Iô=KûcÁúUÚ y°/ ô¨¬® ۀʔy®kàΩý³ðŸJ‘HßcqÔñßõ­øyå]¸lqSSI3ÒÿpÑE[ØcÀ­¤km&4iO™  bix7H9<ô¨µ§v»b #°=ªe'ݹß+:ÛOÙNã|;}p*äÖ–wqî‚L^]%äÑœ+càѰñ È%[¿=+ˆ—Q¼+kC¥ñ‚S_Ó§´¸_1%B¼Ž•àױţܼ‹}µ È]½=«Ü ñdÉÀ˜°ÇVïS«… WLkÄå©…”·GË^+ý“î´âf³q<"&R;†ìkçc·:SÜEq G°ã$c ÿׯÒñ]´Òy{G¹Íy'Ç_‡v~$ðõÅýŒKö¤ýã*¾Bœ~U´k)lyõ°\ªö>½ãqX×1î”gŒ×[â}:K+‡ó¡V†¹w„*ä‚ÄsJo˜Ç ¬Údmm$Ëô§A ŒtéK¸RY¸ì*9nÛ¢u¬OE4YœíAƒµ©©på1Þ©¡2œ–4õÜœu© ¹iX…Ë6O½ÔXEß<Š·cv#ÜÌ;H¬ˆäçž”övSÁâ˜}‡-’=ižx”õ+UšMÑûÔ+.ÓÍØÑó€(èzÓÚq+`Œæ³„§zГ¼oØÏz]ó0IÏJk\8ÍT3rãpÝëQ»:óÞC°ê:ûÓK3?·z¦’dsÁíOkŒ3Àô  $¯zh”‡§Z¨Ówúæ„}À€1š¶e~õ9& Æjš’)å"€5#˜dn8æ¥|L0+67ËàÕ¤sÚ€+ÝC#8=j¡oÜy­èðà ž´¦8ó†\ †ìZÔä¥yìNúV¥áެ¤и±›q\j‹ìñàj¬ÔHÅùÅjA¢‹-.F}Ä튺,ÈÈç#¶*Iº# òdŽ)ñHª3jzÚ¿ÿ®ž-Î0yúUZä¹hC$€µÈ1ÏOjšH1×§±¦KlÊ í=0j’hŽdUvÝÞ¡/ƒŒçéSÉ ÇÝ+U6WÀ\u¡49Ÿ$ k¾O±¥±gŸ¥1¡b:Àay¨‹g<ñO1œ‚j/)òF8õ¦´¿!¨ZL f¥òHSžEFa/÷Täs@ô"bÛºf‚?0ü©á庌RÙ·$úP"7|!ô¨àË09äÔ*:œÔ0#œZt}­û&k-à=GMf,m§ž€¯øŠõW„›q*Àõò¯ìâ94ÏfÈHƒP‰¢Ã ŽE}M¬y°Ì$*!ÀØ)TÞçv_tѰœG¨Â1œ;ÕjÃeÃHùSÛ=ë Êb—qÊÃA9=+·ÔíVh#¸À“rä †¹£©´×$ô<úú²ž0?¼+6X¤…Ã…Î+¥Ô!ÞÛ:dúVeÄßóŠãjÛ…)+VኜTno¤…ÉÞqVAéŠÉÔ[‚ ǽMÎ¥¹ šœ‘Ì®§ÖºíTæžñychXúŠó©e(ûO5×x1•óò¡ã8ÍmMêMzQ•6|ýñ“àÌÐjw³ÚFÒÁvÛÔœ7ZùÛXÐ.ôò©Õ¿g‹Û qtªE¸ÊÝKdMÂÅs¦|þ0„åH¥fÜœqŠê|]àK÷“Å-¼›Ø+×Í_ÙMe!GR¥@$¼ŒÖ6fŠJÅRU9-R¡ÀÉ8ö|c4®Ùlç… “m,s»Š°˜äÕE“ƒNËÇ¥H\rÌK“Îj›Ì.:ãëT×÷Ž1ÅXv€rh “‰0$úŠkIvñZe#®EPry  –Cƒ““M<ÔhøSÞ•=³@‰@%Iã=sLß¼Ÿ›õp0¨Ä|–b€ š›x`¼}j¾ÒŸMaÂ…ÞOj{ ËÇn¢P:óL-‚Ã=ê&|ð8>´ ´Ä…úÒ8ÂõëÒ£YXàgŠ”à¤v +*dŒZ±¹ê)½Ûµ,mƒ@¶Àž”²†0O5Z)°£'Š·ö…‘¨ïI«”…’2b,xÇz¢våO|KÇ\ÔL‘¹ÉÎ}bÝŠº=Ú[¤\˜—ó« <ý~θ¬¼@p¤¤ƒØŠ·ˆPcp`µhs¶í±¨n˜çu©8ô廥¾Ëù­gÂA ÉQM>!„MnbÛì]{ëv6¸=òµ]¯­ Ûà}1ULj­ÈæEüj_íÛR˜WBO5®äÜÚ{Œ˜ŠÒÓ$ÉÛÓÔšA©ÚÍœ4d÷¥[‹wáJ鎴(‰Ë°…4¶8§¾ê‰tû`¿h~xükJËNK¢ Pçž3Šî<7ðžãWq²Ìí~A#¡ªP;GžÇ ZJáVfç€x«ÿ𛕜)èJ澊ðïìì$Ž6’RyÉ=«Õtoƒv0Z.mÑÀèØäVžÍ Ú3âÛ†ÎÁ2ß0È%kVÛàž£$ÁTnŒm¯·­~é–áG¿/C·­kEà«(À Œt;qOÙ¡9Üø†Óàà”Äè_¡ÛßÒ¥Ÿöq¿ˆqã´’¯¹SÃ0q˜Ô•9mJÚ 8ɧ|U*qêR›î~|EðS‘Aò´o‚;âµÇìßxö× å|çk­}Ã7ƒí^9bÂqý)dðô1…cµ?gægÃþøo«ü;ñ†®–²m³•duÛœ€rqøq_MjÓ=üÜÃÌRÆ-뎕ÚÞøj Á•=~ZÉ6vbÙS÷JNßj¤/ÐÃÖäÙÆÁrÈc%³!í^‡¡Þÿhhi»-$_.Aí\¯¢ÜÀÍ4ÛþÍl|>Ôݯd°¸4Švë\ÖkFzõœyÓ/ê1®çlݸéXӯ˸ŽqÏÔêO$¸Û‡Yw:qÎÕÈ=Æ+žq*›²8»¡¶F#£v5‰zÙô<Šëµ]1‡ÏŽEr¨ò¥mÜdV-XöiÚHȼ\®Aã×½vœ@ö§à ×=ʬl p+µð [ù`¹ ‘W Í*Ó´è-lQÎ*Ù´Ì‘ õFÞ<¿9'¾k^ÎÙ”Ž1ë^¤>7vWŸMŽLåsœY·Þ´¿µKh÷Áõ5Ö=°Û*¼¶Å“ŠÜó-m.ñ_ +ÄÐÜC-º‡”`+Á¾+þËææâÖîÆ3’‚)‘G\pü¯±³*äóQ\Ù¬ü:†#<¢æhü´ø…ðSð~ª`x£Ú˜`½Ïjóû‹ !}²eõZýañ?ÃÝ;Ä%Ͳ»|¤k寊߳Êí6*2ÄÏñüˆ¨ä:T6`sÓùÕ}„“ŒãÞºísÂZŽ›w,ofàFøÉk4éSD»šÕÁ?ÂsÇéXò—ÌŒ¸¯¹«*»9 –÷«ÐéÌ6·’üðpµ§ý„È€ùLÙç8éM+…ãÜæX¹s»­.9µnt×ÞXDá}Ö kQó!^)¸ØiǹGkuè 7Ì=8î*ëÛ¯—ÈoΣ{DdœT0mt!I<ÌÛ¦h;ÉÎ{ôbùÔ†ØàÎ{P•År«œ/“ïM²`ƒ´Žwµ;Ú ÜžÔ Rè45anD['ša'ºb¬ý‘ÈÀÅ=-Œ ¤2›“‘œT‰/Ö¬K`ãŒúP–R(#ŒŽÔÄY0£½Lm¤L9ÈSÜÒ$$áJóR=¬¼HŽdàñô5Ö'tkû6ß~ÕZmÁ¿åØÜ^‚º6–Çæ¿•G§Ù¿ÁªFд‡ãûTfµqü³MDÍÊ6<ÒO iŒø0¾µ xWLwÛå¶=Bâ½0øcM“îê¶øÿjGþËZš_Ãä¿ Guo)åÛ¿úV£cÓØòk‡uÃn¾üW{á¯Ùäêíð«2‚:t?ZöÿüXäŽIÄl ìZ„Fl;BgîÒ·KBOðGìãk¤Þ­Î7w’7<¥{và«M=Gn©‘ØWQohL*ìvÿ7Ýæ¨–fÛéÁp¬ {ã­][0G º¶¼T©ož´)}ÀÒÚœö­O$cŽ´yËsê9 ãn)ÏÍl»°WƒÜVmîŒ& {WTm÷uT-ls÷(gTv<ÇUÑn" ÆN=1Ö±‚í&xBÊŒaÖ½rãO\ Šæum D¨Ÿ)ë\òWgT&öcþд –r2ØìjŒhŽJ¶C¹«NtùY$%¢è&xÚO6%`ßÂAoÆ¢œ-#Ü­V9è¶6¢IUKc7º­Ë/*úb°&‰¬õ»_1›ÊpÀàð= vp@0Á²+Ñ‹»>*¾÷ExàÊcÇ·íŽ+N(öÜt¨nbù°8­™æ™ßcËd*,zð+Qa8§¤y 6æƒ7¹ÏIdUóëYš†›Ñɑߊëe·ã¥U’Ô:"“¾4ü’[;ýJĈäÿXnyÏOʾe½ðî· Ä;¡' aŠý(Öôˆ¯`’ †Îkæ‹?°î¾×mö#÷qžÇ=k!6ì|ðºN£end…ç?v.˜úÔ ¤¤1Ž[ª:W_6•¬MpÅ­w·RAëW,¼'ªÈCOfð©è äŸ¥R‰—;èp¤Ù jŽ;bœtëÉuÆœ¡z95ìžøªêä$sy®Û7R=kÕ4ßÙÚY¼µîß÷æaÑF~íW!ª”š>E¹ÒÖ+eó4¬Û`ÍU}* ¨}-Á~U¯´¯gY/õ˜ìŽ,â8ñÛµ6÷öot³eµhšíÀdœõÅ…^GÄZ.œ¯e2ãƒÅW]OmÄÛΊlæ¾Âºý™ï#_”yî:üµ ³µå²þòi[€=Í4|‰ý¥0Å:ú’*!áý‹~öAì9¯¬5¯ÓÙò;`Øãîç&¸­GàíÌ6û0qÉÂãh©pœÏ‡AÐÁ9¸˜;U˜¼9¡»97n1ô¯K¹øz«+bÐ0^¿-QÚF 6 …Š÷©µ…Îúœ:x_C~~Ý&OÒ–? i¸a¨±t+]{xì‰'¦Ñɪ÷þ ·³>\öþTvî9¬/jÎhø/Ow ºª¯ûËSÂiÉ:¬a{dVŒ¾µ`~ò6?¼jðt¼l'*•ß÷ªlªÊ#ÀjOÉ©CƒÓ5f/‡ä.F£nHíWφ­˜.iF:Ôãáp¤»Aõ4X=£þr<006yÈn•2øçë ?]ƒÃ*·Ò­Y_ ÝcAñQcxÎÌuªÀ(©R]¼æ»)8íQVùpy® <uà­z+û@òÛO!Œ¢ôQŒî5ÞiJ®Å³Ñ}+§[(õ xÄ€2zV©\Ùb'IrËX•,`‹PµŒJ«»îmCf"Œ`’HéYÖ{Úͳ£‘ô­µm€)çÞ¶Š²<š³»máƒò1õ¦KïKv«Ày„àtªò£p¦¬å½È /N}i±(ó8R*EIã#@Q1Š ÖåYc1UŠ®ÃÅ[urßwåõ¨»û½h.èÀ¼e ÇéÖ¸OˆêÚL›8”•½+ÒÎŽóÌÒ(“à u…aGQëKÇÍ>øyu¨$[L¤}æ+œW¨hÒ7ó®[çcÎqŒW®éžµÓ@òãP[œsZ_dN1“éZF62ä9MÁözF<˜•vŒ—šÕm8p®kOÊN@¡äS‚¡¢VF|zbã§ãOû /@¿•Y¤j-¬Ù4ô(ÍòÕQn œ¨#°Åi}˜¶Xô¨Ì@ŠL ‡°Gá£ROLŠÌ¿ðÅ¥ôl¦Úzñ]RZn?6EN,T®óÅHúQyð§L¹B†žp57Á=I öq:(¯pþËVN æi‘ ;Î1Eâ·3äoSÄaøY§é±¼‘[£Ýãäb0¼›ÅÙî&žBæF$åE}o{eh –uRs÷É¥®ñ$Ñ9棚={#â]SáƒÛ±A#©sØ{Öx¼Ó¶ïR+ëxE5sgÙ. 9oLòÝGÀw6äí·rG\sEÎ9Dòà›„'mÈÏn”çðuö2.ü«½—Ã,„YÆ;ùmþh2¯ü³‘~ Š‡±,â¢ðޤ~ú6oAŒTŸðŠjÃû¿•vèî¹|ûfœtö^ ½f\O3â„Á 4Kœðs[¾ñQÖnU#ˆyƒ -ÔûW#§ü(ñ?šìÔ²@ÙÍ{ßÂ_ƒ ÆÔ­†þàðEŠ{mWýúáÑç·(AÈVë^ù éûlãBªàqMðîg,`r95ÒÃnq1žÕ±Í-Hc³ŒpZŽ1Ò¥ŠzU•‹M„†ÇÁ©„%OjzFxÇ^õ`'½IDHµ8LRǬ,y€+ªNU Òƒ\äâŸL,)]«õ¨Ù08ëVU:SJäô¢Å$BìühÙV0E"¦8Æi]ÄG„SLžÕiÀÎÓQ˜öRº ¤\°Å!AŒTÊ7LSŒYô¥'sk¢“ÄJíùéZrqŠzÆçÖ¥n]û’Ûîã©øqd}ñe_®Ev-l3ÐT2AÁ¤Ò¹\í#„ºðì»sãTåÒ–•Æx"»ñn¥ŽBöÉf à=(幬j3Ï-,~Åw4#n×]iAAFzf¬Ë¡­Ì4–èjõ¦$*©!p2})òØ©T¹ ™ûD…†èk@XF\3ÇZ·mdeŽMYe+Xìs9.¥%" Í …$VÂàûÕ™T–ãÒª³0^jŽvÝô"û:ã€AE– ÕÀå×jñHb À{g4 ªÖƒm0Ähb­ý኎@Ôa¨‹6¨½H»Wœ ®­––RÌÀ(ª(‘¥Þ¢-È<–5=½“1þïZ²-BŽÆ™ªÑjf4.Çš™-Õqš¼°ß…V”•/Ìh+qŽƒ…ª¬==ªy0à8ÇZ†VEþØ –û¨M!#f0?Z¥ŽÌ„ƒ×’k•fÛz¾sY÷ˆI ù~n+';Æ:Ø’}bRÌÛvôÉëXz¯ŒšØa0W±ÍVÕL²œE(üõªph>y +ocÖ¹¥)7£= t`•ÙBjS¶ab˜?ÃY×þ'ÖæFÃHÇýšíì<6% p©ê*ýÖ‡¦éÆk¦(Eõ¨å”ž¬¹N”tQpjGòy ØÏ2x9ô€qH@ Ó”0i½I­ªÖ#ÇJ‚œÒ°ÛŠc&æçšJìq|ûS€{ç½6`TH\”Å+‡(8 ‚84ß0¯^´ŒÙàóUd›œTÜÑ+"Á˜Èù«PDHÉÕk( Í» kQTïV£q_[J#R1Ö”쎭ùP¸|ã·JÔ™ ?!ô¨ßîÔÊŒÔ2–û sTˆ#g ñPù„“ž*gË\±Á4ÅFò£\–FT㊅w1殲01£XÇ©¦)ÊÄyô¨#FbYÏi”¹>•–IÆI¥qÜŒ¨ã½/—ÅLЪ/'šn~Z.ZV+̤t¦Ž@Å:y0pzÔÖöû°Ý½¨¹J6ò€T†E Œã=jÓ«<þ5˜ìªä›>´\díEIqýj¬ÂVàFƒß&æÝ´UW½w•A Ñs¢*è­t$Œ•a»#’JËû’]Ù8õ5°db9b=:ÔlÞdÛ@R2XÔKSx;Õ6U2zÖ„V"Fà*uŸº¹‡õ±–óàË•Lpf´.u4 ŠÚ[¸ü›Ý'y=iÑøb·n•¤îMlÇjb‰SpÚ:b‘ÉèšµfqÊm™ƒJEè§Þ›%²¨±ZLW8ªRʳЭ ýHTM¹8ö5µRNÖ#õm"ÙÏ¥Aqr»þQÏÖƒ¹Yg 2k>çÈŒô?_sœ3 ª·8;¹ôÆ»Ÿ/MÈÎ_¸ÓSqd8ÍV6ÅCgzÐ+—°¬Š[o·5Ë_øzÚêàÉ$hÌ{šì."%p8¬I­Éçšz(«ïW£ °f ·‰”rqWT h3°Ä^x…]ÜñN#,½w¾”ÿºi¨¡Fîþ”æ|šMXhz¸Áâ£#'=¨Ü§ëGD5#3Ò“$žN1LLçÒ’­JäÊüsɨ‹àô e©¬@9<Š ¾¶NXš‰äÚÞ´æ!†W¥ViLmžÞ´›±KAìþ`"›‘“š7I?ÅM`J›Œô©*ÂGQÓ8ª|´‡œdÔ³mÁËŽi–,.®)E]ŒèlbX U¶O½L;Ôj dةԆ®„¬fôÔðúšRÁ@¯€r:úÔDå˜N3šd7pnH§)À&¢‰=3ëïLið<Õ!±ÞÃ?­8• îá»b«; šŽYð3»šLM¼ÅX"œæœ6ýj¸¸]½Fê…®ve©æ°¬îM3Øjc ƒ5º(29bzRK6ÿ™AÓµ.cu²Ý3U柵B&$’Z‰¦&AëG1V'‘v¦æïZ–_,i»¿Jĺ¹ÚcCÆN+J|€}Ñ×Þ¨eû©šÂ˜3͸t­K‡ß¹A¬öãÝÁ>‚-È ¡l1¢69Ú=j´°O#Ò„øTdÕ«k& ´cŸZ™^Ú¥~¥;‹åßåÆ9ëéZv3Ý(”õÇ5nÃñ!ß"î=ŠÖÅ»ý‘BíÚ¥)=Ês²²+ZèÑBÙÆZ¯*ìZdê¤Q½Ê·v icÜ~Þ§5 äšs9)•‡®iªåŸz Þ…Yç†Áì)C2GÊ|Þ´ç‰óÎ ”$uÏ­¹›qçd9õ" ŠÙÙ‰ù¿•«´n#8>‚˜ù‰sÔN´=Q{BHܼS$@S¥]k•8]Å[¶j¬ò`¬„ÒNàe\BÀÿ³šÏºFS’2=«Jë{± }=ª¬„•!‰Ïû#4ÀÊšHü¼¯Ó™ BÄÖ¥Ô Ýˆã­e´ ¬Al}hW;Ȉô­Æ z©8É?…[r=( ±°ô©" ¢˜¤êOÖšÃ'­K]JH¶Kc›¿csÅWF õ© oõr"Yá‘Mwqô¨’NqR#sÖ„Äãbq!§îµÁúÑ·é·q ìAùzÒ“”ù¸¦˜he*3ži•ÕÈѶ1Îj•nwT9Éù‡ÍíK‚(v$b?7%G­!lŽi»ñÓñ¥rŸq€Aæƒ|žÃµ,’a•3ÌÊ ñëIê.f01àýÑéP´ÙCÀ©lGÀæ©O#*ãŽ8©nÆÄW2€¤“ÏqV4ºbÀ`X·Sœä޾õÐxmKZ+°Áäqî&nÜ ì¨Äª¬yëÒ£–L.§z¬ìIZט‡©uåÊœˆ?ÎßJ¯©¦Fø•5§B»%lÇ8ÍE+¤ç’j)äÁPJ­s9Ç‹¢¹In.2ÀëUç¸ãƒTç¸(95È^^¹­K—bã ¢QÁ&¡šãÌ8íPOv¨¤Ooj¬³<Ý3Ç|T=BÉ2é†8ÏJd·;3Ud¸0$å±Ò¨µã\LR Îùì87.ƲH¬šze9Û“L³³+‚ý{ŠØ†ÉUI|ñØ jìF\ö¦àƒÜV¬ " 3»†¬Ãoì…n=E\òÂó½ñÅjeÌÌëh$Ã+ ¿^¦–æÙÚDǾêéhâ|&O¹¥vߨb˜&f5˜yÌ™­ 88Ù;‘QÇlŠå˜dÕäQÕ[…=*zØ»‚NU¨È=©b™]ß¡úÑs4q¾UqƒM…c*_ çœSõÐ{´N6Ž DaF#'⣕£Y28¡æqŽ}©ŠO±^Iæ¢ Èx}i ××d îj™™þîµ?k €¬¸¦F®îsœ MØ9Q,r/ZŠâ`EL('øTL¥óœcµ¢YYXƒý*)x+sïS:˜Îª’±Ü6­4¬"½ÂÏéT\‚Ø'zæBÌ3Áª²Aõ ,Ê7\©Ú>ç—*|•9úf´§ÁÏ#èk-ã%ïüh4JÇmùrp}êÔRïT Mž¤š¸±aGfqI“Ï9Å.{çÚ£ÚTsÒ€ß/ÒmšY7 ˜³Ž*·™É Š’9øÎ*12’šŸ€Å1Ðcž´ä#h¡W%Œàu§î÷Å@pF3ŠA'Ë–ª‘dH@©¡Ü>;®²äðxô¤iÂöÅgЖI6:Äç#ñ¨³¼ÓëM3,kËëŠÄÛ4Qyé·Ž>´‹6áÆ1RU“Dß#M$¥vàTfEP=j9¦àQtMÉx=zU¶Ÿ7ñgб<Ì©„<šÇ¼8 ’zÔ=M ¯7Ÿ2"¼×k§ÇäÛéÆqX:F÷².;Šè£\.zqЍ€ÙàÒ¡™¥s‚}éªHV=ÇAV+3r;b¢$n-øf–GyêEV»99ôîÅÊÉäÚÙ'RâbÒÜ䌓ϥP‘÷)çšW±h©}~#nœt"ªÜköÚt8i@$d–jÀñÆ 4=}BWË/8äœZøÓâ'Ç‰Ž«46w $éÎKf2}õ¬å-lBŽÖÕl}‰yãëXÙ¿{òŽpÎ=j“|[±GÛHÒHØü}p| eñ‡ÄÚ½üèÏw9;D&Cgøqëî+êÿ‚Ÿ îõ7¶º¿€Áq!БŸ(zf𿹤èF’Ô÷ µ0w$'¸¯@Ñ4Xí£\®M7‡Æ›nŠ«À\ Û¶„ É­mÊ6M]Øh³AÈŒgµ$¶ç†r:â®,Ø`qØTlsÀàÕ- ®g–pÜXvÈæ•·mcjÄœŒ «81óÞƒuøâ¬®Ð¼‘T…Ãã¥A\ž´ÐÖä²ÊÍM$d äU{{o2E#ò«’Êà˜¬Û³4+LÌ &EéÒ«Z^ !<`Ö…â¥Ôl7rxÀ¬Ô´û<¥pqž ¥{…®JØ“5$h22¸4¨Š®9%½1Ò›4»~¦‹ØÌt £§£+•Áõ«¬™žŸZq(È9>•kPLšI¡‚|òx5Í!À8æ­ùjd…>•,¦ì9æP¹'?QU$œÈ}Ä[qóå}©&ÓÓñª[W–BIÝÓڡܸõ54¬¨xéUfP9˜ ™‡=}k>`¨O­]‘לVtíóœŽ(52îØï$Œý+&y‡˜zŠÒ¼—kg8³]F,ëY¶ÑV;è ß5i¶Ð >µ¯`jÜçÓúÒ&Ö,ǨãÞ²ô´¼@÷¥tE\wô¡ |@ü)à«`qL|×Ö>  ;æ‘[k€¹“L/&à@Ç­1¢ËKÉïQ‚Ù4Àø"ŸæmÉÏaÊþXè3Ç5;ù ÅA ;¸<Ñp/ùry¨¼Ä$ü´Ä”ªáŽ}¨šl°P9¢àNšN8£Û‚NN(—(€ךŒ?–y=Å %BñÛ“šk’ÏòøÓQÆ) ¤†bvé@\>ÓÁãÞ³Ô4ÓðwsSÜN» ôýj†rMÞ@ç¡5šÜ£±·}¶ê8àT‹p<³ëU ‘YqŽ•\ÊQ˜gJÛdIhÈš`“ =*²ÎE*MósQÍqY šC!ÏNØ¢ÙBFÙäûÓe`Czpqßši±•n\‚­TòØH­ccæc#uY‹IG;`Ô÷¶‡Ï?´^jZ$z^ž»®'líü£Þ¼7Á¿²ΣºMPÍ&ó¸¢&Iõ5÷ÌžÓ‹y²Z¡~Ì⥎Ö%àFŠM¸Å%O]NøceFŸ$óÿf½+ÃBŸR. ™Ày õÎ8¯uÐ4;]ÜG@9lrkC 1òƒÚ‡~Fk{Xã–"ut’-¸p ý(8íUZb‡vëM.z÷õÍ3-´/Pr2 G,Á½¾•Síaˆ†Îj§!Ç<ݨæB,Ë9CéQ,¡nÏLÔ p†v©<ÀÃÚ—2ï4+üØÆ3Å8²ÌST§ŽDIË)è=)–2æ äõÆ;Ô]$ln0Ú™s÷y¦ùÉp±Ì§ ž*&Üöæ0w u¨âˆ[ÀŠ™89Ô‚KM ÷ÆÎÅ}jÂËŒ‰õªKoåf@I,y2|¨1T‰WêXíåpU¿:¯$€ä•ÅGs1* GnÕ n%L1ÁíUdU‘.a‚Bý* _ïÃõ©˜F ϵ09 RôØVCíX• ÃÒ¬»¾3ÜÒÁù=²(et$¶öªZŒ‚e*~cϵF³ÆÐ~¦›+–sžµZU#æžþÔÅd7 õ¬Ù%yå«SJy;–©Î¸9C• vDSHÝÁ ïTå¸1‚¯óf§–ïhÁ¬ë—WæãÒ¦NÅ”®.RIJç¬É78r*KßÝ>àF; È–âFrT²N+>dQêÖÌ^~µ?š€ÆsÒ³!›j‚:ôÛS,Ø`ZFeæ¹rq’µ8J[$þ5]$\tɧg<‘I”L\3ôéëBɆ?ݨ†2ppi$$Žj@¹«ž§Ë´À¬õvïb—Ì'¯5§@-©Bi„GQéP‰¾ H¾¤2Cc ¢I;Ö£2eŠúR;Ž™¤À] sÞ˜©8žô1-ÓµA<û ÷Ѓ–l?aŠ…œ–-Hªv Çš Ú€ÅH)ó:õíL¸&2¿Ä{P²àdýÑÒ¨ß]ƒ¹›¿Vöધ/ŽIªZoÏq¹zeËnù"–Îo*U?*Œôõ¨,é_P0‘–!zu¤kÝüγe;põ¨^s_n(¸(šßh98"¦†eØ '>µ‚÷ËÔœf¦Š]˹dÈ#MÊmÇ"³•V OcÚ¯@ŠÎzW7§³C+;¿™º´ÅéPÛ@Î:VŒ¯­è¥Ž%RÛE=®|ØÏ¶+Ÿ‚Vž@ò1Àþ8­)&\n*ÐdžIÎIP£Ôf óDm´ñïž*w‚w¯QP´þcò‘žõ\ÖÛ>æûÃ? §y£ ;ŠÌžÏ•aŸAP£MnÙiNÓÇN”s ×6šUo”¶ÓÛÖ˜:ƒ€1Yh*pýGò¥Y²àކ‹ÜE¹$ÝÉãü)Hcòõ¡“ïÿÀj1uå2/UìiŸ$œ‘ïW¡µóÓ®½P³`dÿ`µjYÍåÎÈÃ1·J¨êÒܘåG­U[0—¬¡ù…^ó|·(NèÉëL»¼Nèi¸ÙÕ‹ì¸'œžžµ"Âv±“ÛÒ¥¼Mñèr*Ku,ŽõF Är".íøç)¬››2E6õ¼µ‰‚ª Ar #nçéÚ«ˆ“¼}áSá„…~ø5 7’6õç§¥Y/r0]‡Î›ÀèGjk|­“´qÔR¬Û`8ïšeÛ; Ä ½2)5ÔԱ꓂ý;U8“8Î=ëcòØ6 Ï¥][¿”©6;ÒNì AöŒMH8ÏPj»’Ê ÷¨ä›7~d\²–Î6}EgµÃE‘‘J}ÝË+qúÖ{N²çæùñÒùFÏ:9äk&òF‹){Ôšƒœ{Ö4÷LNÖä¯CXNW4wvwwÏéY2ÈZBO®NÆU k%ä1±Rø5‰GªA:à0íÒ­A'šIÆó¬t“s*Œv­¥Û´zUójFDJpy¡åw_“©ªBbääš°IDëޚДeNXŒŸJož½ñ¨ ¨P œ÷4ƈð>´1$Yr0Á–00yÅQ3ÈûÕÞIÉÇzH£T•0ç­9¥ÛÀ=k5gVùÎY}:SÍÉr¤)Ü–]v2{÷ªêwH=)&˜ú玔ËFÉ8 t§{ ee‰9ëT!s%Æ:­KtÅ¢&£³OSEî2ä°ûTmÀÜ_öÅ·LÓFv‚OËØRQdr¶ã¹ëÍeÎ!sÅZ¹¾ o8â²îçó(ã5!b»8žR>ìkÖ³ïî\͘ñµ}j[©DGË‚9ªª©åã§­bmÊŽƒNÔ·ùŽâG> Õ+K÷»w|À÷5‹¦_‘”ñžœÕMT2¸r¼òhR4§Y­sxVâ5'¾p=*ýÀÃnÂSÐWªë ôK¹r@êkjÓWŠäãÝ@'JjöeΛÞÇe üQÆY»s8¦ËKÊž 6縬xLr¹V¶åB‘ŒV©Ùœ²‰³i1k| ŽøÈëVRo/ïG|Vm  +î$§†\9 ó-o{™²ô²,Š3Ôwê— î1Hzà)çÈd â>lE&IgT}Þ¤Ô`ïb 5˜<` ç$S|îA=E 'e,àÆîæ’0Vf r21H'/€y=ªeAÊŸ½ÔS‘Áœ‚H+Ú˜W%Æ3š†éYœó‚0sëRZN$e§ò¤»Ud‘ŽsÍjD¡À÷¬éÜ$…íW4¹  õÆ9­"g%róÁº>"ª’c;‰ã¹«B€ç¥B@xXõÇ5bQ³, GÁÈ#½Ikv÷}**7SÁ=1QÜÂNøÇžÅ“‡&SžµŸ¬Ý}¬ÌÉ©’VW,ÿJÂñtlŠÜ+ñ6ðGjÍŠ×7mnVlgýgqUõ3²N9&±-µQ™GN¢®Ésö‚‡ ’*&äž0…¶È?:Xäuˆ¬¸<à†âÓz¬ˆÁ¾*HÀÎw³E"ØÃ-NÆê ¨ÅÀß¶O¼;ÓB™cêW¾ W’6aäzqIŒ¶_ì}ÀöíTndd^}ª­ÌÍnFy>ÕU®ÉÉÎAíéUÍ`åêo5<0ëYÒÈFOñc­GuzË18ã]®ˆ09#¨ïQÌYÌêG^¼k Qv ¹zŠ–òà¬ùÏœU9¥ÞHϱrÔÔ¡%æèI*qÖ±'ÕäY È…˜q•jíü† û¹Ý«žœî‘ÈÿxVr“Ú¡» &ØØï7Z¸—d¨X°{çì\ª;‹V´$dÁ=I­.A±k/•ìüƤY·¶Y¸¾µ—ö´ÆÞ£¹§,­#m/­Ap <ŸQÒ™çº=ê£8Ø~”}£~ri…‹Jß1Î*6|0þTÁ(ˆŒäqQ‡ÜXôaÒ–ábI.‰ã ©Vm…ñÍQf àu'½Nóç¿>”l-L‡üêÔ`¯NÓYá€aïÍH/ :Ö™/BiåÞ¤)ã©,Ý|µ$gB&gf'ý*ͳ„\“ŵÉç}ãsPI+°Àa´ ­-ÁÜyàž0ÊQF=©]aã¯ðŽMT’äDŒä·Ü÷¤’c!Ú:u5NòV™À*ô]ŠR†2’[/ê{U;¹`*‚þ¦¬L’OëíTØä•ñ¬Š3ff‚`àÔ׋<î;zÔ—h‚=»rǽs×›b\œs€+šr”v: s~#ÔÊjH¤8.vŒŽã]O†.ϻ˶ îk"yà¸a½w~5¥¤_[ÊHádAÛ½gÝêwTŸ4-cÒ¬o|ÈÔäóÐUø7H¬sŒZætˤ0‚@­nÛ¶äœòkÑ‹¾¨óv4f™â…\}ôýiðêQÜ.Q¶8ëïU‹®ÜéTÄ$;4gÕ§c'n¦Êê„mVÎTä "½ýù àÈ5‹3•œHOQŒÔß®ÒOß^¹ïO˜9S6„þ[ºÿ ô4¥Ã…|áÆ=k-¯D¸ð¢ô¤‹ž‡ô¥ÌFÒÌ£žFO«;Ûnz÷¬ÛFHÊxÁþutÚAØŠõ²nnŒóˆ”àÁ=*´`²òÒ«™ ÌÓµ¸Ü…€©åm©“Êûu¬”¶ç­K-þYøB1G2i’C€G­dÜ®n8 õV]-n o*”'š’úõ #ŽFrH¤ìÅf-枬¬ê1¼`â°ÖG¶™£-ÊôúWI Àe!¹Ìx–'[”–>qQ%a£M.œ¨=M$“+¦K Ü Ȳ½óP.ì’iIç³TfiÅr$A‚wúSÌØ'pÇ¡¬XîŒQžsÉÍK gb½ÇAëEÂÌšîTÉ.¤éÖ²g#Ë&#ø³q!|yýk*òIH$p}*JI†Ü¥XóœÕ9¾C!Ç#¸ïRM 13€wã}ê½ÄëöwÏsš‚ìf^HJ5-ªc'¡÷©žä [挌úV%Ìáƒ.~^p}+;–7P¹Ü…ˆ+ ä®d+3pÇéWç½erÈéÿ×®sQÔ¼‹¦O5F;Pä‘[ž÷Wž½1Vâ"$Á?SÞ±­.×Ç8Á=êÔsoMÍÔž•JI™´Ñ¢—!˜à­¥ÑB=y5›iµ3Ÿ˜ššiŽÂ@ÇÛi¯v£àšH.BFÌOÌ{zÖa|G‚H#ži-®|×Ú@þu<ÝÇc¢ŠU!I9ÀïP­ÈySY— îÎ8ÀÛI[È9ÏÍÍVÐ,j \09'µ +|üŒ ÖlsüÀ{Ô« (2NOlÕ&4’l¨õ’99äñÔÕ&Ÿæ *8îq“ž1Eû £hÊ<³ƒÉâ´(ÀÏt¬¥ºU±ê*4º(V?xöô¥v€Ÿ2#=±QÝÞm u& ŠeØdôª¥Ì®ýÑJãJäâ\«8íQ\L±¶ÁÉQ’jœ«0ÎaëTŒàn$å©] E²[™öÄIûÇŒUXÓ§=M Û¹îOzRËg-“H§dUº#{`óŒ\þ£ K.à × ÒÉ¿·zdè‚ýIɬ¦‹‹±ÇÉÇsŠ,W!F3É5kPÙ·¾j1Ô?Í×W&ÏS{»-óÚPggSï]ž¶¯Õ±šæB ØS¡Œ¢(ä5mË¡’Wgoý¤dplâ¯ÚH­Èl¹®BÚèÆ³/§#šÕ‹RSn¬¼Æ u©ÝÊ&…ìâ<­Óž× ήðj±™çF³Ï4v­ HÏZ\÷Ø‹¶Óno”d‚qZV¶ŸkÁpAúÓtÍ+lÛ–¥lÙ@«H5¼VCÔµel L¯n‚’öpª±Ž"£½ÕÒ5EÁ8޵VÒ6žðÌäã°5¶›",mØ*£`œ`sïZË:ù%¸ÏAX±eI= Yy€M™û¼æªÆeèeŒ’23øÕKûÀ"f-Ž*‰»ËÇØU‰¾ß?”2✤˜ÔZ-X°¸7מµ¢TJFH ¨Åµss“PÞÝ<`8ÏLT Ó–AE¾òûu¬«›ß®7ÇØŽÕYuI‘ØH2à☰5Âî\å¸?J.5æUåérWsÁ©ôýNK„dcÏO¥Z¹Ò AÇ •sÀÆMBæORÝš4â¿/I*õ,·)s‡ËJ§q¢¹2²ägв×B˜ÒF)’‡•¥[Ÿ0n*䌷lÏÌ:Á»ÌŒ“œâ“.ÆšHr.y#TÞãÊLç §¯z¨oàåzjŠK&äl~éõ©¸ìËcT sœc®i—7‘ʹ Î: ÆÜVOœþµ Ü…$„mïPÛн-à‰ 6<ÖF¡z>Ì{Q]]”ù‡qÎ=kµc$YÀpqžÆ§™ÄÓߣ®Ö?‰íXó] Ì¥†åè A}x -Ï̇z×'«kžK¨cž2§5ÎÝ·-Eȇź©ˆ:¤ž\€g“Šó;ÿ¸¹a(vqÆ@Íix“ÄVúý´Šxž2GÂÇ ÝîMc)« ©¬õZD}ÌS_Ÿ®®¢©,xhzªŒÓZ“ÈΧíb=­øR[Í»s·å\ÔZÛ.ãUlF¼c½l¬ØŒz{Tó –ÅÖ“9ÉÁ=~•›pÛQ‰wÉ×·~µ^I6>üñÒŸ0XºóäyTÓy“’O~*3©êªëx $€3Kœ®SZæïÌs8_ëQOöx6çëX²_…]å¹''5ƒ¬ø‘›r£t›©cHÂçO-øòÙó×¾j+)Ì î<á.¼T-ã*Í’z —Bñiºm¡³Ú³U.ìkìùOB £-œ(5¸1cœðeÇz]vnÏ5<ó¬~\{³ë]ji˜Ê7$ž]¶~*…ÝІÒ1œœT·7hfdS…T?ršî¦ÖöȪÙ-Æ} c'Ô‹ê:’3³Ê/OsT,ue[ørÜ;r+ P¿Ãy%°T}Î+“ŸÅ o©Ú€z0f¬/vtòÙHZD®:â¬-§ï:ö®WÂ$„Hîëœð3Î+´…ÄØaŒèJèçz1‰ïOG¥>ÞË"ûóWc„4£t©¢´HÉËüÍÆ+E ’Û!³·Ë è Ïá[vvãÎÞOqùUk+uVÞÝà ¿ò×wE´b‘›6"E·Ü#°5V[Ñ Løç<Uî55¸ä*ñïPàÜ4qžëOBBÊ /oUäûžõÒË·!Nª¼vËŒ äLoWÏyÏéV…bÝÍÂÂp>ñª’ÝÂü“QLK¹%€çƒP –§Ê>µw(ö¸Û+¢Ç>Õ, °Íó9ÅQyVÔI#pXbˆ§[ŒI #ŽšâDu z+7ÈóçÃt*´—û±ÔÜÕ˶¨ Ë/RkDÓ!­ RÒ9R2EZ·¶H‚©T0Ͷfíš‘§ÃàóõªÐÏQo£P¥sÏj¢èŒ?:v§tª#9Á&«¼™ˆœŸ”æ‡fËIõ&¸á88±›åbqв÷È ¨9ʲ/o ¼áòHïÍ"ˆîõ²‘{†õª·÷é$&@y#‘UõË„–Ð0ê9®}u%x lÄqÔÖ‘¢‰fâÿ,-ìV¢%\\žEsOª}šb8*MU¸×#Ž\nŽ¢±¹j&Æ­©´/œ’§Œ÷ª–ºÉ`Q¤ÊŸZÀŸ[[œ«¾ìtªß„bÃcéY¹êUŽ›QÕv9Œ0Vê­s÷ׄåîkçX3±Ëä®95Ÿs¬…†í¬>î{ÓæVXÕ5Øí›{‡OÖ¼Ëľ#|±ä©zU¿ø˜H²FJžJœEyN¥©4·HÞgš§±=+Í&tB&ºi.wrÍžMY4Ÿ1C“ïT4ÄwŸq ƒÛÓÁn¦1žµÌ䙵™é÷z:.ãÚ3\Ö¥§mÝå©Ýž¸®õá%±½Bœwª·6)*ô½xÖ=v®y=ì÷VÌ£÷‡j«¤ø’{6fšá”®~b3’{Šô]KÃðÈ„ìÀ>ƒ­sòx:'$†3ÐÓŽ!¢]8µb–£ñ8év0‹0À?Ë“É>¤þuÇÞüMÕ~ÒÞIÄQåÍ’Ooç[ú—‚_±ô9ÇP+ ïÁÒ¡Ï—‚ÝúWDq–Ñ‹ØE- Ý7Æ7åPÜHÎÓÌþoá×¶ø_â,òX­ºXó•ç½xì>òfC*°‰H©®“HÔ r’±yjUPzWM,B–ˆÆ¥=Ô})àmSíH†WÜìNæ=«¤þ×I••ï^3¢xì(FÛò2ó늻7ŒDZp…i$&àrI¯EM4yœ–v=†-S÷¡‰ÆAÅSÔõ•‹Ëñùדj¿…´6ìÒ`Ƽ’jÅß‹‘Í€yó#óHïœñOœn¡ë¨•mŒ{Y­)–\‘”µÂê>6 pÙ|Ð3z±ÃMñ.5–PÓ où›=}«9KQ¨w=kS×v¬\ÀÆk‘Öus »1~§5Ë\ø°^ÇDÜŸ½ƒßÿÕŠ¥âeœáŸæQž½N+ HèŒl?WñJ9ÍÃí8®{Dø‹ý—v¡¥ÃgOqë^O®øŽk«Öxäa‚z‚¹ç¸»’@ZV8={Ô)$îjàßCï ÆÖ—v°L²©,»Æ=…I‹Rï29'JùÂÞ0Ô4ÿ²Æfm‘ç<×]¦|E™.&ƒyƒÓ½u:‘}NbÏ£Óı!ÜêJ ñ»€Oz¹¬xÖKÍ/Ï·cМmAôúRu#mÉöRf‰|^úmÚ¤øWN=ÏÖ¸_øK~Õ©»©»ñŵ´fS!U$ú sxò ˜ŒûD‹¹?•KÚå8Ûc{Q××O 30qíUõÇ-ƒË”óŠóí_ÄQê–—“8IP 9í\ͦ§*Dñ½ÈÊ®ÜãÞ²r±q‡1è¶¾2O´´r8žù¬ïø€&IÁç"¼ôÞ¥µÆã(e=8«¶zլЫ'ï ô,z ÏÚªzع} ÚÌ«µ¾ã/zŽÛÀ÷W.â—ÌÏÓ.Qï-ã…¢ Á`Œ¶÷Æyé]O…¾4iz4l–ú&Ÿw!äÜ_!‘ºö:úVRJZÉØß–Kdpúg/#À{W9éÅnÂ{¶a^uûRÞya`Òt+EEÛ¾=>=ß®Qø¼š¥ÛÜÜJÍ+òJ¶Ñù+ÉÒF±ŒÞñ;ß)™‘ô«l…¹,UÄ þï¶i&Uf ¯ÅyŸ^‚«K¾ïâ+MËG·¹¦ýqùšÈ«#%ôõv‚±·Pèiq+&ÍÙÆ8­æ…b}§<Ž£¥5mX"Òi09[¿ F#ùvçû¸éYÊÈ1ÁÊñ]ÃÃæd®S𨠹 ¸=ÅlUîqØsy¶öV<*úTwºeÄqFîNHìOzíö«³,¢Ž¥‡Þ¤G$LeÇZÕWœænœ[Øò[Í2úñäóƒÉàg­>âþí/RG\¬ €=}+Ô”² ‡8ªÜx^ pYG=kEˆ“[ŽTWcÆukSœ»’Ûœ’ÄzvÍKiy#!Ë”ÎãÇC_@Ïà»FA¹ Sá ²Ž0¥@ÏlPëIõ§ÐòÏÜÍæÌ’”.yíMñ íÆ¡÷AùXáWÓ¯SÁ–‘e£B|´ÔðtM ûUXv³²W¹àx}îŸ-#=*h<;ò7OJ÷õ𺠞¹ʦƒ@Ž!“Qî+?m#[#Ãí<s³ظê(¼'v³—1¸'­}…DÄFÏCHt(Uy‰Nî8©ö²*µl¤Vð…Œ6Z´sUb?*÷ñyÙı…TqŸ»Åxaµe‹÷mµí]‹ãô˜äÅ‹(ᱞÕìá± å‘åâ(9»££×µH4ÍÌ[Ö _Ái¼Œ7¾0µÄj^?Óçw Œãý²q>*ø§FT«²È8À=ýªÞ%na줾${zøò aF–@ ¾Ð_ZÛ²Öà¸gIƒ,œì+â?øXúÏÚå…fÅ»Rǯ8þµÞx'âËØiÈ·å¶Æ7 œ’OlWLk)+Ü—MôGÔç[Žfn3Ví5ÊF¯ž~bOZù˜üeŠçQÜîb‚1Ò´´ßÒ™d G X³žxÿÒ3O©.›¶ÇÒ"pò’íÀû¢ªÏªª5ßï—žÕóì?<˜^æe;öâ4'±ÿëVK|s©ŽJ#3`tÀÒ©É.¤ª/v}ú§ϸ‹œŸ­Qÿ„Š&ÚÞ`åI…|é«|u‡É¹9ûY¨ÇÅÂn,à]Ùã#׎j9˜Õ>çÑÛë¹H ™Ò3ß$ZẒhXão#šñ- Å“jÁBûbUFÀõç5ëÐ.oórÙÛ&9éZA7±“J;’hIvRsšŠóQ–Rª„ƒÁZé¦ð°·¿ 9Ö¡‡ÃR$¸xñÙÚ·ämÎcê75¼lr Œ’*µ Ì4ƒ zŠéoôä†%VrZºӞi1q»Ò¥ÓaÎG´UT/z¼G™zðœ“PÞX5¦<Žx®kTŸì[ƒ0&³³L½$Ž®ëÄ»Œ2“Œ6:ûÔ:ƾ’ðÀ‡×Ýë¡`—¼àgšäõoˆ0Y†I€1ž=«dÓFv»5ü_¬0±mîK ©„ y<^·Z²I‰c^<ä¯ñâZAt‘Är’®ISœ^=«ë’ÜIº6;’JÊZ= Tm±«ÿ ÅÝäÈ$c†àŒð}«j-RæXÒ#¨_™NyÄèÖŸk¼^Bó`ú×Ks瀯¼Æs–¥Í%¨ãänH/æ”ʲ+ñ’w|ÙúUAu3LCîVÇFã5^ѧVÜdlzúÕò²]•Û€õì+šRLêŠi•™çaŒ“ý)‘G嫱cžàšÜ·Ña–6bì„t¡©ô}O’øÇ<»²xÀ5Ï'cx-nÌÜ"-޾õ©d¯$Yó68æ½×á­–ÍêÏ*1Åh[ü8¶ b•>¤Šã“ovzhó/:áB’û£èÀ ½n‹4A¼Í™ì@¯M ­>`…•‡aÈ5*|#gP|Ä\ö9ÿ „ì_2=MÉÉÉäu8¡s»r²àö#­/œXœ­*ÊYNÇÒ°±ˆÕ _i ç¸íSMhÝÓ¥F²(Nù=Gjr\*n;w¨² ¡Þ^W~R:š"(û‡È©RiŠð2£¥4n•²rt3I¤‘IÜi*@RXƒÔb•áXùê)è îTŽ9¤ÂÆÛ~ð÷¬ÙkÌn#!G9ôª—í“×8«XÉ8¥ Àn{Ô5r“H£N$bNÑŽrsN 9'êÑL‚Lx>µŒ*³SJÃr¸¾DrÇ´çÚ˜m”ž{t5,N ‚28äcšyÚÀa€úÐI_ìçzª³sÅC-©i0ã¾{U†GpãóJŠà›¿iŽåX튩ùƒ`ç§0ÉÚFz¾\’£þUÈ29%HÈ8 D-ÕÛæŽM9`œ²Ž8É«¤“º5 i\œ¨·ÀädëØÓ €¹>‚¥$®2HŒ C©ä)êhî*‘ÉÇJo”Rsž˜«1°`YÒ)ÒlÞG±ÔPI_hù2¾¦›-¤3§Í†ÏcSÀŒ ©RG­Ýdfn@`Ž)¶Þã2B´wÄ„ûŠ£yàÛ ž6kXñÏE×HÈÃîÆ¿‹Sd‰Š+žƒøRç qðþÌù¡`UV\p®gPøtnQzŒ-zÊÛï|åH¨žÑ%©;qÀÅ_<—Q¤¼ð-ÜyŒ.A‘ÍP \Û*B±9ÉàãÖ¾ˆM3Ô†úÕ{¯BÄðGjµVQê1}œ.ìnÎVDaÏOLÖ}Ý´©µ~EÀ<×Ñ7 ¶2’ʬœcšÊ¿øonååœzVžÝ¾¤:iŸ=Üéò\]åP¶}³é^…àÏ \ÝJ&‘DÌǨ?tWhß­Ù lž€ö«:f…7‡f "/î‘ZÓÄ_FÎz´_-âuÿü'nþ!Tp÷yn8ëÒ¾Ó-lô‹#ÚËœcÚ¾zðoˆ­,náYX%Ãà“Íz¯ã57áPd×ÑPœoséŽV±é¿eŠî`À‚3Ðgµ-Äm$° æ¸Ø¼sk¤éŠóÌ¥•,zæ¯~*ZÎ&v˜, çƒÉ®Ïkæ>Ê}ާ]o®‰O–1ÜÖy…¬íŸ£”×›¯Æ«$wg;È$*ŽqïUõŠé{c»s·­Gµ‹vLÓØÉ+´lë:¸Þ¡³òœòïëè³–ó0Ä’sRj>0¸º¤òd'¦¼wÇ>$½†ñ Q¼ŠA$àñšÂu"º—>ˆ×ñ7Ž¢Ór&”dƒ€§½x.½â‹íWRšS#ì-Ç=«oSƒP×§ófF=°ÍC„§d'Êo¡Çø×#«ÔÝQo¡ÍÜÏ=öÖ›oÔqYÉÔÇòýk»²ðƒ²àÅb:þµ«màþ?ÕŒzb±uÕ÷:&=µ·–ÒU•S§jí´Ë×ôÏ2(ÎAÁã¡­KŸL‘y‹Àö­_Ú=”ÓÚ8Ú%å~´*Êzéò»œí¦‹2®Û”(:dö«vz,ÍpÍ §B§µv3ÙfGIp½sZøv êmÁÞÌdö¥(ßmF¥cž°±{@ªñ¬‡ëZuŵ»—’Ä/ûj^Oq"&ô;‰¦K¤Lûqµ—v ޏ¬[5æGa ø‹JžÞ8¤ºXߦÖùs]µ¼…–YSÕb¼èñì ´g8nÕwN‚m6sö{‡ŒŽIÁ¬\u:#4zÈ*3°Ø‚9©D’¸1òûšóû?ÝDæ;‰[g´SYºu ·¸›‹E󣱎Fy@eSüDu«d*|£§¯½+Eœ`ŠfÍË÷sŠÄqÔ™å ÀäÔ,wð©üêiTÜN1ŠM†w… vTX«!èWòž”ÖðÅH+éN ÊJïÛž€Rá£B Ï=E½À®amÁ”‘ü©Ë†ç5j=ÀíØ ìH ¥&‘H¤Ë&Fä s÷G9¡Ô̼¡<ŽõgbÈ8å‡Bk?PÚÆÀ)Éî=jtE%ruA‚F÷4ñ,c8'©ÅsqÝ“,ûãe÷s–*ªŽ=H¬¯r­c¤tVêXàÑ’¹>^}0)tá#Ûìé†àš™”@;ô4Ée8Ã3fHÙGjzíäáŽ{Šœ§”„î,{ lŠJÇ^þ”ÈöS°Ç<æ«2^XS⃠·°ÝïN0q” S@\mÄg’ =pjISfÒ§…?ËÛ³:ÐÒnb™è3ÍŒ?0‘—v=)~D íàÕ…B]J€F9"›±w·½ŠÊ 91à/pjCì)Æ%¤ƒÐSžÙˆ,IÆzSºca  9+ÏOZ{Ò@z‰#ÛÃdóøÔêP½ \àjBµÈ—ÎTñíL–"ÈôÏ5jRTe3·×®W`8äŠÖ)I©!'Ú£xˆ—)…žjì«åu'aõ¦ÉÐOZbÇ· Î3è G*räïã ©c&Ö†„À}½Oz¤±û»š|¶í4|¾Üþµ:Ædä ý(DòÜî<žÕc°%@8'×4“YãÌ }+CÈÆÐ{ŠV„¼g5j6ØW{Ôþ‰îMÌR㳃ް®<­Ü]}^uRsÒ½aXÎq’:VQ„`ð­œvd8§¹ç×>×gbv‡©Æ+RÛÀÀÛ,wÍ"Žsë]LŠ-Û‘HÒ)ÚGLUº•m«H-ŽrßÂ:e¸$B€ç–nI«ãI·„)®@V´1ó|¾ø¥”îP#úæ³ç}XìŒÇÓ•ÆuÆ*·lÑåàŠOb Öà;PÀ=ªÆzõ&…;l;'¹ÎØ—Ü-!üSÃzyÚÖ £øŒc­t,›– zŠhŸåqC›}GÊ»³x~É‘H±·eÄ`S—IÓ‡–¦Û©ê_n+tÄ¢0HÉb£HRd+°œv¦®ú‰Å[chzMÑmÖê§ºŽ†¨_x?IUya²e^U•«¬ŠÌQ· F4Û«}£˜¹èkE9GC'÷GšÜè«ÉdU,1‘ÎkWFÓ#F0· œdJÙšËkö® ÏÒýfŽ.^Ò´ž+ЧQ¨¦qJšæzXËÔ¼ mí£¹G¤8_Fõ5@x^áG¡Ç9Û]DQ›™ö–qc„n@>Õkìñ¯Þ,@ëÍaR¼”´:#B.78yt øÝ¶ˆ}óU#Ó5ˆî0°Àbõ'¥z A•> “PË‘…b1Ð ÏÛ¾Ãö1G }¢kDæ¶”WÛüê•¿ü%öñùkáÐT”‹¸ùµèpä˜cºãúÕŲŒ(ÜPúdSöþDû%ÜÕŽtåñì¼Ñ-êî€3¥A Š>ê¨+ÛšµÇÏ‚1Ú±(@Ï9!— Ü“NXpà =³JÊʤŒ€j¼÷ilÛpHn9^Å­‹ˆìÅpÃÓŠ•±æöèj„WÑ8ÚuR*u©,2;cÒ—5Ɖ …ŸŸZ,Ä8§Fãc¿/jF98vÉ©(7…-¿å Ó²¨*v¦þxÏ 6Ý#Sµsž °¡UKî ž:S€û|ÒØè1Yµ`½‰d%’ Ò’+C’ )>â¢bVN@Ç|Ô¯>À¾ìõ!nmFJå8ÁHC×=»UÇ!ÀÚ¹ôÉáÙ_°õ*l@É)@B•õâ–4iAþ4¬°+&@ìhŽE I; ã¦X†0…²Ç$ýÜRÕ9ÇïQ׊Y$Ô/n´ï0©î¦‚AcÚ~B@Çc à¤ŠʺÏ®2j@®zÜÔ’Gò‚qÖ£+’ð=)5`Cî:ÐÛCuéRÈÝð*%rÙùIûØëRQ1yI\àð8«[pÀíéJ¨ÛUí"£‘d6ERm¨·R1éR0Þ­“ÂóA˜7g¸¨L‹Ü€³ʶܖejVÇÎG¡=­½-¤#z¬ÊÀ‚®* Çž£ÌP‡Õiҳô…È<[Ò–Õ#ª ž8šïýÈÉÁÏ5^H•p£ç#¸§L‘C:q‰½NË¿Ãù¬¥ñÃá*´ Nì“Þ«ÈŒ§!>ÕrIÈQ¸zTUbÊË´ŸÊ *ÈRGõ©M²É†ÚçZwÙYÎWŒt4±‚«‚9Ïz“Z§ïȬ1€HëNEØÊ@ ==*TJæ#^»6/|PÒ3(`­Ž™©$-È~tÌì`75!tIlʪÌÍ÷º*hä ŒSzáö›BL–XžqV£aÝt Gn`ñN |Îy¡LQ¨\לõ¥ÏhãÞ”¶YIV õëšm¶©mÿJ•œ„Ê€ëLˆ‚Ièð‘Ö¡+/šâ6>o=†*×lHǹ¥i7‘…QN'‚œÖÑV%‹æ´k–_½è*d¸„§$Œt¦FQI;½é¼|ÝX0i´˜¯bן ŒôïN‘ãe;€# 5I­ŠB¤àsÐu§A‹;ŽE.Pܲ!ýÖrI3Ö«»…<sJ³¶H ߥ$„’½+}á†Ü í¸”7éLQ½‡!G¡jeÂ@;GjŽ@#gb0Tï“Nˆæã°¥góNиwSUcQÆå8ÁÏz9@ 8Ú=ù *ƒórǹ§›U «´1=M@å§l0ÿyj@'!Î@ëMeÚXàÓ€ ¡_.Þô…|³œ¾õQ×a‘Gº5,Ù953’ÝTR,À)Až3Ås(ø™áÏ #GQ†9aC½Éú Ó’SvH™J1ø™Ð<P[8ǽG‚ªXÜç zW‚x—ö±²„É¢ý¥³…–å¶ zàW™kŸ´_Œµ}ñÇ|º| xŠÚ01øžk¶uik±Ç,m(ì}–ÓQžI+:ë]±µÛç]ÛÃØù’ªãó5ðž¡ãïê.Íu«ÞL{Ÿ<ù Æšþ{¢]î‰êY²MvG+–ó‘ÎóÑ}\xÛÃÖøIµ‹!“ûõâªKñ/ÂÑ£\°À<Ÿ7¥|òHü’HÎO<Ó¥sêùÇLñü«¡e‘þf`ñþGÞ©ñÃ27É­Ù3Çï…iÇâÝ'QTŠ=FÑäAòªL¼þµùäK(19éƒVìµìž9ã’Edlä6),¾}}½9Q÷õÌ–Z‚\Ááð*Ô™Wnx'ñD¾7™•n!½šÙ—TbI~ýê;ŒÞ3ÑdýÆ·q"Ìs'þ‡šÊ¦U>mtèûfBÆHÔ_h ü‹Þ¾_Ñÿj}r6Q©ØZÞÄËG˜ØÃŠôÏ~Ñ^×ÔGrÒi—ô”eqõ~uÃ<hkc²º3Òúž±ç&Ð7ãÓ4Ã?< Õ=?T²Õ-ÒâÆê ¨1ËÆÁ‡éSŒ¹p~•Ë$áñhjÚ{o3$ŸSOF",šdÃjúýje?¸Ï|S-×,Gà)cÈÀmÜõëQÃ#3IÅJÄúš†‚@äè)„N tÏÊzÔ[ŽÃÉ=©‹#!Â’,@ ·'ýêÊÊØ ¸š«$®’¬@©-˜Ë¿qäwª(•‚–ýæGûµ¶ŽRpHO­I†lcñª n€¢×«j~´ýä¹AÇjvÜäÔ3±Q‘E¬‚”n'·j‚vŠÝ·;¬jzn8ªº…ì–¶/"`¸:W—jš•ÍüÆI¥f9§“Ãb« ÝÙÉ9Úp*m`.ùk¸…rNyâœWxu%@Ç~õXÈÌ#'œµX’ÃuàdPK ˆ@úÿJhûä˜À£‰‹#gœT2¹ÁÇâ…¸‰ZåÝ<¼€Àõ¦o‘ŽFHéšsB¨‹Ž¤u4Á¬[9úÖŒ+ydd‚O¥+3u°÷9•@ìX+wÎ)|ƉÔ+`ÕM:1` c=» O.pùI§×4Àű“Ÿ­K dî zUr–máÖO¸Ã†^¹¥Ê(ç“ЦÄÇ%3Å-Õb:޵m$®f66Y”îTž˜éHÉövÚÒOQUåÄr©P'­5†õbÜçÖ²æ]†‰¤%†Tôîk™ñÏÄ='ÀV?iÔæËRÝÏ'Ò¶u›—Óô ë¨ðe·7 €@¯¼[âMGÄš´·w÷/<Ìç©8=®ü&W{˜b*ºTù‘è^=ý¤‚º ü6ñ'ŠŸv‘s2“2ìAï“_Nü%ø9á}3A°Õ Û/naÒ]áOû#•z”vñÇXÔF©ÀT¼ÌFbá.TŽÊ8U5«>WÐe]sPÃêz…­ˆ=UT»éú×wcû(xbÕQ¯5 JúAýÆX×òÁ¯t†5tÜG#ŒÒ}Õ qŽkΞ2¤ÖŒô£ƒ„4<Ê×özðM¸ÿA™¿é´Œ‘¶mþx:Þ£ÃÚq`9-?λ³)vf9^˜¤á‹q\ʬúÉkh‘ÈÇðÛÃ…  iÄ{[¯øPÿ ¼,ÇkøO`z³-u£”ö)Àü¥°2:TJ¤»²½šZY}ðsÁ×i¶_Ù¡õ…JçX—ìéà­A[ÙÜYÈŽŽ?PkÔ®ÜÌ>`? qmâùækiZ6´Œ§NXŸ<꿲M¹Ïövµ$yè·†˜ÿ áõ¯Ù›Åz8v¶[}Eëáüâ¾¹0‘¸bÓnlN{çʺ6½5ﻣ’XL=M•†à¹ñoëåuûn•$gî° Ó~èúWíG«[ØÇþ›ÝÈë21Œ7à3_IÞi6—öòGsoÄyû’ "¼÷Søà˫Ǖ´ ¬üŽ@ü«ªŽ*ž'Y@ËêµééFv^gÿÙgokit-0.25.9/xos/000077500000000000000000000000001426246334200135435ustar00rootroot00000000000000gokit-0.25.9/xos/README.md000066400000000000000000000015051426246334200150230ustar00rootroot00000000000000# GoKit - xos OS kits for Golang development. ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/xos" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xos) ## Example ### Get uid and gid of nobody ```go uid, gid, err := xos.LookupUser("nobody") if err == nil { fmt.Println("uid=", uid, "gid=", gid) } ``` ### Set process user to nobody ```go err := xos.SetUser("nobody") if err != nil { fmt.Println("set user failed", err) } ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/xos/pid.go000066400000000000000000000041401426246334200146450ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xos import ( "errors" "os" "strconv" "strings" "syscall" "github.com/likexian/gokit/xfile" ) // Pidx storing pid info type Pidx struct { file string } var ( // ErrPidExists is pid file exists ErrPidExists = errors.New("xos: process is running") // ErrPidLockFailed is pid lock failed ErrPidLockFailed = errors.New("xos: lock pid file failed") ) // Pid init a new pid func Pid(f string) *Pidx { return &Pidx{f} } // Create check create a new pid file func (p *Pidx) Create() (int, error) { if xfile.Exists(p.file) { pid, err := p.Alive() if err == nil { return pid, ErrPidExists } } fd, err := xfile.New(p.file) if err != nil { return 0, err } defer fd.Close() pid := os.Getpid() _, err = fd.Write([]byte(strconv.Itoa(pid))) if err != nil { return pid, err } err = syscall.Flock(int(fd.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) if errors.Is(err, syscall.EWOULDBLOCK) { return pid, ErrPidLockFailed } return pid, err } // Alive returns pid is alive func (p *Pidx) Alive() (int, error) { pid, err := p.Value() if err != nil { return 0, err } process, err := os.FindProcess(pid) if err != nil { return pid, err } err = process.Signal(os.Signal(syscall.Signal(0))) if err != nil { return pid, err } return pid, nil } // Value returns pid value func (p *Pidx) Value() (int, error) { text, err := xfile.ReadText(p.file) if err != nil { return 0, err } return strconv.Atoi(strings.TrimSpace(text)) } gokit-0.25.9/xos/pid_test.go000066400000000000000000000040761426246334200157140ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xos import ( "errors" "os" "testing" "time" "github.com/likexian/gokit/assert" "github.com/likexian/gokit/xfile" ) const ( pidFile = "/tmp/testing.pid" ) func TestValue(t *testing.T) { defer os.Remove(pidFile) p := Pid(pidFile) _, err := p.Value() assert.NotNil(t, err) err = xfile.WriteText(pidFile, "1") assert.Nil(t, err) pid, err := p.Value() assert.Nil(t, err) assert.Equal(t, pid, 1) } func TestAlive(t *testing.T) { defer os.Remove(pidFile) p := Pid(pidFile) _, err := p.Alive() assert.NotNil(t, err) err = xfile.WriteText(pidFile, "88888888") assert.Nil(t, err) _, err = p.Alive() assert.NotNil(t, err) err = xfile.WriteText(pidFile, "1") assert.Nil(t, err) pid, err := p.Alive() assert.Nil(t, err) assert.Equal(t, pid, 1) } func TestCreate(t *testing.T) { defer os.Remove(pidFile) err := xfile.WriteText(pidFile, "1") assert.Nil(t, err) p := Pid(pidFile) pid, err := p.Create() assert.NotNil(t, err) assert.Equal(t, pid, 1) err = xfile.WriteText(pidFile, "88888888") assert.Nil(t, err) _, err = p.Create() assert.Nil(t, err) } func TestConcurrency(t *testing.T) { defer os.Remove(pidFile) p := Pid(pidFile) for i := 0; i < 1000; i++ { go func() { _, err := p.Create() if err != nil { if !errors.Is(err, ErrPidLockFailed) && !errors.Is(err, ErrPidExists) { t.Errorf("Unexcepted error: %s", err) } } }() } time.Sleep(1 * time.Second) } gokit-0.25.9/xos/xos.go000066400000000000000000000066511426246334200147130ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xos import ( "bytes" "context" "os" "os/exec" "os/user" "path/filepath" "strconv" "syscall" "time" ) // Version returns package version func Version() string { return "0.7.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // Getenv returns system environment variable or default value func Getenv(name, def string) string { value := os.Getenv(name) if value != "" { return value } return def } // Exec exec command and returns func Exec(cmd string, args ...string) (stdout, stderr string, err error) { bufOut := new(bytes.Buffer) bufErr := new(bytes.Buffer) c := exec.Command(cmd, args...) c.Stdout = bufOut c.Stderr = bufErr err = c.Run() return bufOut.String(), bufErr.String(), err } // TimeoutExec exec command with timeout and returns func TimeoutExec(timeout int, cmd string, args ...string) (stdout, stderr string, err error) { ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) defer cancel() bufOut := new(bytes.Buffer) bufErr := new(bytes.Buffer) c := exec.Command(cmd, args...) c.Stdout = bufOut c.Stderr = bufErr c.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} if err = c.Start(); err != nil { return } end := make(chan bool, 1) defer close(end) go func() { select { case <-ctx.Done(): _ = syscall.Kill(-c.Process.Pid, syscall.SIGKILL) return case <-end: return } }() if err = c.Wait(); err != nil { return } return bufOut.String(), bufErr.String(), err } // SetUser Set process user func SetUser(user string) (err error) { uid, gid, err := LookupUser(user) if err != nil { return } err = SetGID(gid) if err == nil { err = SetUID(uid) } return } // LookupUser returns the uid and gid of user func LookupUser(name string) (uid, gid int, err error) { u, err := user.Lookup(name) if err != nil { return } uid, err = strconv.Atoi(u.Uid) if err == nil { gid, err = strconv.Atoi(u.Gid) } return } // SetUID set the uid of process func SetUID(uid int) (err error) { _, _, errno := syscall.RawSyscall(syscall.SYS_SETUID, uintptr(uid), 0, 0) if errno != 0 { err = errno } return } // SetGID set the gid of process func SetGID(gid int) (err error) { _, _, errno := syscall.RawSyscall(syscall.SYS_SETGID, uintptr(gid), 0, 0) if errno != 0 { err = errno } return } // GetPwd returns the abs dir of current path func GetPwd() string { dir, _ := filepath.Abs(filepath.Dir(os.Args[0])) return dir } // GetProcPwd returns the abs dir of current execution file func GetProcPwd() string { file, _ := exec.LookPath(os.Args[0]) dir, _ := filepath.Abs(filepath.Dir(file)) return dir } gokit-0.25.9/xos/xos_test.go000066400000000000000000000043631426246334200157500ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xos import ( "os" "testing" "github.com/likexian/gokit/assert" ) func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } func TestGetenv(t *testing.T) { assert.Equal(t, Getenv("TEST", ""), "") os.Setenv("TEST", "gokit") assert.Equal(t, Getenv("TEST", ""), "gokit") } func TestExec(t *testing.T) { _, _, err := Exec("xx") assert.NotNil(t, err) stdout, stderr, err := Exec("ls", "-lh") assert.Nil(t, err) assert.NotEqual(t, stdout, "") assert.Equal(t, stderr, "") } func TestTimeoutExec(t *testing.T) { _, _, err := TimeoutExec(1, "xxx") assert.NotNil(t, err) _, _, err = TimeoutExec(1, "sleep", "3") assert.NotNil(t, err) stdout, stderr, err := TimeoutExec(3, "sleep", "1") assert.Nil(t, err) assert.Equal(t, stdout, "") assert.Equal(t, stderr, "") } func TestLookupUser(t *testing.T) { uid, gid, err := LookupUser("nobody") assert.Nil(t, err) assert.True(t, uid > 0) assert.True(t, gid > 0) } func TestGetPwd(t *testing.T) { pwd := GetPwd() assert.NotEqual(t, pwd, "", "pwd expect to be not empty") pwd = GetProcPwd() assert.NotEqual(t, pwd, "", "pwd expect to be not empty") } func TestSetid(t *testing.T) { err := SetUID(0) assert.Nil(t, err) err = SetGID(0) assert.Nil(t, err) err = SetUser("root") assert.Nil(t, err) err = SetUser("xxx") assert.NotNil(t, err) /* err = SetUser("nobody") assert.Nil(t, err) err = SetUID(0) assert.NotNil(t, err) err = SetGID(0) assert.NotNil(t, err) err = SetUser("root") assert.NotNil(t, err) */ } gokit-0.25.9/xptr/000077500000000000000000000000001426246334200137275ustar00rootroot00000000000000gokit-0.25.9/xptr/README.md000066400000000000000000000013231426246334200152050ustar00rootroot00000000000000# GoKit - xptr Pointer kits for Golang development. ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/xptr" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xptr) ## Example ### Get pointer of int ```go fmt.Println("&int:", xptr.Int(1)) ``` ### Get pointer of string ```go fmt.Println("&string:", xptr.String("test")) ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/xptr/xptr.go000066400000000000000000000044051426246334200152560ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xptr // Version returns package version func Version() string { return "0.2.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // Int converts an int to a pointer func Int(v int) *int { return &v } // Int8 converts an int8 to a pointer func Int8(v int8) *int8 { return &v } // Int16 converts an int16 to a pointer func Int16(v int16) *int16 { return &v } // Int32 converts an int32 to a pointer func Int32(v int32) *int32 { return &v } // Int64 converts an int64 to a pointer func Int64(v int64) *int64 { return &v } // Uint converts an uint to a pointer func Uint(v uint) *uint { return &v } // Uint8 converts an uint8 to a pointer func Uint8(v uint8) *uint8 { return &v } // Uint16 converts an uint16 to a pointer func Uint16(v uint16) *uint16 { return &v } // Uint32 converts an uint32 to a pointer func Uint32(v uint32) *uint32 { return &v } // Uint64 converts an uint64 to a pointer func Uint64(v uint64) *uint64 { return &v } // Float32 converts an float32 to a pointer func Float32(v float32) *float32 { return &v } // Float64 converts an float64 to a pointer func Float64(v float64) *float64 { return &v } // Bool converts an bool to a pointer func Bool(v bool) *bool { return &v } // Byte converts an byte to a pointer func Byte(v byte) *byte { return &v } // Rune converts an rune to a pointer func Rune(v rune) *rune { return &v } // String converts an string to a pointer func String(v string) *string { return &v } gokit-0.25.9/xptr/xptr_test.go000066400000000000000000000044171426246334200163200ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xptr import ( "testing" "github.com/likexian/gokit/assert" ) func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } func TestInt(t *testing.T) { v := int(1) p := Int(v) assert.Equal(t, *p, v) } func TestInt8(t *testing.T) { v := int8(1) p := Int8(v) assert.Equal(t, *p, v) } func TestInt16(t *testing.T) { v := int16(1) p := Int16(v) assert.Equal(t, *p, v) } func TestInt32(t *testing.T) { v := int32(1) p := Int32(v) assert.Equal(t, *p, v) } func TestInt64(t *testing.T) { v := int64(1) p := Int64(v) assert.Equal(t, *p, v) } func TestUint(t *testing.T) { v := uint(1) p := Uint(v) assert.Equal(t, *p, v) } func TestUint8(t *testing.T) { v := uint8(1) p := Uint8(v) assert.Equal(t, *p, v) } func TestUint16(t *testing.T) { v := uint16(1) p := Uint16(v) assert.Equal(t, *p, v) } func TestUint32(t *testing.T) { v := uint32(1) p := Uint32(v) assert.Equal(t, *p, v) } func TestUint64(t *testing.T) { v := uint64(1) p := Uint64(v) assert.Equal(t, *p, v) } func TestFloat32(t *testing.T) { v := float32(1) p := Float32(v) assert.Equal(t, *p, v) } func TestFloat64(t *testing.T) { v := float64(1) p := Float64(v) assert.Equal(t, *p, v) } func TestBool(t *testing.T) { v := bool(true) p := Bool(v) assert.Equal(t, *p, v) } func TestByte(t *testing.T) { v := byte(1) p := Byte(v) assert.Equal(t, *p, v) } func TestRune(t *testing.T) { v := rune(1) p := Rune(v) assert.Equal(t, *p, v) } func TestString(t *testing.T) { v := string("1") p := String(v) assert.Equal(t, *p, v) } gokit-0.25.9/xrand/000077500000000000000000000000001426246334200140465ustar00rootroot00000000000000gokit-0.25.9/xrand/README.md000066400000000000000000000016631426246334200153330ustar00rootroot00000000000000# GoKit - xrand Rand kits for Golang development. ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/xrand" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xrand) ## Example ### Rand int between 0 and 10000 ```go n := xrand.Int(10000) fmt.Println("rand int between 0 and 10000 is:", n) ``` ### Rand int between 1000 and 10000 ```go n := xrand.IntRange(1000, 10000) fmt.Println("rand int between 1000 and 10000 is:", n) ``` ### Rand bytes with length of 10 ```go b, err := xrand.Bytes(10) if err != nil { fmt.Println("rand bytes:", b) } ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/xrand/xrand.go000066400000000000000000000044301426246334200155120ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xrand import ( crand "crypto/rand" "encoding/base64" "encoding/hex" "math/rand" "time" ) // Version returns package version func Version() string { return "0.1.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // Int returns random int in [0, max) func Int(max int) int { if max <= 0 { return 0 } rand.Seed(time.Now().UnixNano()) return rand.Intn(max) } // IntRange returns random int in [min, max) func IntRange(min, max int) int { if min > max { min, max = max, min } return Int(max-min) + min } // String returns n random string from 0-9,a-z,A-Z func String(n int) string { sources := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" return StringRange(n, sources) } // StringRange returns n random string base on source func StringRange(n int, source string) string { if source == "" { return "" } ss := []rune(source) bs := make([]rune, n) for i := range bs { bs[i] = ss[Int(len(ss))] } return string(bs) } // Bytes returns n random bytes func Bytes(n int) (bs []byte, err error) { bs = make([]byte, n) _, err = crand.Read(bs) return } // Hex returns hex string of n random bytes func Hex(n int) (ss string, err error) { bs, err := Bytes(n) if err != nil { return } ss = hex.EncodeToString(bs) return } // Base64 returns base64 string of n random bytes func Base64(n int) (ss string, err error) { bs, err := Bytes(n) if err != nil { return } ss = base64.StdEncoding.EncodeToString(bs) return } gokit-0.25.9/xrand/xrand_test.go000066400000000000000000000043511426246334200165530ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xrand import ( "testing" "github.com/likexian/gokit/assert" ) func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } func TestInt(t *testing.T) { for i := 0; i < 1000; i++ { v := Int(0) assert.Equal(t, v, 0) v = Int(1) assert.Equal(t, v, 0) v = Int(2) assert.True(t, v == 0 || v == 1) } } func TestIntRange(t *testing.T) { for i := 0; i < 1000; i++ { v := IntRange(0, 0) assert.Equal(t, v, 0) v = IntRange(0, 1) assert.Equal(t, v, 0) v = IntRange(0, 2) assert.True(t, v == 0 || v == 1) v = IntRange(2, 0) assert.True(t, v == 0 || v == 1) v = IntRange(2, 2) assert.True(t, v == 2) v = IntRange(100, 10000) assert.True(t, v >= 100 && v < 10000) } } func TestString(t *testing.T) { for i := 0; i < 1000; i++ { v := String(10) assert.Equal(t, len(v), 10) } } func TestStringRange(t *testing.T) { for i := 0; i < 1000; i++ { v := StringRange(10, "") assert.Equal(t, v, "") v = StringRange(10, "abc") assert.Equal(t, len(v), 10) for _, vv := range v { assert.True(t, vv == 'a' || vv == 'b' || vv == 'c') } } } func TestBytes(t *testing.T) { for i := 0; i < 1000; i++ { v, err := Bytes(10) assert.Nil(t, err) assert.Equal(t, len(v), 10) } } func TestHex(t *testing.T) { for i := 0; i < 1000; i++ { v, err := Hex(10) assert.Nil(t, err) assert.True(t, len(v) > 10) } } func TestBase64(t *testing.T) { for i := 0; i < 1000; i++ { v, err := Base64(10) assert.Nil(t, err) assert.True(t, len(v) > 10) } } gokit-0.25.9/xslice/000077500000000000000000000000001426246334200142215ustar00rootroot00000000000000gokit-0.25.9/xslice/README.md000066400000000000000000000015111426246334200154760ustar00rootroot00000000000000# GoKit - xslice Slice kits for Golang development. ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/xslice" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xslice) ## Example ### Get unique of string array ```go array := xslice.Unique([]string{"a", "a", "b", "b", "b", "c"}) fmt.Println("new array:", array) ``` ### Get unique of int array ```go array := xslice.Unique([]int{0, 0, 1, 1, 1, 2, 2, 3}) fmt.Println("new array:", array) ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/xslice/xslice.go000066400000000000000000000221031426246334200160350ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xslice import ( "fmt" "math" "math/rand" "reflect" ) // Version returns package version func Version() string { return "0.22.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // IsSlice returns whether value is slice func IsSlice(v interface{}) bool { return reflect.ValueOf(v).Kind() == reflect.Slice } // Index returns the index of the first value in v, or -1 if not found func Index(v interface{}, value interface{}) int { vv := reflect.ValueOf(v) if vv.Kind() != reflect.Slice { panic("xslice: v expected to be a slice") } for i := 0; i < vv.Len(); i++ { if reflect.DeepEqual(value, vv.Index(i).Interface()) { return i } } return -1 } // Unique returns unique values of slice func Unique(v interface{}) interface{} { vv := reflect.ValueOf(v) if vv.Kind() != reflect.Slice { panic("xslice: v expected to be a slice") } h := make(map[interface{}]bool) r := reflect.MakeSlice(reflect.TypeOf(v), 0, vv.Cap()) for i := 0; i < vv.Len(); i++ { hv := hashValue(vv.Index(i).Interface()) if _, ok := h[hv]; !ok { h[hv] = true r = reflect.Append(r, vv.Index(i)) } } return r.Interface() } // IsUnique returns whether slice values is unique func IsUnique(v interface{}) bool { vv := reflect.ValueOf(v) if vv.Kind() != reflect.Slice { panic("xslice: v expected to be a slice") } if vv.Len() <= 1 { return true } h := make(map[interface{}]bool) for i := 0; i < vv.Len(); i++ { hv := hashValue(vv.Index(i).Interface()) if _, ok := h[hv]; ok { return false } h[hv] = true } return true } // Intersect returns values in both slices func Intersect(x, y interface{}) interface{} { xx := reflect.ValueOf(x) if xx.Kind() != reflect.Slice { panic("xslice: x expected to be a slice") } yy := reflect.ValueOf(y) if yy.Kind() != reflect.Slice { panic("xslice: y expected to be a slice") } h := make(map[interface{}]bool) for i := 0; i < xx.Len(); i++ { h[hashValue(xx.Index(i).Interface())] = true } r := reflect.MakeSlice(reflect.TypeOf(x), 0, 0) for i := 0; i < yy.Len(); i++ { if _, ok := h[hashValue(yy.Index(i).Interface())]; ok { r = reflect.Append(r, yy.Index(i)) } } return r.Interface() } // Different returns values in x but not in y func Different(x, y interface{}) interface{} { xx := reflect.ValueOf(x) if xx.Kind() != reflect.Slice { panic("xslice: x expected to be a slice") } yy := reflect.ValueOf(y) if yy.Kind() != reflect.Slice { panic("xslice: y expected to be a slice") } h := make(map[interface{}]bool) for i := 0; i < yy.Len(); i++ { h[hashValue(yy.Index(i).Interface())] = true } r := reflect.MakeSlice(reflect.TypeOf(x), 0, 0) for i := 0; i < xx.Len(); i++ { if _, ok := h[hashValue(xx.Index(i).Interface())]; !ok { r = reflect.Append(r, xx.Index(i)) } } return r.Interface() } // Merge append y values to x if not exists in func Merge(x, y interface{}) interface{} { xx := reflect.ValueOf(x) if xx.Kind() != reflect.Slice { panic("xslice: x expected to be a slice") } yy := reflect.ValueOf(y) if yy.Kind() != reflect.Slice { panic("xslice: y expected to be a slice") } h := make(map[interface{}]bool) for i := 0; i < xx.Len(); i++ { h[hashValue(xx.Index(i).Interface())] = true } r := xx.Slice(0, xx.Len()) for i := 0; i < yy.Len(); i++ { if _, ok := h[hashValue(yy.Index(i).Interface())]; !ok { r = reflect.Append(r, yy.Index(i)) } } return r.Interface() } // Reverse returns a slice with elements in reverse order func Reverse(v interface{}) { vv := reflect.ValueOf(v) if vv.Kind() != reflect.Slice { panic("xslice: v expected to be a slice") } swap := reflect.Swapper(v) for i, j := 0, vv.Len()-1; i < j; i, j = i+1, j-1 { swap(i, j) } } // Shuffle shuffle a slice func Shuffle(v interface{}) { vv := reflect.ValueOf(v) if vv.Kind() != reflect.Slice { panic("xslice: v expected to be a slice") } swap := reflect.Swapper(v) for i := vv.Len() - 1; i >= 1; i-- { j := rand.Intn(i + 1) swap(i, j) } } // Fill returns a slice with count number of v values func Fill(v interface{}, count int) interface{} { if count <= 0 { panic("xslice: count expected to be greater than 0") } r := reflect.MakeSlice(reflect.SliceOf(reflect.TypeOf(v)), 0, 0) for i := 0; i < count; i++ { r = reflect.Append(r, reflect.ValueOf(v)) } return r.Interface() } // Chunk split slice into chunks func Chunk(v interface{}, size int) interface{} { vv := reflect.ValueOf(v) if vv.Kind() != reflect.Slice { panic("xslice: v expected to be a slice") } if size <= 0 { panic("xslice: size expected to be greater than 0") } n := int(math.Ceil(float64(vv.Len()) / float64(size))) r := reflect.MakeSlice(reflect.SliceOf(reflect.TypeOf(v)), 0, 0) for i := 0; i < n; i++ { rr := reflect.MakeSlice(reflect.TypeOf(v), 0, 0) for j := 0; j < size; j++ { if i*size+j >= vv.Len() { break } rr = reflect.Append(rr, vv.Index(i*size+j)) } r = reflect.Append(r, rr) } return r.Interface() } // Concat returns a new flatten slice of []slice func Concat(v interface{}) interface{} { vv := reflect.ValueOf(v) if vv.Kind() != reflect.Slice { panic("xslice: v expected to be a slice") } if vv.Len() == 0 { return v } vt := reflect.TypeOf(v) if vt.Elem().Kind() != reflect.Slice { return v } r := reflect.MakeSlice(reflect.TypeOf(v).Elem(), 0, 0) for i := 0; i < vv.Len(); i++ { for j := 0; j < vv.Index(i).Len(); j++ { r = reflect.Append(r, vv.Index(i).Index(j)) } } return r.Interface() } // Filter filter slice values using callback function fn func Filter(v interface{}, fn interface{}) interface{} { vv := reflect.ValueOf(v) if vv.Kind() != reflect.Slice { panic("xslice: v expected to be a slice") } err := CheckIsFunc(fn, 1, 1) if err != nil { panic("xslice: " + err.Error()) } fv := reflect.ValueOf(fn) ot := fv.Type().Out(0) if ot.Kind() != reflect.Bool { panic("xslice: fn expected to return bool but got " + ot.Kind().String()) } r := reflect.MakeSlice(reflect.TypeOf(v), 0, 0) for i := 0; i < vv.Len(); i++ { if fv.Call([]reflect.Value{vv.Index(i)})[0].Interface().(bool) { r = reflect.Append(r, vv.Index(i)) } } return r.Interface() } // Map apply callback function fn to elements of slice func Map(v interface{}, fn interface{}) interface{} { vv := reflect.ValueOf(v) if vv.Kind() != reflect.Slice { panic("xslice: v expected to be a slice") } err := CheckIsFunc(fn, 1, 1) if err != nil { panic("xslice: " + err.Error()) } fv := reflect.ValueOf(fn) ot := fv.Type().Out(0) r := reflect.MakeSlice(reflect.SliceOf(ot), 0, 0) for i := 0; i < vv.Len(); i++ { r = reflect.Append(r, fv.Call([]reflect.Value{vv.Index(i)})[0]) } return r.Interface() } // Reduce reduce the slice values using callback function fn func Reduce(v interface{}, fn interface{}) interface{} { vv := reflect.ValueOf(v) if vv.Kind() != reflect.Slice { panic("xslice: v expected to be a slice") } if vv.Len() == 0 { panic("xslice: v expected to be not empty") } err := CheckIsFunc(fn, 2, 1) if err != nil { panic("xslice: " + err.Error()) } fv := reflect.ValueOf(fn) if vv.Type().Elem() != fv.Type().In(0) || vv.Type().Elem() != fv.Type().In(1) { panic(fmt.Sprintf("xslice: fn expected to have (%s, %s) arguments but got (%s, %s)", vv.Type().Elem(), vv.Type().Elem(), fv.Type().In(0), fv.Type().In(1))) } if vv.Type().Elem() != fv.Type().Out(0) { panic(fmt.Sprintf("xslice: fn expected to return %s but got %s", vv.Type().Elem(), fv.Type().Out(0).String())) } r := vv.Index(0) for i := 1; i < vv.Len(); i++ { r = fv.Call([]reflect.Value{r, vv.Index(i)})[0] } return r.Interface() } // CheckIsFunc check if fn is a function with n[0] arguments and n[1] returns func CheckIsFunc(fn interface{}, n ...int) error { if fn == nil { return fmt.Errorf("fn excepted to be a function but got nil") } ft := reflect.TypeOf(fn) if ft.Kind() != reflect.Func { return fmt.Errorf("fn excepted to be a function but got " + ft.Kind().String()) } if len(n) >= 1 && n[0] != ft.NumIn() { return fmt.Errorf("fn expected to have %d arguments but got %d", n[0], ft.NumIn()) } if len(n) >= 2 && n[1] != ft.NumOut() { return fmt.Errorf("fn expected to have %d returns but got %d", n[1], ft.NumOut()) } return nil } // hashValue returns a hashable value func hashValue(x interface{}) interface{} { return fmt.Sprintf("%#v", x) } gokit-0.25.9/xslice/xslice_b_test.go000066400000000000000000000043671426246334200174110ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xslice import ( "strconv" "testing" ) var ( minData []string midData []string maxData []string ) func init() { for i := 0; i < 100; i++ { minData = append(minData, strconv.Itoa(i)) } for i := 0; i < 1000; i++ { midData = append(midData, strconv.Itoa(i)) } for i := 0; i < 10000; i++ { maxData = append(maxData, strconv.Itoa(i)) } } func benchmarkIsUnique(b *testing.B, v interface{}) { for i := 0; i < b.N; i++ { IsUnique(v) } } func benchmarkUnique(b *testing.B, v interface{}) { for i := 0; i < b.N; i++ { Unique(v) } } func benchmarkReverse(b *testing.B, v interface{}) { for i := 0; i < b.N; i++ { Reverse(v) } } func benchmarkShuffle(b *testing.B, v interface{}) { for i := 0; i < b.N; i++ { Shuffle(v) } } func BenchmarkIsUniqueMin(b *testing.B) { benchmarkIsUnique(b, minData) } func BenchmarkIsUniqueMid(b *testing.B) { benchmarkIsUnique(b, midData) } func BenchmarkIsUniqueMax(b *testing.B) { benchmarkIsUnique(b, maxData) } func BenchmarkUniqueMin(b *testing.B) { benchmarkUnique(b, minData) } func BenchmarkUniqueMid(b *testing.B) { benchmarkUnique(b, midData) } func BenchmarkUniqueMax(b *testing.B) { benchmarkUnique(b, maxData) } func BenchmarkReverseMin(b *testing.B) { benchmarkReverse(b, minData) } func BenchmarkReverseMid(b *testing.B) { benchmarkReverse(b, midData) } func BenchmarkReverseMax(b *testing.B) { benchmarkReverse(b, maxData) } func BenchmarkShuffleMin(b *testing.B) { benchmarkShuffle(b, minData) } func BenchmarkShuffleMid(b *testing.B) { benchmarkShuffle(b, midData) } func BenchmarkShuffleMax(b *testing.B) { benchmarkShuffle(b, maxData) } gokit-0.25.9/xslice/xslice_test.go000066400000000000000000000417231426246334200171050ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xslice import ( "strings" "testing" "github.com/likexian/gokit/assert" ) type a struct { x, y int } type b struct { x, y int } func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } func TestIsSlice(t *testing.T) { assert.False(t, IsSlice(0)) assert.False(t, IsSlice("0")) assert.True(t, IsSlice([]int{0, 1, 2})) assert.True(t, IsSlice([]string{"0", "1", "2"})) } func TestIndex(t *testing.T) { // Not a slice tests := []struct { x interface{} y interface{} z int }{ {1, 1, -1}, {1.0, 1.0, -1}, {true, true, -1}, } for _, v := range tests { assert.Panic(t, func() { Index(v.x, v.y) }) } // Is a slice tests = []struct { x interface{} y interface{} z int }{ {[]int{0, 0, 1, 1, 1, 2, 2, 3}, int(0), 0}, {[]int8{0, 0, 1, 1, 1, 2, 2, 3}, int8(1), 2}, {[]int16{0, 0, 1, 1, 1, 2, 2, 3}, int16(2), 5}, {[]int32{0, 0, 1, 1, 1, 2, 2, 3}, int32(3), 7}, {[]int64{0, 0, 1, 1, 1, 2, 2, 3}, int64(4), -1}, {[]uint{0, 0, 1, 1, 1, 2, 2, 3}, uint(0), 0}, {[]uint8{0, 0, 1, 1, 1, 2, 2, 3}, uint8(1), 2}, {[]uint16{0, 0, 1, 1, 1, 2, 2, 3}, uint16(2), 5}, {[]uint32{0, 0, 1, 1, 1, 2, 2, 3}, uint32(3), 7}, {[]uint64{0, 0, 1, 1, 1, 2, 2, 3}, uint64(4), -1}, {[]float32{0, 0, 1, 1, 1, 2, 2, 3}, float32(0), 0}, {[]float64{0, 0, 1, 1, 1, 2, 2, 3}, float64(4), -1}, {[]string{"a", "a", "b", "b", "b", "c"}, "a", 0}, {[]bool{true, true, true, false}, false, 3}, {[]interface{}{0, 1, 1, "1", 2}, "1", 3}, {[]interface{}{[]int{0, 1}, []int{0, 1}, []int{1, 2}}, []int{1, 2}, 2}, {[]interface{}{a{0, 1}, a{1, 2}, a{0, 1}, b{0, 1}}, a{0, 1}, 0}, } for _, v := range tests { assert.Equal(t, Index(v.x, v.y), v.z) } } func TestUnique(t *testing.T) { // Not a slice tests := []struct { in interface{} out interface{} }{ {1, 1}, {1.0, 1.0}, {true, true}, } for _, v := range tests { assert.Panic(t, func() { Unique(v.in) }) } // Is a slice tests = []struct { in interface{} out interface{} }{ {[]int{0, 0, 1, 1, 1, 2, 2, 3}, []int{0, 1, 2, 3}}, {[]int8{0, 0, 1, 1, 1, 2, 2, 3}, []int8{0, 1, 2, 3}}, {[]int16{0, 0, 1, 1, 1, 2, 2, 3}, []int16{0, 1, 2, 3}}, {[]int32{0, 0, 1, 1, 1, 2, 2, 3}, []int32{0, 1, 2, 3}}, {[]int64{0, 0, 1, 1, 1, 2, 2, 3}, []int64{0, 1, 2, 3}}, {[]uint{0, 0, 1, 1, 1, 2, 2, 3}, []uint{0, 1, 2, 3}}, {[]uint8{0, 0, 1, 1, 1, 2, 2, 3}, []uint8{0, 1, 2, 3}}, {[]uint16{0, 0, 1, 1, 1, 2, 2, 3}, []uint16{0, 1, 2, 3}}, {[]uint32{0, 0, 1, 1, 1, 2, 2, 3}, []uint32{0, 1, 2, 3}}, {[]uint64{0, 0, 1, 1, 1, 2, 2, 3}, []uint64{0, 1, 2, 3}}, {[]float32{0, 0, 1, 1, 1, 2, 2, 3}, []float32{0, 1, 2, 3}}, {[]float64{0, 0, 1, 1, 1, 2, 2, 3}, []float64{0, 1, 2, 3}}, {[]string{"a", "a", "b", "b", "b", "c"}, []string{"a", "b", "c"}}, {[]bool{true, true, true, false}, []bool{true, false}}, {[]interface{}{0, 1, 1, "1", 2}, []interface{}{0, 1, "1", 2}}, {[]interface{}{[]int{0, 1}, []int{0, 1}, []int{1, 2}}, []interface{}{[]int{0, 1}, []int{1, 2}}}, {[]interface{}{a{0, 1}, a{1, 2}, a{0, 1}, b{0, 1}}, []interface{}{a{0, 1}, a{1, 2}, b{0, 1}}}, } for _, v := range tests { assert.Equal(t, Unique(v.in), v.out) } } func TestIsUnique(t *testing.T) { // Not a slice tests := []struct { in interface{} }{ {1}, {1.0}, {true}, } for _, v := range tests { assert.Panic(t, func() { IsUnique(v.in) }) } // Is a slice tests = []struct { in interface{} }{ {[]int{0, 0, 1, 1, 1, 2, 2, 3}}, {[]int8{0, 0, 1, 1, 1, 2, 2, 3}}, {[]int16{0, 0, 1, 1, 1, 2, 2, 3}}, {[]int32{0, 0, 1, 1, 1, 2, 2, 3}}, {[]int64{0, 0, 1, 1, 1, 2, 2, 3}}, {[]uint{0, 0, 1, 1, 1, 2, 2, 3}}, {[]uint8{0, 0, 1, 1, 1, 2, 2, 3}}, {[]uint16{0, 0, 1, 1, 1, 2, 2, 3}}, {[]uint32{0, 0, 1, 1, 1, 2, 2, 3}}, {[]uint64{0, 0, 1, 1, 1, 2, 2, 3}}, {[]float32{0, 0, 1, 1, 1, 2, 2, 3}}, {[]float64{0, 0, 1, 1, 1, 2, 2, 3}}, {[]string{"a", "a", "b", "b", "b", "c"}}, {[]bool{true, true, true, false}}, {[]interface{}{0, 1, 1, "1", 2}}, {[]interface{}{[]int{0, 1}, []int{0, 1}, []int{1, 2}}}, {[]interface{}{a{0, 1}, a{1, 2}, a{0, 1}, b{0, 1}}}, } for _, v := range tests { assert.False(t, IsUnique(v.in)) } // Is a slice tests = []struct { in interface{} }{ {[]int{1}}, {[]int{0, 1, 2, 3}}, {[]int8{0, 1, 2, 3}}, {[]int16{0, 1, 2, 3}}, {[]int32{0, 1, 2, 3}}, {[]int64{0, 1, 2, 3}}, {[]uint{0, 1, 2, 3}}, {[]uint8{0, 1, 2, 3}}, {[]uint16{0, 1, 2, 3}}, {[]uint32{0, 1, 2, 3}}, {[]uint64{0, 1, 2, 3}}, {[]float32{0, 1, 2, 3}}, {[]float64{0, 1, 2, 3}}, {[]string{"a", "b", "c"}}, {[]bool{true, false}}, {[]interface{}{0, 1, "1", 2}}, {[]interface{}{[]int{0, 1}, []int{1, 2}}}, {[]interface{}{a{0, 1}, a{1, 2}, b{0, 1}}}, } for _, v := range tests { assert.True(t, IsUnique(v.in)) } } func TestIntersect(t *testing.T) { // Not a slice tests := []struct { x interface{} y interface{} out interface{} }{ {1, 1, nil}, {1.0, 1.0, nil}, {true, true, nil}, {[]int{1}, 1, nil}, {[]float64{1.0}, 1, nil}, {[]bool{true}, true, nil}, } for _, v := range tests { assert.Panic(t, func() { Intersect(v.x, v.y) }) } // Is a slice tests = []struct { x interface{} y interface{} out interface{} }{ {[]int{0, 1, 2}, []int{1, 2, 3}, []int{1, 2}}, {[]int8{0, 1, 2}, []int8{1, 2, 3}, []int8{1, 2}}, {[]int16{0, 1, 2}, []int16{1, 2, 3}, []int16{1, 2}}, {[]int32{0, 1, 2}, []int32{1, 2, 3}, []int32{1, 2}}, {[]int64{0, 1, 2}, []int64{1, 2, 3}, []int64{1, 2}}, {[]float32{0, 1, 2}, []float32{1, 2, 3}, []float32{1, 2}}, {[]float64{0, 1, 2}, []float64{1, 2, 3}, []float64{1, 2}}, {[]string{"0", "1", "2"}, []string{"1", "2", "3"}, []string{"1", "2"}}, {[]bool{true, false}, []bool{true}, []bool{true}}, {[]interface{}{0, 1, "1", 2}, []interface{}{1, "1", 2, 3}, []interface{}{1, "1", 2}}, {[]interface{}{[]int{0, 1}, []int{1, 2}}, []interface{}{[]int{1, 2}, []int{2, 3}}, []interface{}{[]int{1, 2}}}, {[]interface{}{a{0, 1}, a{1, 2}, b{0, 1}}, []interface{}{a{1, 2}, b{2, 3}}, []interface{}{a{1, 2}}}, } for _, v := range tests { assert.Equal(t, Intersect(v.x, v.y), v.out) } } func TestDifferent(t *testing.T) { // Not a slice tests := []struct { x interface{} y interface{} out interface{} }{ {1, 1, nil}, {1.0, 1.0, nil}, {true, true, nil}, {[]int{1}, 1, nil}, {[]float64{1.0}, 1, nil}, {[]bool{true}, true, nil}, } for _, v := range tests { assert.Panic(t, func() { Different(v.x, v.y) }) } // Is a slice tests = []struct { x interface{} y interface{} out interface{} }{ {[]int{0, 1, 2}, []int{1, 2, 3}, []int{0}}, {[]int8{0, 1, 2}, []int8{1, 2, 3}, []int8{0}}, {[]int16{0, 1, 2}, []int16{1, 2, 3}, []int16{0}}, {[]int32{0, 1, 2}, []int32{1, 2, 3}, []int32{0}}, {[]int64{0, 1, 2}, []int64{1, 2, 3}, []int64{0}}, {[]float32{0, 1, 2}, []float32{1, 2, 3}, []float32{0}}, {[]float64{0, 1, 2}, []float64{1, 2, 3}, []float64{0}}, {[]string{"0", "1", "2"}, []string{"1", "2", "3"}, []string{"0"}}, {[]bool{true, false}, []bool{true}, []bool{false}}, {[]interface{}{0, 1, "1", 2}, []interface{}{1, "1", 2, 3}, []interface{}{0}}, {[]interface{}{[]int{0, 1}, []int{1, 2}}, []interface{}{[]int{1, 2}, []int{2, 3}}, []interface{}{[]int{0, 1}}}, {[]interface{}{a{0, 1}, a{1, 2}, b{0, 1}}, []interface{}{a{1, 2}, b{2, 3}}, []interface{}{a{0, 1}, b{0, 1}}}, } for _, v := range tests { assert.Equal(t, Different(v.x, v.y), v.out) } } func TestMerge(t *testing.T) { // Not a slice tests := []struct { x interface{} y interface{} out interface{} }{ {1, 1, 1}, {1.0, 1.0, 1.0}, {true, true, true}, {[]int{1}, 1, []int{1}}, {[]float64{1.0}, 1, []float64{1.0}}, {[]bool{true}, true, []bool{true}}, } for _, v := range tests { assert.Panic(t, func() { Merge(v.x, v.y) }) } // Is a slice tests = []struct { x interface{} y interface{} out interface{} }{ {[]int{0, 1, 2}, []int{1, 2, 3}, []int{0, 1, 2, 3}}, {[]int8{0, 1, 2}, []int8{1, 2, 3}, []int8{0, 1, 2, 3}}, {[]int16{0, 1, 2}, []int16{1, 2, 3}, []int16{0, 1, 2, 3}}, {[]int32{0, 1, 2}, []int32{1, 2, 3}, []int32{0, 1, 2, 3}}, {[]int64{0, 1, 2}, []int64{1, 2, 3}, []int64{0, 1, 2, 3}}, {[]float32{0, 1, 2}, []float32{1, 2, 3}, []float32{0, 1, 2, 3}}, {[]float64{0, 1, 2}, []float64{1, 2, 3}, []float64{0, 1, 2, 3}}, {[]string{"0", "1", "2"}, []string{"1", "2", "3"}, []string{"0", "1", "2", "3"}}, {[]bool{true, false}, []bool{true}, []bool{true, false}}, {[]interface{}{0, 1, "1", 2}, []interface{}{1, "1", 2, 3}, []interface{}{0, 1, "1", 2, 3}}, {[]interface{}{[]int{0, 1}, []int{1, 2}}, []interface{}{[]int{1, 2}, []int{2, 3}}, []interface{}{[]int{0, 1}, []int{1, 2}, []int{2, 3}}}, {[]interface{}{a{0, 1}, a{1, 2}, b{0, 1}}, []interface{}{a{1, 2}, b{2, 3}}, []interface{}{a{0, 1}, a{1, 2}, b{0, 1}, b{2, 3}}}, } for _, v := range tests { assert.Equal(t, Merge(v.x, v.y), v.out) } } func TestReverse(t *testing.T) { // Not a slice tests := []struct { in interface{} out interface{} }{ {1, 1}, {1.0, 1.0}, {true, true}, } for _, v := range tests { assert.Panic(t, func() { Reverse(v.in) }) } // Is a slice tests = []struct { in interface{} out interface{} }{ {[]int{0, 1, 2, 3, 4}, []int{4, 3, 2, 1, 0}}, {[]int8{0, 1, 2, 3, 4}, []int8{4, 3, 2, 1, 0}}, {[]int16{0, 1, 2, 3, 4}, []int16{4, 3, 2, 1, 0}}, {[]int32{0, 1, 2, 3, 4}, []int32{4, 3, 2, 1, 0}}, {[]int64{0, 1, 2, 3, 4}, []int64{4, 3, 2, 1, 0}}, {[]float32{0, 1, 2, 3, 4}, []float32{4, 3, 2, 1, 0}}, {[]float64{0, 1, 2, 3, 4}, []float64{4, 3, 2, 1, 0}}, {[]string{"a", "b", "c", "d", "e"}, []string{"e", "d", "c", "b", "a"}}, {[]bool{true, false, true, false}, []bool{false, true, false, true}}, {[]interface{}{0, 1, 2, "3", 3}, []interface{}{3, "3", 2, 1, 0}}, {[]interface{}{[]int{0, 1}, []int{1, 2}}, []interface{}{[]int{1, 2}, []int{0, 1}}}, {[]interface{}{a{0, 1}, a{1, 2}, b{0, 1}}, []interface{}{b{0, 1}, a{1, 2}, a{0, 1}}}, } for _, v := range tests { Reverse(v.in) assert.Equal(t, v.in, v.out) } } func TestShuffle(t *testing.T) { // Not a slice tests := []struct { in interface{} out interface{} }{ {1, 1}, {1.0, 1.0}, {true, true}, } for _, v := range tests { assert.Panic(t, func() { Shuffle(v.in) }) } // Is a slice tests = []struct { in interface{} out interface{} }{ {[]int{0, 1, 2, 3, 4}, []int{0, 1, 2, 3, 4}}, {[]int8{0, 1, 2, 3, 4}, []int8{0, 1, 2, 3, 4}}, {[]int16{0, 1, 2, 3, 4}, []int16{0, 1, 2, 3, 4}}, {[]int32{0, 1, 2, 3, 4}, []int32{0, 1, 2, 3, 4}}, {[]int64{0, 1, 2, 3, 4}, []int64{0, 1, 2, 3, 4}}, {[]float32{0, 1, 2, 3, 4}, []float32{0, 1, 2, 3, 4}}, {[]float64{0, 1, 2, 3, 4}, []float64{0, 1, 2, 3, 4}}, {[]string{"a", "b", "c", "d", "e"}, []string{"a", "b", "c", "d", "e"}}, {[]bool{true, false, false, true, true}, []bool{true, false, false, true, true}}, {[]interface{}{0, 1, 2, "3", 3}, []interface{}{0, 1, 2, "3", 3}}, {[]interface{}{[]int{0, 1}, []int{1, 2}, []int{1, 2}}, []interface{}{[]int{0, 1}, []int{1, 2}, []int{1, 2}}}, {[]interface{}{a{0, 1}, a{1, 2}, b{0, 1}}, []interface{}{a{0, 1}, a{1, 2}, b{0, 1}}}, } for _, v := range tests { Shuffle(v.in) assert.NotEqual(t, v.in, v.out) } } func TestFill(t *testing.T) { tests := []struct { v interface{} n int out interface{} }{ {1, -1, nil}, {1, 0, nil}, } for _, v := range tests { assert.Panic(t, func() { Fill(v.v, v.n) }) } tests = []struct { v interface{} n int out interface{} }{ {1, 1, []int{1}}, {1, 3, []int{1, 1, 1}}, {int(1), 3, []int{1, 1, 1}}, {int8(1), 3, []int8{1, 1, 1}}, {int16(1), 3, []int16{1, 1, 1}}, {int32(1), 3, []int32{1, 1, 1}}, {int64(1), 3, []int64{1, 1, 1}}, {float32(1), 3, []float32{1, 1, 1}}, {float64(1), 3, []float64{1, 1, 1}}, {"a", 3, []string{"a", "a", "a"}}, {true, 3, []bool{true, true, true}}, {[]int{1, 2}, 3, [][]int{{1, 2}, {1, 2}, {1, 2}}}, {a{1, 2}, 3, []a{{1, 2}, {1, 2}, {1, 2}}}, {[]interface{}{0, "1"}, 3, [][]interface{}{{0, "1"}, {0, "1"}, {0, "1"}}}, {[]interface{}{[]int{0, 1}}, 3, [][]interface{}{{[]int{0, 1}}, {[]int{0, 1}}, {[]int{0, 1}}}}, {[]interface{}{a{0, 1}}, 3, [][]interface{}{{a{x: 0, y: 1}}, {a{x: 0, y: 1}}, {a{x: 0, y: 1}}}}, } for _, v := range tests { assert.Equal(t, Fill(v.v, v.n), v.out) } } func TestChunk(t *testing.T) { tests := []struct { v interface{} n int out interface{} }{ {1, 1, 1}, {[]int{1}, 0, nil}, } for _, v := range tests { assert.Panic(t, func() { Chunk(v.v, v.n) }) } tests = []struct { v interface{} n int out interface{} }{ {[]int{0, 1, 2}, 1, [][]int{{0}, {1}, {2}}}, {[]int{0, 1, 2, 3, 4}, 2, [][]int{{0, 1}, {2, 3}, {4}}}, {[]int{0, 1, 2, 3, 4, 5}, 2, [][]int{{0, 1}, {2, 3}, {4, 5}}}, {[]string{"a", "b", "c", "d", "e"}, 3, [][]string{{"a", "b", "c"}, {"d", "e"}}}, {[]interface{}{a{0, 1}, b{2, 3}, a{4, 5}}, 2, [][]interface{}{{a{0, 1}, b{2, 3}}, {a{4, 5}}}}, } for _, v := range tests { assert.Equal(t, Chunk(v.v, v.n), v.out) } } func TestConcat(t *testing.T) { tests := []struct { in interface{} out interface{} }{ {1, 1}, } for _, v := range tests { assert.Panic(t, func() { Concat(v.in) }) } tests = []struct { in interface{} out interface{} }{ {[]int{}, []int{}}, {[]int{0, 1, 2, 3, 4}, []int{0, 1, 2, 3, 4}}, {[][]int{{0, 1}, {2, 3}, {4}}, []int{0, 1, 2, 3, 4}}, {[][]string{{"a", "b"}, {"c"}, {"d", "e"}}, []string{"a", "b", "c", "d", "e"}}, {[][]interface{}{{a{0, 1}, b{0, 1}}, {a{1, 2}}}, []interface{}{a{0, 1}, b{0, 1}, a{1, 2}}}, } for _, v := range tests { assert.Equal(t, Concat(v.in), v.out) } } func TestFilter(t *testing.T) { // Panic tests tests := []struct { v interface{} f interface{} out interface{} }{ {1, nil, 1}, {[]int{1}, nil, nil}, {[]int{1}, 1, nil}, {[]int{1}, func() {}, nil}, {[]int{1}, func(v int) {}, nil}, {[]int{1}, func(v int) int { return v }, nil}, } for _, v := range tests { assert.Panic(t, func() { Filter(v.v, v.f) }) } // General tests tests = []struct { v interface{} f interface{} out interface{} }{ {[]interface{}{0, 1, nil, 2}, func(v interface{}) bool { return v != nil }, []interface{}{0, 1, 2}}, {[]int{-2, -1, 0, 1, 2}, func(v int) bool { return v >= 0 }, []int{0, 1, 2}}, {[]string{"a_0", "b_1", "a_1"}, func(v string) bool { return strings.HasPrefix(v, "a_") }, []string{"a_0", "a_1"}}, {[]bool{true, false, false}, func(v bool) bool { return !v }, []bool{false, false}}, } for _, v := range tests { assert.Equal(t, Filter(v.v, v.f), v.out) } } func TestMap(t *testing.T) { // Panic tests tests := []struct { v interface{} f interface{} out interface{} }{ {1, nil, 1}, {[]int{1}, nil, nil}, {[]int{1}, 1, nil}, {[]int{1}, func() {}, nil}, {[]int{1}, func(v int) {}, nil}, } for _, v := range tests { assert.Panic(t, func() { Map(v.v, v.f) }) } // General tests tests = []struct { v interface{} f interface{} out interface{} }{ {[]int{1, 2, 3, 4, 5}, func(v int) int { return v * v * v }, []int{1, 8, 27, 64, 125}}, {[]int{-2, -1, 0, 1, 2}, func(v int) bool { return v > 0 }, []bool{false, false, false, true, true}}, {[]string{"a", "b", "c"}, func(v string) string { return "x_" + v }, []string{"x_a", "x_b", "x_c"}}, {[]bool{true, false, false}, func(v bool) bool { return !v }, []bool{false, true, true}}, {[]interface{}{1, nil}, func(v interface{}) interface{} { return assert.If(v == nil, -1, v) }, []interface{}{1, -1}}, } for _, v := range tests { assert.Equal(t, Map(v.v, v.f), v.out) } } func TestReduce(t *testing.T) { // Panic tests tests := []struct { v interface{} f interface{} out interface{} }{ {1, nil, 1}, {[]int{}, nil, nil}, {[]int{0, 1}, nil, nil}, {[]int{0, 1}, 1, nil}, {[]int{0, 1}, func() {}, nil}, {[]int{0, 1}, func(x int) {}, nil}, {[]int{0, 1}, func(x, y int) {}, nil}, {[]int{0, 1}, func(x bool, y int) int { return y }, nil}, {[]int{0, 1}, func(x int, y bool) int { return x }, nil}, {[]int{0, 1}, func(x int, y int) bool { return true }, nil}, } for _, v := range tests { assert.Panic(t, func() { Reduce(v.v, v.f) }) } // General tests tests = []struct { v interface{} f interface{} out interface{} }{ {[]int{1}, func(x, y int) int { return x + y }, 1}, {[]int{1, 2}, func(x, y int) int { return x + y }, 3}, {[]int{1, 2, 3, 4}, func(x, y int) int { return x * y }, 24}, } for _, v := range tests { assert.Equal(t, Reduce(v.v, v.f).(int), v.out) } } gokit-0.25.9/xstring/000077500000000000000000000000001426246334200144305ustar00rootroot00000000000000gokit-0.25.9/xstring/README.md000066400000000000000000000017461426246334200157170ustar00rootroot00000000000000# GoKit - xstring String kits for Golang development. ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/xstring" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xstring) ## Example ### Check string is all letter ```go s := "abc123" ok := xstring.IsLetter(s) fmt.Println("IsLetter:", ok) ``` ### Check string is a number ```go s := "12345.67" ok := xstring.IsNumeric(s) fmt.Println("IsNumeric:", ok) ``` ### Expand map value to template string ```go t := "i am {name}, i have ${money}." m := map[string]interface{}{"name": "Li Kexian", "money": 100} s := xstring.Expand(t, m) fmt.Println(s) ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/xstring/xstring.go000066400000000000000000000127051426246334200164620ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xstring import ( "bytes" "fmt" "reflect" "strconv" "strings" "unicode" "golang.org/x/text/cases" "golang.org/x/text/language" ) var ( // initialisms is known initialisms initialisms = map[string]bool{ "acl": true, "api": true, "ascii": true, "cpu": true, "css": true, "dns": true, "eof": true, "guid": true, "html": true, "http": true, "https": true, "id": true, "ip": true, "json": true, "lhs": true, "qps": true, "ram": true, "rhs": true, "rpc": true, "sla": true, "smtp": true, "sql": true, "ssh": true, "tcp": true, "tls": true, "ttl": true, "udp": true, "ui": true, "uid": true, "uuid": true, "uri": true, "url": true, "utf": true, "vm": true, "xml": true, "xmpp": true, "xsrf": true, "xss": true, } ) // Version returns package version func Version() string { return "0.6.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // IsLetter returns if s is an english letter func IsLetter(s uint8) bool { n := (s | 0x20) - 'a' return n < 26 } // IsLetters returns if s is all english letter func IsLetters(s string) bool { for _, v := range s { if !IsLetter(uint8(v)) { return false } } return true } // IsNumeric returns if s is a number func IsNumeric(s string) bool { _, err := strconv.ParseFloat(s, 64) return err == nil } // Reverse returns reversed string func Reverse(s string) string { n := len(s) runes := make([]rune, n) for _, v := range s { n-- runes[n] = v } return string(runes[n:]) } // ToString convert v to string func ToString(v interface{}) string { switch vv := v.(type) { case []byte: return string(vv) case string: return vv case bool: return strconv.FormatBool(vv) case int: return strconv.FormatInt(int64(vv), 10) case int8: return strconv.FormatInt(int64(vv), 10) case int16: return strconv.FormatInt(int64(vv), 10) case int32: return strconv.FormatInt(int64(vv), 10) case int64: return strconv.FormatInt(vv, 10) case uint: return strconv.FormatUint(uint64(vv), 10) case uint8: return strconv.FormatUint(uint64(vv), 10) case uint16: return strconv.FormatUint(uint64(vv), 10) case uint32: return strconv.FormatUint(uint64(vv), 10) case uint64: return strconv.FormatUint(vv, 10) case float32: return strconv.FormatFloat(float64(vv), 'f', 2, 64) case float64: return strconv.FormatFloat(vv, 'f', 2, 64) default: return fmt.Sprintf("%v", v) } } // Join concatenates the elements and returns string func Join(v interface{}, sep string) string { vv := reflect.ValueOf(v) if vv.Kind() == reflect.Ptr || vv.Kind() == reflect.Interface { if vv.IsNil() { return "" } vv = vv.Elem() } switch vv.Kind() { case reflect.Slice, reflect.Array: as := []string{} for i := 0; i < vv.Len(); i++ { as = append(as, ToString(vv.Index(i))) } return strings.Join(as, sep) default: return ToString(v) } } // Expand replaces {var} of string s based on the value map m // For example, Expand("i am {name}", map[string]interface{}{"name": "Li Kexian"}) func Expand(s string, m map[string]interface{}) string { var i, j int var buf []byte for { i = LastInIndex(s, "{") if i < 0 { break } j = strings.Index(s[i+1:], "}") if j <= 0 { break } buf = append(buf, s[:i]...) key := s[i+1 : i+1+j] if v, ok := m[key]; ok { buf = append(buf, fmt.Sprint(v)...) } else { buf = append(buf, []byte(fmt.Sprintf("%%!%s(MISSING)", key))...) } s = s[i+1+j+1:] } buf = append(buf, s...) s = string(buf) return s } // LastInIndex find last position at first index // for example, LastInIndex("{{{{{{{{{{name}", "{") // ↑ func LastInIndex(s, f string) int { i := strings.Index(s, f) if i < 0 { return i } t := s[i+1:] for j := 0; j < len(t); j++ { if t[j] != f[0] { return j + i } } return i } // ToSnake returns snake case of string func ToSnake(s string) string { buf := bytes.Buffer{} data := []rune(s) isLower := func(i int) bool { return i >= 0 && i < len(data) && unicode.IsLower(data[i]) } for k, v := range data { if unicode.IsUpper(v) { v = unicode.ToLower(v) if k > 0 && data[k-1] != '_' && (isLower(k-1) || isLower(k+1)) { buf.WriteRune('_') } } buf.WriteRune(v) } return buf.String() } // ToCamel returns camel case of string func ToCamel(s string) string { var result string caser := cases.Title(language.Und) s = strings.Replace(s, "_", " ", -1) for _, v := range strings.Split(s, " ") { if _, ok := initialisms[v]; ok { result += strings.ToUpper(v) } else { result += caser.String(v) } } return result } gokit-0.25.9/xstring/xstring_test.go000066400000000000000000000116661426246334200175260ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xstring import ( "testing" "github.com/likexian/gokit/assert" ) func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } func TestIsLetter(t *testing.T) { tests := []struct { in uint8 out bool }{ {'a', true}, {'z', true}, {'A', true}, {'Z', true}, {'0', false}, {'9', false}, {'+', false}, {'@', false}, {'\t', false}, {'\n', false}, {' ', false}, } for _, v := range tests { assert.Equal(t, IsLetter(v.in), v.out) } } func TestIsLetters(t *testing.T) { tests := []struct { in string out bool }{ {"a", true}, {"ab", true}, {"Ab", true}, {"AB", true}, {"a0", false}, {"10", false}, {"a+", false}, {"a@", false}, {"a.", false}, {"a\n", false}, } for _, v := range tests { assert.Equal(t, IsLetters(v.in), v.out) } } func TestIsNumeric(t *testing.T) { tests := []struct { in string out bool }{ {"", false}, {"a", false}, {"-", false}, {"--1", false}, {"a1", false}, {"1a", false}, {"-1", true}, {"0", true}, {"1", true}, {"-1.1", true}, {"0.1", true}, {"1.1", true}, } for _, v := range tests { assert.Equal(t, IsNumeric(v.in), v.out) } } func TestReverse(t *testing.T) { tests := []struct { in string out string }{ {"a", "a"}, {"abc", "cba"}, {"a123b", "b321a"}, {"中文å¯ä»¥å—?", "?å—以坿–‡ä¸­"}, } for _, v := range tests { assert.Equal(t, Reverse(v.in), v.out) } } func TestToString(t *testing.T) { tests := []struct { in interface{} out string }{ {nil, ""}, {"abc", "abc"}, {[]byte("abc"), "abc"}, {true, "true"}, {int(1), "1"}, {int8(1), "1"}, {int16(1), "1"}, {int32(1), "1"}, {int64(1), "1"}, {uint(1), "1"}, {uint8(1), "1"}, {uint16(1), "1"}, {uint32(1), "1"}, {uint64(1), "1"}, {float32(1), "1.00"}, {float64(1.0), "1.00"}, {[]int{1, 2, 3}, "[1 2 3]"}, {[]string{"1", "2", "3"}, `[1 2 3]`}, {[]interface{}{"1", 2, "3"}, `[1 2 3]`}, {map[string]int{"a": 1}, "map[a:1]"}, } for _, v := range tests { assert.Equal(t, ToString(v.in), v.out, v) } } func TestJoin(t *testing.T) { var s *string var i interface{} = s tests := []struct { in interface{} out string }{ {s, ""}, {nil, ""}, {"abc", "abc"}, {int(1), "1"}, {[]int{1, 2, 3}, "1, 2, 3"}, {[]string{"1", "2", "3"}, `1, 2, 3`}, {[]interface{}{"1", 2, "3"}, `1, 2, 3`}, {map[string]int{"a": 1}, "map[a:1]"}, } for _, v := range tests { assert.Equal(t, Join(v.in, ", "), v.out, v) } x := Join(&i, ",") assert.Contains(t, x, "0x") } func TestExpand(t *testing.T) { h := map[string]interface{}{"hello": "world"} m := map[string]interface{}{"name": "Li Kexian", "money": 100} tests := []struct { in string mv map[string]interface{} out string }{ {"", m, ""}, {"hello", m, "hello"}, {"i am {}", m, "i am {}"}, {"i am name}", m, "i am name}"}, {"i am {name", m, "i am {name"}, {"i am }name{", m, "i am }name{"}, {"i am {name}", h, "i am %!name(MISSING)"}, {"i am {name}", m, "i am Li Kexian"}, {"i am {{name}}", m, "i am {Li Kexian}"}, {"i am {{{{{{name}", m, "i am {{{{{Li Kexian"}, {"i am {{{{{{name}}", m, "i am {{{{{Li Kexian}"}, {"i am {{{{{{name}}}}}}", m, "i am {{{{{Li Kexian}}}}}"}, {"i have ${money}", m, "i have $100"}, {"{name} have ${money}, call {name}.", m, "Li Kexian have $100, call Li Kexian."}, } for _, v := range tests { assert.Equal(t, Expand(v.in, v.mv), v.out) } } func TestLastInIndex(t *testing.T) { tests := []struct { s string f string out int }{ {"a", "b", -1}, {"a", "a", 0}, {"ab", "b", 1}, {"abc", "c", 2}, {"{a}", "{", 0}, {"{{a}", "{", 1}, {"{{{a}", "{", 2}, } for _, v := range tests { assert.Equal(t, LastInIndex(v.s, v.f), v.out) } } func TestSnakeCamel(t *testing.T) { tests := []struct { x string y string }{ {"ID", "id"}, {"URL", "url"}, {"HTTP", "http"}, {"Snake", "snake"}, {"SnakeID", "snake_id"}, {"SnakeCase", "snake_case"}, {"XabcYdefZghi", "xabc_ydef_zghi"}, {"中国人", "中国人"}, {"X中国人", "x中国人"}, {"XiX中国人", "xi_x中国人"}, {"Xab中CYd国EfZg人Hi", "xab中c_yd国_ef_zg人_hi"}, } for _, v := range tests { assert.Equal(t, ToSnake(v.x), v.y) assert.Equal(t, ToCamel(v.y), v.x) } } gokit-0.25.9/xstruct/000077500000000000000000000000001426246334200144465ustar00rootroot00000000000000gokit-0.25.9/xstruct/README.md000066400000000000000000000030461426246334200157300ustar00rootroot00000000000000# GoKit - xstruct Struct kits for Golang development. ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/xstruct" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xstruct) ## Example ### Define a struct first ```go // Define Staff struct type Staff struct { Id int64 `json:"id"` Name string `json:"name"` Enabled bool `json:"enabled"` } // Init staff struct staff := Staff{1, "likexian", true} ``` ### Use as global functions ```go // ["Id", "Name", "Enabled"] names, _ := xstruct.Names(staff) // [1, "likexian", true] values, _ := xstruct.Values(staff) // list all field as [*Field] fields, _ := xstruct.Fields(staff) // get struct field value value, _ := xstruct.Field(staff, "Name").Value() // set struct field value xstruct.Set(staff, "Name", "kexian.li") ``` ### Use as Interactive mode ```go // create a xstruct object s, err := xstruct.New(staff) if err != nil { panic(err) } // ["Id", "Name", "Enabled"] names := s.Names() // [1, "likexian", true] values := s.Values() // list all field as [*Field] fields := s.Fields() // get struct field value value := s.Field("Name").Value() // set struct field value s.Set("Name", "kexian.li") ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/xstruct/xstruct.go000066400000000000000000000200711426246334200165110ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xstruct import ( "errors" "fmt" "reflect" ) var ( // ErrNotStruct not a valid struct ErrNotStruct = errors.New("xstruct: not a valid struct") // ErrNoField field name is not exists ErrNoField = errors.New("xstruct: field name is not exists") // ErrNotExported not an exported field ErrNotExported = errors.New("xstruct: not an exported field") // ErrNotSettable not a settable field ErrNotSettable = errors.New("xstruct: not a settable field") ) // Structx storing struct data type Structx struct { data interface{} value reflect.Value } // Fieldx storing struct field type Fieldx struct { data reflect.StructField value reflect.Value } // Version returns package version func Version() string { return "0.7.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // IsStruct returns if v is a struct func IsStruct(v interface{}) bool { vv := reflect.ValueOf(v) if vv.Kind() == reflect.Ptr { vv = vv.Elem() } return vv.Kind() == reflect.Struct } // Name returns name of struct func Name(v interface{}) (string, error) { s, err := New(v) if err != nil { return "", err } return s.Name(), nil } // Struct returns nested struct with name func Struct(v interface{}, name string) (*Structx, error) { s, err := New(v) if err != nil { return nil, err } return s.Struct(name) } // Map returns struct name value as map func Map(v interface{}) (map[string]interface{}, error) { s, err := New(v) if err != nil { return nil, err } return s.Map(), nil } // Names returns names of struct func Names(v interface{}) ([]string, error) { s, err := New(v) if err != nil { return nil, err } return s.Names(), nil } // Tags returns tags of struct func Tags(v interface{}, key string) (map[string]string, error) { s, err := New(v) if err != nil { return nil, err } return s.Tags(key), nil } // Values returns values of struct func Values(v interface{}) ([]interface{}, error) { s, err := New(v) if err != nil { return nil, err } return s.Values(), nil } // Fields return fields of struct func Fields(v interface{}) ([]*Fieldx, error) { s, err := New(v) if err != nil { return nil, err } return s.Fields(), nil } // MustField returns a field with name, panic if error func MustField(v interface{}, name string) *Fieldx { s, err := New(v) if err != nil { panic(err) } return s.MustField(name) } // Field returns a field with name func Field(v interface{}, name string) (*Fieldx, bool) { s, err := New(v) if err != nil { return nil, false } return s.Field(name) } // Set set value to the field name, must be exported field func Set(v interface{}, name string, value interface{}) error { s, err := New(v) if err != nil { return err } return s.Set(name, value) } // Zero set zero value to the field name, must be exported field func Zero(v interface{}, name string) error { s, err := New(v) if err != nil { return err } return s.Zero(name) } // New returns a new xstruct object func New(v interface{}) (*Structx, error) { vv := reflect.ValueOf(v) if vv.Kind() == reflect.Ptr { vv = vv.Elem() } if vv.Kind() != reflect.Struct { return nil, ErrNotStruct } s := &Structx{ data: v, value: vv, } return s, nil } // Struct returns nested struct with name func (s *Structx) Struct(name string) (*Structx, error) { f, ok := s.Field(name) if !ok { return nil, ErrNoField } return New(f.Value()) } // Name returns name of struct func (s *Structx) Name() string { return s.value.Type().Name() } // Map returns struct name value as map func (s *Structx) Map() map[string]interface{} { result := map[string]interface{}{} fs := s.Fields() for _, v := range fs { if !v.IsExport() { continue } result[v.Name()] = v.Value() } return result } // Names returns names of struct func (s *Structx) Names() []string { result := []string{} fs := s.Fields() for _, v := range fs { result = append(result, v.Name()) } return result } // Tags returns tags of struct func (s *Structx) Tags(key string) map[string]string { result := map[string]string{} fs := s.Fields() for _, v := range fs { if !v.IsExport() { continue } result[v.Name()] = v.Tag(key) } return result } // Values returns values of struct func (s *Structx) Values() []interface{} { result := []interface{}{} fs := s.Fields() for _, v := range fs { if !v.IsExport() { continue } result = append(result, v.Value()) } return result } // Fields return fields of struct func (s *Structx) Fields() []*Fieldx { tt := s.value.Type() fields := []*Fieldx{} for i := 0; i < tt.NumField(); i++ { field := tt.Field(i) f := &Fieldx{ data: field, value: s.value.FieldByName(field.Name), } fields = append(fields, f) } return fields } // HasField returns field is exists func (s *Structx) HasField(name string) bool { _, ok := s.value.Type().FieldByName(name) return ok } // MustField returns a field with name, panic if error func (s *Structx) MustField(name string) *Fieldx { f, ok := s.Field(name) if !ok { panic(ErrNoField) } return f } // Field returns a field with name func (s *Structx) Field(name string) (*Fieldx, bool) { f, ok := s.value.Type().FieldByName(name) if !ok { return nil, false } ff := &Fieldx{ data: f, value: s.value.FieldByName(name), } return ff, true } // IsStruct returns if field name is a struct func (s *Structx) IsStruct(name string) bool { f, ok := s.Field(name) if !ok { return false } return IsStruct(f.Value()) } // Set set value to the field name, must be exported field func (s *Structx) Set(name string, value interface{}) error { f, ok := s.Field(name) if !ok { return ErrNoField } return f.Set(value) } // Zero set zero value to the field name, must be exported field func (s *Structx) Zero(name string) error { f, ok := s.Field(name) if !ok { return ErrNoField } return f.Zero() } // Name returns name of field func (f *Fieldx) Name() string { return f.data.Name } // Kind returns kind of field func (f *Fieldx) Kind() reflect.Kind { return f.value.Kind() } // Tag returns tag of field by key func (f *Fieldx) Tag(key string) string { return f.data.Tag.Get(key) } // Addr returns address of field func (f *Fieldx) Addr() interface{} { return f.value.Addr().Interface() } // Value returns value of field func (f *Fieldx) Value() interface{} { return f.value.Interface() } // IsAnonymous returns if field is anonymous func (f *Fieldx) IsAnonymous() bool { return f.data.Anonymous } // IsExport returns if field is exported func (f *Fieldx) IsExport() bool { return f.data.PkgPath == "" } // IsZero returns if field have zero value, for example not initialized // it panic if field is not exported func (f *Fieldx) IsZero() bool { zero := reflect.Zero(f.value.Type()).Interface() return reflect.DeepEqual(f.Value(), zero) } // Set set value to the field, must be exported field func (f *Fieldx) Set(v interface{}) error { if !f.IsExport() { return ErrNotExported } if !f.value.CanSet() { return ErrNotSettable } vv := reflect.ValueOf(v) if f.Kind() != vv.Kind() { return fmt.Errorf("xstruct: value kind not match, want: %s but got %s", f.Kind(), vv.Kind()) } f.value.Set(vv) return nil } // Zero set field to zero value, must be exported field func (f *Fieldx) Zero() error { zero := reflect.Zero(f.value.Type()).Interface() return f.Set(zero) } gokit-0.25.9/xstruct/xstruct_test.go000066400000000000000000000214451426246334200175560ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xstruct import ( "reflect" "testing" "github.com/likexian/gokit/assert" ) type Techer struct { ID int64 `json:"id"` Name string `json:"name"` Enabled bool `json:"enabled"` } type Student struct { ID int64 `json:"id"` Name string `json:"name"` Enabled bool `json:"enabled"` Techer Techer `json:"techer"` score map[string]int } var techer = Techer{100, "techer.li", true} var student = Student{1, "kexian.li", true, techer, map[string]int{}} func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } func TestIsStruct(t *testing.T) { var i interface{} tests := []struct { in interface{} out bool }{ {nil, false}, {"", false}, {1, false}, {i, false}, {student, true}, {&student, true}, {student.Techer, true}, {student.Techer.Name, false}, } for _, v := range tests { assert.Equal(t, IsStruct(v.in), v.out) } } func TestNew(t *testing.T) { _, err := New(nil) assert.NotNil(t, err) _, err = New("nil") assert.NotNil(t, err) _, err = New(map[string]interface{}{}) assert.NotNil(t, err) s, err := New(student) assert.Nil(t, err) assert.NotNil(t, s) } func TestName(t *testing.T) { s, err := New(student) assert.Nil(t, err) assert.NotNil(t, s) name := s.Name() assert.Equal(t, name, "Student") _, err = Name(nil) assert.NotNil(t, err) name, err = Name(student) assert.Nil(t, err) assert.Equal(t, name, "Student") } func TestStruct(t *testing.T) { s, err := New(student) assert.Nil(t, err) assert.NotNil(t, s) _, err = s.Struct("Id") assert.NotNil(t, err) _, err = s.Struct("not-exists") assert.NotNil(t, err) ss, err := s.Struct("Techer") assert.Nil(t, err) assert.NotNil(t, ss) assert.Equal(t, ss.Name(), "Techer") f, ok := ss.Field("Name") assert.True(t, ok) n := f.Name() assert.Equal(t, n, "Name") v := f.Value() assert.Equal(t, v, "techer.li") k := f.Kind() assert.Equal(t, k, reflect.String) b := f.IsAnonymous() assert.False(t, b) _, err = Struct(nil, "Techer") assert.NotNil(t, err) ss, err = Struct(student, "Techer") assert.Nil(t, err) assert.NotNil(t, ss) assert.Equal(t, ss.Name(), "Techer") } func TestMap(t *testing.T) { s, err := New(student) assert.Nil(t, err) assert.NotNil(t, s) v := s.Map() assert.Len(t, v, 4) assert.Equal(t, v["Name"], "kexian.li") _, err = Map(nil) assert.NotNil(t, err) v, err = Map(student) assert.Nil(t, err) assert.Len(t, v, 4) assert.Equal(t, v["Name"], "kexian.li") } func TestNames(t *testing.T) { s, err := New(student) assert.Nil(t, err) assert.NotNil(t, s) n := s.Names() assert.Len(t, n, 5) _, err = Names(nil) assert.NotNil(t, err) n, err = Names(student) assert.Nil(t, err) assert.Len(t, n, 5) } func TestTags(t *testing.T) { s, err := New(student) assert.Nil(t, err) assert.NotNil(t, s) m := s.Tags("json") assert.Len(t, m, 4) assert.Equal(t, m["Name"], "name") _, err = Tags(nil, "json") assert.NotNil(t, err) m, err = Tags(student, "json") assert.Nil(t, err) assert.Len(t, m, 4) assert.Equal(t, m["Name"], "name") } func TestValues(t *testing.T) { s, err := New(student) assert.Nil(t, err) assert.NotNil(t, s) v := s.Values() assert.Len(t, v, 4) _, err = Values(nil) assert.NotNil(t, err) v, err = Values(student) assert.Nil(t, err) assert.Len(t, v, 4) } func TestFields(t *testing.T) { s, err := New(student) assert.Nil(t, err) assert.NotNil(t, s) f := s.Fields() assert.Len(t, f, 5) _, err = Fields(nil) assert.NotNil(t, err) f, err = Fields(student) assert.Nil(t, err) assert.Len(t, f, 5) } func TestField(t *testing.T) { s, err := New(student) assert.Nil(t, err) assert.NotNil(t, s) _, ok := s.Field("not-exists") assert.False(t, ok) f, ok := s.Field("Name") assert.True(t, ok) n := f.Name() assert.Equal(t, n, "Name") v := f.Value() assert.Equal(t, v, "kexian.li") k := f.Kind() assert.Equal(t, k, reflect.String) b := f.IsAnonymous() assert.False(t, b) _, ok = Field(nil, "Name") assert.Equal(t, ok, false) f, ok = Field(student, "Name") assert.True(t, ok) n = f.Name() assert.Equal(t, n, "Name") } func TestMustField(t *testing.T) { s, err := New(student) assert.Nil(t, err) assert.NotNil(t, s) assert.Panic(t, func() { s.MustField("not-exists") }) f := s.MustField("Name") n := f.Name() assert.Equal(t, n, "Name") v := f.Value() assert.Equal(t, v, "kexian.li") k := f.Kind() assert.Equal(t, k, reflect.String) b := f.IsAnonymous() assert.False(t, b) n = s.MustField("Enabled").Name() assert.Equal(t, n, "Enabled") v = s.MustField("Enabled").Value() assert.Equal(t, v, true) assert.Panic(t, func() { MustField(nil, "not-exists") }) f = MustField(student, "Name") n = f.Name() assert.Equal(t, n, "Name") } func TestHasField(t *testing.T) { s, err := New(student) assert.Nil(t, err) assert.NotNil(t, s) b := s.HasField("not-exists") assert.False(t, b) b = s.HasField("ID") assert.True(t, b) b = s.HasField("Techer") assert.True(t, b) } func TestFieldTag(t *testing.T) { s, err := New(student) assert.Nil(t, err) assert.NotNil(t, s) f, ok := s.Field("Name") assert.True(t, ok) n := f.Tag("not-exists") assert.Equal(t, n, "") n = f.Tag("json") assert.Equal(t, n, "name") } func TestFieldIsExport(t *testing.T) { s, err := New(student) assert.Nil(t, err) assert.NotNil(t, s) f, ok := s.Field("Name") assert.True(t, ok) b := f.IsExport() assert.True(t, b) f, ok = s.Field("score") assert.True(t, ok) b = f.IsExport() assert.False(t, b) } func TestFieldIsZero(t *testing.T) { s, err := New(Student{}) assert.Nil(t, err) assert.NotNil(t, s) f, ok := s.Field("Name") assert.True(t, ok) b := f.IsZero() assert.True(t, b) s, err = New(student) assert.Nil(t, err) assert.NotNil(t, s) f, ok = s.Field("Name") assert.True(t, ok) b = f.IsZero() assert.False(t, b) f, ok = s.Field("score") assert.True(t, ok) assert.Panic(t, func() { f.IsZero() }) } func TestFieldSet(t *testing.T) { s, err := New(&student) assert.Nil(t, err) assert.NotNil(t, s) f, ok := s.Field("score") assert.True(t, ok) err = f.Set(0) assert.Equal(t, err, ErrNotExported) f, ok = s.Field("Name") assert.True(t, ok) err = f.Set("lkx") assert.Nil(t, err) assert.Equal(t, student.Name, "lkx") err = f.Set(0) assert.NotNil(t, err) err = s.Set("not-exists", 0) assert.Equal(t, err, ErrNoField) err = s.Set("score", 0) assert.Equal(t, err, ErrNotExported) err = s.Set("Name", "likexian") assert.Nil(t, err) assert.Equal(t, student.Name, "likexian") s, err = New(student) assert.Nil(t, err) f, ok = s.Field("Name") assert.True(t, ok) err = f.Set("lkx") assert.Equal(t, err, ErrNotSettable) err = Set(nil, "Name", "likexian") assert.NotNil(t, err) err = Set(&student, "Name", "likexian") assert.Nil(t, err) assert.Equal(t, student.Name, "likexian") } func TestFieldZero(t *testing.T) { s, err := New(&student) assert.Nil(t, err) assert.NotNil(t, s) f, ok := s.Field("score") assert.True(t, ok) err = f.Zero() assert.Equal(t, err, ErrNotExported) f, ok = s.Field("ID") assert.True(t, ok) err = f.Zero() assert.Nil(t, err) assert.Equal(t, student.ID, int64(0)) assert.True(t, f.IsZero()) f, ok = s.Field("Name") assert.True(t, ok) err = f.Zero() assert.Nil(t, err) assert.Equal(t, student.Name, "") assert.True(t, f.IsZero()) err = s.Zero("not-exists") assert.Equal(t, err, ErrNoField) err = s.Zero("score") assert.Equal(t, err, ErrNotExported) err = s.Zero("Name") assert.Nil(t, err) assert.Equal(t, student.Name, "") err = Zero(nil, "Name") assert.NotNil(t, err) err = Zero(&student, "Name") assert.Nil(t, err) assert.Equal(t, student.Name, "") } func TestFieldIsStruct(t *testing.T) { s, err := New(&student) assert.Nil(t, err) assert.NotNil(t, s) b := s.IsStruct("not-exists") assert.False(t, b) b = s.IsStruct("Id") assert.False(t, b) b = s.IsStruct("Techer") assert.True(t, b) s, err = s.Struct("Techer") assert.Nil(t, err) b = s.IsStruct("Id") assert.False(t, b) } func TestFieldAddr(t *testing.T) { s, err := New(&student) assert.Nil(t, err) assert.NotNil(t, s) f, ok := s.Field("Name") assert.True(t, ok) a := f.Addr() assert.Equal(t, a, &student.Name) } gokit-0.25.9/xtar/000077500000000000000000000000001426246334200137105ustar00rootroot00000000000000gokit-0.25.9/xtar/README.md000066400000000000000000000016001426246334200151640ustar00rootroot00000000000000# GoKit - xtar Tar kits for Golang development. ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/xtar" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xtar) ## Example ### create a tar with gzip compress ```go err := xtar.Create("likexian.tar.gz", "xtar.go", "xtar_test.go") if err != nil { fmt.Println("Create tar error:", err) } ``` ### Extract a tar with gzip compress ```go err := xtar.Extract("likexian.tar.gz", "tmp") if err != nil { fmt.Println("Extract tar error:", err) } ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/xtar/xtar.go000066400000000000000000000104201426246334200152120ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xtar import ( "archive/tar" "compress/gzip" "errors" "fmt" "io" "os" "strings" "github.com/likexian/gokit/xfile" ) // Version returns package version func Version() string { return "0.2.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // Create compress a list of files func Create(tarFile string, files ...string) (err error) { if xfile.Exists(tarFile) { err = fmt.Errorf("xtar: file name %s is exists", tarFile) return } if len(files) == 0 { err = fmt.Errorf("xtar: no input file specify") return } fd, err := xfile.New(tarFile) if err != nil { return } defer fd.Close() var tw *tar.Writer if IsGzName(tarFile) { gw := gzip.NewWriter(fd) defer gw.Close() tw = tar.NewWriter(gw) defer tw.Close() } else { tw = tar.NewWriter(fd) defer tw.Close() } for _, f := range files { err = addFile(tw, f, "") if err != nil { return } } return } // addFile do compress a file func addFile(tw *tar.Writer, file string, prefix string) error { if prefix != "" && !strings.HasSuffix(prefix, "/") { prefix += "/" } fd, err := os.Open(file) if err != nil { return err } defer fd.Close() f, err := os.Lstat(file) if err != nil { return err } fl := "" if f.Mode()&os.ModeSymlink != 0 { fl, err = os.Readlink(file) if err != nil { return err } } h, err := tar.FileInfoHeader(f, fl) if err != nil { return err } h.Name = prefix + h.Name err = tw.WriteHeader(h) if err != nil { return err } switch mode := f.Mode(); { case mode.IsRegular(): _, err = io.Copy(tw, fd) if err != nil { return err } case mode&os.ModeSymlink != 0: case mode.IsDir(): prefix += f.Name() fs, err := fd.Readdir(0) if err != nil { return err } for _, ff := range fs { err = addFile(tw, fd.Name()+"/"+ff.Name(), prefix) if err != nil { return err } } default: return fmt.Errorf("xtar: unsupport file mode: %v", mode) } return nil } // Extract decompress a tar file to folder func Extract(tarFile, dstFolder string) (err error) { if dstFolder != "" && !strings.HasSuffix(dstFolder, "/") { dstFolder += "/" } fd, err := os.Open(tarFile) if err != nil { return } defer fd.Close() var tr *tar.Reader if IsGzName(tarFile) { gr, err := gzip.NewReader(fd) if err != nil { return err } defer gr.Close() tr = tar.NewReader(gr) } else { tr = tar.NewReader(fd) } for { h, err := tr.Next() if err != nil { if errors.Is(err, io.EOF) { break } return err } fname := strings.TrimPrefix(h.Name, "/") if fname == "" { continue } dstFile := dstFolder + fname switch h.Typeflag { case tar.TypeReg: var ffd *os.File ffd, err = xfile.New(dstFile) if err != nil { return err } defer ffd.Close() _, err = io.Copy(ffd, tr) case tar.TypeLink: err = os.Link(h.Linkname, dstFile) case tar.TypeSymlink: err = os.Symlink(h.Linkname, dstFile) case tar.TypeDir: if !xfile.Exists(dstFile) { err = os.MkdirAll(dstFile, 0755) } default: err = fmt.Errorf("xtar: unsupport file type: %v", h.Typeflag) } if err != nil { return err } _ = os.Chtimes(dstFile, h.AccessTime, h.ModTime) _ = os.Chmod(dstFile, os.FileMode(h.Mode)) _ = os.Chown(dstFile, h.Uid, h.Gid) } return } // IsGzName returns is a tar.gz file name func IsGzName(name string) bool { name = strings.Trim(name, ".") if strings.HasSuffix(name, ".tgz") { return true } if strings.HasSuffix(name, ".tar.gz") { return true } return false } gokit-0.25.9/xtar/xtar_test.go000066400000000000000000000062711426246334200162620ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xtar import ( "os" "os/exec" "testing" "github.com/likexian/gokit/assert" "github.com/likexian/gokit/xfile" ) var ( err error dst = "targz.tar.gz" ) func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } func TestCreate(t *testing.T) { defer os.Remove(dst) err = Create(dst) assert.NotNil(t, err) err = Create(dst, "no.go") assert.NotNil(t, err) err = Create(dst, "xtar.go") assert.NotNil(t, err) os.Remove(dst) err = Create(dst, "/dev/null") assert.NotNil(t, err) os.Remove(dst) err = Create(dst, "xtar.go") assert.Nil(t, err) os.Remove(dst) err = Create(dst, "../xtar") assert.Nil(t, err) } func TestExtract(t *testing.T) { defer os.Remove(dst) err = Create(dst, "../assert", "../LICENSE") assert.Nil(t, err) err = Extract("no.tar.gz", "") assert.NotNil(t, err) err = Extract("targz.go", "") assert.NotNil(t, err) err = Extract(dst, "") assert.Nil(t, err) assert.True(t, xfile.IsDir("assert")) assert.True(t, xfile.IsFile("LICENSE")) os.RemoveAll("assert") os.RemoveAll("LICENSE") err = Extract(dst, "tmp") assert.Nil(t, err) assert.True(t, xfile.IsDir("tmp/assert")) assert.True(t, xfile.IsFile("tmp/LICENSE")) os.RemoveAll("tmp") } func TestComdec(t *testing.T) { tar := "xtar.tar" tgz := "xtar.tar.gz" slk := "xtar.go.link" err = os.Symlink("xtar.go", slk) assert.Nil(t, err) err = Create(tar, "xtar.go", slk) assert.Nil(t, err) err = Extract(tar, "tmp") assert.Nil(t, err) os.Remove(tar) os.Remove(slk) os.RemoveAll("tmp") err = Create(tgz, "xtar.go") assert.Nil(t, err) err = Extract(tgz, "tmp") assert.Nil(t, err) os.Remove(tgz) os.RemoveAll("tmp") } func TestWithSysTar(t *testing.T) { tar := "xtar.tar" tgz := "xtar.tar.gz" _ = exec.Command("tar", "zcvf", tar, "xtar.go").Run() assert.True(t, xfile.Exists(tar)) err = Extract(tar, "tmp") assert.NotNil(t, err) err = os.Rename(tar, tgz) assert.Nil(t, err) err = Extract(tgz, "tmp") assert.Nil(t, err) os.Remove(tgz) os.RemoveAll("tmp") _ = exec.Command("tar", "cvf", tgz, "xtar.go").Run() assert.True(t, xfile.Exists(tgz)) err = Extract(tgz, "tmp") assert.NotNil(t, err) err = os.Rename(tgz, tar) assert.Nil(t, err) err = Extract(tar, "tmp") assert.Nil(t, err) os.Remove(tar) os.RemoveAll("tmp") } func TestIsGzName(t *testing.T) { assert.True(t, IsGzName("targz.tgz")) assert.True(t, IsGzName("targz.tar.gz")) assert.False(t, IsGzName("targz.tar")) assert.False(t, IsGzName("targz.gz")) } gokit-0.25.9/xtime/000077500000000000000000000000001426246334200140605ustar00rootroot00000000000000gokit-0.25.9/xtime/README.md000066400000000000000000000021731426246334200153420ustar00rootroot00000000000000# GoKit - xtime Time kits for Golang development. ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/xtime" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xtime) ## Example ### Get current timestamp ```go // print as int64 unix timestamp, example: 1552314204 fmt.Println(xtime.S()) // print as int64 unix timestamp of millisecond, example: 1552314204000 fmt.Println(xtime.Ms()) // print as YYYY-MM-DD HH:II:SS fmt.Println(xtime.String()) ``` ### Time string to timestamp ```go // print as int64 unix timestamp n, err := xtime.StrToTime("2019-03-11 22:23:24") if err != nil { fmt.Println(n) } ``` ### Timestamp to time string ```go // print as YYYY-MM-DD HH:II:SS s := xtime.TimeToStr(1552314204) if err != nil { fmt.Println(n) } ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/xtime/xtime.go000066400000000000000000000074661426246334200155520ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xtime import ( "errors" "time" ) var ( // ErrCanceled is canceled error ErrCanceled = errors.New("xtime: canceled") // ErrTimeouted is timeouted error ErrTimeouted = errors.New("xtime: timeouted") ) // TimeCallback is a callback with one return value type TimeCallback func() interface{} // Version returns package version func Version() string { return "0.4.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // Now returns time.Now func Now() time.Time { return time.Now() } // String returns string of now func String() string { return TimeToStr(S()) } // S returns unix timestamp in Second func S() int64 { return Now().Unix() } // Ns returns unix timestamp in Nanosecond func Ns() int64 { return Now().UnixNano() } // Us returns unix timestamp in Microsecond func Us() int64 { return Now().UnixNano() / int64(time.Microsecond) } // Ms returns unix timestamp in Millisecond func Ms() int64 { return Now().UnixNano() / int64(time.Millisecond) } // Sleep n Second func Sleep(n int64) { time.Sleep(time.Duration(n) * time.Second) } // Usleep n Microsecond func Usleep(n int64) { time.Sleep(time.Duration(n) * time.Microsecond) } // StrToTime returns unix timestamp of time string func StrToTime(s string, layout ...string) (int64, error) { format := "2006-01-02 15:04:05" if len(layout) > 0 && layout[0] != "" { format = layout[0] } else { if len(s) == 10 { format = format[:10] } } t, err := time.ParseInLocation(format, s, time.Local) if err != nil { return 0, err } return t.Unix(), nil } // TimeToStr returns time string of unix timestamp, format in time.Local func TimeToStr(n int64, layout ...string) string { format := "2006-01-02 15:04:05" if len(layout) > 0 && layout[0] != "" { format = layout[0] } return time.Unix(n, 0).Format(format) } // WithTimeout execute the callback with timeout return a chan and cancel func func WithTimeout(fn TimeCallback, timeout time.Duration) (chan interface{}, func()) { q := make(chan bool) r := make(chan interface{}) go func() { r <- fn() }() go func() { t := time.After(timeout) select { case <-t: r <- ErrTimeouted return case <-q: r <- ErrCanceled return } }() return r, func() { close(q) } } // SetTimeout execute the callback after timeout return a chan and cancel func func SetTimeout(fn TimeCallback, timeout time.Duration) (chan interface{}, func()) { q := make(chan bool) r := make(chan interface{}) go func() { t := time.After(timeout) select { case <-t: r <- fn() case <-q: r <- ErrCanceled return } }() return r, func() { close(q) } } // SetInterval execute the callback every timeout return a chan and cancel func func SetInterval(fn TimeCallback, timeout time.Duration) (chan interface{}, func()) { q := make(chan bool) r := make(chan interface{}) go func() { t := time.NewTicker(timeout) for { select { case <-t.C: go func() { r <- fn() }() case <-q: t.Stop() r <- ErrCanceled return } } }() return r, func() { close(q) } } gokit-0.25.9/xtime/xtime_test.go000066400000000000000000000122561426246334200166020ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xtime import ( "errors" "testing" "time" "github.com/likexian/gokit/assert" ) func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } func TestGetSecond(t *testing.T) { tm := Now() s := String() assert.NotEqual(t, s, "") n := S() assert.True(t, n >= tm.Unix()) n = Ns() assert.True(t, n >= tm.UnixNano()) n = Us() assert.True(t, n >= tm.UnixNano()/int64(time.Microsecond)) n = Ms() assert.True(t, n >= tm.UnixNano()/int64(time.Millisecond)) } func TestSleep(t *testing.T) { tm := S() Sleep(1) assert.Equal(t, S(), tm+1) } func TestUsleep(t *testing.T) { tm := Us() Usleep(1000) assert.True(t, Us() >= tm+1000) } func TestStrToTime(t *testing.T) { n, err := StrToTime("2019-03-11") assert.Nil(t, err) en, _ := time.ParseInLocation("2006-01-02", "2019-03-11", time.Local) assert.Equal(t, n, en.Unix()) n, err = StrToTime("2019-03-11 22:23:24") assert.Nil(t, err) en, _ = time.ParseInLocation("2006-01-02 15:04:05", "2019-03-11 22:23:24", time.Local) assert.Equal(t, n, en.Unix()) n, err = StrToTime("2019-03-11T22:23:24Z", "2006-01-02T15:04:05Z") assert.Nil(t, err) en, _ = time.ParseInLocation("2006-01-02T15:04:05Z", "2019-03-11T22:23:24Z", time.Local) assert.Equal(t, n, en.Unix()) n, err = StrToTime("Mon, 11 Mar 2019 22:23:24", "Mon, 02 Jan 2006 15:04:05") assert.Nil(t, err) en, _ = time.ParseInLocation("Mon, 02 Jan 2006 15:04:05", "Mon, 11 Mar 2019 22:23:24", time.Local) assert.Equal(t, n, en.Unix()) n, err = StrToTime("2019-03-11T22:23:24Z", time.RFC3339) assert.Nil(t, err) en, _ = time.Parse(time.RFC3339, "2019-03-11T22:23:24Z") assert.Equal(t, n, en.Unix()) n, err = StrToTime("2019-03-11T22:23:24+08:00", time.RFC3339) assert.Nil(t, err) en, _ = time.Parse(time.RFC3339, "2019-03-11T22:23:24+08:00") assert.Equal(t, n, en.Unix()) _, err = StrToTime("2019-03-11T22:23:24Z") assert.NotNil(t, err) } func TestTimeToStr(t *testing.T) { s := TimeToStr(0) assert.Equal(t, s, time.Unix(0, 0).Format("2006-01-02 15:04:05")) s = TimeToStr(1552233600) assert.Equal(t, s, time.Unix(1552233600, 0).Format("2006-01-02 15:04:05")) s = TimeToStr(time.Now().Unix()) assert.Equal(t, s, time.Now().Format("2006-01-02 15:04:05")) s = TimeToStr(1552314204, "2006-01-02T15:04:05Z") assert.Equal(t, s, time.Unix(1552314204, 0).Format("2006-01-02T15:04:05Z")) s = TimeToStr(1552314204, "Mon, 02 Jan 2006 15:04:05") assert.Equal(t, s, time.Unix(1552314204, 0).Format("Mon, 02 Jan 2006 15:04:05")) } func TestWithTimeout(t *testing.T) { r, _ := WithTimeout(func() interface{} { return 10000 }, 1*time.Second) assert.Equal(t, <-r, 10000) r, _ = WithTimeout(func() interface{} { return errors.New("some error") }, 1*time.Second) assert.NotNil(t, <-r) r, _ = WithTimeout(func() interface{} { Sleep(2); t.Log("i run after timeout"); return 10000 }, 1*time.Second) assert.Equal(t, <-r, ErrTimeouted) r, cancel := WithTimeout(func() interface{} { Sleep(2); t.Log("i run after cancel"); return 10000 }, 1*time.Second) cancel() assert.Equal(t, <-r, ErrCanceled) Sleep(3) } func TestSetTimeout(t *testing.T) { r, _ := SetTimeout(func() interface{} { return 10000 }, 1*time.Second) start := S() assert.Equal(t, <-r, 10000) assert.Equal(t, S()-start, int64(1)) r, _ = SetTimeout(func() interface{} { return errors.New("some error") }, 1*time.Second) start = S() assert.NotNil(t, <-r) assert.Equal(t, S()-start, int64(1)) r, cancel := SetTimeout(func() interface{} { Sleep(2); t.Log("i will not run"); return 10000 }, 1*time.Second) start = S() cancel() assert.Equal(t, <-r, ErrCanceled) assert.Equal(t, S()-start, int64(0)) Sleep(3) } func TestSetInterval(t *testing.T) { r, _ := SetInterval(func() interface{} { return 10000 }, 1*time.Second) start := S() assert.Equal(t, <-r, 10000) assert.Equal(t, S()-start, int64(1)) r, _ = SetInterval(func() interface{} { return errors.New("some error") }, 1*time.Second) start = S() assert.NotNil(t, <-r) assert.Equal(t, S()-start, int64(1)) r, cancel := SetInterval(func() interface{} { Sleep(2); t.Log("i will not run"); return 10000 }, 1*time.Second) start = S() cancel() assert.Equal(t, <-r, ErrCanceled) assert.Equal(t, S()-start, int64(0)) r, cancel = SetInterval(func() interface{} { return 10000 }, 1*time.Second) start = S() ints := []int{} for i := 0; i < 3; i++ { ints = append(ints, (<-r).(int)) } cancel() assert.Equal(t, <-r, ErrCanceled) assert.Equal(t, ints, []int{10000, 10000, 10000}) assert.Equal(t, S()-start, int64(3)) Sleep(3) } gokit-0.25.9/xtry/000077500000000000000000000000001426246334200137405ustar00rootroot00000000000000gokit-0.25.9/xtry/README.md000066400000000000000000000014341426246334200152210ustar00rootroot00000000000000# GoKit - xtry Retry kits for Golang development. ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/xtry" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xtry) ## Example ```go c := Config{ Timeout: 5 * time.Minute, RetryDelay: 2 * time.Second, } ctx := context.Background() err := c.Run(ctx, func(context.Context) error { return doSomething() }) if err != nil { panic(err) } ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/xtry/xtry.go000066400000000000000000000103211426246334200152720ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xtry import ( "context" "errors" "fmt" "time" ) const ( // Timeout is retry exhausted timeout Timeout ExhaustedType = "Timeout" // MaxTries is retry exhausted max times MaxTries ExhaustedType = "MaxTries" // Cancelled is retry is cancelled Cancelled ExhaustedType = "Cancelled" // NonRetry is non retryable NonRetry ExhaustedType = "NonRetry" ) // ExhaustedType is retry exhausted type type ExhaustedType string // Config represents a retry config type Config struct { // Retry until timeout elapsed, 0 means forever Timeout time.Duration // MaxTries is max retry times, 0 means forever MaxTries int // RetryDelay returns dealy time after failed, default is 1s RetryDelay func() time.Duration // ShouldRetry returns wether error should be retried, default true ShouldRetry func(error) bool } // Version returns package version func Version() string { return "0.4.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // Run calls fn util ctx is cancelled or max retry exhausted func (c Config) Run(ctx context.Context, fn func(context.Context) error) error { retryDelay := func() time.Duration { return 1 * time.Second } if c.RetryDelay != nil { retryDelay = c.RetryDelay } shouldRetry := func(error) bool { return true } if c.ShouldRetry != nil { shouldRetry = c.ShouldRetry } var timeout <-chan time.Time if c.Timeout != 0 { timeout = time.After(c.Timeout) } var err error for try := 0; ; try++ { if c.MaxTries != 0 && try == c.MaxTries { return &RetryExhaustedError{Err: err, Type: MaxTries, Times: try} } if err = fn(ctx); err == nil { return nil } var e *RetryError if ok := errors.As(err, &e); ok { if e == nil { return nil } if !e.Retryable { return &RetryExhaustedError{Err: e.Err, Type: NonRetry, Times: try} } } else { if !shouldRetry(err) { return &RetryExhaustedError{Err: err, Type: NonRetry, Times: try} } } select { case <-ctx.Done(): return &RetryExhaustedError{Err: err, Type: Cancelled, Times: try} case <-timeout: return &RetryExhaustedError{Err: err, Type: Timeout, Times: try} default: time.Sleep(retryDelay()) } } } // Retry do retry with timeout func Retry(ctx context.Context, timeout time.Duration, fn func(context.Context) error) error { return Config{Timeout: timeout}.Run(ctx, fn) } // RetryExhaustedError is max retry exhausted error type RetryExhaustedError struct { Err error Type ExhaustedType Times int } // Error returns string of max retry exhausted error func (err *RetryExhaustedError) Error() string { if err == nil || err.Err == nil { return "" } return fmt.Sprintf("xtry: retry exhausted, type: %s, error: %s", err.Type, err.Err) } // RetryError is an error with retryable info type RetryError struct { Err error Retryable bool } // Error returns string of retry error func (err *RetryError) Error() string { if err == nil || err.Err == nil { return "" } if err.Retryable { return fmt.Sprintf("xtry: retryable error: %s", err.Err) } return fmt.Sprintf("xtry: nonretryable error: %s", err.Err) } // RetryableError returns a retryable error func RetryableError(err error) *RetryError { if err == nil { return nil } return &RetryError{Err: err, Retryable: true} } // NonRetryableError returns a not retryable error func NonRetryableError(err error) *RetryError { if err == nil { return nil } return &RetryError{Err: err, Retryable: false} } gokit-0.25.9/xtry/xtry_test.go000066400000000000000000000126571426246334200163470ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xtry import ( "context" "errors" "fmt" "testing" "time" "github.com/likexian/gokit/assert" ) func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } func TestSucc(t *testing.T) { t.Parallel() ctx := context.Background() c := Config{} err := c.Run(ctx, func(context.Context) error { return nil }) assert.Nil(t, err) } func TestCancel(t *testing.T) { t.Parallel() ctx, cancel := context.WithCancel(context.Background()) time.AfterFunc(500*time.Millisecond, cancel) c := Config{} err := c.Run(ctx, func(context.Context) error { return fmt.Errorf("error") }) assert.NotNil(t, err) var e *RetryExhaustedError ok := errors.As(err, &e) assert.True(t, ok) assert.Equal(t, e.Type, Cancelled) assert.Equal(t, e.Times, 1) } func TestTimeout(t *testing.T) { t.Parallel() ctx := context.Background() c := Config{ Timeout: 1 * time.Second, } err := c.Run(ctx, func(context.Context) error { return fmt.Errorf("error") }) assert.NotNil(t, err) var e *RetryExhaustedError ok := errors.As(err, &e) assert.True(t, ok) assert.Equal(t, e.Type, Timeout) assert.Equal(t, e.Times, 1) } func TestMaxTries(t *testing.T) { t.Parallel() ctx := context.Background() c := Config{ MaxTries: 1, } err := c.Run(ctx, func(context.Context) error { return fmt.Errorf("error") }) assert.NotNil(t, err) var e *RetryExhaustedError ok := errors.As(err, &e) assert.True(t, ok) assert.Equal(t, e.Type, MaxTries) assert.Equal(t, e.Times, 1) } func TestRetryDelay(t *testing.T) { t.Parallel() ctx := context.Background() c := Config{ RetryDelay: func() time.Duration { return 500 * time.Millisecond }, Timeout: 1 * time.Second, } err := c.Run(ctx, func(context.Context) error { return fmt.Errorf("error") }) assert.NotNil(t, err) var e *RetryExhaustedError ok := errors.As(err, &e) assert.True(t, ok) assert.Equal(t, e.Type, Timeout) assert.Equal(t, e.Times, 2) } func TestShouldRetry(t *testing.T) { t.Parallel() ctx := context.Background() c := Config{ ShouldRetry: func(error) bool { return true }, Timeout: 1 * time.Second, } err := c.Run(ctx, func(context.Context) error { return fmt.Errorf("error") }) assert.NotNil(t, err) var e *RetryExhaustedError ok := errors.As(err, &e) assert.True(t, ok) assert.Equal(t, e.Type, Timeout) assert.Equal(t, e.Times, 1) } func TestNonShouldRetry(t *testing.T) { t.Parallel() ctx := context.Background() c := Config{ ShouldRetry: func(error) bool { return false }, } err := c.Run(ctx, func(context.Context) error { return fmt.Errorf("error") }) assert.NotNil(t, err) var e *RetryExhaustedError ok := errors.As(err, &e) assert.True(t, ok) assert.Equal(t, e.Type, NonRetry) assert.Equal(t, e.Times, 0) } func TestRetryableError(t *testing.T) { t.Parallel() ctx := context.Background() c := Config{ Timeout: 1 * time.Second, } err := c.Run(ctx, func(context.Context) error { return RetryableError(nil) }) assert.Nil(t, err) err = c.Run(ctx, func(context.Context) error { return RetryableError(fmt.Errorf("RetryableError")) }) assert.NotNil(t, err) var e *RetryExhaustedError ok := errors.As(err, &e) assert.True(t, ok) assert.Equal(t, e.Type, Timeout) assert.Equal(t, e.Times, 1) } func TestNonRetryableError(t *testing.T) { t.Parallel() ctx := context.Background() c := Config{ Timeout: 1 * time.Second, } err := c.Run(ctx, func(context.Context) error { return NonRetryableError(nil) }) assert.Nil(t, err) err = c.Run(ctx, func(context.Context) error { return NonRetryableError(fmt.Errorf("NonRetryableError")) }) assert.NotNil(t, err) var e *RetryExhaustedError ok := errors.As(err, &e) assert.True(t, ok) assert.Equal(t, e.Type, NonRetry) assert.Equal(t, e.Times, 0) } func TestRetry(t *testing.T) { t.Parallel() ctx := context.Background() err := Retry(ctx, 1*time.Second, func(context.Context) error { return fmt.Errorf("error") }) assert.NotNil(t, err) var e *RetryExhaustedError ok := errors.As(err, &e) assert.True(t, ok) assert.Equal(t, e.Type, Timeout) assert.Equal(t, e.Times, 1) } func TestRetryExhaustedError(t *testing.T) { t.Parallel() err := RetryExhaustedError{Err: nil, Type: Timeout} assert.Equal(t, err.Error(), "") err = RetryExhaustedError{Err: fmt.Errorf("error"), Type: Timeout} assert.Equal(t, err.Error(), "xtry: retry exhausted, type: Timeout, error: error") } func TestRetryError(t *testing.T) { t.Parallel() err := &RetryError{Err: nil, Retryable: true} assert.Equal(t, err.Error(), "") err = &RetryError{Err: fmt.Errorf("error"), Retryable: true} assert.Equal(t, err.Error(), "xtry: retryable error: error") err = &RetryError{Err: fmt.Errorf("error"), Retryable: false} assert.Equal(t, err.Error(), "xtry: nonretryable error: error") } gokit-0.25.9/xversion/000077500000000000000000000000001426246334200146075ustar00rootroot00000000000000gokit-0.25.9/xversion/README.md000066400000000000000000000016271426246334200160740ustar00rootroot00000000000000# GoKit - xversion Version kits for Golang development. ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/xversion" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xversion) ## Example ```go req := &CheckUpdateRequest{ Product: "test", Current: "1.0.0", CacheFile: "check_cache_file", CacheDuration: 1 * time.Hour, CheckPoint: "https://check_url/", } ctx := context.Background() rsp, err := req.Run(ctx) if err != nil { panic(err) } else { fmt.Println(rsp.Outdated) } ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/xversion/xversion.go000066400000000000000000000060401426246334200170130ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xversion import ( "context" "encoding/json" "fmt" "net/http" "net/url" "os" "runtime" "time" "github.com/likexian/gokit/xfile" "github.com/likexian/gokit/xhttp" ) // CheckUpdateRequest is check update request type CheckUpdateRequest struct { Product string `json:"product"` Current string `json:"current"` Arch string `json:"arch"` OS string `json:"os"` CacheFile string `json:"-"` CacheDuration time.Duration `json:"-"` CheckPoint string `json:"-"` } // CheckUpdateResponse is check update response type CheckUpdateResponse struct { Product string `json:"product"` Current string `json:"current"` Latest string `json:"latest"` Outdated bool `json:"outdated"` Emergency bool `json:"emergency"` DownloadURL string `json:"download_url"` ProductURL string `json:"product_url"` } // Version returns package version func Version() string { return "0.2.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // Run do check update func (req *CheckUpdateRequest) Run(ctx context.Context) (rsp *CheckUpdateResponse, err error) { rsp = &CheckUpdateResponse{} if req.CacheDuration > 0 && req.CacheFile != "" { if s, e := os.Stat(req.CacheFile); e == nil { if s.ModTime().Add(req.CacheDuration).Unix() > time.Now().Unix() { data, e := xfile.Read(req.CacheFile) if e == nil { err = json.Unmarshal(data, rsp) if err == nil { return rsp, nil } } } } } if req.Arch == "" { req.Arch = runtime.GOARCH } if req.OS == "" { req.OS = runtime.GOOS } u, err := url.Parse(req.CheckPoint) if err != nil { return } q := u.Query() q.Set("product", req.Product) q.Set("current", req.Current) q.Set("arch", req.Arch) q.Set("os", req.OS) u.RawQuery = q.Encode() httpRsp, err := xhttp.Get(ctx, u.String(), xhttp.Header{"Accept": "application/json"}) if err != nil { return } if httpRsp.StatusCode != http.StatusOK { return rsp, fmt.Errorf("xversion: bas status code: %d", httpRsp.StatusCode) } data, err := httpRsp.Bytes() if err != nil { return } err = json.Unmarshal(data, rsp) if err != nil { return } _ = xfile.Write(req.CacheFile, data) return rsp, nil } gokit-0.25.9/xversion/xversion_test.go000066400000000000000000000057021426246334200200560ustar00rootroot00000000000000/* * Copyright 2012-2022 Li Kexian * * 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. * * A toolkit for Golang development * https://www.likexian.com/ */ package xversion import ( "context" "fmt" "net/http" "os" "testing" "time" "github.com/likexian/gokit/assert" "github.com/likexian/gokit/xhttp" "github.com/likexian/gokit/xjson" ) var ( checkCacheListen = "" checkCacheFile = "check.cache" checkCacheReq = &CheckUpdateRequest{ Product: "test", Current: "1.0.0", CacheFile: checkCacheFile, CacheDuration: 1 * time.Hour, CheckPoint: "", } checkCacheRsp = &CheckUpdateResponse{ Product: "test", Current: "1.0.0", Latest: "1.0.1", Outdated: true, Emergency: true, } ) func init() { checkCacheListen = ServerForTesting() } func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } func TestCheckUpdate(t *testing.T) { defer os.Remove(checkCacheFile) ctx := context.Background() _, err := checkCacheReq.Run(ctx) assert.NotNil(t, err) checkCacheReq.CheckPoint = fmt.Sprintf("http://%s/todo/check", "%s") _, err = checkCacheReq.Run(ctx) assert.NotNil(t, err) checkCacheReq.CheckPoint = fmt.Sprintf("http://%s/todo/check", checkCacheListen) _, err = checkCacheReq.Run(ctx) assert.NotNil(t, err) checkCacheReq.CheckPoint = fmt.Sprintf("http://%s/update/nofound", checkCacheListen) _, err = checkCacheReq.Run(ctx) assert.NotNil(t, err) checkCacheReq.CheckPoint = fmt.Sprintf("http://%s/update/check", checkCacheListen) rsp, err := checkCacheReq.Run(ctx) assert.Nil(t, err) assert.Equal(t, rsp, checkCacheRsp) rsp, err = checkCacheReq.Run(ctx) assert.Nil(t, err) assert.Equal(t, rsp, checkCacheRsp) } func ServerForTesting() string { listen := "127.0.0.1:8080" go func() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {}) http.HandleFunc("/update/nofound", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) }) http.HandleFunc("/update/check", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") text, _ := xjson.Dumps(checkCacheRsp) fmt.Fprint(w, text) }) _ = http.ListenAndServe(listen, http.DefaultServeMux) }() req := xhttp.New() for { _, err := req.Do(context.Background(), "GET", fmt.Sprintf("http://%s/", listen)) if err == nil { break } } return listen }