├── .gitignore
├── .idea
└── vcs.xml
├── .travis.yml
├── LICENSE
├── Makefile
├── README.md
├── action.go
├── ensure_dir.go
├── go.mod
├── internal
├── generator
│ ├── array_field.go
│ ├── chstuff
│ │ ├── ch2generic.go
│ │ ├── chstuff.go
│ │ └── decomposeEnum.go
│ ├── date_field.go
│ ├── datetime_field.go
│ ├── decimal128_field.go
│ ├── decimal32_field.go
│ ├── decimal64_field.go
│ ├── enum16_field.go
│ ├── enum8_field.go
│ ├── field.go
│ ├── field_set.go
│ ├── fixed_string_field.go
│ ├── float32_field.go
│ ├── float64_field.go
│ ├── generation.go
│ ├── generator.go
│ ├── gogen
│ │ ├── encoder.go
│ │ ├── encoding.go
│ │ ├── fieldtypes.go
│ │ ├── filterencoder.go
│ │ ├── generator.go
│ │ ├── helpers.go
│ │ ├── native-typenames.go
│ │ ├── regular-typenames.go
│ │ ├── testing-encoding.go
│ │ ├── testing-typenames.go
│ │ ├── testingencoder.go
│ │ ├── util.go
│ │ └── voidencoder.go
│ ├── int16_field.go
│ ├── int32_field.go
│ ├── int64_field.go
│ ├── int8_field.go
│ ├── nullable_array_field.go
│ ├── nullable_field.go
│ ├── nullable_string_field.go
│ ├── string_field.go
│ ├── uint16_field.go
│ ├── uint32_field.go
│ ├── uint64_field.go
│ ├── uint8_field.go
│ └── uuid_field.go
└── util
│ ├── components_parser.go
│ ├── envch_params.go
│ ├── extractor_lde.go
│ ├── testingch.go
│ ├── testingch_test.go
│ └── userpass_parser.go
├── main.go
└── screenshot.png
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.dll
4 | *.so
5 | *.dylib
6 |
7 | # Test binary, build with `go test -c`
8 | *.test
9 |
10 | # Output of the go coverage tool, specifically when used with LiteIDE
11 | *.out
12 |
13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
14 | .glide/
15 |
16 | # Hide JetBrains stuff
17 | .idea/
18 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: go
3 | go:
4 | - 1.9.x
5 |
6 | install:
7 | - go get -v -d -t github.com/sirkon/ch-encode
8 |
9 | script:
10 | - echo done
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Denis
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | set-version:
2 | git tag | tail -1 | xargs printf 'package main\n\nvar version="%s"\n' > version.go
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ch-encode
2 | [](https://travis-ci.org/sirkon/ch-encode)
3 |
4 | Clickhouse typesafe RowBinary insert data encoder generator for Go. Supported types:
5 |
6 | String | FixedString(N) | UUID* | UIntX | IntX |Decimal(X,Y)| EnumX | Array(T)|FloatX|Nested**|Nullable(T)|
7 | -------|----------------|------|-------|------|-------------|-------|---------|------|------|--------|
8 |
9 | > * UUID type rely on `github.com/google/uuid`
10 |
11 |
12 | > \*\* Nested types are supported in the same sense they are used in the Clickhouse itself: despite having clearly
13 | nested declaration they are represented as ordinary fields with dotted names (`[nested name].[subfield name]`) with DB level
14 | constraint on these fields (what are ordinary arrays of subfield's types) having the same length. We did the same: there's encoder level control implemented for Raw and Testing encoders for these arrays' length. There's no syntactic level grouping for them.
15 |
16 | This piece is for record data generation only. See https://github.com/sirkon/ch-insert for actual data insert
17 |
18 | ## The problem
19 | * Tables can be wide and thus generating proper INSERT statements is a source of errors itself.
20 | * Proper data buffering on INSERT statements is a bit tricky
21 |
22 | ## Solution
23 | * Take Clickhouse table. Map it into the function call (column → function parameter)
24 | * Function generates binary data in Clickhouse RowBinary format. Buffering became trivial (special bufferer is needed anyway -
25 | function call output which represents a table record must not be splitted)
26 |
27 | ## Problems raised
28 | * Scheme desync. Basically solved as we check current table' schema when creating an encoder.
29 |
30 | ## Bonuses
31 | * Testing mock objects can be generated as well.
32 | * It is fast as it avoids any allocation in a process and RawBinary should be the easiest format (I don't know exactly though) after Native for the Clickhouse.
33 |
34 |
35 | # How to use
36 | ```bash
37 | go get github.com/sirkon/ch-encode
38 | ```
39 | then
40 | ```bash
41 | ch-encode --date-field date table1 table2 … tableN
42 | ```
43 | will generated N packages in the current folder with autogenerated encoders and desync tests. Use `--date-field ''` in case if the table doesn't have any `Date` field.
44 |
45 | Example.
46 |
47 | 1. Let's create clickhouse table
48 | ```sql
49 | CREATE TABLE test
50 | (
51 | date Date,
52 | uid String,
53 | hidden UInt8
54 | ) ENGINE = MergeTree(date, (date, uid, hidden), 8192);
55 | ```
56 | 2. Let we have translation dictionary translation.yaml
57 | ```yaml
58 | uid: UID
59 | ```
60 | we use translation in order uid to be translated into UID in generated code. Something like
61 | `first_uid` or `firstUid` will be translated into `firstUID` as well.
62 | 3. Now generate encoder
63 | ```bash
64 | bin/ch-encode --yaml-dict=translation.yaml test
65 | ```
66 | test directory will appear in current directory, it will have two go files, test.go and test_test.go.
67 | this is **test** package. Move it into src to be seen by **go install**
68 | 4. Get [ch-insert](https://github.com/sirkon/ch-insert) package:
69 | ```
70 | go get -u github.com/sirkon/ch-insert
71 | ```
72 | 5. Now, some code
73 | ```go
74 | // file main.go
75 | package main
76 |
77 | import (
78 | "net/http"
79 | "test"
80 | "time"
81 |
82 | chinsert "github.com/sirkon/ch-insert"
83 | )
84 |
85 | func main() {
86 | inserter, err := chinsert.Open("localhost:8123/default", "test", 10*1024*1024, 1024*1024*1024)
87 | if err != nil {
88 | panic(err)
89 | }
90 | defer inserter.Close()
91 |
92 | encoder, err := test.NewTestRawEncoder(inserter)
93 | if err != nil {
94 | panic(err) // must be scheme desync
95 | }
96 | if err := encoder.Encode(test.Date.FromTime(time.Now()), test.UID("123"), test.Hidden(1)); err != nil {
97 | panic(err)
98 | }
99 | if err := encoder.Encode(test.Date.FromTime(time.Now()), test.UID("123"), test.Hidden(0)); err != nil {
100 | panic(err)
101 | }
102 | }
103 |
104 | ```
105 | 6. See test table now
106 |
107 | 
108 |
109 | # Details
110 | Let's see into the generated test.go file.
111 |
112 | There's test encoder
113 | ```go
114 | type TestEncoder interface {
115 | Encode(date DateType, uid UID, hidden Hidden) error
116 | InsertCount() int
117 | ErrorCount() int
118 | }
119 | ```
120 |
121 | And there are 4 types that implements TestEncoder:
122 | ```go
123 | type TestRawEncoder {} // Regular RowBinary data generator
124 | type TestRawEncoderDateFilter{} // Will only generate data for a given date
125 | type TestRawEncoderVoid{} // Will not generate anything
126 | type TestingTestEncoder{} // For testing purposes
127 | ```
128 |
129 | We saw how the TestRawEncoder works. Bother TestRawEncoderDateFilter and TestRawEncoderVoid work the same way. Let's see how to use TestingTestEncoder:
130 | ```go
131 | package main
132 |
133 | import (
134 | "test"
135 | "time"
136 |
137 | "github.com/sanity-io/litter"
138 | )
139 |
140 | func main() {
141 | e := test.NewTestingTestEncoder()
142 | date := time.Date(2006, 1, 2, 3, 4, 5, 0, time.UTC)
143 | err := e.Encode(
144 | test.Date.FromTime(date),
145 | test.UID("123123"),
146 | test.Hidden(1))
147 | if err != nil {
148 | panic(err)
149 | }
150 | err = e.Encode(
151 | test.Date.FromTime(date),
152 | test.UID("321321"),
153 | test.Hidden(0))
154 | if err != nil {
155 | panic(err)
156 | }
157 |
158 | litter.Dump(e.Result)
159 | }
160 |
161 | ```
162 | this program will output
163 | ```go
164 | []test.TestingTestResult{
165 | test.TestingTestResult{
166 | Date: "2006-01-02",
167 | UID: "123123",
168 | Hidden: 1,
169 | },
170 | test.TestingTestResult{
171 | Date: "2006-01-02",
172 | UID: "321321",
173 | Hidden: 0,
174 | },
175 | }
176 | ```
177 | Good for testing, you see.
178 | DateTime type will be represented as `%Y-%m-%dT%H:%M:%S` string, enums will be represented as their text values. Other clickhouse types match directly into Golang equivalents (Int16 -> int16, Float64 -> float64, UInt32 -> uint32, nullables as pointers to types except `Nullable(Array(T))` which are represented as regular `[]τ` slices, etc)
179 |
--------------------------------------------------------------------------------
/action.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "database/sql"
6 | "encoding/json"
7 | "io"
8 | "io/ioutil"
9 | "os"
10 |
11 | "github.com/go-yaml/yaml"
12 |
13 | "github.com/sirkon/gosrcfmt"
14 | "github.com/sirkon/gotify"
15 | "github.com/sirkon/message"
16 |
17 | "github.com/sirkon/ch-encode/internal/generator"
18 | "github.com/sirkon/ch-encode/internal/generator/chstuff"
19 | "github.com/sirkon/ch-encode/internal/generator/gogen"
20 | "github.com/sirkon/ch-encode/internal/util"
21 | )
22 |
23 | func yamlSource(path string) map[string]string {
24 | res := map[string]string{}
25 | data, err := ioutil.ReadFile(path)
26 | if err != nil {
27 | message.Criticalf("Cannot read `%s`: %s", path, err)
28 | }
29 | if err := yaml.Unmarshal(data, &res); err != nil {
30 | message.Criticalf("Cannot parse `%s` as YAML file: %s", path, err)
31 | }
32 | return res
33 | }
34 |
35 | func jsonSource(path string) map[string]string {
36 | res := map[string]string{}
37 | data, err := ioutil.ReadFile(path)
38 | if err != nil {
39 | message.Criticalf("Cannot read `%s`: %s", path, err)
40 | }
41 | if err := json.Unmarshal(data, &res); err != nil {
42 | message.Criticalf("Cannot parse `%s` as JSON file: %s", path, err)
43 | }
44 | return res
45 | }
46 |
47 | func action(isTesting bool, yamlDict string, jsonDict string, dateField string, tables []string, connParams string) error {
48 | var dict map[string]string
49 | if len(yamlDict) > 0 {
50 | dict = yamlSource(yamlDict)
51 | }
52 | if len(jsonDict) > 0 {
53 | dict = jsonSource(jsonDict)
54 | }
55 |
56 | goish := gotify.New(dict)
57 |
58 | prms := util.EnvCHParams(connParams)
59 | connect, err := sql.Open("clickhouse", prms.DBURL())
60 | if err != nil {
61 | panic(err)
62 | }
63 |
64 | for _, table := range tables {
65 | metas, err := chstuff.RetrieveTableMeta(connect, table)
66 | if err != nil {
67 | message.Critical(err)
68 | }
69 |
70 | partWriter := &bytes.Buffer{}
71 | gen := gogen.New(table, goish, partWriter)
72 | fields := generator.NewFieldSet(gen)
73 | dateArg := func(gen generator.Generator) string { return "" }
74 | for _, meta := range metas {
75 | fieldInfo := chstuff.Meta2Field(meta)
76 | if meta.Name == dateField {
77 | dateArg = func(gen generator.Generator) string { return fieldInfo.ArgName(gen) }
78 | }
79 | fields.Add(fieldInfo)
80 | }
81 |
82 | if err = generator.Generate(gen, dateArg, fields); err != nil {
83 | message.Critical(err)
84 | }
85 | writer := &bytes.Buffer{}
86 | var output io.WriteCloser
87 | if isTesting {
88 | output = os.Stdout
89 | } else {
90 | output, err = GoModule(goish, table)
91 | if err != nil {
92 | message.Critical(err)
93 | }
94 | }
95 | gen.Header(writer)
96 | io.Copy(writer, partWriter)
97 | gosrcfmt.FormatReader(output, writer)
98 | if err = output.Close(); err != nil {
99 | message.Critical(err)
100 | }
101 |
102 | message.Noticef("Table `\033[1m%s\033[0m` encoder generated", table)
103 | }
104 | return nil
105 | }
106 |
--------------------------------------------------------------------------------
/ensure_dir.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "log"
7 | "os"
8 | "path/filepath"
9 |
10 | "github.com/sirkon/gotify"
11 | )
12 |
13 | // GoModule creates
14 | func GoModule(goish *gotify.Gotify, table string) (writer io.WriteCloser, err error) {
15 | packagepath := filepath.Join(goish.Goimports(table))
16 | fi, err := os.Stat(packagepath)
17 | if err != nil {
18 | err = os.MkdirAll(packagepath, 0777)
19 | if err != nil {
20 | return
21 | }
22 | } else {
23 | if !fi.IsDir() {
24 | err = fmt.Errorf("%s exists and is not a folder", packagepath)
25 | return
26 | }
27 | }
28 | path := filepath.Join(packagepath, goish.Goimports(table)+".go")
29 | writer, err = os.Create(path)
30 | return
31 | }
32 |
33 | // GoModuleTest creates
34 | func GoModuleTest(goish *gotify.Gotify, table string) (path string) {
35 | packagepath := filepath.Join(goish.Goimports(table))
36 | fi, err := os.Stat(packagepath)
37 | if err != nil {
38 | err = os.MkdirAll(packagepath, 0777)
39 | if err != nil {
40 | log.Fatal(err)
41 | }
42 | } else {
43 | if !fi.IsDir() {
44 | log.Fatalf("%s exists and is not a folder", packagepath)
45 | }
46 | }
47 | path = filepath.Join(packagepath, goish.Goimports(table)+"_test.go")
48 | return
49 | }
50 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/sirkon/ch-encode
2 |
3 | require (
4 | github.com/go-yaml/yaml v0.0.0-20170812160011-eb3733d160e7
5 | github.com/jawher/mow.cli v1.1.0
6 | github.com/kr/pretty v0.1.0 // indirect
7 | github.com/mailru/go-clickhouse v1.1.0
8 | github.com/sirkon/binenc v1.1.0
9 | github.com/sirkon/ch-insert v1.1.0 // indirect
10 | github.com/sirkon/go-diff v1.0.0
11 | github.com/sirkon/gosrcfmt v1.5.0
12 | github.com/sirkon/gotify v0.6.0
13 | github.com/sirkon/message v1.5.1
14 | github.com/stretchr/testify v1.3.0
15 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
16 | gopkg.in/yaml.v2 v2.2.2 // indirect
17 | )
18 |
--------------------------------------------------------------------------------
/internal/generator/array_field.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | // Array Type implementation
4 | type Array struct {
5 | field string
6 | fieldType string
7 | meta Field
8 | }
9 |
10 | // NewArray constructor
11 | func NewArray(field, fieldType string, meta Field) *Array {
12 | return &Array{
13 | field: field,
14 | fieldType: fieldType,
15 | meta: meta,
16 | }
17 | }
18 |
19 | // FieldName ...
20 | func (a *Array) FieldName(gen Generator) string {
21 | return a.field
22 | }
23 |
24 | // FieldTypeName ...
25 | func (a *Array) FieldTypeName(gen Generator) string {
26 | return a.fieldType
27 | }
28 |
29 | // TypeName ...
30 | func (a *Array) TypeName(gen Generator) string {
31 | return gen.EasyTypeName(a.field)
32 | }
33 |
34 | // ArgName ...
35 | func (a *Array) ArgName(gen Generator) string {
36 | return gen.VarName(a.field)
37 | }
38 |
39 | // AccessName ...
40 | func (a *Array) AccessName(gen Generator) string {
41 | return gen.HelperName(a.field)
42 | }
43 |
44 | // NativeTypeName Array implementation
45 | func (a *Array) NativeTypeName(gen Generator) string {
46 | return gen.ArrayNativeTypeName(a.meta)
47 | }
48 |
49 | // Encoding Array implementation
50 | func (a *Array) Encoding(source string, gen Generator) error {
51 | return gen.ArrayEncoding(source, a.meta)
52 | }
53 |
54 | // Helper ...
55 | func (a *Array) Helper(gen Generator) error {
56 | return nil
57 | }
58 |
59 | // TestingTypeName ...
60 | func (a *Array) TestingTypeName(gen Generator) string {
61 | return gen.ArrayTestingTypeName(a.meta)
62 | }
63 |
64 | // TestEncoding ...
65 | func (a *Array) TestEncoding(source string, gen Generator) error {
66 | return gen.ArrayTestEncoding(source, a.meta)
67 | }
68 |
--------------------------------------------------------------------------------
/internal/generator/chstuff/ch2generic.go:
--------------------------------------------------------------------------------
1 | package chstuff
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/sirkon/ch-encode/internal/generator"
7 | )
8 |
9 | // Meta2Field translates clickhouse field metainfo into generator Field object
10 | func Meta2Field(meta FieldMeta) (field generator.Field) {
11 | switch meta.Type {
12 | case "Int8":
13 | field = generator.NewInt8(meta.Name, meta.RawType)
14 | case "Int16":
15 | field = generator.NewInt16(meta.Name, meta.RawType)
16 | case "Int32":
17 | field = generator.NewInt32(meta.Name, meta.RawType)
18 | case "Int64":
19 | field = generator.NewInt64(meta.Name, meta.RawType)
20 | case "UInt8":
21 | field = generator.NewUint8(meta.Name, meta.RawType)
22 | case "UInt16":
23 | field = generator.NewUint16(meta.Name, meta.RawType)
24 | case "UInt32":
25 | field = generator.NewUint32(meta.Name, meta.RawType)
26 | case "UInt64":
27 | field = generator.NewUint64(meta.Name, meta.RawType)
28 | case "Float32":
29 | field = generator.NewFloat32(meta.Name, meta.RawType)
30 | case "Float64":
31 | field = generator.NewFloat64(meta.Name, meta.RawType)
32 | case "String":
33 | field = generator.NewString(meta.Name, meta.RawType)
34 | case "FixedString":
35 | field = generator.NewFixedString(meta.Name, meta.RawType, meta.FixedStringLen)
36 | case "UUID":
37 | field = generator.NewUUIDField(meta.Name, meta.RawType)
38 | case "Date":
39 | field = generator.NewDate(meta.Name, meta.RawType)
40 | case "DateTime":
41 | field = generator.NewDateTime(meta.Name, meta.RawType)
42 | case "Enum8":
43 | field = generator.NewEnum8(meta.Name, meta.RawType, meta.EnumData)
44 | case "Enum16":
45 | field = generator.NewEnum16(meta.Name, meta.RawType, meta.EnumData)
46 | case "Array":
47 | field = generator.NewArray(meta.Name, meta.RawType, Meta2Field(*meta.Subtype))
48 | case "Nullable":
49 | if meta.Subtype == nil {
50 | panic(fmt.Errorf("integrity error, nullable type must be nullable type of T, not just nullable"))
51 | }
52 | switch meta.Subtype.Type {
53 | case "String":
54 | field = generator.NewNullableString(meta.Name, meta.RawType)
55 | case "Array":
56 | field = generator.NewNullableArray(meta.Name, meta.RawType, Meta2Field(*meta.Subtype))
57 | default:
58 | field = generator.NewNullable(meta.Name, meta.RawType, Meta2Field(*meta.Subtype))
59 | }
60 | case "Decimal32":
61 | field = generator.NewDecimal32(meta.Name, meta.RawType, meta.Decimal.Precision, meta.Decimal.Scale)
62 | case "Decimal64":
63 | field = generator.NewDecimal64(meta.Name, meta.RawType, meta.Decimal.Precision, meta.Decimal.Scale)
64 | case "Decimal128":
65 | field = generator.NewDecimal128(meta.Name, meta.RawType, meta.Decimal.Precision, meta.Decimal.Scale)
66 | default:
67 | panic(fmt.Errorf("unsupported clickhouse type %s for field %s", meta.RawType, meta.Name))
68 | }
69 | return
70 | }
71 |
--------------------------------------------------------------------------------
/internal/generator/chstuff/chstuff.go:
--------------------------------------------------------------------------------
1 | package chstuff
2 |
3 | import (
4 | "database/sql"
5 | "strconv"
6 | "strings"
7 |
8 | "github.com/sirkon/ch-encode/internal/util"
9 | )
10 |
11 | // FieldMeta field description
12 | type FieldMeta struct {
13 | Name string
14 | Type string
15 | RawType string
16 | EnumData map[string]int
17 | FixedStringLen int
18 | Decimal struct {
19 | Bits int // разрядность в битах для данного Decimal
20 | Precision int // количество цифр в записи числа
21 | Scale int // количество цифр в дробной части
22 | }
23 | Subtype *FieldMeta
24 | }
25 |
26 | func retreiveField(name, ftype string) (meta FieldMeta, err error) {
27 | decExtractor := util.Extractor{}
28 | meta.Name = name
29 | if strings.HasPrefix(ftype, "Enum8(") && strings.HasSuffix(ftype, ")") {
30 | meta.Type = "Enum8"
31 | meta.RawType = ftype
32 | meta.EnumData, err = decomposeEnumArgs(ftype)
33 | if err != nil {
34 | return
35 | }
36 | } else if strings.HasPrefix(ftype, "Enum16(") && strings.HasSuffix(ftype, ")") {
37 | meta.Type = "Enum16"
38 | meta.RawType = ftype
39 | meta.EnumData, err = decomposeEnumArgs(ftype)
40 | if err != nil {
41 | return
42 | }
43 | } else if strings.HasPrefix(ftype, "Array(") && strings.HasSuffix(ftype, ")") {
44 | meta.Type = "Array"
45 | meta.RawType = ftype
46 | submeta, err := retreiveField("", ftype[6:len(ftype)-1])
47 | if err != nil {
48 | return meta, err
49 | }
50 | meta.Subtype = &submeta
51 | } else if strings.HasPrefix(ftype, "FixedString(") && strings.HasSuffix(ftype, ")") {
52 | meta.Type = "FixedString"
53 | meta.RawType = ftype
54 | submeta := ftype[len("FixedString(") : len(ftype)-1]
55 | length, err := strconv.ParseInt(submeta, 10, 64)
56 | if err != nil {
57 | return meta, err
58 | }
59 | meta.FixedStringLen = int(length)
60 | } else if strings.HasPrefix(ftype, "Nullable(") && strings.HasSuffix(ftype, ")") {
61 | meta.Type = "Nullable"
62 | meta.RawType = ftype
63 | submeta, err := retreiveField(name, ftype[9:len(ftype)-1])
64 | if err != nil {
65 | return meta, err
66 | }
67 | meta.Subtype = &submeta
68 | } else if ok, _ := decExtractor.Extract(ftype); ok {
69 | meta.Type = "Decimal"
70 | meta.RawType = ftype
71 | meta.Decimal.Precision = decExtractor.Precision
72 | meta.Decimal.Scale = decExtractor.Scale
73 | switch {
74 | case decExtractor.Precision <= 9:
75 | meta.Decimal.Bits = 32
76 | meta.Type += "32"
77 | case decExtractor.Precision <= 18:
78 | meta.Decimal.Bits = 64
79 | meta.Type += "64"
80 | case decExtractor.Precision <= 38:
81 | meta.Decimal.Bits = 128
82 | meta.Type += "128"
83 | }
84 | } else {
85 | meta.Type = ftype
86 | meta.RawType = ftype
87 | }
88 | return
89 | }
90 |
91 | // RetrieveTableMeta retrieves clickhouse table metainformation
92 | func RetrieveTableMeta(conn *sql.DB, table string) (res []FieldMeta, err error) {
93 | rows, err := conn.Query("SELECT name, type FROM system.columns WHERE table = ?", table)
94 | if err != nil {
95 | return
96 | }
97 |
98 | var fieldName string
99 | var fieldType string
100 |
101 | for rows.Next() {
102 | if err := rows.Scan(&fieldName, &fieldType); err != nil {
103 | return res, err
104 | }
105 | meta, err := retreiveField(fieldName, fieldType)
106 | if err != nil {
107 | return nil, err
108 | }
109 | res = append(res, meta)
110 | }
111 | if rows.Err() != nil {
112 | return res, rows.Err()
113 | }
114 | return
115 | }
116 |
--------------------------------------------------------------------------------
/internal/generator/chstuff/decomposeEnum.go:
--------------------------------------------------------------------------------
1 | package chstuff
2 |
3 | import (
4 | "fmt"
5 | "strconv"
6 | "strings"
7 | )
8 |
9 | // Enum{8,16}('arg1'=val1,....,'argN'=valN) arguments decompositor
10 | func decomposeEnumArgs(enum string) (res map[string]int, err error) {
11 | res = map[string]int{}
12 | makeError := func(appendix string) {
13 | err = fmt.Errorf("%s is not an enum"+appendix, enum)
14 | }
15 |
16 | // Must be either Enum8 or Enum16
17 | if !(strings.HasPrefix(enum, "Enum8") || strings.HasPrefix(enum, "Enum16")) {
18 | makeError("")
19 | return
20 | }
21 |
22 | // Locate data between ( and )
23 | pos := strings.IndexByte(enum, '(')
24 | if pos < 0 {
25 | makeError("")
26 | return
27 | }
28 | data := enum[pos+1:]
29 | pos = strings.IndexByte(data, ')')
30 | if pos < 0 {
31 | makeError("")
32 | return
33 | }
34 | data = data[:pos]
35 |
36 | // Extract arguments
37 | for _, litCouple := range strings.Split(data, ",") {
38 | couple := strings.Split(litCouple, "=")
39 | if len(couple) != 2 {
40 | makeError(". The most probable reason is enum key value having unnacceptable characters. Try to use alnums only")
41 | return
42 | }
43 | key := strings.Trim(couple[0], " ")
44 | key = strings.TrimPrefix(key, "'")
45 | key = strings.TrimSuffix(key, "'")
46 | num, err := strconv.Atoi(strings.TrimSpace(couple[1]))
47 | if err != nil {
48 | return res, fmt.Errorf("Cannot parse numeric value `%s` for %s", couple[1], key)
49 | }
50 | res[key] = int(num)
51 | }
52 | return
53 | }
54 |
--------------------------------------------------------------------------------
/internal/generator/date_field.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | // Date Type implementation
4 | type Date struct {
5 | field string
6 | fieldType string
7 | }
8 |
9 | // NewDate constructor
10 | func NewDate(field, fieldType string) *Date {
11 | return &Date{
12 | field: field,
13 | fieldType: fieldType,
14 | }
15 | }
16 |
17 | // FieldName ...
18 | func (d *Date) FieldName(gen Generator) string {
19 | return d.field
20 | }
21 |
22 | // FieldTypeName ...
23 | func (d *Date) FieldTypeName(gen Generator) string {
24 | return d.fieldType
25 | }
26 |
27 | // TypeName ...
28 | func (d *Date) TypeName(gen Generator) string {
29 | return gen.UneasyTypeName(d.field)
30 | }
31 |
32 | // ArgName ...
33 | func (d *Date) ArgName(gen Generator) string {
34 | return gen.VarName(d.field)
35 | }
36 |
37 | // AccessName ...
38 | func (d *Date) AccessName(gen Generator) string {
39 | return gen.HelperName(d.field)
40 | }
41 |
42 | // NativeTypeName Date implementation
43 | func (d *Date) NativeTypeName(gen Generator) string {
44 | return gen.Uint16NativeTypeName()
45 | }
46 |
47 | // Encoding ...
48 | func (d *Date) Encoding(source string, gen Generator) error {
49 | return gen.Uint16Encoding(source)
50 | }
51 |
52 | // Helper ...
53 | func (d *Date) Helper(gen Generator) error {
54 | return gen.DateHelpers(d)
55 | }
56 |
57 | // TestingTypeName ...
58 | func (d *Date) TestingTypeName(gen Generator) string {
59 | return gen.DateTestingTypeName()
60 | }
61 |
62 | // TestEncoding ...
63 | func (d *Date) TestEncoding(source string, gen Generator) error {
64 | return gen.DateTestEncoding(source)
65 | }
66 |
--------------------------------------------------------------------------------
/internal/generator/datetime_field.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | // DateTime Type implementation
4 | type DateTime struct {
5 | field string
6 | fieldType string
7 | }
8 |
9 | // NewDateTime constructor
10 | func NewDateTime(field, fieldType string) *DateTime {
11 | return &DateTime{
12 | field: field,
13 | fieldType: fieldType,
14 | }
15 | }
16 |
17 | // FieldName ...
18 | func (dt *DateTime) FieldName(gen Generator) string {
19 | return dt.field
20 | }
21 |
22 | // FieldTypeName ...
23 | func (dt *DateTime) FieldTypeName(gen Generator) string {
24 | return dt.fieldType
25 | }
26 |
27 | // TypeName ...
28 | func (dt *DateTime) TypeName(gen Generator) string {
29 | return gen.UneasyTypeName(dt.field)
30 | }
31 |
32 | // ArgName ...
33 | func (dt *DateTime) ArgName(gen Generator) string {
34 | return gen.VarName(dt.field)
35 | }
36 |
37 | // AccessName ...
38 | func (dt *DateTime) AccessName(gen Generator) string {
39 | return gen.HelperName(dt.field)
40 | }
41 |
42 | // NativeTypeName ...
43 | func (dt *DateTime) NativeTypeName(gen Generator) string {
44 | return gen.Uint32NativeTypeName()
45 | }
46 |
47 | // Encoding ...
48 | func (dt *DateTime) Encoding(source string, gen Generator) error {
49 | return gen.Uint32Encoding(source)
50 | }
51 |
52 | // Helper ...
53 | func (dt *DateTime) Helper(gen Generator) error {
54 | return gen.DateTimeHelpers(dt)
55 | }
56 |
57 | // TestingTypeName ...
58 | func (dt *DateTime) TestingTypeName(gen Generator) string {
59 | return gen.DateTimeTestingTypeName()
60 | }
61 |
62 | // TestEncoding ...
63 | func (dt *DateTime) TestEncoding(source string, gen Generator) error {
64 | return gen.DateTimeTestEncoding(source)
65 | }
66 |
--------------------------------------------------------------------------------
/internal/generator/decimal128_field.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | // Decimal128 type implementation
4 | type Decimal128 struct {
5 | field string
6 | fieldType string
7 | precision int
8 | scale int
9 | }
10 |
11 | func NewDecimal128(field string, fieldType string, precision int, scale int) *Decimal128 {
12 | return &Decimal128{field: field, fieldType: fieldType, precision: precision, scale: scale}
13 | }
14 |
15 | func (d *Decimal128) FieldName(gen Generator) string {
16 | return d.field
17 | }
18 |
19 | func (d *Decimal128) FieldTypeName(gen Generator) string {
20 | return d.fieldType
21 | }
22 |
23 | func (d *Decimal128) TypeName(gen Generator) string {
24 | return gen.UneasyTypeName(d.field)
25 | }
26 |
27 | func (d *Decimal128) ArgName(gen Generator) string {
28 | return gen.VarName(d.field)
29 | }
30 |
31 | func (d *Decimal128) AccessName(gen Generator) string {
32 | return gen.HelperName(d.field)
33 | }
34 |
35 | func (d *Decimal128) NativeTypeName(gen Generator) string {
36 | return gen.Dec128NativeTypeName()
37 | }
38 |
39 | func (d *Decimal128) Encoding(source string, gen Generator) error {
40 | return gen.Dec128Encoding(source)
41 | }
42 |
43 | func (d *Decimal128) Helper(gen Generator) error {
44 | return gen.Dec128Helpers(d)
45 | }
46 |
47 | func (d *Decimal128) TestingTypeName(gen Generator) string {
48 | return gen.Dec128TestingTypeName()
49 | }
50 |
51 | func (d *Decimal128) TestEncoding(source string, gen Generator) error {
52 | return gen.Dec128TestEncoding(d.scale, source)
53 | }
54 |
--------------------------------------------------------------------------------
/internal/generator/decimal32_field.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | // Decimal32 type implementation
4 | type Decimal32 struct {
5 | field string
6 | fieldType string
7 | precision int
8 | scale int
9 | }
10 |
11 | func NewDecimal32(field string, fieldType string, precision int, scale int) *Decimal32 {
12 | return &Decimal32{field: field, fieldType: fieldType, precision: precision, scale: scale}
13 | }
14 |
15 | func (d *Decimal32) FieldName(gen Generator) string {
16 | return d.field
17 | }
18 |
19 | func (d *Decimal32) FieldTypeName(gen Generator) string {
20 | return d.fieldType
21 | }
22 |
23 | func (d *Decimal32) TypeName(gen Generator) string {
24 | return gen.EasyTypeName(d.field)
25 | }
26 |
27 | func (d *Decimal32) ArgName(gen Generator) string {
28 | return gen.VarName(d.field)
29 | }
30 |
31 | func (d *Decimal32) AccessName(gen Generator) string {
32 | return gen.HelperName(d.field)
33 | }
34 |
35 | func (d *Decimal32) NativeTypeName(gen Generator) string {
36 | return gen.Uint32NativeTypeName()
37 | }
38 |
39 | func (d *Decimal32) Encoding(source string, gen Generator) error {
40 | return gen.Uint32Encoding(source)
41 | }
42 |
43 | func (d *Decimal32) Helper(gen Generator) error {
44 | return nil
45 | }
46 |
47 | func (d *Decimal32) TestingTypeName(gen Generator) string {
48 | return gen.Dec32TestingTypeName()
49 | }
50 |
51 | func (d *Decimal32) TestEncoding(source string, gen Generator) error {
52 | return gen.Dec32TestEncoding(d.scale, source)
53 | }
54 |
--------------------------------------------------------------------------------
/internal/generator/decimal64_field.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | // Decimal64 type implementation
4 | type Decimal64 struct {
5 | field string
6 | fieldType string
7 | precision int
8 | scale int
9 | }
10 |
11 | func NewDecimal64(field string, fieldType string, precision int, scale int) *Decimal64 {
12 | return &Decimal64{field: field, fieldType: fieldType, precision: precision, scale: scale}
13 | }
14 |
15 | func (d *Decimal64) FieldName(gen Generator) string {
16 | return d.field
17 | }
18 |
19 | func (d *Decimal64) FieldTypeName(gen Generator) string {
20 | return d.fieldType
21 | }
22 |
23 | func (d *Decimal64) TypeName(gen Generator) string {
24 | return gen.EasyTypeName(d.field)
25 | }
26 |
27 | func (d *Decimal64) ArgName(gen Generator) string {
28 | return gen.VarName(d.field)
29 | }
30 |
31 | func (d *Decimal64) AccessName(gen Generator) string {
32 | return gen.HelperName(d.field)
33 | }
34 |
35 | func (d *Decimal64) NativeTypeName(gen Generator) string {
36 | return gen.Uint64NativeTypeName()
37 | }
38 |
39 | func (d *Decimal64) Encoding(source string, gen Generator) error {
40 | return gen.Uint64Encoding(source)
41 | }
42 |
43 | func (d *Decimal64) Helper(gen Generator) error {
44 | return nil
45 | }
46 |
47 | func (d *Decimal64) TestingTypeName(gen Generator) string {
48 | return gen.Dec64TestingTypeName()
49 | }
50 |
51 | func (d *Decimal64) TestEncoding(source string, gen Generator) error {
52 | return gen.Dec64TestEncoding(d.scale, source)
53 | }
54 |
--------------------------------------------------------------------------------
/internal/generator/enum16_field.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | // Enum16 Type implementation
4 | type Enum16 struct {
5 | field string
6 | fieldType string
7 | choices map[string]int
8 | }
9 |
10 | // NewEnum16 constructor
11 | func NewEnum16(field, fieldType string, choices map[string]int) *Enum16 {
12 | return &Enum16{
13 | field: field,
14 | fieldType: fieldType,
15 | choices: choices,
16 | }
17 | }
18 |
19 | // FieldName ...
20 | func (e16 *Enum16) FieldName(gen Generator) string {
21 | return e16.field
22 | }
23 |
24 | // FieldTypeName ...
25 | func (e16 *Enum16) FieldTypeName(gen Generator) string {
26 | return e16.fieldType
27 | }
28 |
29 | // TypeName ...
30 | func (e16 *Enum16) TypeName(gen Generator) string {
31 | return gen.UneasyTypeName(e16.field)
32 | }
33 |
34 | // ArgName ...
35 | func (e16 *Enum16) ArgName(gen Generator) string {
36 | return gen.VarName(e16.field)
37 | }
38 |
39 | // AccessName ...
40 | func (e16 *Enum16) AccessName(gen Generator) string {
41 | return gen.HelperName(e16.field)
42 | }
43 |
44 | // NativeTypeName ...
45 | func (e16 *Enum16) NativeTypeName(gen Generator) string {
46 | return gen.Int16NativeTypeName()
47 | }
48 |
49 | // Encoding *Enum16 implementation
50 | func (e16 *Enum16) Encoding(source string, gen Generator) error {
51 | return gen.Int16Encoding(source)
52 | }
53 |
54 | // Helper ...
55 | func (e16 *Enum16) Helper(gen Generator) error {
56 | return gen.EnumHelpers(e16, e16.choices)
57 | }
58 |
59 | // TestingTypeName ...
60 | func (e16 *Enum16) TestingTypeName(gen Generator) string {
61 | return gen.EnumTestingTypeName()
62 | }
63 |
64 | // TestEncoding ...
65 | func (e16 *Enum16) TestEncoding(source string, gen Generator) error {
66 | return gen.EnumTestEncoding(source, e16.choices)
67 | }
68 |
--------------------------------------------------------------------------------
/internal/generator/enum8_field.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | // Enum8 Type implementation
4 | type Enum8 struct {
5 | field string
6 | fieldType string
7 | choices map[string]int
8 | }
9 |
10 | // NewEnum8 constructor
11 | func NewEnum8(field, fieldType string, choices map[string]int) *Enum8 {
12 | return &Enum8{
13 | field: field,
14 | fieldType: fieldType,
15 | choices: choices,
16 | }
17 | }
18 |
19 | // FieldName ...
20 | func (e8 *Enum8) FieldName(gen Generator) string {
21 | return e8.field
22 | }
23 |
24 | // FieldTypeName ...
25 | func (e8 *Enum8) FieldTypeName(gen Generator) string {
26 | return e8.fieldType
27 | }
28 |
29 | // TypeName ...
30 | func (e8 *Enum8) TypeName(gen Generator) string {
31 | return gen.UneasyTypeName(e8.field)
32 | }
33 |
34 | // ArgName ...
35 | func (e8 *Enum8) ArgName(gen Generator) string {
36 | return gen.VarName(e8.field)
37 | }
38 |
39 | // AccessName ...
40 | func (e8 *Enum8) AccessName(gen Generator) string {
41 | return gen.HelperName(e8.field)
42 | }
43 |
44 | // NativeTypeName ...
45 | func (e8 *Enum8) NativeTypeName(gen Generator) string {
46 | return gen.Int8NativeTypeName()
47 | }
48 |
49 | // Encoding *Enum8 implementation
50 | func (e8 *Enum8) Encoding(source string, gen Generator) error {
51 | return gen.Int8Encoding(source)
52 | }
53 |
54 | // Helper ...
55 | func (e8 *Enum8) Helper(gen Generator) error {
56 | return gen.EnumHelpers(e8, e8.choices)
57 | }
58 |
59 | // TestingTypeName ...
60 | func (e8 *Enum8) TestingTypeName(gen Generator) string {
61 | return gen.EnumTestingTypeName()
62 | }
63 |
64 | // TestEncoding ...
65 | func (e8 *Enum8) TestEncoding(source string, gen Generator) error {
66 | return gen.EnumTestEncoding(source, e8.choices)
67 | }
68 |
--------------------------------------------------------------------------------
/internal/generator/field.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | // Field is a generic field representation
4 | type Field interface {
5 | // FieldName returns raw field name generation as it was in a clickhouse
6 | FieldName(gen Generator) string
7 |
8 | // FieldTypeName causes a raw field type generation as it was in a clickhouse
9 | FieldTypeName(gen Generator) string
10 |
11 | // TypeName returns safe type name
12 | // It will be (Golang)
13 | // type Access int32
14 | // for access Int32 clickhouse field
15 | // Or (C++)
16 | // typedef Access int32_t;
17 | TypeName(gen Generator) string
18 |
19 | // ArgName returns argument name to use as a TypeName parameter
20 | // It will be (Golang)
21 | // … access Access …
22 | // for access clickhouse field
23 | // Or (C++)
24 | // … Access access …
25 | ArgName(gen Generator) string
26 |
27 | // AccessName returns "namespace" name that keeps helpers.
28 | // For instance we have
29 | // direction Enum8('to'=0, 'from'=1, 'blocked'=2)
30 | // field in a clickhouse instance table `table`
31 | // It obviously a bad idea to use codes (0, 1, 2) for encoding direction
32 | // In this case (C++) the TypeName will be table::DirectionType instead of table::Direction
33 | // and the access name will be table::Direction.
34 | // Then the code like
35 | // namespace table {
36 | // …
37 | // typedef char DirectionType;
38 | // namespace Direction {
39 | // const DirectionType to = 0;
40 | // const DirectionType from = 1;
41 | // const DirectionType blocked = 2;
42 | // }
43 | // …
44 | // }
45 | // The Go code is a bit more sophisticated for this piece due to its lack of nested namespaces
46 | // (will use special public object instead and a bit of boilerplate code to fill the data)
47 | AccessName(gen Generator) string
48 |
49 | // NativeTypeName returns native type name generation
50 | NativeTypeName(gen Generator) string
51 |
52 | // Encoding causes an encoding code generation
53 | Encoding(source string, gen Generator) error
54 |
55 | // Helper generation for the field
56 | Helper(gen Generator) error
57 |
58 | // Testing stuff
59 |
60 | // TestingTypeName returns a type name used for testing
61 | TestingTypeName(gen Generator) string
62 |
63 | // TestEncoding causes an encoding code generation used for testing purposes
64 | TestEncoding(source string, gen Generator) error
65 | }
66 |
--------------------------------------------------------------------------------
/internal/generator/field_set.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | import "strings"
4 |
5 | // FieldSet represents whole set of table fields
6 | type FieldSet struct {
7 | g Generator
8 | fields []Field
9 | nests map[string][]Field
10 | }
11 |
12 | // NewFieldSet constructor
13 | func NewFieldSet(g Generator) *FieldSet {
14 | return &FieldSet{
15 | g: g,
16 | nests: map[string][]Field{},
17 | }
18 | }
19 |
20 | // Add adds field to the field set
21 | func (fs *FieldSet) Add(field Field) {
22 | data := strings.Split(field.FieldName(fs.g), ".")
23 | fs.fields = append(fs.fields, field)
24 | if len(data) > 1 {
25 | fieldName := data[0]
26 | fs.nests[fieldName] = append(fs.nests[fieldName], field)
27 | }
28 | }
29 |
30 | // List returns list of fields
31 | func (fs *FieldSet) List() []Field {
32 | return fs.fields
33 | }
34 |
35 | // Nests returns dictionary of { nested field → list of its subfields }
36 | func (fs *FieldSet) Nests() map[string][]Field {
37 | return fs.nests
38 | }
39 |
--------------------------------------------------------------------------------
/internal/generator/fixed_string_field.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | // FixedString Type implementation
4 | type FixedString struct {
5 | field string
6 | fieldType string
7 | stringLength int
8 | }
9 |
10 | // NewFixedString constructor
11 | func NewFixedString(field, fieldType string, length int) *FixedString {
12 | return &FixedString{
13 | field: field,
14 | fieldType: fieldType,
15 | stringLength: length,
16 | }
17 | }
18 |
19 | // FieldName ...
20 | func (fs *FixedString) FieldName(gen Generator) string {
21 | return fs.field
22 | }
23 |
24 | // FieldTypeName ...
25 | func (fs *FixedString) FieldTypeName(gen Generator) string {
26 | return fs.fieldType
27 | }
28 |
29 | // TypeName ...
30 | func (fs *FixedString) TypeName(gen Generator) string {
31 | return gen.EasyTypeName(fs.field)
32 | }
33 |
34 | // ArgName ...
35 | func (fs *FixedString) ArgName(gen Generator) string {
36 | return gen.VarName(fs.field)
37 | }
38 |
39 | // AccessName ...
40 | func (fs *FixedString) AccessName(gen Generator) string {
41 | return gen.HelperName(fs.field)
42 | }
43 |
44 | // NativeTypeName ...
45 | func (fs *FixedString) NativeTypeName(gen Generator) string {
46 | return gen.FixedStringNativeTypeName()
47 | }
48 |
49 | // Encoding ...
50 | func (fs *FixedString) Encoding(source string, gen Generator) error {
51 | return gen.FixedStringEncoding(source, fs.stringLength)
52 | }
53 |
54 | // Helper ...
55 | func (fs *FixedString) Helper(gen Generator) error {
56 | return nil
57 | }
58 |
59 | // TestingTypeName ...
60 | func (fs *FixedString) TestingTypeName(gen Generator) string {
61 | return gen.FixedStringTestingTypeName()
62 | }
63 |
64 | // TestEncoding ...
65 | func (fs *FixedString) TestEncoding(source string, gen Generator) error {
66 | return gen.FixedStringTestEncoding(source, fs.stringLength)
67 | }
68 |
--------------------------------------------------------------------------------
/internal/generator/float32_field.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | // Float32 Type implementation
4 | type Float32 struct {
5 | field string
6 | fieldType string
7 | }
8 |
9 | // NewFloat32 constructor
10 | func NewFloat32(field string, fieldType string) *Float32 {
11 | return &Float32{
12 | field: field,
13 | fieldType: fieldType,
14 | }
15 | }
16 |
17 | // FieldName ...
18 | func (f32 *Float32) FieldName(gen Generator) string {
19 | return f32.field
20 | }
21 |
22 | // FieldTypeName ...
23 | func (f32 *Float32) FieldTypeName(gen Generator) string {
24 | return f32.fieldType
25 | }
26 |
27 | // TypeName ...
28 | func (f32 *Float32) TypeName(gen Generator) string {
29 | return gen.EasyTypeName(f32.field)
30 | }
31 |
32 | // ArgName ...
33 | func (f32 *Float32) ArgName(gen Generator) string {
34 | return gen.VarName(f32.field)
35 | }
36 |
37 | // AccessName ...
38 | func (f32 *Float32) AccessName(gen Generator) string {
39 | return gen.HelperName(f32.field)
40 | }
41 |
42 | // NativeTypeName ...
43 | func (f32 Float32) NativeTypeName(gen Generator) string {
44 | return gen.Float32NativeTypeName()
45 | }
46 |
47 | // Encoding ...
48 | func (f32 Float32) Encoding(source string, gen Generator) error {
49 | return gen.Float32Encoding(source)
50 | }
51 |
52 | // Helper ...
53 | func (f32 *Float32) Helper(gen Generator) error {
54 | return nil
55 | }
56 |
57 | // TestingTypeName ...
58 | func (f32 Float32) TestingTypeName(gen Generator) string {
59 | return gen.Float32TestingTypeName()
60 | }
61 |
62 | // TestEncoding ...
63 | func (f32 Float32) TestEncoding(source string, gen Generator) error {
64 | return gen.Float32TestEncoding(source)
65 | }
66 |
--------------------------------------------------------------------------------
/internal/generator/float64_field.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | // Float64 Type implementation
4 | type Float64 struct {
5 | field string
6 | fieldType string
7 | }
8 |
9 | // NewFloat64 constructor
10 | func NewFloat64(field string, fieldType string) *Float64 {
11 | return &Float64{
12 | field: field,
13 | fieldType: fieldType,
14 | }
15 | }
16 |
17 | // FieldName ...
18 | func (f64 *Float64) FieldName(gen Generator) string {
19 | return f64.field
20 | }
21 |
22 | // FieldTypeName ...
23 | func (f64 *Float64) FieldTypeName(gen Generator) string {
24 | return f64.fieldType
25 | }
26 |
27 | // TypeName ...
28 | func (f64 *Float64) TypeName(gen Generator) string {
29 | return gen.EasyTypeName(f64.field)
30 | }
31 |
32 | // ArgName ...
33 | func (f64 *Float64) ArgName(gen Generator) string {
34 | return gen.VarName(f64.field)
35 | }
36 |
37 | // AccessName ...
38 | func (f64 *Float64) AccessName(gen Generator) string {
39 | return gen.HelperName(f64.field)
40 | }
41 |
42 | // NativeTypeName ...
43 | func (f64 Float64) NativeTypeName(gen Generator) string {
44 | return gen.Float64NativeTypeName()
45 | }
46 |
47 | // Encoding ...
48 | func (f64 Float64) Encoding(source string, gen Generator) error {
49 | return gen.Float64Encoding(source)
50 | }
51 |
52 | // Helper ...
53 | func (f64 *Float64) Helper(gen Generator) error {
54 | return nil
55 | }
56 |
57 | // TestingTypeName ...
58 | func (f64 Float64) TestingTypeName(gen Generator) string {
59 | return gen.Float64TestingTypeName()
60 | }
61 |
62 | // TestEncoding ...
63 | func (f64 Float64) TestEncoding(source string, gen Generator) error {
64 | return gen.Float64TestEncoding(source)
65 | }
66 |
--------------------------------------------------------------------------------
/internal/generator/generation.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | // Generate generates encoding stuff
4 | func Generate(gen Generator, dateField func(gen Generator) string, fields *FieldSet) (err error) {
5 | dateFieldName := dateField(gen)
6 | pieceGens := []func(*FieldSet) error{
7 | gen.Types,
8 | gen.EncoderInterface,
9 | gen.EncoderDef,
10 | gen.EncodingMethod,
11 | }
12 | if dateFieldName != "" {
13 | pieceGens = append(pieceGens,
14 | gen.DateFilterEncoderDef,
15 | func(f *FieldSet) error { return gen.DateFilterEncodingMethod(dateField(gen), f) },
16 | )
17 | }
18 | pieceGens = append(pieceGens,
19 | gen.VoidEncoderDef,
20 | gen.VoidEncodingMethod,
21 | gen.TestDef,
22 | gen.TestEncoderDef,
23 | gen.TestEncodingMethod,
24 | )
25 | for _, piece := range pieceGens {
26 | if err = piece(fields); err != nil {
27 | return
28 | }
29 | }
30 |
31 | return
32 | }
33 |
--------------------------------------------------------------------------------
/internal/generator/generator.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | import "io"
4 |
5 | // Generator abstraction
6 | type Generator interface {
7 | RawData(string) error
8 |
9 | // Custom dest is to workaround strict Golang policies unused imports
10 | Header(dest io.Writer) error
11 |
12 | ////////////////////////////////////
13 | ////////////// Production purposes
14 |
15 | // Types generates type definitions for all fields
16 | Types(*FieldSet) error
17 |
18 | // TypeDef generates type definition for the field
19 | TypeDef(Field) error
20 |
21 | // HelperDef generates type helpers for the field
22 | HelperDef(Field) error
23 |
24 | // EncoderInterface generates encoding interface
25 | EncoderInterface(*FieldSet) error
26 |
27 | // EncoderDef generates production encoder
28 | EncoderDef(*FieldSet) error
29 |
30 | // EncodeMethod generates
31 | EncodingMethod(*FieldSet) error
32 |
33 | ////////////////////////////////////
34 | ////////////// Production date filter
35 |
36 | // FilterEncoderDef generates encoder with filter
37 | DateFilterEncoderDef(*FieldSet) error
38 |
39 | // FilterEncodingMethod generates
40 | DateFilterEncodingMethod(dateField string, fset *FieldSet) error
41 |
42 | ////////////////////////////////////
43 | ////////////// Void purposes
44 |
45 | // VoidEncoderDef generates encoder that does nothing
46 | VoidEncoderDef(*FieldSet) error
47 |
48 | //VoidEncodingMethod
49 | VoidEncodingMethod(*FieldSet) error
50 |
51 | ////////////////////////////////////
52 | ////////////// Testing purposes
53 |
54 | // TestDef generates record representation for testing purposes
55 | TestDef(*FieldSet) error
56 |
57 | // TestEncoderDef generates encoder aimed for testing
58 | TestEncoderDef(*FieldSet) error
59 |
60 | // TestEncodingMethod Encode method generator
61 | TestEncodingMethod(*FieldSet) error
62 |
63 | EasyTypeName(string) string
64 | UneasyTypeName(string) string
65 | HelperName(string) string
66 | VarName(string) string
67 |
68 | Int8NativeTypeName() string
69 | Int16NativeTypeName() string
70 | Int32NativeTypeName() string
71 | Int64NativeTypeName() string
72 | Uint8NativeTypeName() string
73 | Uint16NativeTypeName() string
74 | Uint32NativeTypeName() string
75 | Uint64NativeTypeName() string
76 | Dec128NativeTypeName() string
77 | Float32NativeTypeName() string
78 | Float64NativeTypeName() string
79 | StringNativeTypeName() string
80 | FixedStringNativeTypeName() string
81 | UUIDNativeTypeName() string
82 | ArrayNativeTypeName(itemType Field) string
83 | NullableNativeTypeName(itemType Field) string
84 | NullableStringNativeTypeName() string
85 | NullableArrayNativeTypeName(itemType Field) string
86 |
87 | Int8TestingTypeName() string
88 | Int16TestingTypeName() string
89 | Int32TestingTypeName() string
90 | Int64TestingTypeName() string
91 | Uint8TestingTypeName() string
92 | Uint16TestingTypeName() string
93 | Uint32TestingTypeName() string
94 | Uint64TestingTypeName() string
95 | Dec32TestingTypeName() string
96 | Dec64TestingTypeName() string
97 | Dec128TestingTypeName() string
98 | Float32TestingTypeName() string
99 | Float64TestingTypeName() string
100 | EnumTestingTypeName() string
101 | DateTestingTypeName() string
102 | DateTimeTestingTypeName() string
103 | StringTestingTypeName() string
104 | FixedStringTestingTypeName() string
105 | UUIDTestingTypeName() string
106 | ArrayTestingTypeName(itemType Field) string
107 | NullableTestingTypeName(itemType Field) string
108 | NullableStringTestingTypeName() string
109 | NullableArrayTestingTypeName(itemType Field) string
110 |
111 | Int8Encoding(string) error
112 | Int16Encoding(string) error
113 | Int32Encoding(string) error
114 | Int64Encoding(string) error
115 | Uint8Encoding(string) error
116 | Uint16Encoding(string) error
117 | Uint32Encoding(string) error
118 | Uint64Encoding(string) error
119 | Dec128Encoding(string) error
120 | Float32Encoding(string) error
121 | Float64Encoding(string) error
122 | DateEncoding(string) error
123 | DateTimeEncoding(string) error
124 | StringEncoding(string) error
125 | FixedStringEncoding(string, int) error
126 | UUIDEncoding(string) error
127 | ArrayEncoding(string, Field) error
128 | NullableEncoding(string, Field) error
129 | NullableArrayEncoding(string, Field) error
130 | NullableStringEncoding(string) error
131 |
132 | Int8TestEncoding(string) error
133 | Int16TestEncoding(string) error
134 | Int32TestEncoding(string) error
135 | Int64TestEncoding(string) error
136 | Uint8TestEncoding(string) error
137 | Uint16TestEncoding(string) error
138 | Uint32TestEncoding(string) error
139 | Uint64TestEncoding(string) error
140 | Dec32TestEncoding(int, string) error
141 | Dec64TestEncoding(int, string) error
142 | Dec128TestEncoding(int, string) error
143 | Float32TestEncoding(string) error
144 | Float64TestEncoding(string) error
145 | EnumTestEncoding(string, map[string]int) error
146 | DateTestEncoding(string) error
147 | DateTimeTestEncoding(string) error
148 | StringTestEncoding(string) error
149 | FixedStringTestEncoding(string, int) error
150 | UUIDTestEncoding(string) error
151 | ArrayTestEncoding(string, Field) error
152 | NullableTestEncoding(string, Field) error
153 | NullableStringTestEncoding(string) error
154 | NullableArrayTestEncoding(string, Field) error
155 |
156 | EnumHelpers(Field, map[string]int) error
157 | DateHelpers(Field) error
158 | DateTimeHelpers(Field) error
159 | Dec128Helpers(Field) error
160 | }
161 |
--------------------------------------------------------------------------------
/internal/generator/gogen/encoder.go:
--------------------------------------------------------------------------------
1 | package gogen
2 |
3 | import (
4 | "fmt"
5 | "text/template"
6 |
7 | "github.com/sirkon/ch-encode/internal/generator"
8 | )
9 |
10 | // EncoderInterface ...
11 | func (gg *GoGen) EncoderInterface(field *generator.FieldSet) error {
12 | text :=
13 | `
14 | type {{.encoderName}} interface {
15 | Encode({{.args}}) error
16 | InsertCount() int
17 | ErrorCount() int
18 | }
19 | `
20 | tmpl, err := template.New("interface").Parse(text)
21 | if err != nil {
22 | return err
23 | }
24 | return tmpl.Execute(gg.dest, map[string]string{
25 | "encoderName": gg.interfaceEncoderName(),
26 | "args": gg.argList(field)},
27 | )
28 | }
29 |
30 | // EncoderDef ...
31 | func (gg *GoGen) EncoderDef(fs *generator.FieldSet) error {
32 | text :=
33 | `
34 | type {{.encoderName}} struct {
35 | insertCounter int
36 | buffer *bytes.Buffer
37 | helper *binenc.Encoder
38 | zeroes []byte
39 | dest io.Writer
40 | }
41 |
42 | func checkSchema(curCols []chinsert.Column) error {
43 | sample := []chinsert.Column{
44 | {{ range .columns }}{Name: {{.Name}}, Type: {{.Type}}},
45 | {{ end }}}
46 | for i, col := range sample {
47 | if i >= len(curCols) {
48 | return fmt.Errorf("column %s(%s) is missing in the current schema for table {{.table}}, outdated encoder?", col.Name, col.Type)
49 | }
50 | var colNum string
51 | switch i {
52 | case 0:
53 | colNum = "1st"
54 | case 1:
55 | colNum = "2nd"
56 | case 2:
57 | colNum = "3rd"
58 | default:
59 | colNum = fmt.Sprintf("%dth", i+1)
60 | }
61 | if col.Name != curCols[i].Name || col.Type != curCols[i].Type {
62 | return fmt.Errorf("table {{.table}} encoder expects for a %s column to be %s(%s), while it is %s(%s) in actual schema", colNum, col.Name, col.Type, curCols[i].Name, curCols[i].Type)
63 | }
64 | }
65 | if len(curCols) > len(sample) {
66 | mismatch := curCols[len(sample)]
67 | return fmt.Errorf("unexpected extra column %s(%s) in the current schema for table {{.table}}, outdated encoder?", mismatch.Name, mismatch.Type)
68 | }
69 | return nil
70 | }
71 |
72 | func New{{.encoderName}}(w chinsert.WriterWithSchemaCheck) (*{{.encoderName}},error) {
73 | columns, err := w.Schema()
74 | if err != nil {
75 | return nil,err
76 | }
77 | if err := checkSchema(columns); err != nil {
78 | return nil, err
79 | }
80 | buffer := &bytes.Buffer{}
81 | buffer.Grow(4096)
82 | return &{{.encoderName}}{
83 | buffer: buffer,
84 | helper: binenc.New(),
85 | dest: w,
86 | zeroes: make([]byte, 64),
87 | }, nil
88 | }
89 |
90 | func (enc *{{.encoderName}}) InsertCount() int {
91 | return enc.insertCounter
92 | }
93 | `
94 | gg.regImport("", "github.com/sirkon/ch-insert")
95 | gg.regImport("", "fmt")
96 | tmpl, err := template.New("encoder").Parse(text)
97 | if err != nil {
98 | return err
99 | }
100 |
101 | var fields []struct {
102 | Name string
103 | Type string
104 | }
105 | for _, f := range fs.List() {
106 | fields = append(fields, struct {
107 | Name string
108 | Type string
109 | }{Name: "`" + f.FieldName(gg) + "`", Type: "`" + f.FieldTypeName(gg) + "`"})
110 | }
111 |
112 | return tmpl.Execute(gg.dest, map[string]interface{}{
113 | "encoderName": gg.encoderName(),
114 | "columns": fields,
115 | "table": gg.table,
116 | })
117 | }
118 |
119 | // EncodingMethod ...
120 | func (gg *GoGen) EncodingMethod(fields *generator.FieldSet) (err error) {
121 | text := "func (enc *%s) Encode(%s) error {\nenc.buffer.Reset();\n"
122 | if err = gg.RawData(fmt.Sprintf(text, gg.encoderName(), gg.argList(fields))); err != nil {
123 | return
124 | }
125 | if err = gg.constraints(fields); err != nil {
126 | return
127 | }
128 | for _, field := range fields.List() {
129 | if err = field.Encoding(field.ArgName(gg), gg); err != nil {
130 | return
131 | }
132 | if err = gg.RawData("\n"); err != nil {
133 | return
134 | }
135 | }
136 | err = gg.RawData(`
137 | enc.insertCounter++;
138 | _, err := enc.dest.Write(enc.buffer.Bytes());
139 | return err}`)
140 | return
141 | }
142 |
--------------------------------------------------------------------------------
/internal/generator/gogen/encoding.go:
--------------------------------------------------------------------------------
1 | package gogen
2 |
3 | import (
4 | "fmt"
5 | "text/template"
6 |
7 | "io"
8 |
9 | "github.com/sirkon/ch-encode/internal/generator"
10 | )
11 |
12 | // Int8Encoding ...
13 | func (gg *GoGen) Int8Encoding(source string) error {
14 | _, err := fmt.Fprintf(gg.dest, "enc.buffer.Write(enc.helper.Byte(byte(%s)));\n", source)
15 | return err
16 | }
17 |
18 | // Int16Encoding ...
19 | func (gg *GoGen) Int16Encoding(source string) error {
20 | _, err := fmt.Fprintf(gg.dest, "enc.buffer.Write(enc.helper.Int16(int16(%s)));\n", source)
21 | return err
22 | }
23 |
24 | // Int32Encoding ...
25 | func (gg *GoGen) Int32Encoding(source string) error {
26 | _, err := fmt.Fprintf(gg.dest, "enc.buffer.Write(enc.helper.Int32(int32(%s)));\n", source)
27 | return err
28 | }
29 |
30 | // Int64Encoding ...
31 | func (gg *GoGen) Int64Encoding(source string) error {
32 | _, err := fmt.Fprintf(gg.dest, "enc.buffer.Write(enc.helper.Int64(int64(%s)));\n", source)
33 | return err
34 | }
35 |
36 | // Uint8Encoding ...
37 | func (gg *GoGen) Uint8Encoding(source string) error {
38 | _, err := fmt.Fprintf(gg.dest, "enc.buffer.Write(enc.helper.Byte(byte(%s)));\n", source)
39 | return err
40 | }
41 |
42 | // Uint16Encoding ...
43 | func (gg *GoGen) Uint16Encoding(source string) error {
44 | _, err := fmt.Fprintf(gg.dest, "enc.buffer.Write(enc.helper.Uint16(uint16(%s)));\n", source)
45 | return err
46 | }
47 |
48 | // Uint32Encoding ...
49 | func (gg *GoGen) Uint32Encoding(source string) error {
50 | _, err := fmt.Fprintf(gg.dest, "enc.buffer.Write(enc.helper.Uint32(uint32(%s)));\n", source)
51 | return err
52 | }
53 |
54 | // Uint64Encoding ...
55 | func (gg *GoGen) Uint64Encoding(source string) error {
56 | _, err := fmt.Fprintf(gg.dest, "enc.buffer.Write(enc.helper.Uint64(uint64(%s)));\n", source)
57 | return err
58 | }
59 |
60 | func (gg *GoGen) Dec128Encoding(source string) error {
61 | _, err := fmt.Fprintf(gg.dest,
62 | ""+
63 | "enc.buffer.Write(enc.helper.Uint64(uint64(%s.Lo)));\n"+
64 | "enc.buffer.Write(enc.helper.Uint64(uint64(%s.Hi)));\n",
65 | source, source)
66 | return err
67 | }
68 |
69 | // Float32Encoding ...
70 | func (gg *GoGen) Float32Encoding(source string) error {
71 | gg.useUnsafe()
72 | _, err := fmt.Fprintf(gg.dest, "enc.buffer.Write(enc.helper.Uint32(*(*uint32)(unsafe.Pointer(&%s))));\n", source)
73 | return err
74 | }
75 |
76 | // Float64Encoding ...
77 | func (gg *GoGen) Float64Encoding(source string) error {
78 | gg.useUnsafe()
79 | _, err := fmt.Fprintf(gg.dest, "enc.buffer.Write(enc.helper.Uint64(*(*uint64)(unsafe.Pointer(&%s))));\n", source)
80 | return err
81 | }
82 |
83 | // DateEncoding ...
84 | func (gg *GoGen) DateEncoding(source string) error {
85 | gg.useTime()
86 | _, err := fmt.Fprintf(gg.dest, "enc.buffer.Write(enc.helper.Uint16(uint16(%s)));\n", source)
87 | return err
88 | }
89 |
90 | // DateTimeEncoding ...
91 | func (gg *GoGen) DateTimeEncoding(source string) error {
92 | gg.useTime()
93 | _, err := fmt.Fprintf(gg.dest, "enc.buffer.Write(enc.helper.Uint32(uint32(%s)));\n", source)
94 | return err
95 | }
96 |
97 | // StringEncoding ...
98 | func (gg *GoGen) StringEncoding(source string) error {
99 | _, err := fmt.Fprintf(gg.dest,
100 | ""+
101 | "enc.buffer.Write(enc.helper.Uleb128(uint32(len([]byte(%s)))));\n"+
102 | "enc.buffer.Write([]byte(%s));\n",
103 | source, source)
104 | return err
105 | }
106 |
107 | // FixedStringEncoding ...
108 | func (gg *GoGen) FixedStringEncoding(source string, length int) error {
109 | gg.useFmt()
110 | text := `
111 | if len({{.var}}) != {{.length}} {
112 | return fmt.Errorf("string {{.var}} must be {{.length}} bytes long, got %d bytes intsead (\"\033[1m%s\033[0m\", %v)", len({{.var}}), string({{.var}}), {{.var}})
113 | }
114 | enc.buffer.Write([]byte({{.var}}))
115 | `
116 | t, err := template.New("fixed_string_encoding").Parse(text)
117 | if err != nil {
118 | return err
119 | }
120 | err = t.Execute(gg.dest, map[string]interface{}{
121 | "var": source,
122 | "length": length,
123 | })
124 | return err
125 | }
126 |
127 | // UUIDEncoding ...
128 | func (gg *GoGen) UUIDEncoding(source string) error {
129 | _, err := fmt.Fprintf(gg.dest, "enc.buffer.Write(%s[:]);\n", source)
130 | return err
131 | }
132 |
133 | // ArrayEncoding ...
134 | func (gg *GoGen) ArrayEncoding(source string, field generator.Field) error {
135 | text := `
136 | enc.buffer.Write(enc.helper.Uleb128(uint32(len({{.var}}))));
137 | for _, arrayItem := range {{.var}} {
138 | `
139 | tmpl, err := template.New("array_encoding").Parse(text)
140 | if err != nil {
141 | return err
142 | }
143 | err = tmpl.Execute(gg.dest, map[string]string{
144 | "var": source,
145 | })
146 | if err != nil {
147 | return err
148 | }
149 | if err = field.Encoding("arrayItem", gg); err != nil {
150 | return err
151 | }
152 | return gg.RawData("}")
153 | }
154 |
155 | // NullableEncoding ...
156 | func (gg *GoGen) NullableEncoding(source string, field generator.Field) error {
157 | if _, err := fmt.Fprintf(gg.dest, ";if %s != nil {\n", source); err != nil {
158 | return err
159 | }
160 | if _, err := io.WriteString(gg.dest, ";enc.buffer.WriteByte(byte(0));"); err != nil {
161 | return err
162 | }
163 | if err := field.Encoding("*"+source, gg); err != nil {
164 | return err
165 | }
166 | return gg.RawData("} else { enc.buffer.WriteByte(byte(1)) }")
167 | }
168 |
169 | // NullableArrayEncoding ...
170 | func (gg *GoGen) NullableArrayEncoding(source string, field generator.Field) error {
171 | return gg.NullableEncoding(source, field)
172 | }
173 |
174 | // NullableStringEncoding
175 | func (gg *GoGen) NullableStringEncoding(source string) error {
176 | if _, err := fmt.Fprintf(gg.dest, ";if %s != nil {\n", source); err != nil {
177 | return err
178 | }
179 | _, err := fmt.Fprintf(gg.dest, ""+"enc.buffer.WriteByte(byte(0));\n")
180 | if err != nil {
181 | return err
182 | }
183 | if err := gg.StringEncoding(source); err != nil {
184 | return err
185 | }
186 | if _, err := fmt.Fprintf(gg.dest, "; } else { enc.buffer.WriteByte(byte(1)) }"); err != nil {
187 | return err
188 | }
189 | return nil
190 | }
191 |
--------------------------------------------------------------------------------
/internal/generator/gogen/fieldtypes.go:
--------------------------------------------------------------------------------
1 | package gogen
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/sirkon/ch-encode/internal/generator"
7 | )
8 |
9 | // Types ...
10 | func (gg *GoGen) Types(fields *generator.FieldSet) (err error) {
11 | for _, field := range fields.List() {
12 | if err = gg.TypeDef(field); err != nil {
13 | return
14 | }
15 | if err = gg.HelperDef(field); err != nil {
16 | return
17 | }
18 | }
19 | return gg.RawData("\n")
20 | }
21 |
22 | // TypeDef ...
23 | func (gg *GoGen) TypeDef(field generator.Field) (err error) {
24 | _, err = fmt.Fprintf(gg.dest, "type %s %s;\n", field.TypeName(gg), field.NativeTypeName(gg))
25 | return
26 | }
27 |
28 | // HelperDef ...
29 | func (gg *GoGen) HelperDef(field generator.Field) (err error) {
30 | if err = field.Helper(gg); err != nil {
31 | return
32 | }
33 | return gg.RawData("\n")
34 | }
35 |
--------------------------------------------------------------------------------
/internal/generator/gogen/filterencoder.go:
--------------------------------------------------------------------------------
1 | package gogen
2 |
3 | import (
4 | "strings"
5 | "text/template"
6 |
7 | "github.com/sirkon/ch-encode/internal/generator"
8 | )
9 |
10 | // DateFilterEncoderDef ...
11 | func (gg *GoGen) DateFilterEncoderDef(field *generator.FieldSet) error {
12 | text :=
13 | `
14 | type {{.encoder}} struct {
15 | dayNo uint16
16 | encoder {{.interface}}
17 | }
18 |
19 | func New{{.encoder}}(dayNo uint16, enc {{.interface}}) *{{.encoder}} {
20 | return &{{.encoder}}{
21 | dayNo: dayNo,
22 | encoder: enc,
23 | }
24 | }
25 |
26 | func (enc *{{.encoder}}) InsertCount() int {
27 | return enc.encoder.InsertCount();
28 | }
29 | func (enc *{{.encoder}}) ErrorCount() int {
30 | return enc.encoder.ErrorCount();
31 | }
32 | `
33 | tmpl, err := template.New("filterer").Parse(text)
34 | if err != nil {
35 | return err
36 | }
37 | return tmpl.Execute(gg.dest, map[string]string{
38 | "encoder": gg.filterEncoderName(),
39 | "interface": gg.interfaceEncoderName(),
40 | })
41 | }
42 |
43 | // DateFilterEncodingMethod ...
44 | func (gg *GoGen) DateFilterEncodingMethod(dateArg string, fields *generator.FieldSet) error {
45 | text :=
46 | `
47 | func (enc *{{.encoder}}) Encode({{.args}}) error {
48 | if enc.dayNo != uint16({{.dateArg}}) {
49 | return nil
50 | }
51 | return enc.encoder.Encode({{.app}})
52 | }
53 | `
54 | tmpl, err := template.New("filterer").Parse(text)
55 | if err != nil {
56 | return err
57 | }
58 | app := []string{}
59 | for _, field := range fields.List() {
60 | app = append(app, field.ArgName(gg))
61 | }
62 | return tmpl.Execute(gg.dest, map[string]string{
63 | "encoder": gg.filterEncoderName(),
64 | "args": gg.argList(fields),
65 | "app": strings.Join(app, ", "),
66 | "dateArg": dateArg,
67 | })
68 | }
69 |
--------------------------------------------------------------------------------
/internal/generator/gogen/generator.go:
--------------------------------------------------------------------------------
1 | package gogen
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "text/template"
7 |
8 | "github.com/sirkon/ch-encode/internal/generator"
9 | "github.com/sirkon/gotify"
10 | )
11 |
12 | var _ generator.Generator = &GoGen{}
13 |
14 | // GoGen encoder code generation for Go language
15 | type GoGen struct {
16 | goish *gotify.Gotify
17 | table string
18 | dest io.Writer
19 | imports map[string]string // path -> access name
20 | }
21 |
22 | // New Go generator constructor
23 | func New(table string, g *gotify.Gotify, dest io.Writer) *GoGen {
24 | res := &GoGen{
25 | table: table,
26 | dest: dest,
27 | goish: g,
28 | imports: map[string]string{},
29 | }
30 | res.useBytes()
31 | res.useBinEnc()
32 | res.useIO()
33 | return res
34 | }
35 |
36 | // RawData ...
37 | func (gg *GoGen) RawData(v string) error {
38 | _, err := gg.dest.Write([]byte(v))
39 | return err
40 | }
41 |
42 | // regImport registers new import path
43 | func (gg *GoGen) regImport(importAs string, path string) {
44 | if prev, ok := gg.imports[path]; ok {
45 | if prev != importAs {
46 | panic(fmt.Errorf(`attempt to import path "%s" as %s, when it was registered before as %s`, path, importAs, prev))
47 | }
48 | }
49 | gg.imports[path] = importAs
50 | }
51 |
52 | func (gg *GoGen) useTime() {
53 | gg.regImport("stdtime", "time")
54 | }
55 |
56 | func (gg *GoGen) useBinEnc() {
57 | gg.regImport("", "github.com/sirkon/binenc")
58 | }
59 |
60 | func (gg *GoGen) useBytes() {
61 | gg.regImport("", "bytes")
62 | }
63 |
64 | func (gg *GoGen) useIO() {
65 | gg.regImport("", "io")
66 | }
67 |
68 | func (gg *GoGen) useUnsafe() {
69 | gg.regImport("", "unsafe")
70 | }
71 |
72 | func (gg *GoGen) useFmt() {
73 | gg.regImport("", "fmt")
74 | }
75 |
76 | func (gg *GoGen) useGoogleUUID() {
77 | gg.regImport("googleUUID", "github.com/google/uuid")
78 | }
79 |
80 | const headerTemplate = `
81 | package {{ .Package }}
82 |
83 |
84 | import (
85 | {{ range $path, $access := .Imports }}{{ $access }} "{{ $path }}" {{ printf "\n" }}{{ end }})
86 | `
87 |
88 | // Header ...
89 | func (gg *GoGen) Header(dest io.Writer) error {
90 | pkgname := gg.goish.Package(gg.table)
91 | t := template.New("encoder header")
92 | tmpl, err := t.Parse(headerTemplate)
93 | if err != nil {
94 | return err
95 | }
96 | var ctx struct {
97 | Package string
98 | Imports map[string]string
99 | }
100 | ctx.Package = pkgname
101 | ctx.Imports = gg.imports
102 | if err := tmpl.Execute(dest, ctx); err != nil {
103 | return err
104 | }
105 | return nil
106 | }
107 |
108 | const constraintCheck = `
109 | if len({{.First}}) != len({{.Current}}) {
110 | return fmt.Errorf("length mismatch between {{.First}} and {{.Current}} (%d != %d), lengths must be the same being subfields of {{.Nest}}",
111 | len({{.First}}), len({{.Current}}))
112 | }
113 | `
114 |
115 | // Constraints ...
116 | func (gg *GoGen) constraints(fields *generator.FieldSet) error {
117 | nests := fields.Nests()
118 | t := template.New("constraint if")
119 | tmpl, err := t.Parse(constraintCheck)
120 | if err != nil {
121 | return err
122 | }
123 | var ctx struct {
124 | First string
125 | Current string
126 | Nest string
127 | }
128 | for nested, subfields := range nests {
129 | ctx.Nest = nested
130 | if len(subfields) <= 1 {
131 | continue
132 | }
133 | gg.useFmt()
134 | gg.dest.Write([]byte(fmt.Sprintf("\n// constraints on %s nested field", nested)))
135 | ctx.First = subfields[0].ArgName(gg)
136 | for _, restItem := range subfields[1:] {
137 | ctx.Current = restItem.ArgName(gg)
138 | if err = tmpl.Execute(gg.dest, ctx); err != nil {
139 | return err
140 | }
141 | }
142 | }
143 | return nil
144 | }
145 |
--------------------------------------------------------------------------------
/internal/generator/gogen/helpers.go:
--------------------------------------------------------------------------------
1 | package gogen
2 |
3 | import (
4 | "fmt"
5 | "sort"
6 | "strings"
7 | "text/template"
8 |
9 | "github.com/sirkon/ch-encode/internal/generator"
10 | )
11 |
12 | // EnumHelpers ...
13 | func (gg *GoGen) EnumHelpers(field generator.Field, safeValues map[string]int) error {
14 | lines := []string{
15 | fmt.Sprintf("type compl%s struct {", field.TypeName(gg)),
16 | }
17 | enumItems := NewEnumItemSlice(safeValues)
18 | sort.Sort(enumItems)
19 | for _, item := range enumItems {
20 | lines = append(lines, fmt.Sprintf("%s %s", gg.goish.Public(item.Key), field.TypeName(gg)))
21 | }
22 | lines = append(lines, "}")
23 | lines = append(lines,
24 | fmt.Sprintf("var %s compl%s = compl%s {",
25 | field.AccessName(gg), field.TypeName(gg), field.TypeName(gg)))
26 | for _, item := range enumItems {
27 | lines = append(lines, fmt.Sprintf("%s: %s(%d),", gg.goish.Public(item.Key), field.TypeName(gg), item.Value))
28 | }
29 | lines = append(lines, "}")
30 | lines = append(lines, fmt.Sprintf("var %sEnumMapping = map[string]%s {", field.FieldName(gg), field.TypeName(gg)))
31 | for _, item := range enumItems {
32 | lines = append(lines, fmt.Sprintf(`"%s": %s.%s,`, item.Key, field.AccessName(gg), gg.goish.Public(item.Key)))
33 | }
34 | lines = append(lines, "}")
35 | lines = append(
36 | lines,
37 | fmt.Sprintf(
38 | "func (ct *compl%s) FromString(key string) (res %s, ok bool) {",
39 | field.TypeName(gg), field.TypeName(gg)),
40 | )
41 | lines = append(lines, fmt.Sprintf("res, ok = %sEnumMapping[key]", field.FieldName(gg)))
42 | lines = append(lines, "return")
43 | lines = append(lines, "}")
44 |
45 | return gg.RawData(strings.Join(lines, "\n"))
46 | }
47 |
48 | // DateHelpers ...
49 | func (gg *GoGen) DateHelpers(field generator.Field) error {
50 | gg.useTime()
51 | lines := []string{
52 | fmt.Sprintf("type compl%s struct{}", field.TypeName(gg)),
53 | fmt.Sprintf(`
54 | func (c compl%s) FromTime(t stdtime.Time) %s {
55 | return %s(t.Unix()/86400)
56 | }
57 | func (c compl%s) FromTimestamp(t int64) %s {
58 | return %s(t/86400)
59 | }
60 | `,
61 | field.TypeName(gg), field.TypeName(gg), field.TypeName(gg),
62 | field.TypeName(gg), field.TypeName(gg), field.TypeName(gg)),
63 | fmt.Sprintf("var %s compl%s", field.AccessName(gg), field.TypeName(gg)),
64 | }
65 | return gg.RawData(strings.Join(lines, "\n"))
66 | }
67 |
68 | // DateTimeHelpers ...
69 | func (gg *GoGen) DateTimeHelpers(field generator.Field) error {
70 | gg.useTime()
71 | lines := []string{
72 | fmt.Sprintf("type compl%s struct{}", field.TypeName(gg)),
73 | fmt.Sprintf(`
74 | func (c compl%s) FromTime(t stdtime.Time) %s {
75 | return %s(t.Unix())
76 | }
77 | func (c compl%s) FromTimestamp(t int64) %s {
78 | return %s(t)
79 | }
80 | `,
81 | field.TypeName(gg), field.TypeName(gg), field.TypeName(gg),
82 | field.TypeName(gg), field.TypeName(gg), field.TypeName(gg)),
83 | fmt.Sprintf("var %s compl%s", field.AccessName(gg), field.TypeName(gg)),
84 | }
85 | return gg.RawData(strings.Join(lines, "\n"))
86 | }
87 |
88 | // Dec128Helpers ...
89 | func (gg *GoGen) Dec128Helpers(field generator.Field) error {
90 | format := `
91 | func {{.FuncName}}(lo, hi uint64) {{.TypeName}} {
92 | return {{.TypeName}}{
93 | Lo: lo,
94 | Hi: hi,
95 | }
96 | }
97 |
98 | func {{.FuncName}}Struct(item struct {Lo uint64; Hi uint64}) {{.TypeName}} {
99 | return {{.TypeName}}{
100 | Lo: item.Lo,
101 | Hi: item.Hi,
102 | }
103 | }
104 | `
105 |
106 | gen := template.New("test")
107 | gen, err := gen.Parse(format)
108 | if err != nil {
109 | panic("failed to parse template: " + err.Error())
110 | }
111 | err = gen.Execute(gg.dest, map[string]string{
112 | "FuncName": gg.EasyTypeName(field.AccessName(gg)),
113 | "TypeName": gg.UneasyTypeName(field.AccessName(gg)),
114 | })
115 | return err
116 | }
117 |
--------------------------------------------------------------------------------
/internal/generator/gogen/native-typenames.go:
--------------------------------------------------------------------------------
1 | package gogen
2 |
3 | import (
4 | "github.com/sirkon/ch-encode/internal/generator"
5 | )
6 |
7 | // Int8NativeTypeName ...
8 | func (gg *GoGen) Int8NativeTypeName() string {
9 | return "int8"
10 | }
11 |
12 | // Int16NativeTypeName ...
13 | func (gg *GoGen) Int16NativeTypeName() string {
14 | return "int16"
15 | }
16 |
17 | // Int32NativeTypeName ...
18 | func (gg *GoGen) Int32NativeTypeName() string {
19 | return "int32"
20 | }
21 |
22 | // Int64NativeTypeName ...
23 | func (gg *GoGen) Int64NativeTypeName() string {
24 | return "int64"
25 | }
26 |
27 | // Uint8NativeTypeName ...
28 | func (gg *GoGen) Uint8NativeTypeName() string {
29 | return "byte"
30 | }
31 |
32 | // Uint16NativeTypeName ...
33 | func (gg *GoGen) Uint16NativeTypeName() string {
34 | return "uint16"
35 | }
36 |
37 | // Uint32NativeTypeName ...
38 | func (gg *GoGen) Uint32NativeTypeName() string {
39 | return "uint32"
40 | }
41 |
42 | // Uint64NativeTypeName ...
43 | func (gg *GoGen) Uint64NativeTypeName() string {
44 | return "uint64"
45 | }
46 |
47 | // Dec128NativeTypeName ...
48 | func (gg *GoGen) Dec128NativeTypeName() string {
49 | return `struct {
50 | Lo uint64
51 | Hi uint64
52 | }`
53 | }
54 |
55 | // Float32NativeTypeName ...
56 | func (gg *GoGen) Float32NativeTypeName() string {
57 | return "float32"
58 | }
59 |
60 | // Float64NativeTypeName ...
61 | func (gg *GoGen) Float64NativeTypeName() string {
62 | return "float64"
63 | }
64 |
65 | // StringNativeTypeName ...
66 | func (gg *GoGen) StringNativeTypeName() string {
67 | return "[]byte"
68 | }
69 |
70 | // FixedStringNativeTypeName ...
71 | func (gg *GoGen) FixedStringNativeTypeName() string {
72 | return "[]byte"
73 | }
74 |
75 | // UUIDNativeTypeName ...
76 | func (gg *GoGen) UUIDNativeTypeName() string {
77 | gg.useGoogleUUID()
78 | return "googleUUID.UUID"
79 | }
80 |
81 | // ArrayNativeTypeName ...
82 | func (gg *GoGen) ArrayNativeTypeName(itemType generator.Field) string {
83 | return "[]" + itemType.NativeTypeName(gg)
84 | }
85 |
86 | // NullableNativeTypeName ...
87 | func (gg *GoGen) NullableNativeTypeName(itemType generator.Field) string {
88 | return "*" + itemType.NativeTypeName(gg)
89 | }
90 |
91 | // NullableStringNativeTypeName ...
92 | func (gg *GoGen) NullableStringNativeTypeName() string {
93 | return gg.StringNativeTypeName()
94 | }
95 |
96 | // NullableArrayNativeTypeName ...
97 | func (gg *GoGen) NullableArrayNativeTypeName(itemType generator.Field) string {
98 | return gg.ArrayNativeTypeName(itemType)
99 | }
100 |
--------------------------------------------------------------------------------
/internal/generator/gogen/regular-typenames.go:
--------------------------------------------------------------------------------
1 | package gogen
2 |
3 | // EasyTypeName ...
4 | func (gg *GoGen) EasyTypeName(name string) string {
5 | return gg.goish.Public(name)
6 | }
7 |
8 | // UneasyTypeName ...
9 | func (gg *GoGen) UneasyTypeName(name string) string {
10 | return gg.goish.Public(name + "_type")
11 | }
12 |
13 | // HelperName ...
14 | func (gg *GoGen) HelperName(name string) string {
15 | return gg.goish.Public(name)
16 | }
17 |
18 | var reservedKeywords = map[string]struct{}{
19 | "break": {}, "case": {}, "chan": {}, "const": {}, "continue": {}, "default": {}, "defer": {},
20 | "else": {}, "fallthrough": {}, "for": {}, "func": {}, "go": {}, "goto": {}, "if": {},
21 | "import": {}, "interface": {}, "map": {}, "package": {}, "range": {}, "return": {},
22 | "select": {}, "struct": {}, "switch": {}, "type": {}, "var": {},
23 | }
24 |
25 | // VarName ...
26 | func (gg *GoGen) VarName(name string) string {
27 | if _, ok := reservedKeywords[name]; ok {
28 | return gg.goish.Private("thisIsReallyStrangeIfYouTookSuchANameForAField" + name)
29 | }
30 | return gg.goish.Private(name)
31 | }
32 |
--------------------------------------------------------------------------------
/internal/generator/gogen/testing-encoding.go:
--------------------------------------------------------------------------------
1 | package gogen
2 |
3 | import (
4 | "fmt"
5 | "sort"
6 | "text/template"
7 |
8 | "github.com/sirkon/ch-encode/internal/generator"
9 | )
10 |
11 | // Int8TestEncoding ...
12 | func (gg *GoGen) Int8TestEncoding(source string) error {
13 | _, err := fmt.Fprintf(gg.dest, "byte(%s),", source)
14 | return err
15 | }
16 |
17 | // Int16TestEncoding ...
18 | func (gg *GoGen) Int16TestEncoding(source string) error {
19 | _, err := fmt.Fprintf(gg.dest, "int16(%s),", source)
20 | return err
21 | }
22 |
23 | // Int32TestEncoding ...
24 | func (gg *GoGen) Int32TestEncoding(source string) error {
25 | _, err := fmt.Fprintf(gg.dest, "int32(%s),", source)
26 | return err
27 | }
28 |
29 | // Int64TestEncoding ...
30 | func (gg *GoGen) Int64TestEncoding(source string) error {
31 | _, err := fmt.Fprintf(gg.dest, "int64(%s),", source)
32 | return err
33 | }
34 |
35 | // Uint8TestEncoding ...
36 | func (gg *GoGen) Uint8TestEncoding(source string) error {
37 | _, err := fmt.Fprintf(gg.dest, "byte(%s),", source)
38 | return err
39 | }
40 |
41 | // Uint16TestEncoding ...
42 | func (gg *GoGen) Uint16TestEncoding(source string) error {
43 | _, err := fmt.Fprintf(gg.dest, "uint16(%s),", source)
44 | return err
45 | }
46 |
47 | // Uint32TestEncoding ...
48 | func (gg *GoGen) Uint32TestEncoding(source string) error {
49 | _, err := fmt.Fprintf(gg.dest, "uint32(%s),", source)
50 | return err
51 | }
52 |
53 | // Uint64TestEncoding ...
54 | func (gg *GoGen) Uint64TestEncoding(source string) error {
55 | _, err := fmt.Fprintf(gg.dest, "uint64(%s),", source)
56 | return err
57 | }
58 |
59 | // Dec32TestEncoding ...
60 | func (gg *GoGen) Dec32TestEncoding(scale int, source string) error {
61 | gg.regImport("", "github.com/sirkon/decconv")
62 | text := "decconv.Encode32(%d, int32(%s)),"
63 | _, err := fmt.Fprintf(gg.dest, text, scale, source)
64 | return err
65 | }
66 |
67 | // Dec64TestEncoding ...
68 | func (gg *GoGen) Dec64TestEncoding(scale int, source string) error {
69 | gg.regImport("", "github.com/sirkon/decconv")
70 | text := "decconv.Encode64(%d, int64(%s)),"
71 | _, err := fmt.Fprintf(gg.dest, text, scale, source)
72 | return err
73 | }
74 |
75 | // Dec128TestEncoding ...
76 | func (gg *GoGen) Dec128TestEncoding(scale int, source string) error {
77 | gg.regImport("", "github.com/sirkon/decconv")
78 | text := "decconv.Encode128(%d, %s.Lo, %s.Hi),"
79 | _, err := fmt.Fprintf(gg.dest, text, scale, source, source)
80 | return err
81 | }
82 |
83 | // Float32TestEncoding ...
84 | func (gg *GoGen) Float32TestEncoding(source string) error {
85 | _, err := fmt.Fprintf(gg.dest, "float32(%s),", source)
86 | return err
87 | }
88 |
89 | // Float64TestEncoding ...
90 | func (gg *GoGen) Float64TestEncoding(source string) error {
91 | _, err := fmt.Fprintf(gg.dest, "float64(%s),", source)
92 | return err
93 | }
94 |
95 | // EnumTestEncoding ...
96 | func (gg *GoGen) EnumTestEncoding(source string, safeMapping map[string]int) error {
97 | enumItems := NewEnumItemSlice(safeMapping)
98 | sort.Sort(enumItems)
99 | text :=
100 | `
101 | func() string {
102 | revMapping := map[int]string{
103 | {{ range .mapping }} {{ .Value }}: "{{ .Key }}",
104 | {{ end }}
105 | }
106 | key, ok := revMapping[int({{ .source }})]
107 | if !ok {
108 | panic(fmt.Errorf("Value %d has no key mapped to", {{ .source }}));
109 | }
110 | return key
111 | }(),
112 | `
113 | tmpl, err := template.New("enum_test_encoding").Parse(text)
114 | if err != nil {
115 | return err
116 | }
117 | gg.regImport("", "fmt")
118 | err = tmpl.Execute(gg.dest, map[string]interface{}{
119 | "source": source,
120 | "mapping": enumItems,
121 | })
122 | return err
123 | }
124 |
125 | // DateTestEncoding ...
126 | func (gg *GoGen) DateTestEncoding(source string) error {
127 | text :=
128 | `
129 | func() string {
130 | timestamp := int64(%s)*3600*24
131 | tm := stdtime.Unix(timestamp, 0)
132 | moscowZone, _ := stdtime.LoadLocation("Europe/Moscow")
133 | moscow := tm.In(moscowZone)
134 | return moscow.Format("2006-01-02")
135 | }(),
136 | `
137 | _, err := fmt.Fprintf(gg.dest, text, source)
138 | return err
139 | }
140 |
141 | // DateTimeTestEncoding ...
142 | func (gg *GoGen) DateTimeTestEncoding(source string) error {
143 | text :=
144 | `
145 | func() string {
146 | timestamp := int64(%s)
147 | tm := stdtime.Unix(timestamp, 0)
148 | moscowZone, _ := stdtime.LoadLocation("Europe/Moscow")
149 | moscow := tm.In(moscowZone)
150 | return moscow.Format("2006-01-02T15:04:05")
151 | }(),
152 | `
153 | _, err := fmt.Fprintf(gg.dest, text, source)
154 | return err
155 |
156 | }
157 |
158 | // StringTestEncoding ...
159 | func (gg *GoGen) StringTestEncoding(source string) error {
160 | _, err := fmt.Fprintf(gg.dest, "string(%s),", source)
161 | return err
162 | }
163 |
164 | // FixedStringTestEncoding ...
165 | func (gg *GoGen) FixedStringTestEncoding(source string, length int) error {
166 | _, err := fmt.Fprintf(gg.dest, "string(%s),", source)
167 | return err
168 | }
169 |
170 | // UUIDTestEncoding ...
171 | func (gg *GoGen) UUIDTestEncoding(source string) error {
172 | gg.useGoogleUUID()
173 | _, err := fmt.Fprintf(gg.dest, "googleUUID.UUID(%s).String(),", source)
174 | return err
175 | }
176 |
177 | // ArrayTestEncoding ...
178 | func (gg *GoGen) ArrayTestEncoding(source string, field generator.Field) error {
179 | text :=
180 | `
181 | func() (res []%s) {
182 | for _, arrayItem := range %s {
183 | res = append(res, `
184 | _, err := fmt.Fprintf(gg.dest, text, field.TestingTypeName(gg), source)
185 | if err != nil {
186 | return err
187 | }
188 | if err = field.TestEncoding("arrayItem", gg); err != nil {
189 | return err
190 | }
191 | if err = gg.RawData(");\n};\nreturn res;\n}(),\n"); err != nil {
192 | return err
193 | }
194 | return nil
195 | }
196 |
197 | // NullableTestEncoding ...
198 | func (gg *GoGen) NullableTestEncoding(source string, field generator.Field) error {
199 | _, err := fmt.Fprintf(gg.dest, "(*%s)(%s),\n", field.TestingTypeName(gg), source)
200 | return err
201 | }
202 |
203 | // NullableStringTestEncoding
204 | func (gg *GoGen) NullableStringTestEncoding(source string) error {
205 | text :=
206 | `
207 | func() *string {
208 | if %s == nil { return nil}
209 | res := string(%s)
210 | return &res
211 | }(),`
212 | _, err := fmt.Fprintf(gg.dest, text, source, source)
213 | return err
214 | }
215 |
216 | // NullableArrayTestEncoding
217 | func (gg *GoGen) NullableArrayTestEncoding(source string, field generator.Field) error {
218 | return gg.NullableTestEncoding(source, field)
219 | }
220 |
--------------------------------------------------------------------------------
/internal/generator/gogen/testing-typenames.go:
--------------------------------------------------------------------------------
1 | package gogen
2 |
3 | import (
4 | "github.com/sirkon/ch-encode/internal/generator"
5 | )
6 |
7 | // Int8TestingTypeName ...
8 | func (gg *GoGen) Int8TestingTypeName() string {
9 | return "byte"
10 | }
11 |
12 | // Int16TestingTypeName ...
13 | func (gg *GoGen) Int16TestingTypeName() string {
14 | return "int16"
15 | }
16 |
17 | // Int32TestingTypeName ...
18 | func (gg *GoGen) Int32TestingTypeName() string {
19 | return "int32"
20 | }
21 |
22 | // Int64TestingTypeName ...
23 | func (gg *GoGen) Int64TestingTypeName() string {
24 | return "int64"
25 | }
26 |
27 | // Uint8TestingTypeName ...
28 | func (gg *GoGen) Uint8TestingTypeName() string {
29 | return "byte"
30 | }
31 |
32 | // Uint16TestingTypeName ...
33 | func (gg *GoGen) Uint16TestingTypeName() string {
34 | return "uint16"
35 | }
36 |
37 | // Uint32TestingTypeName ...
38 | func (gg *GoGen) Uint32TestingTypeName() string {
39 | return "uint32"
40 | }
41 |
42 | // Uint64TestingTypeName ...
43 | func (gg *GoGen) Uint64TestingTypeName() string {
44 | return "uint64"
45 | }
46 |
47 | // Dec32TestingTypeName ...
48 | func (gg *GoGen) Dec32TestingTypeName() string {
49 | return "string"
50 | }
51 |
52 | // Dec64TestingTypeName ...
53 | func (gg *GoGen) Dec64TestingTypeName() string {
54 | return "string"
55 | }
56 |
57 | // Dec128TestingTypeName ...
58 | func (gg *GoGen) Dec128TestingTypeName() string {
59 | return "string"
60 | }
61 |
62 | // Float32TestingTypeName ...
63 | func (gg *GoGen) Float32TestingTypeName() string {
64 | return "float32"
65 | }
66 |
67 | // Float64TestingTypeName ...
68 | func (gg *GoGen) Float64TestingTypeName() string {
69 | return "float64"
70 | }
71 |
72 | // EnumTestingTypeName ...
73 | func (gg *GoGen) EnumTestingTypeName() string {
74 | return "string"
75 | }
76 |
77 | // DateTestingTypeName ...
78 | func (gg *GoGen) DateTestingTypeName() string {
79 | return "string"
80 | }
81 |
82 | // DateTimeTestingTypeName ...
83 | func (gg *GoGen) DateTimeTestingTypeName() string {
84 | return "string"
85 | }
86 |
87 | // StringTestingTypeName ...
88 | func (gg *GoGen) StringTestingTypeName() string {
89 | return "string"
90 | }
91 |
92 | // FixedStringTestingTypeName ...
93 | func (gg *GoGen) FixedStringTestingTypeName() string {
94 | return "string"
95 | }
96 |
97 | // UUIDTestingTypeName ...
98 | func (gg *GoGen) UUIDTestingTypeName() string {
99 | return "string"
100 | }
101 |
102 | // ArrayTestingTypeName ...
103 | func (gg *GoGen) ArrayTestingTypeName(itemType generator.Field) string {
104 | return "[]" + itemType.TestingTypeName(gg)
105 | }
106 |
107 | // NullableTestingTypeName ...
108 | func (gg *GoGen) NullableTestingTypeName(itemType generator.Field) string {
109 | return "*" + itemType.TestingTypeName(gg)
110 | }
111 |
112 | // NullableStringTestingTypeName ...
113 | func (gg *GoGen) NullableStringTestingTypeName() string {
114 | return "*string"
115 | }
116 |
117 | // NullableArrayTestingTypeName ...
118 | func (gg *GoGen) NullableArrayTestingTypeName(itemType generator.Field) string {
119 | return gg.ArrayTestingTypeName(itemType)
120 | }
121 |
--------------------------------------------------------------------------------
/internal/generator/gogen/testingencoder.go:
--------------------------------------------------------------------------------
1 | package gogen
2 |
3 | import (
4 | "fmt"
5 | "text/template"
6 |
7 | "github.com/sirkon/ch-encode/internal/generator"
8 | )
9 |
10 | // TestDef ...
11 | func (gg *GoGen) TestDef(fields *generator.FieldSet) error {
12 | text :=
13 | `
14 | type {{.record}} struct {
15 | {{ range .fields }} {{.Name}} {{.Type}}
16 | {{ end}}
17 | }
18 | `
19 | type item struct {
20 | Name string
21 | Type string
22 | }
23 | items := make([]item, len(fields.List()))
24 | for i, field := range fields.List() {
25 | items[i].Name = field.AccessName(gg)
26 | items[i].Type = field.TestingTypeName(gg)
27 | }
28 | tmpl, err := template.New("testing").Parse(text)
29 | if err != nil {
30 | return err
31 | }
32 | return tmpl.Execute(gg.dest, map[string]interface{}{
33 | "record": gg.testingResultName(),
34 | "fields": items,
35 | })
36 | }
37 |
38 | // TestEncoderDef ...
39 | func (gg *GoGen) TestEncoderDef(*generator.FieldSet) error {
40 | text :=
41 | `
42 | type {{.encoder}} struct {
43 | Result []{{.record}}
44 | }
45 |
46 | func New{{.encoder}}() *{{.encoder}} {
47 | return &{{.encoder}}{
48 | Result: make([]{{.record}}, 0, 3),
49 | }
50 | }
51 |
52 | func (enc *{{.encoder}}) InsertCount() int {
53 | return len(enc.Result);
54 | }
55 | func (enc *{{.encoder}}) ErrorCount() int {
56 | return 0;
57 | }
58 | `
59 | tmpl, err := template.New("testingEncoder").Parse(text)
60 | if err != nil {
61 | return err
62 | }
63 | return tmpl.Execute(gg.dest, map[string]string{
64 | "record": gg.testingResultName(),
65 | "encoder": gg.testingEncoderName(),
66 | })
67 | }
68 |
69 | // TestEncodingMethod ...
70 | func (gg *GoGen) TestEncodingMethod(fields *generator.FieldSet) (err error) {
71 | if err = gg.RawData(fmt.Sprintf("func (enc *%s)Encode(%s) error{", gg.testingEncoderName(), gg.argList(fields))); err != nil {
72 | return
73 | }
74 | if err = gg.constraints(fields); err != nil {
75 | return
76 | }
77 | if err = gg.RawData(fmt.Sprintf("enc.Result = append(enc.Result, %s{\n", gg.testingResultName())); err != nil {
78 | return
79 | }
80 | for _, field := range fields.List() {
81 | if _, err = fmt.Fprintf(gg.dest, "%s:", field.AccessName(gg)); err != nil {
82 | return
83 | }
84 | if err = field.TestEncoding(field.ArgName(gg), gg); err != nil {
85 | return
86 | }
87 | if err = gg.RawData("\n"); err != nil {
88 | return
89 | }
90 | }
91 | return gg.RawData("\n}); return nil}\n")
92 | }
93 |
--------------------------------------------------------------------------------
/internal/generator/gogen/util.go:
--------------------------------------------------------------------------------
1 | package gogen
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "github.com/sirkon/ch-encode/internal/generator"
8 | )
9 |
10 | func (gg *GoGen) interfaceEncoderName() string {
11 | return fmt.Sprintf("%sEncoder", gg.goish.Public(gg.table))
12 | }
13 |
14 | func (gg *GoGen) encoderName() string {
15 | return fmt.Sprintf("%sRawEncoder", gg.goish.Public(gg.table))
16 | }
17 |
18 | func (gg *GoGen) filterEncoderName() string {
19 | return fmt.Sprintf("%sRawEncoderDateFilter", gg.goish.Public(gg.table))
20 | }
21 |
22 | func (gg *GoGen) voidEncoderName() string {
23 | return fmt.Sprintf("New%sRawEncoderVoid", gg.goish.Public(gg.table))
24 | }
25 |
26 | func (gg *GoGen) testingEncoderName() string {
27 | return fmt.Sprintf("Testing%sEncoder", gg.goish.Public(gg.table))
28 | }
29 |
30 | func (gg *GoGen) testingResultName() string {
31 | return fmt.Sprintf("Testing%sResult", gg.goish.Public(gg.table))
32 | }
33 |
34 | func (gg *GoGen) argList(fields *generator.FieldSet) string {
35 | res := []string{}
36 | for _, field := range fields.List() {
37 | res = append(res, fmt.Sprintf("%s %s", field.ArgName(gg), field.TypeName(gg)))
38 | }
39 | return strings.Join(res, ", ")
40 | }
41 |
42 | // EnumItem represents enumX key→value mapping
43 | type EnumItem struct {
44 | Key string
45 | Value int
46 | }
47 |
48 | // EnumItemSlice represents
49 | type EnumItemSlice []EnumItem
50 |
51 | // NewEnumItemSlice constructor
52 | func NewEnumItemSlice(input map[string]int) (res EnumItemSlice) {
53 | for key, value := range input {
54 | res = append(res, EnumItem{Key: key, Value: value})
55 | }
56 | return res
57 | }
58 |
59 | //// Now implement sort.Interface for EnumItemSlice
60 |
61 | // Len for sort.Interface
62 | func (eis EnumItemSlice) Len() int { return len(eis) }
63 |
64 | // Less for sort.Interface
65 | func (eis EnumItemSlice) Less(i, j int) bool { return eis[i].Value < eis[j].Value }
66 |
67 | // Swap for sort.Interface
68 | func (eis EnumItemSlice) Swap(i, j int) {
69 | eis[i], eis[j] = eis[j], eis[i]
70 | }
71 |
--------------------------------------------------------------------------------
/internal/generator/gogen/voidencoder.go:
--------------------------------------------------------------------------------
1 | package gogen
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/sirkon/ch-encode/internal/generator"
7 | )
8 |
9 | // VoidEncoderDef ...
10 | func (gg *GoGen) VoidEncoderDef(*generator.FieldSet) error {
11 | _, err := fmt.Fprintf(gg.dest, "\ntype %s bool\n", gg.voidEncoderName())
12 | return err
13 | }
14 |
15 | // VoidEncodingMethod ...
16 | func (gg *GoGen) VoidEncodingMethod(fields *generator.FieldSet) error {
17 | _, err := fmt.Fprintf(
18 | gg.dest,
19 | `
20 | func (enc %s) Encode(%s) error {return nil;}
21 | func (enc %s) InsertCount() int {return 0;}
22 | func (enc %s) ErrorCount() int {return 0;}
23 | `,
24 | gg.voidEncoderName(), gg.argList(fields), gg.voidEncoderName(), gg.voidEncoderName())
25 | return err
26 | }
27 |
--------------------------------------------------------------------------------
/internal/generator/int16_field.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | // Int16 Type implementation
4 | type Int16 struct {
5 | field string
6 | fieldType string
7 | }
8 |
9 | // NewInt16 constructor
10 | func NewInt16(field string, fieldType string) *Int16 {
11 | return &Int16{
12 | field: field,
13 | fieldType: fieldType,
14 | }
15 | }
16 |
17 | // FieldName ...
18 | func (i16 *Int16) FieldName(gen Generator) string {
19 | return i16.field
20 | }
21 |
22 | // FieldTypeName ...
23 | func (i16 *Int16) FieldTypeName(gen Generator) string {
24 | return i16.fieldType
25 | }
26 |
27 | // TypeName ...
28 | func (i16 *Int16) TypeName(gen Generator) string {
29 | return gen.EasyTypeName(i16.field)
30 | }
31 |
32 | // ArgName ...
33 | func (i16 *Int16) ArgName(gen Generator) string {
34 | return gen.VarName(i16.field)
35 | }
36 |
37 | // AccessName ...
38 | func (i16 *Int16) AccessName(gen Generator) string {
39 | return gen.HelperName(i16.field)
40 | }
41 |
42 | // NativeTypeName ...
43 | func (i16 Int16) NativeTypeName(gen Generator) string {
44 | return gen.Int16NativeTypeName()
45 | }
46 |
47 | // Encoding ...
48 | func (i16 Int16) Encoding(source string, gen Generator) error {
49 | return gen.Int16Encoding(source)
50 | }
51 |
52 | // Helper ...
53 | func (i16 *Int16) Helper(gen Generator) error {
54 | return nil
55 | }
56 |
57 | // TestingTypeName ...
58 | func (i16 Int16) TestingTypeName(gen Generator) string {
59 | return gen.Int16TestingTypeName()
60 | }
61 |
62 | // TestEncoding ...
63 | func (i16 Int16) TestEncoding(source string, gen Generator) error {
64 | return gen.Int16TestEncoding(source)
65 | }
66 |
--------------------------------------------------------------------------------
/internal/generator/int32_field.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | // Int32 Type implementation
4 | type Int32 struct {
5 | field string
6 | fieldType string
7 | }
8 |
9 | // NewInt32 constructor
10 | func NewInt32(field string, fieldType string) *Int32 {
11 | return &Int32{
12 | field: field,
13 | fieldType: fieldType,
14 | }
15 | }
16 |
17 | // FieldName ...
18 | func (i32 *Int32) FieldName(gen Generator) string {
19 | return i32.field
20 | }
21 |
22 | // FieldTypeName ...
23 | func (i32 *Int32) FieldTypeName(gen Generator) string {
24 | return i32.fieldType
25 | }
26 |
27 | // TypeName ...
28 | func (i32 *Int32) TypeName(gen Generator) string {
29 | return gen.EasyTypeName(i32.field)
30 | }
31 |
32 | // ArgName ...
33 | func (i32 *Int32) ArgName(gen Generator) string {
34 | return gen.VarName(i32.field)
35 | }
36 |
37 | // AccessName ...
38 | func (i32 *Int32) AccessName(gen Generator) string {
39 | return gen.HelperName(i32.field)
40 | }
41 |
42 | // NativeTypeName ...
43 | func (i32 Int32) NativeTypeName(gen Generator) string {
44 | return gen.Int32NativeTypeName()
45 | }
46 |
47 | // Encoding ...
48 | func (i32 Int32) Encoding(source string, gen Generator) error {
49 | return gen.Int32Encoding(source)
50 | }
51 |
52 | // Helper ...
53 | func (i32 *Int32) Helper(gen Generator) error {
54 | return nil
55 | }
56 |
57 | // TestingTypeName ...
58 | func (i32 Int32) TestingTypeName(gen Generator) string {
59 | return gen.Int32TestingTypeName()
60 | }
61 |
62 | // TestEncoding ...
63 | func (i32 Int32) TestEncoding(source string, gen Generator) error {
64 | return gen.Int32TestEncoding(source)
65 | }
66 |
--------------------------------------------------------------------------------
/internal/generator/int64_field.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | // Int64 Type implementation
4 | type Int64 struct {
5 | field string
6 | fieldType string
7 | }
8 |
9 | // NewInt64 constructor
10 | func NewInt64(field string, fieldType string) *Int64 {
11 | return &Int64{
12 | field: field,
13 | fieldType: fieldType,
14 | }
15 | }
16 |
17 | // FieldName ...
18 | func (i64 *Int64) FieldName(gen Generator) string {
19 | return i64.field
20 | }
21 |
22 | // FieldTypeName ...
23 | func (i64 *Int64) FieldTypeName(gen Generator) string {
24 | return i64.fieldType
25 | }
26 |
27 | // TypeName ...
28 | func (i64 *Int64) TypeName(gen Generator) string {
29 | return gen.EasyTypeName(i64.field)
30 | }
31 |
32 | // ArgName ...
33 | func (i64 *Int64) ArgName(gen Generator) string {
34 | return gen.VarName(i64.field)
35 | }
36 |
37 | // AccessName ...
38 | func (i64 *Int64) AccessName(gen Generator) string {
39 | return gen.HelperName(i64.field)
40 | }
41 |
42 | // NativeTypeName ...
43 | func (i64 Int64) NativeTypeName(gen Generator) string {
44 | return gen.Int64NativeTypeName()
45 | }
46 |
47 | // Encoding ...
48 | func (i64 Int64) Encoding(source string, gen Generator) error {
49 | return gen.Int64Encoding(source)
50 | }
51 |
52 | // Helper ...
53 | func (i64 *Int64) Helper(gen Generator) error {
54 | return nil
55 | }
56 |
57 | // TestingTypeName ...
58 | func (i64 Int64) TestingTypeName(gen Generator) string {
59 | return gen.Int64TestingTypeName()
60 | }
61 |
62 | // TestEncoding ...
63 | func (i64 Int64) TestEncoding(source string, gen Generator) error {
64 | return gen.Int64TestEncoding(source)
65 | }
66 |
--------------------------------------------------------------------------------
/internal/generator/int8_field.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | // Int8 Type implementation
4 | type Int8 struct {
5 | field string
6 | fieldType string
7 | }
8 |
9 | // NewInt8 constructor
10 | func NewInt8(field string, fieldType string) *Int8 {
11 | return &Int8{
12 | field: field,
13 | fieldType: fieldType,
14 | }
15 | }
16 |
17 | // FieldName ...
18 | func (i8 *Int8) FieldName(gen Generator) string {
19 | return i8.field
20 | }
21 |
22 | // FieldTypeName ...
23 | func (i8 *Int8) FieldTypeName(gen Generator) string {
24 | return i8.fieldType
25 | }
26 |
27 | // TypeName ...
28 | func (i8 *Int8) TypeName(gen Generator) string {
29 | return gen.EasyTypeName(i8.field)
30 | }
31 |
32 | // ArgName ...
33 | func (i8 *Int8) ArgName(gen Generator) string {
34 | return gen.VarName(i8.field)
35 | }
36 |
37 | // AccessName ...
38 | func (i8 *Int8) AccessName(gen Generator) string {
39 | return gen.HelperName(i8.field)
40 | }
41 |
42 | // NativeTypeName ...
43 | func (i8 Int8) NativeTypeName(gen Generator) string {
44 | return gen.Int8NativeTypeName()
45 | }
46 |
47 | // Encoding ...
48 | func (i8 Int8) Encoding(source string, gen Generator) error {
49 | return gen.Int8Encoding(source)
50 | }
51 |
52 | // Helper ...
53 | func (i8 *Int8) Helper(gen Generator) error {
54 | return nil
55 | }
56 |
57 | // TestingTypeName ...
58 | func (i8 Int8) TestingTypeName(gen Generator) string {
59 | return gen.Int8TestingTypeName()
60 | }
61 |
62 | // TestEncoding ...
63 | func (i8 Int8) TestEncoding(source string, gen Generator) error {
64 | return gen.Int8TestEncoding(source)
65 | }
66 |
--------------------------------------------------------------------------------
/internal/generator/nullable_array_field.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | // NullableArray Type implementation
4 | type NullableArray struct {
5 | field string
6 | fieldType string
7 | meta Field
8 | }
9 |
10 | // NewNullableArray constructor
11 | func NewNullableArray(field, fieldType string, meta Field) *NullableArray {
12 | return &NullableArray{
13 | field: field,
14 | fieldType: fieldType,
15 | meta: meta,
16 | }
17 | }
18 |
19 | // FieldName ...
20 | func (a *NullableArray) FieldName(gen Generator) string {
21 | return a.field
22 | }
23 |
24 | // FieldTypeName ...
25 | func (a *NullableArray) FieldTypeName(gen Generator) string {
26 | return a.fieldType
27 | }
28 |
29 | // TypeName ...
30 | func (a *NullableArray) TypeName(gen Generator) string {
31 | return gen.EasyTypeName(a.field)
32 | }
33 |
34 | // ArgName ...
35 | func (a *NullableArray) ArgName(gen Generator) string {
36 | return gen.VarName(a.field)
37 | }
38 |
39 | // AccessName ...
40 | func (a *NullableArray) AccessName(gen Generator) string {
41 | return gen.HelperName(a.field)
42 | }
43 |
44 | // NativeTypeName NullableArray implementation
45 | func (a *NullableArray) NativeTypeName(gen Generator) string {
46 | return gen.NullableArrayNativeTypeName(a.meta)
47 | }
48 |
49 | // Encoding NullableArray implementation
50 | func (a *NullableArray) Encoding(source string, gen Generator) error {
51 | return gen.NullableArrayEncoding(source, a.meta)
52 | }
53 |
54 | // Helper ...
55 | func (a *NullableArray) Helper(gen Generator) error {
56 | return nil
57 | }
58 |
59 | // TestingTypeName ...
60 | func (a *NullableArray) TestingTypeName(gen Generator) string {
61 | return gen.NullableArrayTestingTypeName(a.meta)
62 | }
63 |
64 | // TestEncoding ...
65 | func (a *NullableArray) TestEncoding(source string, gen Generator) error {
66 | return gen.NullableArrayTestEncoding(source, a.meta)
67 | }
68 |
--------------------------------------------------------------------------------
/internal/generator/nullable_field.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | // Nullable Type implementation
4 | type Nullable struct {
5 | field string
6 | fieldType string
7 | meta Field
8 | }
9 |
10 | // NewNullable constructor
11 | func NewNullable(field, fieldType string, meta Field) *Nullable {
12 | return &Nullable{
13 | field: field,
14 | fieldType: fieldType,
15 | meta: meta,
16 | }
17 | }
18 |
19 | // FieldName ...
20 | func (n *Nullable) FieldName(gen Generator) string {
21 | return n.field
22 | }
23 |
24 | // FieldTypeName ...
25 | func (n *Nullable) FieldTypeName(gen Generator) string {
26 | return n.fieldType
27 | }
28 |
29 | // TypeName ...
30 | func (n *Nullable) TypeName(gen Generator) string {
31 | return gen.EasyTypeName(n.field)
32 | }
33 |
34 | // ArgName ...
35 | func (n *Nullable) ArgName(gen Generator) string {
36 | return gen.VarName(n.field)
37 | }
38 |
39 | // AccessName ...
40 | func (n *Nullable) AccessName(gen Generator) string {
41 | return gen.HelperName(n.field)
42 | }
43 |
44 | // NativeTypeName Array implementation
45 | func (n *Nullable) NativeTypeName(gen Generator) string {
46 | return gen.NullableNativeTypeName(n.meta)
47 | }
48 |
49 | // Encoding Array implementation
50 | func (n *Nullable) Encoding(source string, gen Generator) error {
51 | return gen.NullableEncoding(source, n.meta)
52 | }
53 |
54 | // Helper ...
55 | func (n *Nullable) Helper(gen Generator) error {
56 | return nil
57 | }
58 |
59 | // TestingTypeName ...
60 | func (n *Nullable) TestingTypeName(gen Generator) string {
61 | return gen.NullableTestingTypeName(n.meta)
62 | }
63 |
64 | // TestEncoding ...
65 | func (n *Nullable) TestEncoding(source string, gen Generator) error {
66 | return gen.NullableTestEncoding(source, n.meta)
67 | }
68 |
--------------------------------------------------------------------------------
/internal/generator/nullable_string_field.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | // NullableString Type implementation
4 | type NullableString struct {
5 | field string
6 | fieldType string
7 | }
8 |
9 | // NewNullableString constructor
10 | func NewNullableString(field, fieldType string) *NullableString {
11 | return &NullableString{
12 | field: field,
13 | fieldType: fieldType,
14 | }
15 | }
16 |
17 | // FieldName ...
18 | func (s *NullableString) FieldName(gen Generator) string {
19 | return s.field
20 | }
21 |
22 | // FieldTypeName ...
23 | func (s *NullableString) FieldTypeName(gen Generator) string {
24 | return s.fieldType
25 | }
26 |
27 | // TypeName ...
28 | func (s *NullableString) TypeName(gen Generator) string {
29 | return gen.EasyTypeName(s.field)
30 | }
31 |
32 | // ArgName ...
33 | func (s *NullableString) ArgName(gen Generator) string {
34 | return gen.VarName(s.field)
35 | }
36 |
37 | // AccessName ...
38 | func (s *NullableString) AccessName(gen Generator) string {
39 | return gen.HelperName(s.field)
40 | }
41 |
42 | // NativeTypeName NullableString implementation
43 | func (s *NullableString) NativeTypeName(gen Generator) string {
44 | return gen.NullableStringNativeTypeName()
45 | }
46 |
47 | // Encoding NullableString implementation
48 | func (s *NullableString) Encoding(source string, gen Generator) error {
49 | return gen.NullableStringEncoding(source)
50 | }
51 |
52 | // Helper ...
53 | func (s *NullableString) Helper(gen Generator) error {
54 | return nil
55 | }
56 |
57 | // TestingTypeName ...
58 | func (s *NullableString) TestingTypeName(gen Generator) string {
59 | return gen.NullableStringTestingTypeName()
60 | }
61 |
62 | // TestEncoding ...
63 | func (s *NullableString) TestEncoding(source string, gen Generator) error {
64 | return gen.NullableStringTestEncoding(source)
65 | }
66 |
--------------------------------------------------------------------------------
/internal/generator/string_field.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | // String Type implementation
4 | type String struct {
5 | field string
6 | fieldType string
7 | }
8 |
9 | // NewString constructor
10 | func NewString(field, fieldType string) *String {
11 | return &String{
12 | field: field,
13 | fieldType: fieldType,
14 | }
15 | }
16 |
17 | // FieldName ...
18 | func (s *String) FieldName(gen Generator) string {
19 | return s.field
20 | }
21 |
22 | // FieldTypeName ...
23 | func (s *String) FieldTypeName(gen Generator) string {
24 | return s.fieldType
25 | }
26 |
27 | // TypeName ...
28 | func (s *String) TypeName(gen Generator) string {
29 | return gen.EasyTypeName(s.field)
30 | }
31 |
32 | // ArgName ...
33 | func (s *String) ArgName(gen Generator) string {
34 | return gen.VarName(s.field)
35 | }
36 |
37 | // AccessName ...
38 | func (s *String) AccessName(gen Generator) string {
39 | return gen.HelperName(s.field)
40 | }
41 |
42 | // NativeTypeName String implementation
43 | func (s *String) NativeTypeName(gen Generator) string {
44 | return gen.StringNativeTypeName()
45 | }
46 |
47 | // Encoding String implementation
48 | func (s *String) Encoding(source string, gen Generator) error {
49 | return gen.StringEncoding(source)
50 | }
51 |
52 | // Helper ...
53 | func (s *String) Helper(gen Generator) error {
54 | return nil
55 | }
56 |
57 | // TestingTypeName ...
58 | func (s *String) TestingTypeName(gen Generator) string {
59 | return gen.StringTestingTypeName()
60 | }
61 |
62 | // TestEncoding ...
63 | func (s *String) TestEncoding(source string, gen Generator) error {
64 | return gen.StringTestEncoding(source)
65 | }
66 |
--------------------------------------------------------------------------------
/internal/generator/uint16_field.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | // Uint16 Type implementation
4 | type Uint16 struct {
5 | field string
6 | fieldType string
7 | }
8 |
9 | // NewUint16 constructor
10 | func NewUint16(field string, fieldType string) *Uint16 {
11 | return &Uint16{
12 | field: field,
13 | fieldType: fieldType,
14 | }
15 | }
16 |
17 | // FieldName ...
18 | func (ui16 *Uint16) FieldName(gen Generator) string {
19 | return ui16.field
20 | }
21 |
22 | // FieldTypeName ...
23 | func (ui16 *Uint16) FieldTypeName(gen Generator) string {
24 | return ui16.fieldType
25 | }
26 |
27 | // TypeName ...
28 | func (ui16 *Uint16) TypeName(gen Generator) string {
29 | return gen.EasyTypeName(ui16.field)
30 | }
31 |
32 | // ArgName ...
33 | func (ui16 *Uint16) ArgName(gen Generator) string {
34 | return gen.VarName(ui16.field)
35 | }
36 |
37 | // AccessName ...
38 | func (ui16 *Uint16) AccessName(gen Generator) string {
39 | return gen.HelperName(ui16.field)
40 | }
41 |
42 | // NativeTypeName ...
43 | func (ui16 Uint16) NativeTypeName(gen Generator) string {
44 | return gen.Uint16NativeTypeName()
45 | }
46 |
47 | // Encoding ...
48 | func (ui16 Uint16) Encoding(source string, gen Generator) error {
49 | return gen.Uint16Encoding(source)
50 | }
51 |
52 | // Helper ...
53 | func (ui16 *Uint16) Helper(gen Generator) error {
54 | return nil
55 | }
56 |
57 | // TestingTypeName ...
58 | func (ui16 Uint16) TestingTypeName(gen Generator) string {
59 | return gen.Uint16TestingTypeName()
60 | }
61 |
62 | // TestEncoding ...
63 | func (ui16 Uint16) TestEncoding(source string, gen Generator) error {
64 | return gen.Uint16TestEncoding(source)
65 | }
66 |
--------------------------------------------------------------------------------
/internal/generator/uint32_field.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | // Uint32 Type implementation
4 | type Uint32 struct {
5 | field string
6 | fieldType string
7 | }
8 |
9 | // NewUint32 constructor
10 | func NewUint32(field string, fieldType string) *Uint32 {
11 | return &Uint32{
12 | field: field,
13 | fieldType: fieldType,
14 | }
15 | }
16 |
17 | // FieldName ...
18 | func (ui32 *Uint32) FieldName(gen Generator) string {
19 | return ui32.field
20 | }
21 |
22 | // FieldTypeName ...
23 | func (ui32 *Uint32) FieldTypeName(gen Generator) string {
24 | return ui32.fieldType
25 | }
26 |
27 | // TypeName ...
28 | func (ui32 *Uint32) TypeName(gen Generator) string {
29 | return gen.EasyTypeName(ui32.field)
30 | }
31 |
32 | // ArgName ...
33 | func (ui32 *Uint32) ArgName(gen Generator) string {
34 | return gen.VarName(ui32.field)
35 | }
36 |
37 | // AccessName ...
38 | func (ui32 *Uint32) AccessName(gen Generator) string {
39 | return gen.HelperName(ui32.field)
40 | }
41 |
42 | // NativeTypeName ...
43 | func (ui32 Uint32) NativeTypeName(gen Generator) string {
44 | return gen.Uint32NativeTypeName()
45 | }
46 |
47 | // Encoding ...
48 | func (ui32 Uint32) Encoding(source string, gen Generator) error {
49 | return gen.Uint32Encoding(source)
50 | }
51 |
52 | // Helper ...
53 | func (ui32 *Uint32) Helper(gen Generator) error {
54 | return nil
55 | }
56 |
57 | // TestingTypeName ...
58 | func (ui32 Uint32) TestingTypeName(gen Generator) string {
59 | return gen.Uint32TestingTypeName()
60 | }
61 |
62 | // TestEncoding ...
63 | func (ui32 Uint32) TestEncoding(source string, gen Generator) error {
64 | return gen.Uint32TestEncoding(source)
65 | }
66 |
--------------------------------------------------------------------------------
/internal/generator/uint64_field.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | // Uint64 Type implementation
4 | type Uint64 struct {
5 | field string
6 | fieldType string
7 | }
8 |
9 | // NewUint64 constructor
10 | func NewUint64(field string, fieldType string) *Uint64 {
11 | return &Uint64{
12 | field: field,
13 | fieldType: fieldType,
14 | }
15 | }
16 |
17 | // FieldName ...
18 | func (ui64 *Uint64) FieldName(gen Generator) string {
19 | return ui64.field
20 | }
21 |
22 | // FieldTypeName ...
23 | func (ui64 *Uint64) FieldTypeName(gen Generator) string {
24 | return ui64.fieldType
25 | }
26 |
27 | // TypeName ...
28 | func (ui64 *Uint64) TypeName(gen Generator) string {
29 | return gen.EasyTypeName(ui64.field)
30 | }
31 |
32 | // ArgName ...
33 | func (ui64 *Uint64) ArgName(gen Generator) string {
34 | return gen.VarName(ui64.field)
35 | }
36 |
37 | // AccessName ...
38 | func (ui64 *Uint64) AccessName(gen Generator) string {
39 | return gen.HelperName(ui64.field)
40 | }
41 |
42 | // NativeTypeName ...
43 | func (ui64 Uint64) NativeTypeName(gen Generator) string {
44 | return gen.Uint64NativeTypeName()
45 | }
46 |
47 | // Encoding ...
48 | func (ui64 Uint64) Encoding(source string, gen Generator) error {
49 | return gen.Uint64Encoding(source)
50 | }
51 |
52 | // Helper ...
53 | func (ui64 *Uint64) Helper(gen Generator) error {
54 | return nil
55 | }
56 |
57 | // TestingTypeName ...
58 | func (ui64 Uint64) TestingTypeName(gen Generator) string {
59 | return gen.Uint64TestingTypeName()
60 | }
61 |
62 | // TestEncoding ...
63 | func (ui64 Uint64) TestEncoding(source string, gen Generator) error {
64 | return gen.Uint64TestEncoding(source)
65 | }
66 |
--------------------------------------------------------------------------------
/internal/generator/uint8_field.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | // Uint8 Type implementation
4 | type Uint8 struct {
5 | field string
6 | fieldType string
7 | }
8 |
9 | // NewUint8 constructor
10 | func NewUint8(field string, fieldType string) *Uint8 {
11 | return &Uint8{
12 | field: field,
13 | fieldType: fieldType,
14 | }
15 | }
16 |
17 | // FieldName ...
18 | func (ui8 *Uint8) FieldName(gen Generator) string {
19 | return ui8.field
20 | }
21 |
22 | // FieldTypeName ...
23 | func (ui8 *Uint8) FieldTypeName(gen Generator) string {
24 | return ui8.fieldType
25 | }
26 |
27 | // TypeName ...
28 | func (ui8 *Uint8) TypeName(gen Generator) string {
29 | return gen.EasyTypeName(ui8.field)
30 | }
31 |
32 | // ArgName ...
33 | func (ui8 *Uint8) ArgName(gen Generator) string {
34 | return gen.VarName(ui8.field)
35 | }
36 |
37 | // AccessName ...
38 | func (ui8 *Uint8) AccessName(gen Generator) string {
39 | return gen.HelperName(ui8.field)
40 | }
41 |
42 | // NativeTypeName ...
43 | func (ui8 Uint8) NativeTypeName(gen Generator) string {
44 | return gen.Uint8NativeTypeName()
45 | }
46 |
47 | // Encoding ...
48 | func (ui8 Uint8) Encoding(source string, gen Generator) error {
49 | return gen.Uint8Encoding(source)
50 | }
51 |
52 | // Helper ...
53 | func (ui8 *Uint8) Helper(gen Generator) error {
54 | return nil
55 | }
56 |
57 | // TestingTypeName ...
58 | func (ui8 Uint8) TestingTypeName(gen Generator) string {
59 | return gen.Uint8TestingTypeName()
60 | }
61 |
62 | // TestEncoding ...
63 | func (ui8 Uint8) TestEncoding(source string, gen Generator) error {
64 | return gen.Uint8TestEncoding(source)
65 | }
66 |
--------------------------------------------------------------------------------
/internal/generator/uuid_field.go:
--------------------------------------------------------------------------------
1 | package generator
2 |
3 | // NewUUIDField генерация поля с типом UUID
4 | func NewUUIDField(field string, fieldType string) *UUIDField {
5 | return &UUIDField{field: field, fieldType: fieldType}
6 | }
7 |
8 | var _ Field = &UUIDField{}
9 |
10 | // UUIDField ...
11 | type UUIDField struct {
12 | field string
13 | fieldType string
14 | }
15 |
16 | // FieldName ...
17 | func (f *UUIDField) FieldName(gen Generator) string {
18 | return f.field
19 | }
20 |
21 | // FieldTypeName ...
22 | func (f *UUIDField) FieldTypeName(gen Generator) string {
23 | return f.fieldType
24 | }
25 |
26 | // TypeName ...
27 | func (f *UUIDField) TypeName(gen Generator) string {
28 | return gen.UneasyTypeName(f.field)
29 | }
30 |
31 | // ArgName ...
32 | func (f *UUIDField) ArgName(gen Generator) string {
33 | return gen.VarName(f.field)
34 | }
35 |
36 | // AccessName ...
37 | func (f *UUIDField) AccessName(gen Generator) string {
38 | return gen.HelperName(f.field)
39 | }
40 |
41 | // NativeTypeName ...
42 | func (f *UUIDField) NativeTypeName(gen Generator) string {
43 | return gen.UUIDNativeTypeName()
44 | }
45 |
46 | // Encoding ...
47 | func (f *UUIDField) Encoding(source string, gen Generator) error {
48 | return gen.UUIDEncoding(source)
49 | }
50 |
51 | // Helper ...
52 | func (f *UUIDField) Helper(gen Generator) error {
53 | return nil
54 | }
55 |
56 | // TestingTypeName ...
57 | func (f *UUIDField) TestingTypeName(gen Generator) string {
58 | return gen.UUIDTestingTypeName()
59 | }
60 |
61 | // TestEncoding ...
62 | func (f *UUIDField) TestEncoding(source string, gen Generator) error {
63 | return gen.UUIDTestEncoding(source)
64 | }
65 |
--------------------------------------------------------------------------------
/internal/util/components_parser.go:
--------------------------------------------------------------------------------
1 | /*
2 | * THE FILE WAS GENERATED WITH logparsergen --source=testingch.script --package=util
3 | * DO NOT TOUCH IT!
4 | */
5 |
6 | package util
7 |
8 | import (
9 | "bytes"
10 | "fmt"
11 | "strconv"
12 | "unsafe"
13 | )
14 |
15 | type components struct {
16 | rest []byte
17 | Auth struct {
18 | Valid bool
19 | Data []byte
20 | }
21 | Host []byte
22 | Port uint16
23 | DBName []byte
24 | }
25 |
26 | func (p *components) Parse(line []byte) (bool, error) {
27 | p.rest = line
28 | var pos int
29 | var tmp []byte
30 | var restAuth []byte
31 | restAuth = p.rest
32 | if pos = bytes.IndexByte(p.rest, '@'); pos < 0 {
33 | p.Auth.Valid = false
34 | p.rest = restAuth
35 | goto outAuth
36 | }
37 | p.Auth.Data = p.rest[:pos]
38 | p.rest = p.rest[pos+1:]
39 | p.Auth.Valid = true
40 | outAuth:
41 | if pos = bytes.IndexByte(p.rest, ':'); pos < 0 {
42 | return false, fmt.Errorf("`[1m%s[0m` is not a prefix of \033[1m%s\033[0m", string(':'), string(p.rest))
43 | }
44 | p.Host = p.rest[:pos]
45 | p.rest = p.rest[pos+1:]
46 | if pos = bytes.IndexByte(p.rest, '/'); pos < 0 {
47 | if value, err := strconv.ParseUint(*(*string)(unsafe.Pointer(&p.rest)), 10, 16); err == nil {
48 | p.Port = uint16(value)
49 | } else {
50 | return false, fmt.Errorf("Cannot convert `[1m%s[0m` into uint16 (field Port)", string(p.rest))
51 | }
52 | p.rest = p.rest[len(p.rest):]
53 | } else {
54 | tmp = p.rest[:pos]
55 | if value, err := strconv.ParseUint(*(*string)(unsafe.Pointer(&tmp)), 10, 16); err == nil {
56 | p.Port = uint16(value)
57 | } else {
58 | return false, fmt.Errorf("Cannot convert `[1m%s[0m` into uint16 (field Port)", string(p.rest[:pos]))
59 | }
60 | p.rest = p.rest[pos+1:]
61 | }
62 | p.DBName = p.rest
63 | p.rest = p.rest[len(p.rest):]
64 | return true, nil
65 | }
66 |
67 | func (p *components) GetAuthData() (res []byte) {
68 | if p.Auth.Valid {
69 | res = p.Auth.Data
70 | }
71 | return
72 | }
73 |
--------------------------------------------------------------------------------
/internal/util/envch_params.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | // EnvCHParams extracts clickhouse connection parameters from CLICKHOUSE environment variable
4 | func EnvCHParams(connParams string) CHParams {
5 | return ExtractCHParams(connParams)
6 | }
7 |
--------------------------------------------------------------------------------
/internal/util/extractor_lde.go:
--------------------------------------------------------------------------------
1 | /*
2 | This file was autogenerated via
3 | ------------------------------------------
4 | ldetool generate --go-string extractor.lde
5 | ------------------------------------------
6 | do not touch it with bare hands!
7 | */
8 |
9 | package util
10 |
11 | import (
12 | "fmt"
13 | "strconv"
14 | "strings"
15 | )
16 |
17 | var commaSpace = ", "
18 | var decimalLbrack = "Decimal("
19 |
20 | // Extractor ...
21 | type Extractor struct {
22 | Rest string
23 | Precision int
24 | Scale int
25 | }
26 |
27 | // Extract ...
28 | func (p *Extractor) Extract(line string) (bool, error) {
29 | p.Rest = line
30 | var err error
31 | var pos int
32 | var tmp string
33 | var tmpInt int64
34 |
35 | // Checks if the rest starts with `"Decimal("` and pass it
36 | if strings.HasPrefix(p.Rest, decimalLbrack) {
37 | p.Rest = p.Rest[len(decimalLbrack):]
38 | } else {
39 | return false, nil
40 | }
41 |
42 | // Take until ", " as Precision(int)
43 | pos = strings.Index(p.Rest, commaSpace)
44 | if pos >= 0 {
45 | tmp = p.Rest[:pos]
46 | p.Rest = p.Rest[pos+len(commaSpace):]
47 | } else {
48 | return false, nil
49 | }
50 | if tmpInt, err = strconv.ParseInt(tmp, 10, 64); err != nil {
51 | return false, fmt.Errorf("Cannot parse `%s`: %s", string(tmp), err)
52 | }
53 | p.Precision = int(tmpInt)
54 |
55 | // Take until ')' as Scale(int)
56 | pos = strings.IndexByte(p.Rest, ')')
57 | if pos >= 0 {
58 | tmp = p.Rest[:pos]
59 | p.Rest = p.Rest[pos+1:]
60 | } else {
61 | return false, nil
62 | }
63 | if tmpInt, err = strconv.ParseInt(tmp, 10, 64); err != nil {
64 | return false, fmt.Errorf("Cannot parse `%s`: %s", string(tmp), err)
65 | }
66 | p.Scale = int(tmpInt)
67 |
68 | return true, nil
69 | }
70 |
--------------------------------------------------------------------------------
/internal/util/testingch.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | )
7 |
8 | // CHParams clickhouse connection coordinates
9 | type CHParams struct {
10 | Host string
11 | Port int
12 | User string
13 | Password string
14 | DBName string
15 | }
16 |
17 | // URL for clickhouse connection
18 | func (ch CHParams) URL() string {
19 | req, err := http.NewRequest("GET", fmt.Sprintf("http://%s:%d", ch.Host, ch.Port), nil)
20 | if err != nil {
21 | panic(err)
22 | }
23 | q := req.URL.Query()
24 | if len(ch.User) > 0 {
25 | q.Set("user", ch.User)
26 | }
27 | if len(ch.Password) > 0 {
28 | q.Set("password", ch.Password)
29 | }
30 | if len(ch.DBName) > 0 {
31 | q.Set("database", ch.DBName)
32 | }
33 | req.URL.RawQuery = q.Encode()
34 | return req.URL.String()
35 | }
36 |
37 | // DBURL is a URL generator for Mail.RU's clickhouse connector
38 | func (ch CHParams) DBURL() string {
39 | req, err := http.NewRequest("GET", fmt.Sprintf("http://%s:%d/%s", ch.Host, ch.Port, ch.DBName), nil)
40 | if err != nil {
41 | panic(err)
42 | }
43 | q := req.URL.Query()
44 | if len(ch.User) > 0 {
45 | q.Set("user", ch.User)
46 | }
47 | if len(ch.Password) > 0 {
48 | q.Set("password", ch.Password)
49 | }
50 | req.URL.RawQuery = q.Encode()
51 | return req.URL.String()
52 | }
53 |
54 | // ExtractCHParams extracts CH connection components from string
55 | // [user[:password]@]host:port[/dbname]
56 | func ExtractCHParams(data string) (res CHParams) {
57 | c := &components{}
58 | up := &userpass{}
59 |
60 | if ok, err := c.Parse([]byte(data)); !ok || err != nil {
61 | panic(err)
62 | }
63 |
64 | if ok, err := up.Parse(c.GetAuthData()); !ok || err != nil {
65 | panic(err)
66 | }
67 |
68 | res.Host = string(c.Host)
69 | res.Port = int(c.Port)
70 | res.DBName = string(c.DBName)
71 | res.User = string(up.User)
72 | res.Password = string(up.Password)
73 | return
74 | }
75 |
--------------------------------------------------------------------------------
/internal/util/testingch_test.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | )
8 |
9 | func TestExtractCHParams(t *testing.T) {
10 | require.Equal(t,
11 | CHParams{
12 | Host: "localhost",
13 | Port: 8123,
14 | DBName: "default",
15 | User: "default",
16 | },
17 | ExtractCHParams("default@localhost:8123/default"),
18 | )
19 | require.Equal(t,
20 | CHParams{
21 | Host: "localhost",
22 | Port: 8123,
23 | },
24 | ExtractCHParams("localhost:8123"),
25 | )
26 | require.Equal(t,
27 | CHParams{
28 | Host: "localhost",
29 | Port: 8123,
30 | DBName: "default",
31 | User: "default",
32 | Password: "bugaga",
33 | },
34 | ExtractCHParams("default:bugaga@localhost:8123/default"),
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/internal/util/userpass_parser.go:
--------------------------------------------------------------------------------
1 | /*
2 | * THE FILE WAS GENERATED WITH logparsergen --source=testingch.script --package=util
3 | * DO NOT TOUCH IT!
4 | */
5 | package util
6 |
7 | import (
8 | "bytes"
9 | )
10 |
11 | type userpass struct {
12 | rest []byte
13 | User []byte
14 | Password []byte
15 | }
16 |
17 | func (p *userpass) Parse(line []byte) (bool, error) {
18 | p.rest = line
19 | var pos int
20 | if pos = bytes.IndexByte(p.rest, ':'); pos < 0 {
21 | p.User = p.rest
22 | p.rest = p.rest[len(p.rest):]
23 | } else {
24 | p.User = p.rest[:pos]
25 | p.rest = p.rest[pos+1:]
26 | }
27 | p.Password = p.rest
28 | p.rest = p.rest[len(p.rest):]
29 | return true, nil
30 | }
31 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 |
6 | cli "github.com/jawher/mow.cli"
7 | _ "github.com/mailru/go-clickhouse" // Mail.RU's clickhouse connector
8 | _ "github.com/sirkon/binenc" // Binary encoding library go get's "dependency", generated package will need it
9 | _ "github.com/sirkon/go-diff" // Diff for testing
10 | "github.com/sirkon/message"
11 | )
12 |
13 | const (
14 | version = "0.1.0"
15 | )
16 |
17 | func chDefaultParams() string {
18 | res := os.Getenv("CLICKHOUSE")
19 | if len(res) > 0 {
20 | return res
21 | }
22 | return "default@localhost:8123/default"
23 | }
24 |
25 | func main() {
26 | app := cli.App("ch-encode", "Go code generator for Clickhouse data insert")
27 | var (
28 | test = app.BoolOpt("test", false, "Don't save generated code, just write it into the stdout")
29 | yamlDict = app.StringOpt("yaml-dict", "", "Use this YAML formatted dictionary to generate Goish names")
30 | jsonDict = app.StringOpt("json-dict", "", "User this JSON formatted dictionary to generate Goish names")
31 | dateField = app.StringOpt("date-field", "", "Use this field as a date")
32 | chConn = app.StringOpt("clickhouse", chDefaultParams(), "Clickhouse connection params")
33 | tables = app.StringsArg("TABLES", nil, "List of clickhouse tables")
34 | )
35 | app.Spec = `[--test] [--yaml-dict|--json-dict] [--date-field] [--clickhouse] TABLES...`
36 | app.Action = func() {
37 | if err := action(*test, *yamlDict, *jsonDict, *dateField, *tables, *chConn); err != nil {
38 | message.Fatal(err)
39 | }
40 |
41 | }
42 |
43 | if err := app.Run(os.Args); err != nil {
44 | message.Fatal(err)
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sirkon/ch-encode/c438692c28c4b7e50e557bdb00b281c08ef258c0/screenshot.png
--------------------------------------------------------------------------------