Skip to content

Commit 14dd79b

Browse files
committed
Replaced pongo2 with templates from standard library.
1 parent 1d1e8b1 commit 14dd79b

File tree

10 files changed

+125
-75
lines changed

10 files changed

+125
-75
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Changed
1111

12-
- String templates replaced with jinja like
12+
- Field `template` in the `string` data type now not only specifies the pattern,
13+
but also allows you to use the values of any columns of the generated model.
1314

1415
### Breaking changes
1516

1617
- The old version of string template in `type_params` of `string` type is no longer supported,
17-
`{{ pattern('pattern_expression') }}` should be used instead.
18+
`{{ "pattern_expression" | pattern }}` should be used instead.
1819

1920
## [0.0.1](https://github.com/tarantool/sdvg/compare/36d0930..0.0.1) - 2025-07-21
2021

doc/en/usage.md

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,8 @@ Structure `models[*].columns[*].type_params` for data type `string`:
158158
- `min_length`: Minimum string length. Default is `1`.
159159
- `max_length`: Maximum string length. Default is `32`.
160160
- `logical_type`: Logical type of string. Supported values: `first_name`, `last_name`, `phone`, `text`.
161-
- `template`: Jinja-like template for string generation. Allows you to use any fields of the generated model and
162-
specify the pattern of the string using the `pattern` function. Information about the filters and functions
161+
- `template`: Template for string generation. Allows you to use the values of any columns of the generated model and
162+
specify the pattern of the string using the `pattern` function. Information about the functions
163163
available in template strings is described at the end of this section.
164164
- `locale`: Locale for generated strings. Supported values: `ru`, `en`. Default is `en`.
165165
- `without_large_letters`: Flag indicating if uppercase letters should be excluded from the string.
@@ -195,15 +195,12 @@ Structure `output.params` for format `http`:
195195
- `workers_count`: Number of threads for writing data. Default is `1`. *Experimental field.*
196196
- `headers`: HTTP request headers specified as a dictionary. Default is none.
197197
- `format_template`: Template-based format for sending data, configured using Golang templates.
198-
Available for use in `format_template`:
199-
200-
- fields:
198+
There are 2 fields available for use in `format_template`:
201199
* `ModelName` - name of the model.
202200
* `Rows` - array of records, where each element is a dictionary representing a data row.
203201
Dictionary keys correspond to column names, and values correspond to data in those columns.
204-
- functions:
205-
* `len` - returns the length of the given element.
206-
* `json` - converts the given element to a JSON string.
202+
203+
You can read about the available functions and the use of template strings at the end of this section.
207204

208205
Example value for the `format_template` field:
209206

@@ -241,17 +238,36 @@ Structure of `output.params` for `tcs` format:
241238
Similar to the structure for the `http` format,
242239
except that the `format_template` field is immutable and always set to its default value.
243240

244-
Filters and functions used in template strings:
241+
Using Template Strings::
242+
243+
Template strings are implemented using the standard golang library, you can read about
244+
all its features and available functions in this [documentation](https://pkg.go.dev/text/template).
245+
246+
Accessing Data:
245247

246-
Template strings are implemented using the `pongo2` library, you can read
247-
all available filters and functions in the [pongo2](https://github.com/flosch/pongo2) repository.
248+
In a template, data is accessed using `.`(the object or value passed to the template)
249+
and the field name, for example: `{{ .var }}`.
248250

249-
In addition, `1` function has been added:
251+
Function calls:
250252

251-
- pattern: allows you to create a string pattern using special characters.
253+
- direct call: `{{ upper .name }}`.
254+
- using pipe: `{{ .name | upper }}`.
255+
256+
In addition to standard functions, the project provides `5` custom functions:
257+
258+
- `pattern`: allows you to create a string pattern using special characters.
252259
The `A` symbol is any capital letter, the `a` symbol is any small letter,
253260
symbol `0` is any digit, the `#` symbol is any character, and the other characters remain as they are.
254261
The function is available only in the `template` field of the `string` data type.
262+
- `upper`: converts the string to upper case.
263+
- `lower`: converts the string to lower case.
264+
- `len`: returns the length of the element.
265+
- `json`: converts the element to a JSON string.
266+
267+
Usage restrictions:
268+
269+
The `pattern`, `lower`, and `upper` functions are available only in the `template` field of the `string` data type.
270+
The `len` and `json` functions are available only in the `format_template` field of the output parameters.
255271

256272
#### Examples of data generation configuration
257273

@@ -318,13 +334,13 @@ models:
318334
- name: passport
319335
type: string
320336
type_params:
321-
template: "{{ pattern('AA 00 000 000') }}"
337+
template: '{{ "AA 00 000 000" | pattern }}'
322338
distinct_percentage: 1
323339
ordered: true
324340
- name: email
325341
type: string
326342
type_params:
327-
template: "{{ first_name_en | lower }}.{{ id }}@example.com"
343+
template: "{{ .first_name_en | lower }}.{{ .id }}@example.com"
328344
- name: rating
329345
type: float
330346
type_params:

doc/ru/usage.md

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,9 @@ open_ai:
164164
- `min_length`: Минимальная длина строки. По умолчанию `1`.
165165
- `max_length`: Максимальная длина строки. По умолчанию `32`.
166166
- `logical_type`: Логический тип строки. Поддерживаемые значения: `first_name`, `last_name`, `phone`, `text`.
167-
- `template`: Jinja-подобный шаблон для генерации строки. Позволяет использовать любые поля генерируемой модели и
168-
задавать паттерн строки с помощью функции `pattern`. Информация о фильтрах и функциях, доступных в шаблонных
169-
строках описана в конце данного раздела.
167+
- `template`: Шаблон для генерации строки. Позволяет использовать значения любых столбов генерируемой модели и
168+
задавать паттерн строки с помощью функции `pattern`. Информация о том, как использовать шаблонные строки,
169+
описана в конце данного раздела.
170170
- `locale`: Локаль для генерации строк. Поддерживаемые значения: `ru`, `en`. По умолчанию `en`.
171171
- `without_large_letters`: Флаг, указывающий, исключать ли большие буквы из строки.
172172
- `without_small_letters`: Флаг, указывающий, исключать ли маленькие буквы из строки.
@@ -201,15 +201,12 @@ open_ai:
201201
- `workers_count`: Количество потоков для записи данных. По умолчанию `1`. *Является экспериментальным полем.*
202202
- `headers`: Заголовки http запроса, указываются в формате словаря. По умолчанию отсутствуют.
203203
- `format_template`: Формат отправляемых данных, конфигурируемый с помощью шаблонов Golang.
204-
Для использования в поле `format_template` доступны:
205-
206-
- поля:
204+
Для использования в `format_template` доступно 2 поля:
207205
* `ModelName` - имя модели.
208206
* `Rows` - массив записей, где каждый элемент является словарем, который представляет собой строку данных.
209207
Ключи словаря соответствуют названиям столбцов, а значения — данным в этих столбцах.
210-
- функции:
211-
* `len` - возвращает длину переданного элемента.
212-
* `json` - преобразует переданный элемент в JSON строку.
208+
209+
О доступных функциях и использовании шаблонных строк можно прочитать в конце данного раздела.
213210

214211
Пример значения поля `format_template`:
215212

@@ -247,17 +244,35 @@ open_ai:
247244
Подобна структуре для формата `http`, за исключением того,
248245
что поле `format_template` неизменяемое и всегда равняется значению по умолчанию.
249246

250-
Фильтры и функции, используемые в шаблонных строках:
247+
Использование шаблонных строк:
248+
249+
Шаблонные строки реализованы с использованием стандартной библиотеки golang, ознакомиться
250+
со всеми ее возможностями и доступными функциями можно данной [документации](https://pkg.go.dev/text/template).
251+
252+
Доступ к данным:
251253

252-
Шаблонные строки реализованы с использованием библиотеки `pongo2`, ознакомиться
253-
со всеми доступными фильтрами и функциями можно в репозитории [pongo2](https://github.com/flosch/pongo2).
254+
Обращение к данным в шаблоне выполняется с помощью `.`(объект или значение, переданное шаблону)
255+
и имени переменной, например, `{{ .var }}`.
254256

255-
Вдобавок к ним была добавлена 1 функция:
257+
Вызовы функций:
256258

257-
- pattern: позволяет создать паттерн строки при помощи специальных символов.
259+
- прямой вызов: `{{ upper .name }}`.
260+
- с помощью pipe: `{{ .name | upper }}`.
261+
262+
В проекте помимо стандартных функций доступны `5` пользовательских:
263+
264+
- `pattern`: позволяет создать паттерн строки при помощи специальных символов.
258265
Символ `A` - любая большая буква, символ `a` - любая маленькая буква,
259266
символ `0` - любая цифра, символ `#` - любой символ, а остальные символы остаются как есть.
260-
Функция доступна только в поле `template` типа данных `string`.
267+
- `upper`: преобразует строку в верхний регистр.
268+
- `lower`: преобразует строку в нижний регистр.
269+
- `len`: возвращает длину элемента.
270+
- `json`: преобразует элемент в JSON строку.
271+
272+
Ограничения по использованию:
273+
274+
Функции `pattern`, `lower`, и `upper` доступны только в поле `template` типа данных `string`.
275+
Функции `len` и `json` доступны только в поле `format_template` параметров вывода.
261276

262277
#### Примеры конфигурации генерации данных
263278

@@ -324,13 +339,13 @@ models:
324339
- name: passport
325340
type: string
326341
type_params:
327-
template: "{{ pattern('AA 00 000 000') }}"
342+
template: '{{ "AA 00 000 000" | pattern }}'
328343
distinct_percentage: 1
329344
ordered: true
330345
- name: email
331346
type: string
332347
type_params:
333-
template: "{{ first_name_en | lower }}.{{ id }}@example.com"
348+
template: "{{ .first_name_en | lower }}.{{ .id }}@example.com"
334349
- name: rating
335350
type: float
336351
type_params:

go.mod

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ go 1.23.8
55
require (
66
github.com/apache/arrow-go/v18 v18.2.0
77
github.com/charmbracelet/huh/spinner v0.0.0-20250203114958-f07ae1af69ae
8-
github.com/flosch/pongo2 v0.0.0-20200913210552-0d938eb266f3
98
github.com/google/uuid v1.6.0
109
github.com/hashicorp/go-retryablehttp v0.7.7
1110
github.com/ilyakaznacheev/cleanenv v1.5.0

go.sum

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6
4545
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
4646
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
4747
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
48-
github.com/flosch/pongo2 v0.0.0-20200913210552-0d938eb266f3 h1:fmFk0Wt3bBxxwZnu48jqMdaOR/IZ4vdtJFuaFV8MpIE=
49-
github.com/flosch/pongo2 v0.0.0-20200913210552-0d938eb266f3/go.mod h1:bJWSKrZyQvfTnb2OudyUjurSG4/edverV7n82+K3JiM=
5048
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
5149
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
5250
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
@@ -133,7 +131,6 @@ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELU
133131
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
134132
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a h1:2MaM6YC3mGu54x+RKAA6JiFFHlHDY1UbkxqppT7wYOg=
135133
github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ=
136-
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
137134
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
138135
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
139136
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
@@ -271,7 +268,6 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
271268
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
272269
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
273270
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
274-
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
275271
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
276272
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
277273
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=

internal/generator/common/utils.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ func CtxClosed(ctx context.Context) bool {
361361
}
362362

363363
func ExtractValuesFromTemplate(template string) []string {
364-
re := regexp.MustCompile(`{{\s*([^\s|(){}]+)[^}]*}}`)
364+
re := regexp.MustCompile(`{{.*?\.([^\s|}]+).*?}}`)
365365
matches := re.FindAllStringSubmatch(template, -1)
366366

367367
values := make([]string, 0, len(matches))
@@ -387,18 +387,18 @@ func ExtractValuesFromTemplate(template string) []string {
387387
func TopologicalSort[T any](items []T, nodeFunc func(T) (string, []string)) ([]string, bool, error) {
388388
var (
389389
graph = make(map[string][]string, len(items))
390-
sortedVertexes = make([]string, 0, len(items))
390+
sortedVertexes = make([]string, len(items))
391391
hasDependencies bool
392392
err error
393393
)
394394

395-
for _, item := range items {
395+
for i, item := range items {
396396
name, dependencies := nodeFunc(item)
397397
if len(dependencies) > 0 {
398398
hasDependencies = true
399399
}
400400

401-
sortedVertexes = append(sortedVertexes, name)
401+
sortedVertexes[i] = name
402402
graph[name] = dependencies
403403
}
404404

internal/generator/common/utils_test.go

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -713,18 +713,13 @@ func TestExtractValuesFromTemplate(t *testing.T) {
713713
},
714714
{
715715
name: "Valid template",
716-
template: "{{ foo }}.{{boo}}",
716+
template: "{{ .foo }}.{{.boo}}",
717717
expected: []string{"foo", "boo"},
718718
},
719-
{
720-
name: "Template with filters",
721-
template: "{{ foo | upper | lower }}",
722-
expected: []string{"foo"},
723-
},
724719
{
725720
name: "Template with functions",
726-
template: "{{ upper('foo') | lower }}@{{ boo }}",
727-
expected: []string{"boo"},
721+
template: "{{ upper .foo | lower }}@{{ .boo }}",
722+
expected: []string{"foo", "boo"},
728723
},
729724
{
730725
name: "Invalid template",

internal/generator/usecase/general/generator/value/string.go

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package value
22

33
import (
4+
"bytes"
5+
"fmt"
46
"math"
57
"math/big"
68
"regexp"
79
"slices"
810
"strings"
11+
"sync"
12+
"text/template"
913

10-
"github.com/flosch/pongo2"
1114
"github.com/pkg/errors"
1215
"github.com/tarantool/sdvg/internal/generator/common"
1316
"github.com/tarantool/sdvg/internal/generator/models"
@@ -17,7 +20,8 @@ import (
1720
)
1821

1922
var (
20-
rePatternVal = regexp.MustCompile(`pattern\((?:'([^']*)'|"([^"]*)")\)`)
23+
rePatternFunc = regexp.MustCompile(`{{\s*pattern\(\s*(?:'([^']*)'|"([^"]*)")\s*\)\s*}}`)
24+
rePatternFilter = regexp.MustCompile(`{{\s*(?:pattern\s+"([^"]+)"|"([^"]+)"\s*\|\s*pattern)\s*}}`)
2125
)
2226

2327
// Verify interface compliance in compile time.
@@ -27,8 +31,9 @@ var _ Generator = (*StringGenerator)(nil)
2731
type StringGenerator struct {
2832
*models.ColumnStringParams
2933
totalValuesCount uint64
34+
template *template.Template
35+
bufPool *sync.Pool
3036
localeModule locale.LocalModule
31-
template *pongo2.Template
3237
charset []rune
3338
countByPrefix []float64
3439
sumByPrefix []float64
@@ -38,12 +43,25 @@ type StringGenerator struct {
3843
//nolint:cyclop
3944
func (g *StringGenerator) Prepare() error {
4045
if g.Template != "" {
41-
template, err := pongo2.FromString(g.Template)
46+
tmpl, err := template.New("template").
47+
Funcs(template.FuncMap{
48+
"upper": strings.ToUpper,
49+
"lower": strings.ToLower,
50+
"pattern": func(s string) string {
51+
return fmt.Sprintf("{{pattern('%s')}}", s)
52+
},
53+
}).
54+
Parse(g.Template)
4255
if err != nil {
4356
return errors.Errorf("failed to parse template: %s", err.Error())
4457
}
4558

46-
g.template = template
59+
g.template = tmpl
60+
g.bufPool = &sync.Pool{
61+
New: func() any {
62+
return new(bytes.Buffer)
63+
},
64+
}
4765
}
4866

4967
switch g.Locale {
@@ -188,20 +206,28 @@ func (g *StringGenerator) calculateCompletions(length int) []int64 {
188206
}
189207

190208
// templateString returns n-th string by template.
209+
//
210+
//nolint:forcetypeassert
191211
func (g *StringGenerator) templateString(number float64, rowValues map[string]any) (string, error) {
192-
if rowValues == nil {
193-
rowValues = make(map[string]any)
194-
}
195-
196-
rowValues["pattern"] = func(pattern string) *pongo2.Value {
197-
return pongo2.AsSafeValue(g.patternString(number, pattern))
198-
}
212+
buf := g.bufPool.Get().(*bytes.Buffer)
213+
buf.Reset()
199214

200-
val, err := g.template.Execute(rowValues)
215+
err := g.template.Execute(buf, rowValues)
201216
if err != nil {
217+
g.bufPool.Put(buf)
218+
202219
return "", errors.New(err.Error())
203220
}
204221

222+
val := buf.String()
223+
g.bufPool.Put(buf)
224+
225+
val = rePatternFunc.ReplaceAllStringFunc(val, func(m string) string {
226+
sub := rePatternFunc.FindStringSubmatch(m)
227+
228+
return g.patternString(number, sub[1])
229+
})
230+
205231
return val, nil
206232
}
207233

@@ -514,7 +540,7 @@ func (g *StringGenerator) ValuesCount(distinctValuesCountByColumn map[string]uin
514540
func (g *StringGenerator) templateCardinality(distinctValuesCountByColumn map[string]uint64) float64 {
515541
total := 1.0
516542

517-
patternValMatches := rePatternVal.FindAllStringSubmatch(g.Template, -1)
543+
patternValMatches := rePatternFilter.FindAllStringSubmatch(g.Template, -1)
518544
for _, match := range patternValMatches {
519545
pattern := match[1]
520546
if pattern == "" {

0 commit comments

Comments
 (0)