project initialized
This commit is contained in:
commit
9b562b23ea
69
.gitignore
vendored
Normal file
69
.gitignore
vendored
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
# Created by .ignore support plugin (hsz.mobi)
|
||||||
|
### JetBrains template
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff:
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/dictionaries
|
||||||
|
|
||||||
|
# Sensitive or high-churn files:
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.xml
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
|
||||||
|
# Gradle:
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# Mongo Explorer plugin:
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
## File-based project format:
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
## Plugin-specific files:
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
### Go template
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, build with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||||
|
.glide/
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
|
||||||
|
vendor/
|
||||||
|
glide.lock
|
||||||
|
.DS_Store
|
||||||
|
dist/
|
||||||
|
debug
|
||||||
|
debug.test
|
32
.vscode/launch.json
vendored
Normal file
32
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Debug",
|
||||||
|
"type": "go",
|
||||||
|
"request": "launch",
|
||||||
|
"mode": "debug",
|
||||||
|
"remotePath": "",
|
||||||
|
"port": 2345,
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"program": "${workspaceRoot}/main.go",
|
||||||
|
"env": {},
|
||||||
|
"args": [],
|
||||||
|
"showLog": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "File Debug",
|
||||||
|
"type": "go",
|
||||||
|
"request": "launch",
|
||||||
|
"mode": "debug",
|
||||||
|
"remotePath": "",
|
||||||
|
"port": 2345,
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"program": "${fileDirname}",
|
||||||
|
"env": {},
|
||||||
|
"args": [],
|
||||||
|
"showLog": true
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
// Place your settings in this file to overwrite default and user settings.
|
||||||
|
{
|
||||||
|
}
|
17
annotation.go
Normal file
17
annotation.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package annotation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
AnnotationTag = "annotation"
|
||||||
|
|
||||||
|
NameTag = "@name"
|
||||||
|
DefaultTag = "@default"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
AnnotationRGX = regexp.MustCompile(`@((?s).*?)\(((?s).*?)\)`)
|
||||||
|
AnnotationBodyRGX = regexp.MustCompile(`'([^\\\\']|\\')*'`)
|
||||||
|
)
|
8
definition.go
Normal file
8
definition.go
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package annotation
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
|
type Definition struct {
|
||||||
|
t reflect.Type
|
||||||
|
rt reflect.Type
|
||||||
|
}
|
158
registry.go
Normal file
158
registry.go
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
package annotation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
cur "git.loafle.net/overflow/util-go/reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Registry interface {
|
||||||
|
Register(t reflect.Type) error
|
||||||
|
Get(f *reflect.StructField) (map[reflect.Type]Annotation, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var SystemRegistry = &AnnotationRegistry{
|
||||||
|
parent: nil,
|
||||||
|
definitions: make(map[string]*Definition, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(parent Registry) Registry {
|
||||||
|
r := &AnnotationRegistry{
|
||||||
|
parent: parent,
|
||||||
|
definitions: make(map[string]*Definition, 0),
|
||||||
|
}
|
||||||
|
if nil == r.parent {
|
||||||
|
r.parent = SystemRegistry
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
type AnnotationRegistry struct {
|
||||||
|
parent Registry
|
||||||
|
definitions map[string]*Definition
|
||||||
|
}
|
||||||
|
|
||||||
|
func Register(t reflect.Type) error {
|
||||||
|
return SystemRegistry.Register(t)
|
||||||
|
}
|
||||||
|
func (r *AnnotationRegistry) Register(t reflect.Type) error {
|
||||||
|
rt, _, _ := cur.GetTypeInfo(t)
|
||||||
|
|
||||||
|
fields := findAnnotatedFields(t, AnnotationType, false)
|
||||||
|
switch len(fields) {
|
||||||
|
case 0:
|
||||||
|
return fmt.Errorf("type[%s] is not Annotation", rt.Name())
|
||||||
|
case 1:
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("type[%s] have only one Annotation", rt.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
f := fields[AnnotationName]
|
||||||
|
name := strings.TrimSpace(f.Tag.Get(NameTag))
|
||||||
|
if "" == name {
|
||||||
|
return fmt.Errorf("annotation name of type[%s] is not valid", rt.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := r.definitions[name]; ok {
|
||||||
|
return fmt.Errorf("name[%s] of annotation exist already", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.definitions[name] = &Definition{
|
||||||
|
t: t,
|
||||||
|
rt: rt,
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(f *reflect.StructField) (map[reflect.Type]Annotation, error) {
|
||||||
|
return SystemRegistry.Get(f)
|
||||||
|
}
|
||||||
|
func (r *AnnotationRegistry) Get(f *reflect.StructField) (map[reflect.Type]Annotation, error) {
|
||||||
|
annotations := make(map[reflect.Type]Annotation, 0)
|
||||||
|
|
||||||
|
tag := strings.TrimSpace(f.Tag.Get(AnnotationTag))
|
||||||
|
if "" == tag {
|
||||||
|
return annotations, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !AnnotationRGX.MatchString(tag) {
|
||||||
|
return nil, fmt.Errorf("Tag of annotation[%s] is not match", tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
rss := AnnotationRGX.FindAllStringSubmatch(tag, -1)
|
||||||
|
if nil == rss || 0 == len(rss) {
|
||||||
|
return annotations, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rs := range rss {
|
||||||
|
if 3 != len(rs) {
|
||||||
|
return nil, fmt.Errorf("Tag of annotation[%s] is not valid", rs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
name := fmt.Sprintf("@%s", strings.TrimSpace(rs[1]))
|
||||||
|
body := rs[2]
|
||||||
|
|
||||||
|
if !AnnotationBodyRGX.MatchString(body) {
|
||||||
|
return nil, fmt.Errorf("Body[%s] of annotation[%s] is not valid", body, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
body = AnnotationBodyRGX.ReplaceAllStringFunc(body, func(token string) string {
|
||||||
|
switch len(token) {
|
||||||
|
case 0, 1, 2:
|
||||||
|
return "\"\""
|
||||||
|
default:
|
||||||
|
return strings.Replace(fmt.Sprintf("\"%s\"", token[1:len(token)-1]), "\\'", "'", -1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
body = fmt.Sprintf("{%s}", strings.TrimSpace(body))
|
||||||
|
|
||||||
|
def, ok := r.definitions[name]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("annotation[%s] is not exist", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
v := reflect.New(def.rt)
|
||||||
|
i := v.Interface()
|
||||||
|
|
||||||
|
err := json.Unmarshal([]byte(body), i)
|
||||||
|
if nil != err {
|
||||||
|
return nil, fmt.Errorf("Unmarshal failed %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
annotations[def.t] = i
|
||||||
|
}
|
||||||
|
|
||||||
|
return annotations, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findAnnotatedFields(t reflect.Type, ft reflect.Type, deep bool) map[string]*reflect.StructField {
|
||||||
|
fields := make(map[string]*reflect.StructField, 0)
|
||||||
|
|
||||||
|
rt, _, _ := cur.GetTypeInfo(t)
|
||||||
|
if reflect.Struct != rt.Kind() {
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
LOOP:
|
||||||
|
for i := 0; i < rt.NumField(); i++ {
|
||||||
|
f := rt.Field(i)
|
||||||
|
|
||||||
|
if f.Anonymous {
|
||||||
|
if f.Type == ft {
|
||||||
|
fields[f.Name] = &f
|
||||||
|
continue LOOP
|
||||||
|
}
|
||||||
|
if deep {
|
||||||
|
_fields := findAnnotatedFields(f.Type, ft, deep)
|
||||||
|
for _n, _f := range _fields {
|
||||||
|
fields[_n] = _f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fields
|
||||||
|
}
|
208
registry_test.go
Normal file
208
registry_test.go
Normal file
|
@ -0,0 +1,208 @@
|
||||||
|
package annotation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var InjectableAnnotationType = reflect.TypeOf((*InjectableAnnotation)(nil))
|
||||||
|
|
||||||
|
type InjectableAnnotation struct {
|
||||||
|
Annotation `@name:"@Injectable"`
|
||||||
|
Name string `json:"name" @default:""`
|
||||||
|
}
|
||||||
|
|
||||||
|
var TestServiceType = reflect.TypeOf((*TestService)(nil))
|
||||||
|
|
||||||
|
type TestService struct {
|
||||||
|
TypeAnnotation `annotation:"@Injectable('name': 'TestService')"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
parent Registry
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want Registry
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "New of nil",
|
||||||
|
args: args{
|
||||||
|
parent: nil,
|
||||||
|
},
|
||||||
|
want: New(nil),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := New(tt.args.parent); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("New() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegister(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
t reflect.Type
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Register Injectable",
|
||||||
|
args: args{
|
||||||
|
t: InjectableAnnotationType,
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Register duplicate Injectable",
|
||||||
|
args: args{
|
||||||
|
t: InjectableAnnotationType,
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if err := Register(tt.args.t); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("Register() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnnotationRegistry_Register(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
parent Registry
|
||||||
|
definitions map[string]*Definition
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
t reflect.Type
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
// TODO: Add test cases.
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
r := &AnnotationRegistry{
|
||||||
|
parent: tt.fields.parent,
|
||||||
|
definitions: tt.fields.definitions,
|
||||||
|
}
|
||||||
|
if err := r.Register(tt.args.t); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("AnnotationRegistry.Register() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGet(t *testing.T) {
|
||||||
|
Register(InjectableAnnotationType)
|
||||||
|
|
||||||
|
ts := TestService{}
|
||||||
|
tsT := reflect.TypeOf(ts)
|
||||||
|
|
||||||
|
f, _ := tsT.FieldByName("TypeAnnotation")
|
||||||
|
|
||||||
|
type args struct {
|
||||||
|
f *reflect.StructField
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want map[reflect.Type]Annotation
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Get TestService",
|
||||||
|
args: args{
|
||||||
|
f: &f,
|
||||||
|
},
|
||||||
|
want: map[reflect.Type]Annotation{
|
||||||
|
TestServiceType: &InjectableAnnotation{
|
||||||
|
Name: "TestService",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := Get(tt.args.f)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("Get() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("Get() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAnnotationRegistry_Get(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
parent Registry
|
||||||
|
definitions map[string]*Definition
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
f *reflect.StructField
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
want map[reflect.Type]Annotation
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
// TODO: Add test cases.
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
r := &AnnotationRegistry{
|
||||||
|
parent: tt.fields.parent,
|
||||||
|
definitions: tt.fields.definitions,
|
||||||
|
}
|
||||||
|
got, err := r.Get(tt.args.f)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("AnnotationRegistry.Get() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("AnnotationRegistry.Get() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_findAnnotatedFields(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
t reflect.Type
|
||||||
|
ft reflect.Type
|
||||||
|
deep bool
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want map[string]*reflect.StructField
|
||||||
|
}{
|
||||||
|
// TODO: Add test cases.
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := findAnnotatedFields(tt.args.t, tt.args.ft, tt.args.deep); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("findAnnotatedFields() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
28
type.go
Normal file
28
type.go
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package annotation
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
|
var AnnotationType = reflect.TypeOf((*Annotation)(nil)).Elem()
|
||||||
|
|
||||||
|
const AnnotationName = "Annotation"
|
||||||
|
|
||||||
|
type Annotation interface {
|
||||||
|
}
|
||||||
|
|
||||||
|
var TypeAnnotationType = reflect.TypeOf((*TypeAnnotation)(nil)).Elem()
|
||||||
|
|
||||||
|
type TypeAnnotation interface {
|
||||||
|
Annotation
|
||||||
|
}
|
||||||
|
|
||||||
|
var ConstructorAnnotationType = reflect.TypeOf((*ConstructorAnnotation)(nil)).Elem()
|
||||||
|
|
||||||
|
type ConstructorAnnotation interface {
|
||||||
|
Annotation
|
||||||
|
}
|
||||||
|
|
||||||
|
var MethodAnnotationType = reflect.TypeOf((*MethodAnnotation)(nil)).Elem()
|
||||||
|
|
||||||
|
type MethodAnnotation interface {
|
||||||
|
Annotation
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user