mirror of
https://gitee.com/johng/gf.git
synced 2024-11-30 03:07:45 +08:00
commit
b81a1e7c9c
3
.github/workflows/go.yml
vendored
3
.github/workflows/go.yml
vendored
@ -57,7 +57,8 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
date
|
date
|
||||||
find . -name "*.go" | xargs gofmt -w
|
find . -name "*.go" | xargs gofmt -w
|
||||||
git diff --name-only --exit-code || exit 1
|
git diff --name-only --exit-code || if [ $? != 0 ];then echo "Notice: gofmt check failed,please gofmt before pr." && exit 1; fi
|
||||||
|
echo "gofmt check pass."
|
||||||
sudo echo "127.0.0.1 local" | sudo tee -a /etc/hosts
|
sudo echo "127.0.0.1 local" | sudo tee -a /etc/hosts
|
||||||
|
|
||||||
- name: Run i386 Arch Test
|
- name: Run i386 Arch Test
|
||||||
|
@ -179,10 +179,3 @@ func (j *Json) Dump() {
|
|||||||
defer j.mu.RUnlock()
|
defer j.mu.RUnlock()
|
||||||
gutil.Dump(*j.p)
|
gutil.Dump(*j.p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export returns `j` as a string with more manually readable.
|
|
||||||
func (j *Json) Export() string {
|
|
||||||
j.mu.RLock()
|
|
||||||
defer j.mu.RUnlock()
|
|
||||||
return gutil.Export(*j.p, gutil.ExportOption{})
|
|
||||||
}
|
|
||||||
|
@ -8,6 +8,7 @@ package g
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"io"
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/container/gvar"
|
"github.com/gogf/gf/v2/container/gvar"
|
||||||
"github.com/gogf/gf/v2/internal/empty"
|
"github.com/gogf/gf/v2/internal/empty"
|
||||||
@ -39,12 +40,22 @@ func Dump(values ...interface{}) {
|
|||||||
gutil.Dump(values...)
|
gutil.Dump(values...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DumpTo writes variables `values` as a string in to `writer` with more manually readable
|
||||||
|
func DumpTo(writer io.Writer, value interface{}, option gutil.DumpOption) {
|
||||||
|
gutil.DumpTo(writer, value, option)
|
||||||
|
}
|
||||||
|
|
||||||
// DumpWithType acts like Dump, but with type information.
|
// DumpWithType acts like Dump, but with type information.
|
||||||
// Also see Dump.
|
// Also see Dump.
|
||||||
func DumpWithType(values ...interface{}) {
|
func DumpWithType(values ...interface{}) {
|
||||||
gutil.DumpWithType(values...)
|
gutil.DumpWithType(values...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DumpWithOption returns variables `values` as a string with more manually readable.
|
||||||
|
func DumpWithOption(value interface{}, option gutil.DumpOption) {
|
||||||
|
gutil.DumpWithOption(value, option)
|
||||||
|
}
|
||||||
|
|
||||||
// Throw throws an exception, which can be caught by TryCatch function.
|
// Throw throws an exception, which can be caught by TryCatch function.
|
||||||
func Throw(exception interface{}) {
|
func Throw(exception interface{}) {
|
||||||
gutil.Throw(exception)
|
gutil.Throw(exception)
|
||||||
|
@ -7,6 +7,8 @@
|
|||||||
package gres_test
|
package gres_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
_ "github.com/gogf/gf/v2/os/gres/testdata/data"
|
||||||
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -20,22 +22,26 @@ import (
|
|||||||
|
|
||||||
func Test_PackToGoFile(t *testing.T) {
|
func Test_PackToGoFile(t *testing.T) {
|
||||||
gtest.C(t, func(t *gtest.T) {
|
gtest.C(t, func(t *gtest.T) {
|
||||||
srcPath := gdebug.TestDataPath("files")
|
var (
|
||||||
goFilePath := gdebug.TestDataPath("testdata.go")
|
srcPath = gdebug.TestDataPath("files")
|
||||||
pkgName := "testdata"
|
goFilePath = gfile.TempDir(gtime.TimestampNanoStr(), "testdata.go")
|
||||||
err := gres.PackToGoFile(srcPath, goFilePath, pkgName)
|
pkgName = "testdata"
|
||||||
|
err = gres.PackToGoFile(srcPath, goFilePath, pkgName)
|
||||||
|
)
|
||||||
t.Assert(err, nil)
|
t.Assert(err, nil)
|
||||||
|
_ = gfile.Remove(goFilePath)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_Pack(t *testing.T) {
|
func Test_Pack(t *testing.T) {
|
||||||
gtest.C(t, func(t *gtest.T) {
|
gtest.C(t, func(t *gtest.T) {
|
||||||
srcPath := gdebug.TestDataPath("files")
|
var (
|
||||||
data, err := gres.Pack(srcPath)
|
srcPath = gdebug.TestDataPath("files")
|
||||||
|
data, err = gres.Pack(srcPath)
|
||||||
|
)
|
||||||
t.Assert(err, nil)
|
t.Assert(err, nil)
|
||||||
|
|
||||||
r := gres.New()
|
r := gres.New()
|
||||||
|
|
||||||
err = r.Add(string(data))
|
err = r.Add(string(data))
|
||||||
t.Assert(err, nil)
|
t.Assert(err, nil)
|
||||||
t.Assert(r.Contains("files/"), true)
|
t.Assert(r.Contains("files/"), true)
|
||||||
@ -44,9 +50,11 @@ func Test_Pack(t *testing.T) {
|
|||||||
|
|
||||||
func Test_PackToFile(t *testing.T) {
|
func Test_PackToFile(t *testing.T) {
|
||||||
gtest.C(t, func(t *gtest.T) {
|
gtest.C(t, func(t *gtest.T) {
|
||||||
srcPath := gdebug.TestDataPath("files")
|
var (
|
||||||
dstPath := gfile.TempDir(gtime.TimestampNanoStr())
|
srcPath = gdebug.TestDataPath("files")
|
||||||
err := gres.PackToFile(srcPath, dstPath)
|
dstPath = gfile.TempDir(gtime.TimestampNanoStr())
|
||||||
|
err = gres.PackToFile(srcPath, dstPath)
|
||||||
|
)
|
||||||
t.Assert(err, nil)
|
t.Assert(err, nil)
|
||||||
|
|
||||||
defer gfile.Remove(dstPath)
|
defer gfile.Remove(dstPath)
|
||||||
@ -60,33 +68,42 @@ func Test_PackToFile(t *testing.T) {
|
|||||||
|
|
||||||
func Test_PackMulti(t *testing.T) {
|
func Test_PackMulti(t *testing.T) {
|
||||||
gtest.C(t, func(t *gtest.T) {
|
gtest.C(t, func(t *gtest.T) {
|
||||||
srcPath := gdebug.TestDataPath("files")
|
var (
|
||||||
goFilePath := gdebug.TestDataPath("data/data.go")
|
srcPath = gdebug.TestDataPath("files")
|
||||||
pkgName := "data"
|
goFilePath = gfile.TempDir(gtime.TimestampNanoStr(), "data.go")
|
||||||
array, err := gfile.ScanDir(srcPath, "*", false)
|
pkgName = "data"
|
||||||
|
array, err = gfile.ScanDir(srcPath, "*", false)
|
||||||
|
)
|
||||||
t.Assert(err, nil)
|
t.Assert(err, nil)
|
||||||
err = gres.PackToGoFile(strings.Join(array, ","), goFilePath, pkgName)
|
err = gres.PackToGoFile(strings.Join(array, ","), goFilePath, pkgName)
|
||||||
t.Assert(err, nil)
|
t.Assert(err, nil)
|
||||||
|
_ = gfile.Remove(goFilePath)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_PackWithPrefix1(t *testing.T) {
|
func Test_PackWithPrefix1(t *testing.T) {
|
||||||
gtest.C(t, func(t *gtest.T) {
|
gtest.C(t, func(t *gtest.T) {
|
||||||
srcPath := gdebug.TestDataPath("files")
|
var (
|
||||||
goFilePath := gfile.TempDir("testdata.go")
|
srcPath = gdebug.TestDataPath("files")
|
||||||
pkgName := "testdata"
|
goFilePath = gfile.TempDir(gtime.TimestampNanoStr(), "testdata.go")
|
||||||
err := gres.PackToGoFile(srcPath, goFilePath, pkgName, "www/gf-site/test")
|
pkgName = "testdata"
|
||||||
t.Assert(err, nil)
|
err = gres.PackToGoFile(srcPath, goFilePath, pkgName, "www/gf-site/test")
|
||||||
|
)
|
||||||
|
t.AssertNil(err)
|
||||||
|
_ = gfile.Remove(goFilePath)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_PackWithPrefix2(t *testing.T) {
|
func Test_PackWithPrefix2(t *testing.T) {
|
||||||
gtest.C(t, func(t *gtest.T) {
|
gtest.C(t, func(t *gtest.T) {
|
||||||
srcPath := gdebug.TestDataPath("files")
|
var (
|
||||||
goFilePath := gfile.TempDir("testdata.go")
|
srcPath = gdebug.TestDataPath("files")
|
||||||
pkgName := "testdata"
|
goFilePath = gfile.TempDir(gtime.TimestampNanoStr(), "testdata.go")
|
||||||
err := gres.PackToGoFile(srcPath, goFilePath, pkgName, "/var/www/gf-site/test")
|
pkgName = "testdata"
|
||||||
|
err = gres.PackToGoFile(srcPath, goFilePath, pkgName, "/var/www/gf-site/test")
|
||||||
|
)
|
||||||
t.Assert(err, nil)
|
t.Assert(err, nil)
|
||||||
|
_ = gfile.Remove(goFilePath)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
os/gres/testdata/data/data.go
vendored
2
os/gres/testdata/data/data.go
vendored
File diff suppressed because one or more lines are too long
2
os/gres/testdata/testdata.go
vendored
2
os/gres/testdata/testdata.go
vendored
File diff suppressed because one or more lines are too long
@ -7,6 +7,7 @@
|
|||||||
package gview
|
package gview
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
htmltpl "html/template"
|
htmltpl "html/template"
|
||||||
@ -22,13 +23,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// buildInFuncDump implements build-in template function: dump
|
// buildInFuncDump implements build-in template function: dump
|
||||||
func (view *View) buildInFuncDump(values ...interface{}) (result string) {
|
func (view *View) buildInFuncDump(values ...interface{}) string {
|
||||||
result += "<!--\n"
|
buffer := bytes.NewBuffer(nil)
|
||||||
|
buffer.WriteString("<!--\n")
|
||||||
for _, v := range values {
|
for _, v := range values {
|
||||||
result += gutil.Export(v, gutil.ExportOption{WithoutType: true}) + "\n"
|
gutil.DumpTo(buffer, v, gutil.DumpOption{})
|
||||||
|
buffer.WriteString("\n")
|
||||||
}
|
}
|
||||||
result += "-->\n"
|
buffer.WriteString("-->\n")
|
||||||
return result
|
return buffer.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildInFuncMap implements build-in template function: map
|
// buildInFuncMap implements build-in template function: map
|
||||||
|
@ -8,7 +8,7 @@ package gview_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io/ioutil"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@ -215,39 +215,39 @@ func Test_FuncNl2Br(t *testing.T) {
|
|||||||
|
|
||||||
func Test_FuncInclude(t *testing.T) {
|
func Test_FuncInclude(t *testing.T) {
|
||||||
gtest.C(t, func(t *gtest.T) {
|
gtest.C(t, func(t *gtest.T) {
|
||||||
header := `<h1>HEADER</h1>`
|
var (
|
||||||
main := `<h1>hello gf</h1>`
|
header = `<h1>HEADER</h1>`
|
||||||
footer := `<h1>FOOTER</h1>`
|
main = `<h1>hello gf</h1>`
|
||||||
layout := `{{include "header.html" .}}
|
footer = `<h1>FOOTER</h1>`
|
||||||
|
layout = `{{include "header.html" .}}
|
||||||
{{include "main.html" .}}
|
{{include "main.html" .}}
|
||||||
{{include "footer.html" .}}`
|
{{include "footer.html" .}}`
|
||||||
templatePath := gfile.Pwd() + gfile.Separator + "template"
|
templatePath = gfile.TempDir("template")
|
||||||
|
)
|
||||||
|
|
||||||
gfile.Mkdir(templatePath)
|
gfile.Mkdir(templatePath)
|
||||||
defer gfile.Remove(templatePath)
|
defer gfile.Remove(templatePath)
|
||||||
// headerFile, _ := gfile.Create(templatePath + gfile.Separator + "header.html")
|
|
||||||
err := ioutil.WriteFile(templatePath+gfile.Separator+"header.html", []byte(header), 0644)
|
t.AssertNil(gfile.PutContents(gfile.Join(templatePath, `header.html`), header))
|
||||||
if err != nil {
|
t.AssertNil(gfile.PutContents(gfile.Join(templatePath, `main.html`), main))
|
||||||
t.Error(err)
|
t.AssertNil(gfile.PutContents(gfile.Join(templatePath, `footer.html`), footer))
|
||||||
}
|
t.AssertNil(gfile.PutContents(gfile.Join(templatePath, `layout.html`), layout))
|
||||||
ioutil.WriteFile(templatePath+gfile.Separator+"main.html", []byte(main), 0644)
|
|
||||||
ioutil.WriteFile(templatePath+gfile.Separator+"footer.html", []byte(footer), 0644)
|
|
||||||
ioutil.WriteFile(templatePath+gfile.Separator+"layout.html", []byte(layout), 0644)
|
|
||||||
view := gview.New(templatePath)
|
view := gview.New(templatePath)
|
||||||
result, err := view.Parse(context.TODO(), "notfound.html")
|
result, err := view.Parse(context.TODO(), "notfound.html")
|
||||||
t.Assert(err != nil, true)
|
t.AssertNE(err, nil)
|
||||||
t.Assert(result, ``)
|
t.Assert(result, ``)
|
||||||
|
|
||||||
result, err = view.Parse(context.TODO(), "layout.html")
|
result, err = view.Parse(context.TODO(), "layout.html")
|
||||||
t.Assert(err != nil, false)
|
t.AssertNil(err)
|
||||||
t.Assert(result, `<h1>HEADER</h1>
|
t.Assert(result, `<h1>HEADER</h1>
|
||||||
<h1>hello gf</h1>
|
<h1>hello gf</h1>
|
||||||
<h1>FOOTER</h1>`)
|
<h1>FOOTER</h1>`)
|
||||||
notfoundPath := templatePath + gfile.Separator + "template" + gfile.Separator + "notfound.html"
|
|
||||||
gfile.Mkdir(templatePath + gfile.Separator + "template")
|
t.AssertNil(gfile.PutContents(gfile.Join(templatePath, `notfound.html`), "notfound"))
|
||||||
gfile.Create(notfoundPath)
|
|
||||||
ioutil.WriteFile(notfoundPath, []byte("notfound"), 0644)
|
|
||||||
result, err = view.Parse(context.TODO(), "notfound.html")
|
result, err = view.Parse(context.TODO(), "notfound.html")
|
||||||
t.Assert(err != nil, true)
|
t.AssertNil(err)
|
||||||
t.Assert(result, ``)
|
t.Assert(result, `notfound`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -411,6 +411,7 @@ func Test_BuildInFuncDump(t *testing.T) {
|
|||||||
})
|
})
|
||||||
r, err := v.ParseContent(context.TODO(), "{{dump .}}")
|
r, err := v.ParseContent(context.TODO(), "{{dump .}}")
|
||||||
t.Assert(err, nil)
|
t.Assert(err, nil)
|
||||||
|
fmt.Println(r)
|
||||||
t.Assert(gstr.Contains(r, `"name": "john"`), true)
|
t.Assert(gstr.Contains(r, `"name": "john"`), true)
|
||||||
t.Assert(gstr.Contains(r, `"score": 100`), true)
|
t.Assert(gstr.Contains(r, `"score": 100`), true)
|
||||||
})
|
})
|
||||||
|
@ -94,7 +94,7 @@ func CaseKebabScreaming(s string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CaseDelimited converts a string to snake.case.delimited.
|
// CaseDelimited converts a string to snake.case.delimited.
|
||||||
func CaseDelimited(s string, del uint8) string {
|
func CaseDelimited(s string, del byte) string {
|
||||||
return CaseDelimitedScreaming(s, del, false)
|
return CaseDelimitedScreaming(s, del, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,8 +7,9 @@
|
|||||||
package gstr
|
package gstr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gogf/gf/v2/util/gconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CompareVersion compares `a` and `b` as standard GNU version.
|
// CompareVersion compares `a` and `b` as standard GNU version.
|
||||||
@ -71,12 +72,18 @@ func CompareVersion(a, b string) int {
|
|||||||
// v4.20.0+incompatible
|
// v4.20.0+incompatible
|
||||||
// etc.
|
// etc.
|
||||||
func CompareVersionGo(a, b string) int {
|
func CompareVersionGo(a, b string) int {
|
||||||
|
a = Trim(a)
|
||||||
|
b = Trim(b)
|
||||||
if a != "" && a[0] == 'v' {
|
if a != "" && a[0] == 'v' {
|
||||||
a = a[1:]
|
a = a[1:]
|
||||||
}
|
}
|
||||||
if b != "" && b[0] == 'v' {
|
if b != "" && b[0] == 'v' {
|
||||||
b = b[1:]
|
b = b[1:]
|
||||||
}
|
}
|
||||||
|
var (
|
||||||
|
rawA = a
|
||||||
|
rawB = b
|
||||||
|
)
|
||||||
if Count(a, "-") > 1 {
|
if Count(a, "-") > 1 {
|
||||||
if i := PosR(a, "-"); i > 0 {
|
if i := PosR(a, "-"); i > 0 {
|
||||||
a = a[:i]
|
a = a[:i]
|
||||||
@ -108,6 +115,7 @@ func CompareVersionGo(a, b string) int {
|
|||||||
if len(array1) <= 3 && len(array2) > 3 {
|
if len(array1) <= 3 && len(array2) > 3 {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
diff = len(array2) - len(array1)
|
diff = len(array2) - len(array1)
|
||||||
for i := 0; i < diff; i++ {
|
for i := 0; i < diff; i++ {
|
||||||
array1 = append(array1, "0")
|
array1 = append(array1, "0")
|
||||||
@ -128,5 +136,13 @@ func CompareVersionGo(a, b string) int {
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Specially in Golang:
|
||||||
|
// "v4.20.1+incompatible" < "v4.20.1"
|
||||||
|
if Contains(rawA, "incompatible") {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if Contains(rawB, "incompatible") {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
1236
text/gstr/gstr_z_example_test.go
Normal file
1236
text/gstr/gstr_z_example_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -40,7 +40,7 @@ func Test_CompareVersionGo(t *testing.T) {
|
|||||||
t.AssertEQ(gstr.CompareVersionGo("1.0.0", "v1.0.0"), 0)
|
t.AssertEQ(gstr.CompareVersionGo("1.0.0", "v1.0.0"), 0)
|
||||||
t.AssertEQ(gstr.CompareVersionGo("v0.0.0-20190626092158-b2ccc519800e", "0.0.0-20190626092158"), 0)
|
t.AssertEQ(gstr.CompareVersionGo("v0.0.0-20190626092158-b2ccc519800e", "0.0.0-20190626092158"), 0)
|
||||||
t.AssertEQ(gstr.CompareVersionGo("v0.0.0-20190626092159-b2ccc519800e", "0.0.0-20190626092158"), 1)
|
t.AssertEQ(gstr.CompareVersionGo("v0.0.0-20190626092159-b2ccc519800e", "0.0.0-20190626092158"), 1)
|
||||||
t.AssertEQ(gstr.CompareVersionGo("v4.20.0+incompatible", "4.20.0"), 0)
|
t.AssertEQ(gstr.CompareVersionGo("v4.20.0+incompatible", "4.20.0"), -1)
|
||||||
t.AssertEQ(gstr.CompareVersionGo("v4.20.0+incompatible", "4.20.1"), -1)
|
t.AssertEQ(gstr.CompareVersionGo("v4.20.0+incompatible", "4.20.1"), -1)
|
||||||
// Note that this comparison a < b.
|
// Note that this comparison a < b.
|
||||||
t.AssertEQ(gstr.CompareVersionGo("v1.12.2-0.20200413154443-b17e3a6804fa", "v1.12.2"), -1)
|
t.AssertEQ(gstr.CompareVersionGo("v1.12.2-0.20200413154443-b17e3a6804fa", "v1.12.2"), -1)
|
||||||
|
@ -27,19 +27,17 @@ type iMarshalJSON interface {
|
|||||||
MarshalJSON() ([]byte, error)
|
MarshalJSON() ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExportOption specifies the behavior of function Export.
|
// DumpOption specifies the behavior of function Export.
|
||||||
type ExportOption struct {
|
type DumpOption struct {
|
||||||
WithoutType bool // WithoutType specifies exported content has no type information.
|
WithType bool // WithType specifies dumping content with type information.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dump prints variables `values` to stdout with more manually readable.
|
// Dump prints variables `values` to stdout with more manually readable.
|
||||||
func Dump(values ...interface{}) {
|
func Dump(values ...interface{}) {
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
if s := Export(value, ExportOption{
|
DumpWithOption(value, DumpOption{
|
||||||
WithoutType: true,
|
WithType: false,
|
||||||
}); s != "" {
|
})
|
||||||
fmt.Println(s)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,37 +45,35 @@ func Dump(values ...interface{}) {
|
|||||||
// Also see Dump.
|
// Also see Dump.
|
||||||
func DumpWithType(values ...interface{}) {
|
func DumpWithType(values ...interface{}) {
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
if s := Export(value, ExportOption{
|
DumpWithOption(value, DumpOption{
|
||||||
WithoutType: false,
|
WithType: true,
|
||||||
}); s != "" {
|
})
|
||||||
fmt.Println(s)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export returns variables `values` as a string with more manually readable.
|
// DumpWithOption returns variables `values` as a string with more manually readable.
|
||||||
func Export(value interface{}, option ExportOption) string {
|
func DumpWithOption(value interface{}, option DumpOption) {
|
||||||
buffer := bytes.NewBuffer(nil)
|
buffer := bytes.NewBuffer(nil)
|
||||||
ExportTo(buffer, value, ExportOption{
|
DumpTo(buffer, value, DumpOption{
|
||||||
WithoutType: option.WithoutType,
|
WithType: option.WithType,
|
||||||
})
|
})
|
||||||
return buffer.String()
|
fmt.Println(buffer.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExportTo writes variables `values` as a string in to `writer` with more manually readable
|
// DumpTo writes variables `values` as a string in to `writer` with more manually readable
|
||||||
func ExportTo(writer io.Writer, value interface{}, option ExportOption) {
|
func DumpTo(writer io.Writer, value interface{}, option DumpOption) {
|
||||||
buffer := bytes.NewBuffer(nil)
|
buffer := bytes.NewBuffer(nil)
|
||||||
doExport(value, "", buffer, doExportOption{
|
doDump(value, "", buffer, doDumpOption{
|
||||||
WithoutType: option.WithoutType,
|
WithType: option.WithType,
|
||||||
})
|
})
|
||||||
_, _ = writer.Write(buffer.Bytes())
|
_, _ = writer.Write(buffer.Bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
type doExportOption struct {
|
type doDumpOption struct {
|
||||||
WithoutType bool
|
WithType bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func doExport(value interface{}, indent string, buffer *bytes.Buffer, option doExportOption) {
|
func doDump(value interface{}, indent string, buffer *bytes.Buffer, option doDumpOption) {
|
||||||
if value == nil {
|
if value == nil {
|
||||||
buffer.WriteString(`<nil>`)
|
buffer.WriteString(`<nil>`)
|
||||||
return
|
return
|
||||||
@ -89,186 +85,284 @@ func doExport(value interface{}, indent string, buffer *bytes.Buffer, option doE
|
|||||||
newIndent = indent + dumpIndent
|
newIndent = indent + dumpIndent
|
||||||
)
|
)
|
||||||
reflectTypeName = strings.ReplaceAll(reflectTypeName, `[]uint8`, `[]byte`)
|
reflectTypeName = strings.ReplaceAll(reflectTypeName, `[]uint8`, `[]byte`)
|
||||||
if option.WithoutType {
|
if !option.WithType {
|
||||||
reflectTypeName = ""
|
reflectTypeName = ""
|
||||||
}
|
}
|
||||||
for reflectKind == reflect.Ptr {
|
for reflectKind == reflect.Ptr {
|
||||||
reflectValue = reflectValue.Elem()
|
reflectValue = reflectValue.Elem()
|
||||||
reflectKind = reflectValue.Kind()
|
reflectKind = reflectValue.Kind()
|
||||||
}
|
}
|
||||||
|
var (
|
||||||
|
exportInternalInput = doDumpInternalInput{
|
||||||
|
Value: value,
|
||||||
|
Indent: indent,
|
||||||
|
NewIndent: newIndent,
|
||||||
|
Buffer: buffer,
|
||||||
|
Option: option,
|
||||||
|
ReflectValue: reflectValue,
|
||||||
|
ReflectTypeName: reflectTypeName,
|
||||||
|
}
|
||||||
|
)
|
||||||
switch reflectKind {
|
switch reflectKind {
|
||||||
case reflect.Slice, reflect.Array:
|
case reflect.Slice, reflect.Array:
|
||||||
if b, ok := value.([]byte); ok {
|
doDumpSlice(exportInternalInput)
|
||||||
if option.WithoutType {
|
|
||||||
buffer.WriteString(fmt.Sprintf(`"%s"`, gstr.AddSlashes(string(b))))
|
|
||||||
} else {
|
|
||||||
buffer.WriteString(fmt.Sprintf(
|
|
||||||
`%s(%d) "%s"`,
|
|
||||||
reflectTypeName,
|
|
||||||
len(reflectValue.String()),
|
|
||||||
string(b),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if reflectValue.Len() == 0 {
|
|
||||||
if option.WithoutType {
|
|
||||||
buffer.WriteString("[]")
|
|
||||||
} else {
|
|
||||||
buffer.WriteString(fmt.Sprintf("%s(0) []", reflectTypeName))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if option.WithoutType {
|
|
||||||
buffer.WriteString("[\n")
|
|
||||||
} else {
|
|
||||||
buffer.WriteString(fmt.Sprintf("%s(%d) [\n", reflectTypeName, reflectValue.Len()))
|
|
||||||
}
|
|
||||||
for i := 0; i < reflectValue.Len(); i++ {
|
|
||||||
buffer.WriteString(newIndent)
|
|
||||||
doExport(reflectValue.Index(i).Interface(), newIndent, buffer, option)
|
|
||||||
buffer.WriteString(",\n")
|
|
||||||
}
|
|
||||||
buffer.WriteString(fmt.Sprintf("%s]", indent))
|
|
||||||
|
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
var (
|
doDumpMap(exportInternalInput)
|
||||||
mapKeys = reflectValue.MapKeys()
|
|
||||||
)
|
|
||||||
if len(mapKeys) == 0 {
|
|
||||||
if option.WithoutType {
|
|
||||||
buffer.WriteString("{}")
|
|
||||||
} else {
|
|
||||||
buffer.WriteString(fmt.Sprintf("%s(0) {}", reflectTypeName))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
maxSpaceNum = 0
|
|
||||||
tmpSpaceNum = 0
|
|
||||||
mapKeyStr = ""
|
|
||||||
)
|
|
||||||
for _, key := range mapKeys {
|
|
||||||
tmpSpaceNum = len(fmt.Sprintf(`%v`, key.Interface()))
|
|
||||||
if tmpSpaceNum > maxSpaceNum {
|
|
||||||
maxSpaceNum = tmpSpaceNum
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if option.WithoutType {
|
|
||||||
buffer.WriteString("{\n")
|
|
||||||
} else {
|
|
||||||
buffer.WriteString(fmt.Sprintf("%s(%d) {\n", reflectTypeName, len(mapKeys)))
|
|
||||||
}
|
|
||||||
for _, mapKey := range mapKeys {
|
|
||||||
tmpSpaceNum = len(fmt.Sprintf(`%v`, mapKey.Interface()))
|
|
||||||
if mapKey.Kind() == reflect.String {
|
|
||||||
mapKeyStr = fmt.Sprintf(`"%v"`, mapKey.Interface())
|
|
||||||
} else {
|
|
||||||
mapKeyStr = fmt.Sprintf(`%v`, mapKey.Interface())
|
|
||||||
}
|
|
||||||
if option.WithoutType {
|
|
||||||
buffer.WriteString(fmt.Sprintf(
|
|
||||||
"%s%v:%s",
|
|
||||||
newIndent,
|
|
||||||
mapKeyStr,
|
|
||||||
strings.Repeat(" ", maxSpaceNum-tmpSpaceNum+1),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
buffer.WriteString(fmt.Sprintf(
|
|
||||||
"%s%s(%v):%s",
|
|
||||||
newIndent,
|
|
||||||
mapKey.Type().String(),
|
|
||||||
mapKeyStr,
|
|
||||||
strings.Repeat(" ", maxSpaceNum-tmpSpaceNum+1),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
doExport(reflectValue.MapIndex(mapKey).Interface(), newIndent, buffer, option)
|
|
||||||
buffer.WriteString(",\n")
|
|
||||||
}
|
|
||||||
buffer.WriteString(fmt.Sprintf("%s}", indent))
|
|
||||||
|
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
structFields, _ := structs.Fields(structs.FieldsInput{
|
doDumpStruct(exportInternalInput)
|
||||||
Pointer: value,
|
|
||||||
RecursiveOption: structs.RecursiveOptionEmbeddedNoTag,
|
|
||||||
})
|
|
||||||
if len(structFields) == 0 {
|
|
||||||
var (
|
|
||||||
structContentStr = ""
|
|
||||||
attributeCountStr = "0"
|
|
||||||
)
|
|
||||||
if v, ok := value.(iString); ok {
|
|
||||||
structContentStr = v.String()
|
|
||||||
} else if v, ok := value.(iMarshalJSON); ok {
|
|
||||||
b, _ := v.MarshalJSON()
|
|
||||||
structContentStr = string(b)
|
|
||||||
}
|
|
||||||
if structContentStr == "" {
|
|
||||||
structContentStr = "{}"
|
|
||||||
} else {
|
|
||||||
structContentStr = fmt.Sprintf(`"%s"`, gstr.AddSlashes(structContentStr))
|
|
||||||
attributeCountStr = fmt.Sprintf(`%d`, len(structContentStr)-2)
|
|
||||||
}
|
|
||||||
if option.WithoutType {
|
|
||||||
buffer.WriteString(structContentStr)
|
|
||||||
} else {
|
|
||||||
buffer.WriteString(fmt.Sprintf(
|
|
||||||
"%s(%s) %s",
|
|
||||||
reflectTypeName,
|
|
||||||
attributeCountStr,
|
|
||||||
structContentStr,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
maxSpaceNum = 0
|
|
||||||
tmpSpaceNum = 0
|
|
||||||
)
|
|
||||||
for _, field := range structFields {
|
|
||||||
tmpSpaceNum = len(field.Name())
|
|
||||||
if tmpSpaceNum > maxSpaceNum {
|
|
||||||
maxSpaceNum = tmpSpaceNum
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if option.WithoutType {
|
|
||||||
buffer.WriteString("{\n")
|
|
||||||
} else {
|
|
||||||
buffer.WriteString(fmt.Sprintf("%s(%d) {\n", reflectTypeName, len(structFields)))
|
|
||||||
}
|
|
||||||
for _, field := range structFields {
|
|
||||||
tmpSpaceNum = len(fmt.Sprintf(`%v`, field.Name()))
|
|
||||||
buffer.WriteString(fmt.Sprintf(
|
|
||||||
"%s%s:%s",
|
|
||||||
newIndent,
|
|
||||||
field.Name(),
|
|
||||||
strings.Repeat(" ", maxSpaceNum-tmpSpaceNum+1),
|
|
||||||
))
|
|
||||||
doExport(field.Value.Interface(), newIndent, buffer, option)
|
|
||||||
buffer.WriteString(",\n")
|
|
||||||
}
|
|
||||||
buffer.WriteString(fmt.Sprintf("%s}", indent))
|
|
||||||
|
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
s, _ := value.(string)
|
doDumpString(exportInternalInput)
|
||||||
if option.WithoutType {
|
|
||||||
buffer.WriteString(fmt.Sprintf(`"%v"`, gstr.AddSlashes(s)))
|
case reflect.Bool:
|
||||||
|
if reflectValue.Bool() {
|
||||||
|
buffer.WriteString(`true`)
|
||||||
} else {
|
} else {
|
||||||
buffer.WriteString(fmt.Sprintf(
|
buffer.WriteString(`false`)
|
||||||
`%s(%d) "%v"`,
|
|
||||||
reflectTypeName,
|
|
||||||
len(reflectValue.String()),
|
|
||||||
gstr.AddSlashes(s),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case
|
||||||
|
reflect.Int,
|
||||||
|
reflect.Int8,
|
||||||
|
reflect.Int16,
|
||||||
|
reflect.Int32,
|
||||||
|
reflect.Int64,
|
||||||
|
reflect.Uint,
|
||||||
|
reflect.Uint8,
|
||||||
|
reflect.Uint16,
|
||||||
|
reflect.Uint32,
|
||||||
|
reflect.Uint64,
|
||||||
|
reflect.Float32,
|
||||||
|
reflect.Float64,
|
||||||
|
reflect.Complex64,
|
||||||
|
reflect.Complex128:
|
||||||
|
doDumpNumber(exportInternalInput)
|
||||||
|
|
||||||
|
case reflect.Chan:
|
||||||
|
buffer.WriteString(`<chan>`)
|
||||||
|
|
||||||
|
case reflect.Func:
|
||||||
|
buffer.WriteString(`<func>`)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if option.WithoutType {
|
doDumpDefault(exportInternalInput)
|
||||||
buffer.WriteString(fmt.Sprintf("%v", value))
|
|
||||||
} else {
|
|
||||||
buffer.WriteString(fmt.Sprintf("%s(%v)", reflectTypeName, value))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type doDumpInternalInput struct {
|
||||||
|
Value interface{}
|
||||||
|
Indent string
|
||||||
|
NewIndent string
|
||||||
|
Buffer *bytes.Buffer
|
||||||
|
Option doDumpOption
|
||||||
|
ReflectValue reflect.Value
|
||||||
|
ReflectTypeName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func doDumpSlice(in doDumpInternalInput) {
|
||||||
|
if b, ok := in.Value.([]byte); ok {
|
||||||
|
if !in.Option.WithType {
|
||||||
|
in.Buffer.WriteString(fmt.Sprintf(`"%s"`, addSlashesForString(string(b))))
|
||||||
|
} else {
|
||||||
|
in.Buffer.WriteString(fmt.Sprintf(
|
||||||
|
`%s(%d) "%s"`,
|
||||||
|
in.ReflectTypeName,
|
||||||
|
len(string(b)),
|
||||||
|
string(b),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if in.ReflectValue.Len() == 0 {
|
||||||
|
if !in.Option.WithType {
|
||||||
|
in.Buffer.WriteString("[]")
|
||||||
|
} else {
|
||||||
|
in.Buffer.WriteString(fmt.Sprintf("%s(0) []", in.ReflectTypeName))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !in.Option.WithType {
|
||||||
|
in.Buffer.WriteString("[\n")
|
||||||
|
} else {
|
||||||
|
in.Buffer.WriteString(fmt.Sprintf("%s(%d) [\n", in.ReflectTypeName, in.ReflectValue.Len()))
|
||||||
|
}
|
||||||
|
for i := 0; i < in.ReflectValue.Len(); i++ {
|
||||||
|
in.Buffer.WriteString(in.NewIndent)
|
||||||
|
doDump(in.ReflectValue.Index(i).Interface(), in.NewIndent, in.Buffer, in.Option)
|
||||||
|
in.Buffer.WriteString(",\n")
|
||||||
|
}
|
||||||
|
in.Buffer.WriteString(fmt.Sprintf("%s]", in.Indent))
|
||||||
|
}
|
||||||
|
|
||||||
|
func doDumpMap(in doDumpInternalInput) {
|
||||||
|
var (
|
||||||
|
mapKeys = in.ReflectValue.MapKeys()
|
||||||
|
)
|
||||||
|
if len(mapKeys) == 0 {
|
||||||
|
if !in.Option.WithType {
|
||||||
|
in.Buffer.WriteString("{}")
|
||||||
|
} else {
|
||||||
|
in.Buffer.WriteString(fmt.Sprintf("%s(0) {}", in.ReflectTypeName))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
maxSpaceNum = 0
|
||||||
|
tmpSpaceNum = 0
|
||||||
|
mapKeyStr = ""
|
||||||
|
)
|
||||||
|
for _, key := range mapKeys {
|
||||||
|
tmpSpaceNum = len(fmt.Sprintf(`%v`, key.Interface()))
|
||||||
|
if tmpSpaceNum > maxSpaceNum {
|
||||||
|
maxSpaceNum = tmpSpaceNum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !in.Option.WithType {
|
||||||
|
in.Buffer.WriteString("{\n")
|
||||||
|
} else {
|
||||||
|
in.Buffer.WriteString(fmt.Sprintf("%s(%d) {\n", in.ReflectTypeName, len(mapKeys)))
|
||||||
|
}
|
||||||
|
for _, mapKey := range mapKeys {
|
||||||
|
tmpSpaceNum = len(fmt.Sprintf(`%v`, mapKey.Interface()))
|
||||||
|
if mapKey.Kind() == reflect.String {
|
||||||
|
mapKeyStr = fmt.Sprintf(`"%v"`, mapKey.Interface())
|
||||||
|
} else {
|
||||||
|
mapKeyStr = fmt.Sprintf(`%v`, mapKey.Interface())
|
||||||
|
}
|
||||||
|
if !in.Option.WithType {
|
||||||
|
in.Buffer.WriteString(fmt.Sprintf(
|
||||||
|
"%s%v:%s",
|
||||||
|
in.NewIndent,
|
||||||
|
mapKeyStr,
|
||||||
|
strings.Repeat(" ", maxSpaceNum-tmpSpaceNum+1),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
in.Buffer.WriteString(fmt.Sprintf(
|
||||||
|
"%s%s(%v):%s",
|
||||||
|
in.NewIndent,
|
||||||
|
mapKey.Type().String(),
|
||||||
|
mapKeyStr,
|
||||||
|
strings.Repeat(" ", maxSpaceNum-tmpSpaceNum+1),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
doDump(in.ReflectValue.MapIndex(mapKey).Interface(), in.NewIndent, in.Buffer, in.Option)
|
||||||
|
in.Buffer.WriteString(",\n")
|
||||||
|
}
|
||||||
|
in.Buffer.WriteString(fmt.Sprintf("%s}", in.Indent))
|
||||||
|
}
|
||||||
|
|
||||||
|
func doDumpStruct(in doDumpInternalInput) {
|
||||||
|
structFields, _ := structs.Fields(structs.FieldsInput{
|
||||||
|
Pointer: in.Value,
|
||||||
|
RecursiveOption: structs.RecursiveOptionEmbedded,
|
||||||
|
})
|
||||||
|
if len(structFields) == 0 {
|
||||||
|
var (
|
||||||
|
structContentStr = ""
|
||||||
|
attributeCountStr = "0"
|
||||||
|
)
|
||||||
|
if v, ok := in.Value.(iString); ok {
|
||||||
|
structContentStr = v.String()
|
||||||
|
} else if v, ok := in.Value.(iMarshalJSON); ok {
|
||||||
|
b, _ := v.MarshalJSON()
|
||||||
|
structContentStr = string(b)
|
||||||
|
}
|
||||||
|
if structContentStr == "" {
|
||||||
|
structContentStr = "{}"
|
||||||
|
} else {
|
||||||
|
structContentStr = fmt.Sprintf(`"%s"`, addSlashesForString(structContentStr))
|
||||||
|
attributeCountStr = fmt.Sprintf(`%d`, len(structContentStr)-2)
|
||||||
|
}
|
||||||
|
if !in.Option.WithType {
|
||||||
|
in.Buffer.WriteString(structContentStr)
|
||||||
|
} else {
|
||||||
|
in.Buffer.WriteString(fmt.Sprintf(
|
||||||
|
"%s(%s) %s",
|
||||||
|
in.ReflectTypeName,
|
||||||
|
attributeCountStr,
|
||||||
|
structContentStr,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
maxSpaceNum = 0
|
||||||
|
tmpSpaceNum = 0
|
||||||
|
)
|
||||||
|
for _, field := range structFields {
|
||||||
|
tmpSpaceNum = len(field.Name())
|
||||||
|
if tmpSpaceNum > maxSpaceNum {
|
||||||
|
maxSpaceNum = tmpSpaceNum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !in.Option.WithType {
|
||||||
|
in.Buffer.WriteString("{\n")
|
||||||
|
} else {
|
||||||
|
in.Buffer.WriteString(fmt.Sprintf("%s(%d) {\n", in.ReflectTypeName, len(structFields)))
|
||||||
|
}
|
||||||
|
for _, field := range structFields {
|
||||||
|
tmpSpaceNum = len(fmt.Sprintf(`%v`, field.Name()))
|
||||||
|
in.Buffer.WriteString(fmt.Sprintf(
|
||||||
|
"%s%s:%s",
|
||||||
|
in.NewIndent,
|
||||||
|
field.Name(),
|
||||||
|
strings.Repeat(" ", maxSpaceNum-tmpSpaceNum+1),
|
||||||
|
))
|
||||||
|
doDump(field.Value.Interface(), in.NewIndent, in.Buffer, in.Option)
|
||||||
|
in.Buffer.WriteString(",\n")
|
||||||
|
}
|
||||||
|
in.Buffer.WriteString(fmt.Sprintf("%s}", in.Indent))
|
||||||
|
}
|
||||||
|
|
||||||
|
func doDumpNumber(in doDumpInternalInput) {
|
||||||
|
if v, ok := in.Value.(iString); ok {
|
||||||
|
s := v.String()
|
||||||
|
if !in.Option.WithType {
|
||||||
|
in.Buffer.WriteString(fmt.Sprintf(`"%v"`, addSlashesForString(s)))
|
||||||
|
} else {
|
||||||
|
in.Buffer.WriteString(fmt.Sprintf(
|
||||||
|
`%s(%d) "%v"`,
|
||||||
|
in.ReflectTypeName,
|
||||||
|
len(s),
|
||||||
|
addSlashesForString(s),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
doDumpDefault(in)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func doDumpString(in doDumpInternalInput) {
|
||||||
|
s := in.ReflectValue.String()
|
||||||
|
if !in.Option.WithType {
|
||||||
|
in.Buffer.WriteString(fmt.Sprintf(`"%v"`, addSlashesForString(s)))
|
||||||
|
} else {
|
||||||
|
in.Buffer.WriteString(fmt.Sprintf(
|
||||||
|
`%s(%d) "%v"`,
|
||||||
|
in.ReflectTypeName,
|
||||||
|
len(s),
|
||||||
|
addSlashesForString(s),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func doDumpDefault(in doDumpInternalInput) {
|
||||||
|
s := fmt.Sprintf("%v", in.Value)
|
||||||
|
s = gstr.Trim(s, `<>`)
|
||||||
|
if !in.Option.WithType {
|
||||||
|
in.Buffer.WriteString(s)
|
||||||
|
} else {
|
||||||
|
in.Buffer.WriteString(fmt.Sprintf("%s(%s)", in.ReflectTypeName, s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addSlashesForString(s string) string {
|
||||||
|
return gstr.ReplaceByMap(s, map[string]string{
|
||||||
|
`"`: `\"`,
|
||||||
|
"\r": `\r`,
|
||||||
|
"\t": `\t`,
|
||||||
|
"\n": `\n`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user