Skip to content

Commit fbf8d5c

Browse files
author
Evgeny Khabarov
committed
Initial commit.
1 parent 29e41dc commit fbf8d5c

47 files changed

Lines changed: 2693 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.coverprofile

.golangci.yml

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
run:
2+
skip-dirs:
3+
4+
# default is true. Enables skipping of directories:
5+
# vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
6+
skip-dirs-use-default: true
7+
8+
9+
# all available settings of specific linters
10+
linters-settings:
11+
dogsled:
12+
# checks assignments with too many blank identifiers; default is 2
13+
max-blank-identifiers: 2
14+
dupl:
15+
# tokens count to trigger issue, 150 by default
16+
threshold: 150
17+
errcheck:
18+
# report about not checking of errors in type assertions: `a := b.(MyStruct)`;
19+
# default is false: such cases aren't reported by default.
20+
check-type-assertions: true
21+
22+
# report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
23+
# default is false: such cases aren't reported by default.
24+
check-blank: true
25+
26+
funlen:
27+
lines: 100
28+
statements: 45
29+
gocognit:
30+
# minimal code complexity to report, 30 by default (but we recommend 10-20)
31+
min-complexity: 10
32+
goconst:
33+
# minimal length of string constant, 3 by default
34+
min-len: 3
35+
# minimal occurrences count to trigger, 3 by default
36+
min-occurrences: 3
37+
gocyclo:
38+
# minimal code complexity to report, 30 by default (but we recommend 10-20)
39+
min-complexity: 18
40+
gofmt:
41+
# simplify code: gofmt with `-s` option, true by default
42+
simplify: true
43+
goimports:
44+
# put imports beginning with prefix after 3rd-party packages;
45+
# it's a comma-separated list of prefixes
46+
local-prefixes: github.com/ekhabarov/sts
47+
golint:
48+
# minimal confidence for issues, default is 0.8
49+
min-confidence: 0.8
50+
gomnd:
51+
settings:
52+
mnd:
53+
# the list of enabled checks, see https://github.com/tommy-muehle/go-mnd/#checks for description.
54+
checks: argument,case,condition,operation,return,assign
55+
govet:
56+
# report about shadowed variables
57+
check-shadowing: true
58+
59+
# settings per analyzer
60+
settings:
61+
printf: # analyzer name, run `go tool vet help` to see all analyzers
62+
funcs: # run `go tool vet help printf` to see available settings for `printf` analyzer
63+
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof
64+
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf
65+
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf
66+
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf
67+
68+
# enable or disable analyzers by name
69+
enable:
70+
- atomicalign
71+
enable-all: false
72+
disable:
73+
- shadow
74+
disable-all: false
75+
lll:
76+
# max line length, lines longer will be reported. Default is 120.
77+
# '\t' is counted as 1 character by default, and can be changed with the tab-width option
78+
line-length: 80
79+
# tab width in spaces. Default to 1.
80+
tab-width: 1
81+
maligned:
82+
# print struct with more effective memory layout or not, false by default
83+
suggest-new: true
84+
misspell:
85+
# Correct spellings using locale preferences for US or UK.
86+
# Default is to use a neutral variety of English.
87+
# Setting locale to US will correct the British spelling of 'colour' to 'color'.
88+
locale: US
89+
ignore-words:
90+
- someword
91+
nakedret:
92+
# make an issue if func has more lines of code than this setting and it has naked returns; default is 30
93+
max-func-lines: 30
94+
95+
linters:
96+
disable-all: true
97+
enable:
98+
- bodyclose
99+
- deadcode
100+
- depguard
101+
- dogsled
102+
- dupl
103+
- errcheck
104+
- funlen
105+
- gochecknoinits
106+
- goconst
107+
- gocritic
108+
- gocyclo
109+
- gofmt
110+
- goimports
111+
- golint
112+
# - gomnd
113+
- goprintffuncname
114+
- gosec
115+
- gosimple
116+
- govet
117+
- ineffassign
118+
- interfacer
119+
- lll
120+
- misspell
121+
- nakedret
122+
- rowserrcheck
123+
- scopelint
124+
- staticcheck
125+
- structcheck
126+
- stylecheck
127+
- typecheck
128+
- unconvert
129+
- unparam
130+
- unused
131+
- varcheck
132+
- whitespace
133+
issues:
134+
# Excluding configuration per-path, per-linter, per-text and per-source
135+
exclude-rules:
136+
- path: _test\.go
137+
linters:
138+
- gomnd

.travis.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
language: go
2+
3+
cache:
4+
directories:
5+
- $GOPATH/pkg/mod
6+
7+
env:
8+
- GO111MODULE=on
9+
10+
go:
11+
- 1.12.x
12+
- 1.13.x
13+
- 1.14.x
14+
- tip
15+
16+
before_install:
17+
- GO111MODULE=off go get github.com/onsi/gomega
18+
- GO111MODULE=off go get github.com/onsi/ginkgo/ginkgo
19+
- go mod download
20+
- go mod verify
21+
22+
script:
23+
- ginkgo -r -race -coverprofile=coverage.txt -covermode=atomic
24+
25+
matrix:
26+
fast_finish: true
27+
allow_failures:
28+
- go: tip
29+
30+
after_success:
31+
- bash <(curl -s https://codecov.io/bash)

README.md

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# sts: struct to struct: generator of transformation functions
2+
3+
[![codecov](https://codecov.io/gh/ekhabarov/sts/branch/master/graph/badge.svg)](https://codecov.io/gh/ekhabarov/sts)
4+
[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/ekhabarov/sts)](https://github.com/ekhabarov/sts/releases)
5+
[![Travis (.org)](https://img.shields.io/travis/ekhabarov/sts)](https://travis-ci.org/ekhabarov/sts)
6+
[![GoDoc](https://godoc.org/https://godoc.org/github.com/ekhabarov/sts?status.svg)](https://godoc.org/github.com/ekhabarov/sts)
7+
8+
<!-- vim-markdown-toc GFM -->
9+
10+
* [Install](#install)
11+
* [Motivation](#motivation)
12+
* [Idea](#idea)
13+
* [Other implementations.](#other-implementations)
14+
* [How](#how)
15+
* [Step 1](#step-1)
16+
* [Step 2](#step-2)
17+
* [Example matcher](#example-matcher)
18+
* [Int2Bool, NullsTime2TimeTimePtr wait, what?](#int2bool-nullstime2timetimeptr-wait-what)
19+
* [go generate](#go-generate)
20+
* [License](#license)
21+
22+
<!-- vim-markdown-toc -->
23+
24+
## Install
25+
26+
```shell
27+
go get -u github.com/ekhabarov/sts
28+
```
29+
30+
## Motivation
31+
Working on integration between one app and different APIs (most of them,
32+
fortunately, have Go clients) includes pretty much code which transforms one
33+
structure into another, because for Go two structures with identical field set
34+
and identical types are different types. Identical types could be converted one
35+
into another with simple conversion: `targetType(destType)`, but having
36+
[identical](https://golang.org/ref/spec#Type_identity) type is too rare case.
37+
38+
That means it's necessary to write such transformations manually, which is, from
39+
one hand is tediously from another one is straightforward.
40+
41+
## Idea
42+
The idea is as simple as possible: produce set of functions which allow convert
43+
one type into another.
44+
45+
It can be done within three steps:
46+
47+
1. Source code analyze.
48+
1. Field type matching.
49+
1. Generations pair of functions: forward `SourceType2DestType` and reverse `DestType2SourceType`.
50+
51+
### Other implementations.
52+
There is a [plugin](http://github.com/bold-commerce/protoc-gen-struct-transformer) for Protobuf with the same idea.
53+
54+
## How
55+
56+
### Step 1
57+
On first step `sts` have to obtain information about structures which will be
58+
involved into transformation process by analyzing source code files contained
59+
these structures. To achieve this, packages [go/ast](https://golang.org/pkg/go/ast), [go/types](https://golang.org/pkg/go/types), etc., from
60+
standard library can be used.
61+
62+
Using these packages `sts` builds a map with data types information. For details
63+
see [parser.go](./parser.go).
64+
65+
### Step 2
66+
Information from previous step is passes to matcher. Matcher lookups two
67+
structures by name (structures names are passed via CLI params, see examples
68+
below), source (left) and destination (right). Then it builds field pairs using
69+
next rules:
70+
71+
* field on the left structure with `sts` tag will be matched with field on right side by right-side field name equals to `sts` tag value.
72+
* if right-side field not found by name, then `sts` tag value will be compared with value of provided tag list.
73+
* any fields without `sts` or other source tags will be skipped.
74+
75+
#### Example matcher
76+
Let's say we have two structures
77+
78+
```go
79+
type Source struct {
80+
I int
81+
S string
82+
I1 int `sts:"I64"`
83+
I2 int `sts:"B"`
84+
PT *time.Time `sts:"Nt"`
85+
JJ string `sts:"json_field"`
86+
D int32 `sts:"db_field"`
87+
}
88+
```
89+
90+
and
91+
92+
```go
93+
type Dest struct {
94+
I int
95+
S string
96+
I64 int64
97+
B bool
98+
Nt nulls.Time
99+
JsonField string `json:"json_field"`
100+
DB int64 `db:"db_field"`
101+
}
102+
```
103+
104+
after run a command
105+
106+
```shell
107+
sts -src /path/to/src.go:Source -dst /path/to/dst.go:Dest -o ./output -dt json,db
108+
```
109+
110+
matcher consider next combinations
111+
112+
113+
Source | Destination | Conversion | Note
114+
-------|-------------|-------------------------|------
115+
`I` | `--` | `--` | source field has not tag
116+
`S` | `--` | `--` | source field has not tag
117+
`I1` | `I64` | direct | matched `sts` tag value and field name
118+
`I2` | `B` | `Int2Bool` | matched `sts` tag value and field name
119+
`PT` | `Nt` | `NullsTime2TimeTimePtr` | matched `sts` tag value and field name
120+
`JJ` | `JsonField` | none | matched `sts` tag value and `json` tag value. `json` tag passed via `-dt` CLI parameter.
121+
`DB` | `D` | direct | matched `sts` tag value and `db` tag value. `db` tag passed via `-dt` CLI parameter.
122+
123+
124+
##### Int2Bool, NullsTime2TimeTimePtr wait, what?
125+
Matcher uses type info provided by `go/types` package. When it compares field it
126+
also checks paired field for [assignability](https://golang.org/pkg/go/types/#AssignableTo) and [convertibility](https://golang.org/pkg/go/types/#ConvertibleTo).
127+
* Assignability shows can one field be assigned to another without any conversion.
128+
* Convertibility shows can one field be directly converted to another one.
129+
130+
But in cases when fields in pair are not `assignable` and are not `convertable`,
131+
the tool just generate conversion function with name of format
132+
133+
```go
134+
<SourceType>2<DestType>
135+
// and
136+
<DestType>2<SourceType>
137+
```
138+
139+
that means it's necessary to write these helper functions manually. Fortunately,
140+
quantity of such function should be low. Number of examples can be found in
141+
[examples](./examples/output/helpers.go) package.
142+
143+
144+
## go generate
145+
Go has a command `go generate` ([blog](https://blog.golang.org/generate)|[proposal](https://docs.google.com/document/d/1V03LUfjSADDooDMhe-_K59EgpTEm3V8uvQRuNMAEnjg/edit)).
146+
This command allows to run tools mentioned in special comments in Go code, like
147+
this:
148+
149+
```go
150+
//go:generate sts -src $GOFILE:Source -dst $GOFILE:Dest -o ./output -dt json,db
151+
type Source struct {
152+
I int
153+
...
154+
```
155+
156+
after `go generate ./...` will be run, it in turn, will run `sts` tool with
157+
given parameters. `$GOFILE` variable will be replaced with a path to current
158+
`.go` file by `go generate` tool.
159+
160+
161+
## License
162+
163+
MIT License
164+
165+
Copyright (c) 2020 Evgeny Khabarov
166+
167+
Permission is hereby granted, free of charge, to any person obtaining a copy
168+
of this software and associated documentation files (the "Software"), to deal
169+
in the Software without restriction, including without limitation the rights
170+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
171+
copies of the Software, and to permit persons to whom the Software is
172+
furnished to do so, subject to the following conditions:
173+
174+
The above copyright notice and this permission notice shall be included in all
175+
copies or substantial portions of the Software.
176+
177+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
178+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
179+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
180+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
181+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
182+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
183+
SOFTWARE.
184+

0 commit comments

Comments
 (0)