目录调整及项目包名调整 github=>github.com ==!........

This commit is contained in:
xing 2023-01-18 23:02:59 +08:00
parent a748d53f5a
commit 86adc60c51
65 changed files with 283 additions and 174 deletions

2
cache/map.go vendored
View File

@ -4,7 +4,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"github/fthvgb1/wp-go/safety" "github.com/fthvgb1/wp-go/safety"
"sync" "sync"
"time" "time"
) )

2
cache/slice.go vendored
View File

@ -4,7 +4,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"github/fthvgb1/wp-go/safety" "github.com/fthvgb1/wp-go/safety"
"sync" "sync"
"time" "time"
) )

6
go.mod
View File

@ -1,22 +1,23 @@
module github/fthvgb1/wp-go module github.com/fthvgb1/wp-go
go 1.18 go 1.18
require ( require (
github.com/dlclark/regexp2 v1.7.0 github.com/dlclark/regexp2 v1.7.0
github.com/elliotchance/phpserialize v1.3.3
github.com/gin-contrib/gzip v0.0.6 github.com/gin-contrib/gzip v0.0.6
github.com/gin-contrib/pprof v1.4.0 github.com/gin-contrib/pprof v1.4.0
github.com/gin-contrib/sessions v0.0.5 github.com/gin-contrib/sessions v0.0.5
github.com/gin-gonic/gin v1.8.1 github.com/gin-gonic/gin v1.8.1
github.com/go-sql-driver/mysql v1.6.0 github.com/go-sql-driver/mysql v1.6.0
github.com/jmoiron/sqlx v1.3.5 github.com/jmoiron/sqlx v1.3.5
github.com/leeqvip/gophp v1.0.0
github.com/soxfmr/gomail v0.0.0-20200806033254-80bf84e583f0 github.com/soxfmr/gomail v0.0.0-20200806033254-80bf84e583f0
golang.org/x/crypto v0.0.0-20220924013350-4ba4fb4dd9e7 golang.org/x/crypto v0.0.0-20220924013350-4ba4fb4dd9e7
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
) )
require ( require (
github.com/elliotchance/phpserialize v1.3.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect
@ -26,7 +27,6 @@ require (
github.com/gorilla/securecookie v1.1.1 // indirect github.com/gorilla/securecookie v1.1.1 // indirect
github.com/gorilla/sessions v1.2.1 // indirect github.com/gorilla/sessions v1.2.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/leeqvip/gophp v1.0.0 // indirect
github.com/leodido/go-urn v1.2.1 // indirect github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect github.com/mattn/go-isatty v0.0.16 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect

View File

@ -4,14 +4,14 @@ import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"github.com/fthvgb1/wp-go/cache"
"github.com/fthvgb1/wp-go/helper"
"github.com/fthvgb1/wp-go/internal/mail"
cache2 "github.com/fthvgb1/wp-go/internal/pkg/cache"
"github.com/fthvgb1/wp-go/internal/pkg/config"
"github.com/fthvgb1/wp-go/internal/pkg/logs"
"github.com/fthvgb1/wp-go/internal/wpconfig"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github/fthvgb1/wp-go/cache"
"github/fthvgb1/wp-go/helper"
"github/fthvgb1/wp-go/internal/mail"
cache2 "github/fthvgb1/wp-go/internal/pkg/cache"
"github/fthvgb1/wp-go/internal/pkg/config"
"github/fthvgb1/wp-go/internal/pkg/logs"
"github/fthvgb1/wp-go/internal/wpconfig"
"io" "io"
"net/http" "net/http"
"net/url" "net/url"

View File

@ -2,14 +2,14 @@ package actions
import ( import (
"fmt" "fmt"
"github.com/fthvgb1/wp-go/helper"
cache2 "github.com/fthvgb1/wp-go/internal/pkg/cache"
"github.com/fthvgb1/wp-go/internal/pkg/logs"
"github.com/fthvgb1/wp-go/internal/pkg/models"
"github.com/fthvgb1/wp-go/internal/plugins"
"github.com/fthvgb1/wp-go/internal/wpconfig"
"github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github/fthvgb1/wp-go/helper"
cache2 "github/fthvgb1/wp-go/internal/pkg/cache"
"github/fthvgb1/wp-go/internal/pkg/logs"
"github/fthvgb1/wp-go/internal/pkg/models"
"github/fthvgb1/wp-go/internal/plugins"
"github/fthvgb1/wp-go/internal/wpconfig"
"math/rand" "math/rand"
"net/http" "net/http"
"net/url" "net/url"

View File

@ -2,16 +2,16 @@ package actions
import ( import (
"fmt" "fmt"
"github.com/fthvgb1/wp-go/cache"
"github.com/fthvgb1/wp-go/helper"
cache2 "github.com/fthvgb1/wp-go/internal/pkg/cache"
"github.com/fthvgb1/wp-go/internal/pkg/logs"
"github.com/fthvgb1/wp-go/internal/pkg/models"
"github.com/fthvgb1/wp-go/internal/plugins"
"github.com/fthvgb1/wp-go/internal/wpconfig"
"github.com/fthvgb1/wp-go/plugin/digest"
"github.com/fthvgb1/wp-go/rss2"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github/fthvgb1/wp-go/cache"
"github/fthvgb1/wp-go/helper"
cache3 "github/fthvgb1/wp-go/internal/pkg/cache"
"github/fthvgb1/wp-go/internal/pkg/logs"
models2 "github/fthvgb1/wp-go/internal/pkg/models"
"github/fthvgb1/wp-go/internal/plugins"
"github/fthvgb1/wp-go/internal/wpconfig"
"github/fthvgb1/wp-go/plugin/digest"
"github/fthvgb1/wp-go/rss2"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
@ -78,17 +78,17 @@ func Feed(c *gin.Context) {
func feed(arg ...any) (xml []string, err error) { func feed(arg ...any) (xml []string, err error) {
c := arg[0].(*gin.Context) c := arg[0].(*gin.Context)
r := cache3.RecentPosts(c, 10) r := cache2.RecentPosts(c, 10)
ids := helper.SliceMap(r, func(t models2.Posts) uint64 { ids := helper.SliceMap(r, func(t models.Posts) uint64 {
return t.Id return t.Id
}) })
posts, err := cache3.GetPostsByIds(c, ids) posts, err := cache2.GetPostsByIds(c, ids)
if err != nil { if err != nil {
return return
} }
rs := templateRss rs := templateRss
rs.LastBuildDate = time.Now().Format(timeFormat) rs.LastBuildDate = time.Now().Format(timeFormat)
rs.Items = helper.SliceMap(posts, func(t models2.Posts) rss2.Item { rs.Items = helper.SliceMap(posts, func(t models.Posts) rss2.Item {
desc := "无法提供摘要。这是一篇受保护的文章。" desc := "无法提供摘要。这是一篇受保护的文章。"
plugins.PasswordProjectTitle(&t) plugins.PasswordProjectTitle(&t)
if t.PostPassword != "" { if t.PostPassword != "" {
@ -102,7 +102,7 @@ func feed(arg ...any) (xml []string, err error) {
} else if t.CommentStatus == "open" && t.CommentCount == 0 { } else if t.CommentStatus == "open" && t.CommentCount == 0 {
l = fmt.Sprintf("%s/p/%d#respond", wpconfig.Options.Value("siteurl"), t.Id) l = fmt.Sprintf("%s/p/%d#respond", wpconfig.Options.Value("siteurl"), t.Id)
} }
user := cache3.GetUserById(c, t.PostAuthor) user := cache2.GetUserById(c, t.PostAuthor)
return rss2.Item{ return rss2.Item{
Title: t.PostTitle, Title: t.PostTitle,
@ -158,17 +158,17 @@ func postFeed(arg ...any) (x string, err error) {
} }
} }
ID := uint64(Id) ID := uint64(Id)
maxId, err := cache3.GetMaxPostId(c) maxId, err := cache2.GetMaxPostId(c)
logs.ErrPrintln(err, "get max post id") logs.ErrPrintln(err, "get max post id")
if ID > maxId || err != nil { if ID > maxId || err != nil {
return return
} }
post, err := cache3.GetPostById(c, ID) post, err := cache2.GetPostById(c, ID)
if post.Id == 0 || err != nil { if post.Id == 0 || err != nil {
return return
} }
plugins.PasswordProjectTitle(&post) plugins.PasswordProjectTitle(&post)
comments, err := cache3.PostComments(c, post.Id) comments, err := cache2.PostComments(c, post.Id)
if err != nil { if err != nil {
return return
} }
@ -195,7 +195,7 @@ func postFeed(arg ...any) (x string, err error) {
} }
} }
} else { } else {
rs.Items = helper.SliceMap(comments, func(t models2.Comments) rss2.Item { rs.Items = helper.SliceMap(comments, func(t models.Comments) rss2.Item {
return rss2.Item{ return rss2.Item{
Title: fmt.Sprintf("评价者:%s", t.CommentAuthor), Title: fmt.Sprintf("评价者:%s", t.CommentAuthor),
Link: fmt.Sprintf("%s/p/%d#comment-%d", wpconfig.Options.Value("siteurl"), post.Id, t.CommentId), Link: fmt.Sprintf("%s/p/%d#comment-%d", wpconfig.Options.Value("siteurl"), post.Id, t.CommentId),
@ -228,19 +228,19 @@ func CommentsFeed(c *gin.Context) {
func commentsFeed(args ...any) (r []string, err error) { func commentsFeed(args ...any) (r []string, err error) {
c := args[0].(*gin.Context) c := args[0].(*gin.Context)
commens := cache3.RecentComments(c, 10) commens := cache2.RecentComments(c, 10)
rs := templateRss rs := templateRss
rs.Title = fmt.Sprintf("\"%s\"的评论", wpconfig.Options.Value("blogname")) rs.Title = fmt.Sprintf("\"%s\"的评论", wpconfig.Options.Value("blogname"))
rs.LastBuildDate = time.Now().Format(timeFormat) rs.LastBuildDate = time.Now().Format(timeFormat)
rs.AtomLink = fmt.Sprintf("%s/comments/feed", wpconfig.Options.Value("siteurl")) rs.AtomLink = fmt.Sprintf("%s/comments/feed", wpconfig.Options.Value("siteurl"))
com, err := cache3.GetCommentByIds(c, helper.SliceMap(commens, func(t models2.Comments) uint64 { com, err := cache2.GetCommentByIds(c, helper.SliceMap(commens, func(t models.Comments) uint64 {
return t.CommentId return t.CommentId
})) }))
if nil != err { if nil != err {
return []string{}, err return []string{}, err
} }
rs.Items = helper.SliceMap(com, func(t models2.Comments) rss2.Item { rs.Items = helper.SliceMap(com, func(t models.Comments) rss2.Item {
post, _ := cache3.GetPostById(c, t.CommentPostId) post, _ := cache2.GetPostById(c, t.CommentPostId)
plugins.PasswordProjectTitle(&post) plugins.PasswordProjectTitle(&post)
desc := "评论受保护:要查看请输入密码。" desc := "评论受保护:要查看请输入密码。"
content := t.CommentContent content := t.CommentContent

View File

@ -2,11 +2,11 @@ package actions
import ( import (
"fmt" "fmt"
"github.com/fthvgb1/wp-go/helper"
"github.com/fthvgb1/wp-go/internal/wpconfig"
"github.com/fthvgb1/wp-go/phpass"
"github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github/fthvgb1/wp-go/helper"
"github/fthvgb1/wp-go/internal/wpconfig"
"github/fthvgb1/wp-go/phpass"
"net/http" "net/http"
"strings" "strings"
) )

View File

@ -1,16 +1,16 @@
package route package route
import ( import (
"github.com/fthvgb1/wp-go/internal/actions"
"github.com/fthvgb1/wp-go/internal/middleware"
"github.com/fthvgb1/wp-go/internal/pkg/config"
"github.com/fthvgb1/wp-go/internal/static"
"github.com/fthvgb1/wp-go/internal/theme"
"github.com/gin-contrib/gzip" "github.com/gin-contrib/gzip"
"github.com/gin-contrib/pprof" "github.com/gin-contrib/pprof"
"github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie" "github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github/fthvgb1/wp-go/internal/actions"
"github/fthvgb1/wp-go/internal/middleware"
"github/fthvgb1/wp-go/internal/pkg/config"
"github/fthvgb1/wp-go/internal/static"
"github/fthvgb1/wp-go/internal/templates"
"net/http" "net/http"
) )
@ -26,7 +26,7 @@ func SetupRouter() (*gin.Engine, func()) {
} }
} }
r.HTMLRender = templates.NewFsTemplate(templates.FuncMap()).SetTemplate() r.HTMLRender = theme.NewFsTemplate(theme.FuncMap()).SetTemplate()
validServerName, reloadValidServerNameFn := middleware.ValidateServerNames() validServerName, reloadValidServerNameFn := middleware.ValidateServerNames()
fl, flReload := middleware.FlowLimit(c.MaxRequestSleepNum, c.MaxRequestNum, c.SleepTime) fl, flReload := middleware.FlowLimit(c.MaxRequestSleepNum, c.MaxRequestNum, c.SleepTime)
r.Use( r.Use(

View File

@ -3,8 +3,8 @@ package mail
import ( import (
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"github.com/fthvgb1/wp-go/internal/pkg/config"
"github.com/soxfmr/gomail" "github.com/soxfmr/gomail"
"github/fthvgb1/wp-go/internal/pkg/config"
"mime" "mime"
"path" "path"
) )

View File

@ -1,7 +1,7 @@
package mail package mail
import ( import (
"github/fthvgb1/wp-go/internal/pkg/config" "github.com/fthvgb1/wp-go/internal/pkg/config"
"testing" "testing"
) )

View File

@ -1,9 +1,9 @@
package middleware package middleware
import ( import (
"github.com/fthvgb1/wp-go/helper"
"github.com/fthvgb1/wp-go/safety"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github/fthvgb1/wp-go/helper"
"github/fthvgb1/wp-go/safety"
"net/http" "net/http"
"strings" "strings"
"sync/atomic" "sync/atomic"

View File

@ -3,11 +3,11 @@ package middleware
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"github.com/fthvgb1/wp-go/internal/mail"
"github.com/fthvgb1/wp-go/internal/pkg/config"
"github.com/fthvgb1/wp-go/internal/pkg/logs"
"github.com/fthvgb1/wp-go/internal/wpconfig"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github/fthvgb1/wp-go/internal/mail"
"github/fthvgb1/wp-go/internal/pkg/config"
"github/fthvgb1/wp-go/internal/pkg/logs"
"github/fthvgb1/wp-go/internal/wpconfig"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"

View File

@ -1,8 +1,8 @@
package middleware package middleware
import ( import (
"github.com/fthvgb1/wp-go/helper"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github/fthvgb1/wp-go/helper"
"strings" "strings"
) )

View File

@ -1,9 +1,9 @@
package middleware package middleware
import ( import (
"github.com/fthvgb1/wp-go/internal/pkg/config"
"github.com/fthvgb1/wp-go/safety"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github/fthvgb1/wp-go/internal/pkg/config"
"github/fthvgb1/wp-go/safety"
"net/http" "net/http"
"strings" "strings"
) )

View File

@ -2,11 +2,11 @@ package cache
import ( import (
"context" "context"
"github/fthvgb1/wp-go/cache" "github.com/fthvgb1/wp-go/cache"
"github/fthvgb1/wp-go/internal/pkg/config" "github.com/fthvgb1/wp-go/internal/pkg/config"
"github/fthvgb1/wp-go/internal/pkg/dao" "github.com/fthvgb1/wp-go/internal/pkg/dao"
"github/fthvgb1/wp-go/internal/pkg/logs" "github.com/fthvgb1/wp-go/internal/pkg/logs"
"github/fthvgb1/wp-go/internal/pkg/models" "github.com/fthvgb1/wp-go/internal/pkg/models"
"sync" "sync"
"time" "time"
) )

View File

@ -2,8 +2,8 @@ package cache
import ( import (
"context" "context"
"github/fthvgb1/wp-go/internal/pkg/logs" "github.com/fthvgb1/wp-go/internal/pkg/logs"
"github/fthvgb1/wp-go/internal/pkg/models" "github.com/fthvgb1/wp-go/internal/pkg/models"
"time" "time"
) )

View File

@ -3,10 +3,10 @@ package cache
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/fthvgb1/wp-go/helper"
"github.com/fthvgb1/wp-go/internal/pkg/logs"
"github.com/fthvgb1/wp-go/internal/pkg/models"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github/fthvgb1/wp-go/helper"
"github/fthvgb1/wp-go/internal/pkg/logs"
"github/fthvgb1/wp-go/internal/pkg/models"
"time" "time"
) )

View File

@ -2,9 +2,9 @@ package cache
import ( import (
"context" "context"
"github/fthvgb1/wp-go/internal/pkg/logs" "github.com/fthvgb1/wp-go/internal/pkg/logs"
"github/fthvgb1/wp-go/internal/pkg/models" "github.com/fthvgb1/wp-go/internal/pkg/models"
"github/fthvgb1/wp-go/model" "github.com/fthvgb1/wp-go/model"
"time" "time"
) )

View File

@ -2,7 +2,7 @@ package config
import ( import (
"fmt" "fmt"
"github/fthvgb1/wp-go/safety" "github.com/fthvgb1/wp-go/safety"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
"os" "os"
"time" "time"

View File

@ -2,9 +2,9 @@ package common
import ( import (
"context" "context"
"github/fthvgb1/wp-go/helper" "github.com/fthvgb1/wp-go/helper"
"github/fthvgb1/wp-go/internal/pkg/models" "github.com/fthvgb1/wp-go/internal/pkg/models"
"github/fthvgb1/wp-go/model" "github.com/fthvgb1/wp-go/model"
"strconv" "strconv"
) )

View File

@ -3,9 +3,9 @@ package common
import ( import (
"context" "context"
"fmt" "fmt"
models2 "github/fthvgb1/wp-go/internal/pkg/models" models2 "github.com/fthvgb1/wp-go/internal/pkg/models"
"github/fthvgb1/wp-go/internal/wpconfig" "github.com/fthvgb1/wp-go/internal/wpconfig"
"github/fthvgb1/wp-go/model" "github.com/fthvgb1/wp-go/model"
) )
var TotalRaw int64 var TotalRaw int64

View File

@ -3,10 +3,10 @@ package common
import ( import (
"context" "context"
"fmt" "fmt"
"github/fthvgb1/wp-go/helper" "github.com/fthvgb1/wp-go/helper"
"github/fthvgb1/wp-go/internal/pkg/logs" "github.com/fthvgb1/wp-go/internal/pkg/logs"
"github/fthvgb1/wp-go/internal/pkg/models" "github.com/fthvgb1/wp-go/internal/pkg/models"
"github/fthvgb1/wp-go/model" "github.com/fthvgb1/wp-go/model"
"strconv" "strconv"
"strings" "strings"
) )

View File

@ -4,10 +4,10 @@ import (
"context" "context"
"database/sql" "database/sql"
"fmt" "fmt"
"github/fthvgb1/wp-go/helper" "github.com/fthvgb1/wp-go/helper"
"github/fthvgb1/wp-go/internal/pkg/models" "github.com/fthvgb1/wp-go/internal/pkg/models"
"github/fthvgb1/wp-go/internal/wpconfig" "github.com/fthvgb1/wp-go/internal/wpconfig"
"github/fthvgb1/wp-go/model" "github.com/fthvgb1/wp-go/model"
"strings" "strings"
"sync/atomic" "sync/atomic"
"time" "time"

View File

@ -2,8 +2,8 @@ package common
import ( import (
"context" "context"
"github/fthvgb1/wp-go/internal/pkg/models" "github.com/fthvgb1/wp-go/internal/pkg/models"
"github/fthvgb1/wp-go/model" "github.com/fthvgb1/wp-go/model"
) )
func GetUserById(a ...any) (r models.Users, err error) { func GetUserById(a ...any) (r models.Users, err error) {

View File

@ -3,9 +3,9 @@ package db
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/fthvgb1/wp-go/internal/pkg/config"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
"github/fthvgb1/wp-go/internal/pkg/config"
"log" "log"
"os" "os"
"strconv" "strconv"

View File

@ -1,8 +1,8 @@
package models package models
import ( import (
"github.com/fthvgb1/wp-go/helper"
"github.com/leeqvip/gophp" "github.com/leeqvip/gophp"
"github/fthvgb1/wp-go/helper"
) )
type Postmeta struct { type Postmeta struct {

View File

@ -2,11 +2,11 @@ package plugins
import ( import (
"fmt" "fmt"
"github.com/fthvgb1/wp-go/cache"
"github.com/fthvgb1/wp-go/internal/pkg/config"
"github.com/fthvgb1/wp-go/internal/pkg/models"
"github.com/fthvgb1/wp-go/plugin/digest"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github/fthvgb1/wp-go/cache"
"github/fthvgb1/wp-go/internal/pkg/config"
"github/fthvgb1/wp-go/internal/pkg/models"
"github/fthvgb1/wp-go/plugin/digest"
"strings" "strings"
"time" "time"
) )

View File

@ -2,8 +2,8 @@ package plugins
import ( import (
"fmt" "fmt"
"github.com/fthvgb1/wp-go/internal/pkg/models"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github/fthvgb1/wp-go/internal/pkg/models"
) )
func NewPostPlugin(ctx *gin.Context, scene uint) *Plugin[models.Posts] { func NewPostPlugin(ctx *gin.Context, scene uint) *Plugin[models.Posts] {

View File

@ -1,4 +1,4 @@
package templates package theme
import ( import (
"embed" "embed"
@ -9,7 +9,7 @@ import (
"strings" "strings"
) )
//go:embed *[^templatefs.go] //go:embed *[^.go]
var TemplateFs embed.FS var TemplateFs embed.FS
type FsTemplate struct { type FsTemplate struct {

View File

@ -1,8 +1,7 @@
package templates package theme
import ( import (
"errors" "github.com/fthvgb1/wp-go/internal/wpconfig"
"github/fthvgb1/wp-go/internal/wpconfig"
"html/template" "html/template"
"time" "time"
) )
@ -23,14 +22,9 @@ func FuncMap() template.FuncMap {
return funcs return funcs
} }
func InitTemplateFunc() { func AddTemplateFunc(fnName string, fn any) {
}
func AddTemplateFunc(fnName string, fn any) error {
if _, ok := funcs[fnName]; ok { if _, ok := funcs[fnName]; ok {
return errors.New("a same name func exists") panic("exists same name func")
} }
funcs[fnName] = fn funcs[fnName] = fn
return nil
} }

View File

@ -52,9 +52,11 @@
</div> </div>
<footer id="colophon" class="site-footer"> <footer id="colophon" class="site-footer">
<div class="wrap">
<div class="site-info"> <div class="site-info">
<a href="https://cn.wordpress.org/" class="imprint">自豪地采用WordPress</a> <a href="https://cn.wordpress.org/" class="imprint">自豪地采用WordPress</a>
</div> </div>
</div>
</footer> </footer>
</div> </div>

View File

@ -2,13 +2,15 @@ package twentyseventeen
import ( import (
"github.com/elliotchance/phpserialize" "github.com/elliotchance/phpserialize"
"github.com/fthvgb1/wp-go/helper"
"github.com/fthvgb1/wp-go/internal/pkg/cache"
"github.com/fthvgb1/wp-go/internal/pkg/logs"
"github.com/fthvgb1/wp-go/internal/pkg/models"
"github.com/fthvgb1/wp-go/internal/plugins"
"github.com/fthvgb1/wp-go/internal/wpconfig"
"github.com/fthvgb1/wp-go/plugin/pagination"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github/fthvgb1/wp-go/helper" "strings"
"github/fthvgb1/wp-go/internal/pkg/cache"
"github/fthvgb1/wp-go/internal/pkg/logs"
"github/fthvgb1/wp-go/internal/pkg/models"
"github/fthvgb1/wp-go/internal/plugins"
"github/fthvgb1/wp-go/internal/wpconfig"
) )
const ThemeName = "twentyseventeen" const ThemeName = "twentyseventeen"
@ -28,13 +30,27 @@ type ImageData struct {
Width int64 `json:"width,omitempty"` Width int64 `json:"width,omitempty"`
} }
func Hook(c *gin.Context, h gin.H, scene int) (r string) { func Hook(status int, c *gin.Context, h gin.H, scene int) {
templ := "twentyseventeen/posts/index.gohtml"
if _, ok := plugins.IndexSceneMap[scene]; ok { if _, ok := plugins.IndexSceneMap[scene]; ok {
r = "twentyseventeen/posts/index.gohtml"
h["HeaderImage"] = getHeaderImage(c) h["HeaderImage"] = getHeaderImage(c)
} else if _, ok := plugins.DetailSceneMap[scene]; ok { p, ok := h["pagination"]
r = "twentyseventeen/posts/detail.gohtml" if ok {
pp, ok := p.(pagination.ParsePagination)
if ok {
f := plugins.TwentyFifteenPagination()
f.PrevEle = `<a class="prev page-numbers" href="%s"><svg class="icon icon-arrow-left" aria-hidden="true" role="img"> <use href="#icon-arrow-left" xlink:href="#icon-arrow-left"></use> </svg>
<span class="screen-reader-text">上一页</span></a>`
f.NextEle = strings.Replace(f.NextEle, "下一页", `<span class="screen-reader-text">下一页</span>
<svg class="icon icon-arrow-right" aria-hidden="true" role="img"> <use href="#icon-arrow-right" xlink:href="#icon-arrow-right"></use>
</svg>`, 1)
h["pagination"] = pagination.Paginate(f, pp)
} }
}
} else if _, ok := plugins.DetailSceneMap[scene]; ok {
templ = "twentyseventeen/posts/detail.gohtml"
}
c.HTML(status, templ, h)
return return
} }

View File

@ -2,9 +2,9 @@ package wpconfig
import ( import (
"context" "context"
"github/fthvgb1/wp-go/internal/pkg/models" "github.com/fthvgb1/wp-go/internal/pkg/models"
"github/fthvgb1/wp-go/model" "github.com/fthvgb1/wp-go/model"
"github/fthvgb1/wp-go/safety" "github.com/fthvgb1/wp-go/safety"
) )
var Options safety.Map[string, string] var Options safety.Map[string, string]

View File

@ -2,9 +2,9 @@ package wpconfig
import ( import (
"context" "context"
"github/fthvgb1/wp-go/internal/pkg/models" "github.com/fthvgb1/wp-go/internal/pkg/models"
"github/fthvgb1/wp-go/model" "github.com/fthvgb1/wp-go/model"
"github/fthvgb1/wp-go/safety" "github.com/fthvgb1/wp-go/safety"
) )
var Terms safety.Map[uint64, models.Terms] var Terms safety.Map[uint64, models.Terms]

View File

@ -2,7 +2,7 @@ package model
import ( import (
"fmt" "fmt"
"github/fthvgb1/wp-go/helper" "github.com/fthvgb1/wp-go/helper"
"strconv" "strconv"
"strings" "strings"
) )

View File

@ -3,7 +3,7 @@ package model
import ( import (
"context" "context"
"fmt" "fmt"
"github/fthvgb1/wp-go/helper" "github.com/fthvgb1/wp-go/helper"
"math/rand" "math/rand"
"strings" "strings"
"time" "time"

View File

@ -3,26 +3,123 @@ package model
import ( import (
"context" "context"
"database/sql" "database/sql"
"github/fthvgb1/wp-go/helper" "github.com/fthvgb1/wp-go/helper"
"github/fthvgb1/wp-go/internal/pkg/config" _ "github.com/go-sql-driver/mysql"
"github/fthvgb1/wp-go/internal/pkg/db" "github.com/jmoiron/sqlx"
models2 "github/fthvgb1/wp-go/internal/pkg/models"
"reflect" "reflect"
"testing" "testing"
"time"
) )
type post struct {
Id uint64 `gorm:"column:ID" db:"ID" json:"ID" form:"ID"`
PostAuthor uint64 `gorm:"column:post_author" db:"post_author" json:"post_author" form:"post_author"`
PostDate time.Time `gorm:"column:post_date" db:"post_date" json:"post_date" form:"post_date"`
PostDateGmt time.Time `gorm:"column:post_date_gmt" db:"post_date_gmt" json:"post_date_gmt" form:"post_date_gmt"`
PostContent string `gorm:"column:post_content" db:"post_content" json:"post_content" form:"post_content"`
PostTitle string `gorm:"column:post_title" db:"post_title" json:"post_title" form:"post_title"`
PostExcerpt string `gorm:"column:post_excerpt" db:"post_excerpt" json:"post_excerpt" form:"post_excerpt"`
PostStatus string `gorm:"column:post_status" db:"post_status" json:"post_status" form:"post_status"`
CommentStatus string `gorm:"column:comment_status" db:"comment_status" json:"comment_status" form:"comment_status"`
PingStatus string `gorm:"column:ping_status" db:"ping_status" json:"ping_status" form:"ping_status"`
PostPassword string `gorm:"column:post_password" db:"post_password" json:"post_password" form:"post_password"`
PostName string `gorm:"column:post_name" db:"post_name" json:"post_name" form:"post_name"`
ToPing string `gorm:"column:to_ping" db:"to_ping" json:"to_ping" form:"to_ping"`
Pinged string `gorm:"column:pinged" db:"pinged" json:"pinged" form:"pinged"`
PostModified time.Time `gorm:"column:post_modified" db:"post_modified" json:"post_modified" form:"post_modified"`
PostModifiedGmt time.Time `gorm:"column:post_modified_gmt" db:"post_modified_gmt" json:"post_modified_gmt" form:"post_modified_gmt"`
PostContentFiltered string `gorm:"column:post_content_filtered" db:"post_content_filtered" json:"post_content_filtered" form:"post_content_filtered"`
PostParent uint64 `gorm:"column:post_parent" db:"post_parent" json:"post_parent" form:"post_parent"`
Guid string `gorm:"column:guid" db:"guid" json:"guid" form:"guid"`
MenuOrder int `gorm:"column:menu_order" db:"menu_order" json:"menu_order" form:"menu_order"`
PostType string `gorm:"column:post_type" db:"post_type" json:"post_type" form:"post_type"`
PostMimeType string `gorm:"column:post_mime_type" db:"post_mime_type" json:"post_mime_type" form:"post_mime_type"`
CommentCount int64 `gorm:"column:comment_count" db:"comment_count" json:"comment_count" form:"comment_count"`
}
type user struct {
Id uint64 `gorm:"column:ID" db:"ID" json:"ID"`
UserLogin string `gorm:"column:user_login" db:"user_login" json:"user_login"`
UserPass string `gorm:"column:user_pass" db:"user_pass" json:"user_pass"`
UserNicename string `gorm:"column:user_nicename" db:"user_nicename" json:"user_nicename"`
UserEmail string `gorm:"column:user_email" db:"user_email" json:"user_email"`
UserUrl string `gorm:"column:user_url" db:"user_url" json:"user_url"`
UserRegistered time.Time `gorm:"column:user_registered" db:"user_registered" json:"user_registered"`
UserActivationKey string `gorm:"column:user_activation_key" db:"user_activation_key" json:"user_activation_key"`
UserStatus int `gorm:"column:user_status" db:"user_status" json:"user_status"`
DisplayName string `gorm:"column:display_name" db:"display_name" json:"display_name"`
}
type termTaxonomy struct {
TermTaxonomyId uint64 `gorm:"column:term_taxonomy_id" db:"term_taxonomy_id" json:"term_taxonomy_id" form:"term_taxonomy_id"`
TermId uint64 `gorm:"column:term_id" db:"term_id" json:"term_id" form:"term_id"`
Taxonomy string `gorm:"column:taxonomy" db:"taxonomy" json:"taxonomy" form:"taxonomy"`
Description string `gorm:"column:description" db:"description" json:"description" form:"description"`
Parent uint64 `gorm:"column:parent" db:"parent" json:"parent" form:"parent"`
Count int64 `gorm:"column:count" db:"count" json:"count" form:"count"`
}
type terms struct {
TermId uint64 `gorm:"column:term_id" db:"term_id" json:"term_id" form:"term_id"`
Name string `gorm:"column:name" db:"name" json:"name" form:"name"`
Slug string `gorm:"column:slug" db:"slug" json:"slug" form:"slug"`
TermGroup int64 `gorm:"column:term_group" db:"term_group" json:"term_group" form:"term_group"`
}
func (t terms) PrimaryKey() string {
return "term_id"
}
func (t terms) Table() string {
return "wp_terms"
}
func (w termTaxonomy) PrimaryKey() string {
return "term_taxonomy_id"
}
func (w termTaxonomy) Table() string {
return "wp_term_taxonomy"
}
func (u user) Table() string {
return "wp_users"
}
func (u user) PrimaryKey() string {
return "ID"
}
func (p post) PrimaryKey() string {
return "ID"
}
func (p post) Table() string {
return "wp_posts"
}
type SqlxDb struct {
sqlx *sqlx.DB
}
var Db *SqlxDb
func (r SqlxDb) Select(ctx context.Context, dest any, sql string, params ...any) error {
return r.sqlx.Select(dest, sql, params...)
}
func (r SqlxDb) Get(ctx context.Context, dest any, sql string, params ...any) error {
return r.sqlx.Get(dest, sql, params...)
}
var ctx = context.Background() var ctx = context.Background()
func init() { func init() {
err := config.InitConfig("../config.yaml") db, err := sqlx.Open("mysql", "root:root@tcp(192.168.66.47:3306)/wordpress?charset=utf8mb4&parseTime=True&loc=Local")
if err != nil { if err != nil {
panic(err) panic(err)
} }
err = db.InitDb() Db = &SqlxDb{db}
if err != nil { InitDB(Db)
panic(err)
}
InitDB(db.NewSqlxDb(db.Db))
} }
func TestFind(t *testing.T) { func TestFind(t *testing.T) {
type args struct { type args struct {
@ -36,7 +133,7 @@ func TestFind(t *testing.T) {
in [][]any in [][]any
} }
type posts struct { type posts struct {
models2.Posts post
N int `db:"n"` N int `db:"n"`
} }
tests := []struct { tests := []struct {
@ -107,7 +204,7 @@ func TestFind(t *testing.T) {
in: nil, in: nil,
}, },
wantR: func() []posts { wantR: func() []posts {
r, err := Select[posts](ctx, "select post_status,count(*) n from "+models2.Posts{}.Table()+" where ID<1000 group by post_status having n>1") r, err := Select[posts](ctx, "select post_status,count(*) n from "+post{}.Table()+" where ID<1000 group by post_status having n>1")
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -152,10 +249,10 @@ func TestFind(t *testing.T) {
group: "a.post_author", group: "a.post_author",
order: SqlBuilder{{"n", "desc"}}, order: SqlBuilder{{"n", "desc"}},
join: SqlBuilder{ join: SqlBuilder{
{"a", "left join", models2.Users{}.Table() + " b", "a.post_author=b.ID"}, {"a", "left join", user{}.Table() + " b", "a.post_author=b.ID"},
{"left join", "wp_term_relationships c", "a.Id=c.object_id"}, {"left join", "wp_term_relationships c", "a.Id=c.object_id"},
{"left join", models2.TermTaxonomy{}.Table() + " d", "c.term_taxonomy_id=d.term_taxonomy_id"}, {"left join", termTaxonomy{}.Table() + " d", "c.term_taxonomy_id=d.term_taxonomy_id"},
{"left join", models2.Terms{}.Table() + " e", "d.term_id=e.term_id"}, {"left join", terms{}.Table() + " e", "d.term_id=e.term_id"},
}, },
having: SqlBuilder{{"n", ">", "0", "int"}}, having: SqlBuilder{{"n", ">", "0", "int"}},
limit: 10, limit: 10,
@ -193,7 +290,7 @@ func TestFindOneById(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
args args args args
want models2.Posts want post
wantErr bool wantErr bool
}{ }{
{ {
@ -201,8 +298,8 @@ func TestFindOneById(t *testing.T) {
args: args{ args: args{
1, 1,
}, },
want: func() models2.Posts { want: func() post {
r, err := Get[models2.Posts](ctx, "select * from "+models2.Posts{}.Table()+" where ID=?", 1) r, err := Get[post](ctx, "select * from "+post{}.Table()+" where ID=?", 1)
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
panic(err) panic(err)
} else if err == sql.ErrNoRows { } else if err == sql.ErrNoRows {
@ -215,7 +312,7 @@ func TestFindOneById(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
got, err := FindOneById[models2.Posts](ctx, tt.args.id) got, err := FindOneById[post](ctx, tt.args.id)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
err = nil err = nil
} }
@ -240,7 +337,7 @@ func TestFirstOne(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
args args args args
want models2.Posts want post
wantErr bool wantErr bool
}{ }{
{ {
@ -252,8 +349,8 @@ func TestFirstOne(t *testing.T) {
in: nil, in: nil,
}, },
wantErr: false, wantErr: false,
want: func() models2.Posts { want: func() post {
r, err := Get[models2.Posts](ctx, "select * from "+models2.Posts{}.Table()+" where post_status='publish' order by ID desc limit 1") r, err := Get[post](ctx, "select * from "+post{}.Table()+" where post_status='publish' order by ID desc limit 1")
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
panic(err) panic(err)
} else if err == sql.ErrNoRows { } else if err == sql.ErrNoRows {
@ -265,7 +362,7 @@ func TestFirstOne(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
got, err := FirstOne[models2.Posts](ctx, tt.args.where, tt.args.fields, tt.args.order, tt.args.in...) got, err := FirstOne[post](ctx, tt.args.where, tt.args.fields, tt.args.order, tt.args.in...)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("FirstOne() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("FirstOne() error = %v, wantErr %v", err, tt.wantErr)
return return
@ -286,7 +383,7 @@ func TestLastOne(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
args args args args
want models2.Posts want post
wantErr bool wantErr bool
}{ }{
{ {
@ -298,8 +395,8 @@ func TestLastOne(t *testing.T) {
fields: "*", fields: "*",
in: nil, in: nil,
}, },
want: func() models2.Posts { want: func() post {
r, err := Get[models2.Posts](ctx, "select * from "+models2.Posts{}.Table()+" where post_status='publish' order by "+models2.Posts{}.PrimaryKey()+" desc limit 1") r, err := Get[post](ctx, "select * from "+post{}.Table()+" where post_status='publish' order by "+post{}.PrimaryKey()+" desc limit 1")
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -309,7 +406,7 @@ func TestLastOne(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
got, err := LastOne[models2.Posts](ctx, tt.args.where, tt.args.fields, tt.args.in...) got, err := LastOne[post](ctx, tt.args.where, tt.args.fields, tt.args.in...)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("LastOne() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("LastOne() error = %v, wantErr %v", err, tt.wantErr)
return return
@ -330,7 +427,7 @@ func TestSimpleFind(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
args args args args
want []models2.Posts want []post
wantErr bool wantErr bool
}{ }{
{ {
@ -342,8 +439,8 @@ func TestSimpleFind(t *testing.T) {
fields: "*", fields: "*",
in: [][]any{{1, 2}}, in: [][]any{{1, 2}},
}, },
want: func() (r []models2.Posts) { want: func() (r []post) {
r, err := Select[models2.Posts](ctx, "select * from "+models2.Posts{}.Table()+" where ID in (?,?)", 1, 2) r, err := Select[post](ctx, "select * from "+post{}.Table()+" where ID in (?,?)", 1, 2)
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
panic(err) panic(err)
} else if err == sql.ErrNoRows { } else if err == sql.ErrNoRows {
@ -356,7 +453,7 @@ func TestSimpleFind(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
got, err := SimpleFind[models2.Posts](ctx, tt.args.where, tt.args.fields, tt.args.in...) got, err := SimpleFind[post](ctx, tt.args.where, tt.args.fields, tt.args.in...)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("SimpleFind() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("SimpleFind() error = %v, wantErr %v", err, tt.wantErr)
return return
@ -383,7 +480,7 @@ func TestSimplePagination(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
args args args args
wantR []models2.Posts wantR []post
wantTotal int wantTotal int
wantErr bool wantErr bool
}{ }{
@ -402,8 +499,8 @@ func TestSimplePagination(t *testing.T) {
having: nil, having: nil,
in: [][]any{helper.SliceMap[int, any](helper.RangeSlice(431, 440, 1), helper.ToAny[int])}, in: [][]any{helper.SliceMap[int, any](helper.RangeSlice(431, 440, 1), helper.ToAny[int])},
}, },
wantR: func() (r []models2.Posts) { wantR: func() (r []post) {
r, err := Select[models2.Posts](ctx, "select * from "+models2.Posts{}.Table()+" where ID in (?,?,?,?,?)", helper.SliceMap[int, any](helper.RangeSlice(431, 435, 1), helper.ToAny[int])...) r, err := Select[post](ctx, "select * from "+post{}.Table()+" where ID in (?,?,?,?,?)", helper.SliceMap[int, any](helper.RangeSlice(431, 435, 1), helper.ToAny[int])...)
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
panic(err) panic(err)
} else if err == sql.ErrNoRows { } else if err == sql.ErrNoRows {
@ -417,7 +514,7 @@ func TestSimplePagination(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
gotR, gotTotal, err := SimplePagination[models2.Posts](ctx, tt.args.where, tt.args.fields, tt.args.group, tt.args.page, tt.args.pageSize, tt.args.order, tt.args.join, tt.args.having, tt.args.in...) gotR, gotTotal, err := SimplePagination[post](ctx, tt.args.where, tt.args.fields, tt.args.group, tt.args.page, tt.args.pageSize, tt.args.order, tt.args.join, tt.args.having, tt.args.in...)
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("SimplePagination() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("SimplePagination() error = %v, wantErr %v", err, tt.wantErr)
return return

View File

@ -3,7 +3,7 @@ package phpass
import ( import (
"crypto/md5" "crypto/md5"
"fmt" "fmt"
"github/fthvgb1/wp-go/helper" "github.com/fthvgb1/wp-go/helper"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"io" "io"
"os" "os"

View File

@ -2,7 +2,7 @@ package digest
import ( import (
"fmt" "fmt"
"github/fthvgb1/wp-go/helper" "github.com/fthvgb1/wp-go/helper"
"regexp" "regexp"
"strings" "strings"
"unicode/utf8" "unicode/utf8"

View File

@ -2,7 +2,7 @@ package rss2
import ( import (
"fmt" "fmt"
"github/fthvgb1/wp-go/helper" "github.com/fthvgb1/wp-go/helper"
"strconv" "strconv"
"strings" "strings"
) )

View File

@ -2,7 +2,7 @@ package safety
import ( import (
"fmt" "fmt"
"github/fthvgb1/wp-go/helper" "github.com/fthvgb1/wp-go/helper"
"testing" "testing"
"time" "time"
) )

View File

@ -1,8 +1,8 @@
package stream package stream
import ( import (
"github/fthvgb1/wp-go/safety" "github.com/fthvgb1/wp-go/safety"
"github/fthvgb1/wp-go/taskPools" "github.com/fthvgb1/wp-go/taskPools"
"sync" "sync"
) )

View File

@ -2,7 +2,7 @@ package stream
import ( import (
"fmt" "fmt"
"github/fthvgb1/wp-go/helper" "github.com/fthvgb1/wp-go/helper"
"reflect" "reflect"
"strconv" "strconv"
"testing" "testing"

View File

@ -1,9 +1,9 @@
package stream package stream
import ( import (
"github/fthvgb1/wp-go/helper" "github.com/fthvgb1/wp-go/helper"
"github/fthvgb1/wp-go/safety" "github.com/fthvgb1/wp-go/safety"
"github/fthvgb1/wp-go/taskPools" "github.com/fthvgb1/wp-go/taskPools"
) )
func SimpleParallelFilterAndMap[R, T any](a SimpleSliceStream[T], fn func(T) (R, bool), c int) SimpleSliceStream[R] { func SimpleParallelFilterAndMap[R, T any](a SimpleSliceStream[T], fn func(T) (R, bool), c int) SimpleSliceStream[R] {

View File

@ -2,7 +2,7 @@ package stream
import ( import (
"fmt" "fmt"
"github/fthvgb1/wp-go/helper" "github.com/fthvgb1/wp-go/helper"
"reflect" "reflect"
"strconv" "strconv"
"testing" "testing"