Upload code
This commit is contained in:
commit
db23e5c41d
112
.gitignore
vendored
Normal file
112
.gitignore
vendored
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
.idea
|
||||||
|
# Goland's output filename can not be set manually
|
||||||
|
/go_build_*
|
||||||
|
|
||||||
|
# MS VSCode
|
||||||
|
.vscode
|
||||||
|
__debug_bin
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.test
|
||||||
|
*.prof
|
||||||
|
|
||||||
|
*coverage.out
|
||||||
|
coverage.all
|
||||||
|
cpu.out
|
||||||
|
|
||||||
|
/modules/migration/bindata.go
|
||||||
|
/modules/migration/bindata.go.hash
|
||||||
|
/modules/options/bindata.go
|
||||||
|
/modules/options/bindata.go.hash
|
||||||
|
/modules/public/bindata.go
|
||||||
|
/modules/public/bindata.go.hash
|
||||||
|
/modules/templates/bindata.go
|
||||||
|
/modules/templates/bindata.go.hash
|
||||||
|
|
||||||
|
*.db
|
||||||
|
*.log
|
||||||
|
*.log.*.gz
|
||||||
|
|
||||||
|
/gitea
|
||||||
|
/gitea-vet
|
||||||
|
/debug
|
||||||
|
/integrations.test
|
||||||
|
|
||||||
|
/bin
|
||||||
|
/dist
|
||||||
|
/custom/*
|
||||||
|
!/custom/conf/app.example.ini
|
||||||
|
/data
|
||||||
|
/indexers
|
||||||
|
/log
|
||||||
|
/public/img/avatar
|
||||||
|
/tests/integration/gitea-integration-*
|
||||||
|
/tests/integration/indexers-*
|
||||||
|
/tests/e2e/gitea-e2e-*
|
||||||
|
/tests/e2e/indexers-*
|
||||||
|
/tests/e2e/reports
|
||||||
|
/tests/e2e/test-artifacts
|
||||||
|
/tests/e2e/test-snapshots
|
||||||
|
/tests/*.ini
|
||||||
|
/tests/**/*.git/**/*.sample
|
||||||
|
/node_modules
|
||||||
|
/.venv
|
||||||
|
/yarn.lock
|
||||||
|
/yarn-error.log
|
||||||
|
/npm-debug.log*
|
||||||
|
/public/assets/js
|
||||||
|
/public/assets/css
|
||||||
|
/public/assets/fonts
|
||||||
|
/public/assets/licenses.txt
|
||||||
|
/public/assets/img/webpack
|
||||||
|
/vendor
|
||||||
|
/web_src/fomantic/node_modules
|
||||||
|
/web_src/fomantic/build/*
|
||||||
|
!/web_src/fomantic/build/semantic.js
|
||||||
|
!/web_src/fomantic/build/semantic.css
|
||||||
|
!/web_src/fomantic/build/themes
|
||||||
|
/web_src/fomantic/build/themes/*
|
||||||
|
!/web_src/fomantic/build/themes/default
|
||||||
|
/web_src/fomantic/build/themes/default/assets/*
|
||||||
|
!/web_src/fomantic/build/themes/default/assets/fonts
|
||||||
|
/web_src/fomantic/build/themes/default/assets/fonts/*
|
||||||
|
!/web_src/fomantic/build/themes/default/assets/fonts/icons.woff2
|
||||||
|
!/web_src/fomantic/build/themes/default/assets/fonts/outline-icons.woff2
|
||||||
|
/VERSION
|
||||||
|
/.air
|
||||||
|
/.go-licenses
|
||||||
|
|
||||||
|
# Snapcraft
|
||||||
|
/gitea_a*.txt
|
||||||
|
snap/.snapcraft/
|
||||||
|
parts/
|
||||||
|
stage/
|
||||||
|
prime/
|
||||||
|
*.snap
|
||||||
|
*.snap-build
|
||||||
|
*_source.tar.bz2
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Make evidence files
|
||||||
|
/.make_evidence
|
||||||
|
|
||||||
|
# Manpage
|
||||||
|
/man
|
35
README.md
Normal file
35
README.md
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# omc
|
||||||
|
|
||||||
|
Oracle Multi Querier (omc) - программа для выгрузки результатов SQL-запроса с нескольких серверов Oracle.
|
||||||
|
|
||||||
|
## SQL-скрипты
|
||||||
|
|
||||||
|
1. Скрипты должны лежать в папке `sql` с расширением `.sql`.
|
||||||
|
2. Скрипт может начинаться со строки с комментарием `-- 1`, где `1` - номер колонки, значение которой будет заменяться наименованием филиала (нумерация с единицы). Если комментарий не обнаружен, будет использовано значение `1`.
|
||||||
|
|
||||||
|
## Параметры подключения к серверам
|
||||||
|
|
||||||
|
Список серверов должен быть указан в файле с расширением `.ini` со следующей структурой:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[<HOST>/<SERVICE>]
|
||||||
|
Login = <LOGIN>
|
||||||
|
Password = <PASSWORD>
|
||||||
|
Name = <NAME>
|
||||||
|
```
|
||||||
|
|
||||||
|
где:
|
||||||
|
* `<HOST>` - адрес сервера
|
||||||
|
* `<SERVICE>` - наименование сервиса
|
||||||
|
* `<LOGIN>` - логин
|
||||||
|
* `<PASSWORD>` - пароль
|
||||||
|
* `<NAME>` - наименование филиала
|
||||||
|
|
||||||
|
например:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[start-kr.mrk.vt.ru/STARTW]
|
||||||
|
Login = MRF_SOTNIKOV_AV
|
||||||
|
Password = p@$$w0rd
|
||||||
|
Name = Киров
|
||||||
|
```
|
35
encodings.go
Normal file
35
encodings.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"golang.org/x/text/encoding"
|
||||||
|
"golang.org/x/text/encoding/charmap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Encoding string
|
||||||
|
|
||||||
|
const (
|
||||||
|
EncodingUtf8 Encoding = "UTF-8"
|
||||||
|
EncodingWin1251 Encoding = "Win-1251"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e Encoding) Encoder() (*encoding.Encoder, error) {
|
||||||
|
switch e {
|
||||||
|
case EncodingWin1251:
|
||||||
|
return charmap.Windows1251.NewEncoder(), nil
|
||||||
|
case EncodingUtf8:
|
||||||
|
return &encoding.Encoder{Transformer: new(DummyTransformer)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("unknown encoding: %s", string(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
type DummyTransformer struct{}
|
||||||
|
|
||||||
|
func (e *DummyTransformer) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
|
||||||
|
copy(dst, src)
|
||||||
|
|
||||||
|
return len(dst), len(src), nil
|
||||||
|
}
|
||||||
|
func (e *DummyTransformer) Reset() {}
|
28
export.go
Normal file
28
export.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExportFormat string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ExportFormatExcel ExportFormat = "XLSX"
|
||||||
|
ExportFormatCsv ExportFormat = "CSV"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Exporter - интерфейс экспорта
|
||||||
|
type Exporter interface {
|
||||||
|
Convert(filePath string, rows chan []any) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ExportFormat) GetExporter(encoding Encoding) (Exporter, error) {
|
||||||
|
switch e {
|
||||||
|
case ExportFormatExcel:
|
||||||
|
return new(XlsxExporter), nil
|
||||||
|
case ExportFormatCsv:
|
||||||
|
return &CsvExporter{Encoding: encoding}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("unknown format: %s", string(e))
|
||||||
|
}
|
90
export_csv.go
Normal file
90
export_csv.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"encoding/csv"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Экспорт в CSV, сжатый в ZIP-архив
|
||||||
|
type CsvExporter struct {
|
||||||
|
Encoding Encoding
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CsvExporter) FileExt() string {
|
||||||
|
return ".zip"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CsvExporter) Convert(filePath string, rows chan []any) error {
|
||||||
|
f, err := os.Create(filePath + c.FileExt())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
z := zip.NewWriter(f)
|
||||||
|
|
||||||
|
zw, err := z.Create(filepath.Base(filePath) + ".csv")
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
/*var enc io.Writer
|
||||||
|
if c.Encoding == EncodingWndows1251 {
|
||||||
|
enc = charmap.Windows1251.NewEncoder().Writer(zw)
|
||||||
|
} else {
|
||||||
|
enc = zw
|
||||||
|
}*/
|
||||||
|
|
||||||
|
enc, err := c.Encoding.Encoder()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w := csv.NewWriter(enc.Writer(zw))
|
||||||
|
w.Comma = ';'
|
||||||
|
|
||||||
|
for row := range rows {
|
||||||
|
rowsStr := make([]string, len(row))
|
||||||
|
for i := range row {
|
||||||
|
rowsStr[i] = toCsvField(row[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.Write(rowsStr)
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Flush()
|
||||||
|
|
||||||
|
if w.Error() != nil {
|
||||||
|
f.Close()
|
||||||
|
return w.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = z.Close()
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func toCsvField(a any) string {
|
||||||
|
if a == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := a.(type) {
|
||||||
|
case time.Time:
|
||||||
|
return v.Format("02.01.2006 15:04:05")
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprint(a)
|
||||||
|
}
|
43
export_excel.go
Normal file
43
export_excel.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/xuri/excelize/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Экспорт в Excel
|
||||||
|
type XlsxExporter struct{}
|
||||||
|
|
||||||
|
func (x *XlsxExporter) FileExt() string {
|
||||||
|
return ".xlsx"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *XlsxExporter) Convert(filePath string, rows chan []any) error {
|
||||||
|
excelFile := excelize.NewFile()
|
||||||
|
defer excelFile.Close()
|
||||||
|
|
||||||
|
excelFile.Path = filePath + x.FileExt()
|
||||||
|
|
||||||
|
stream, err := excelFile.NewStreamWriter("Sheet1")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rowNum := 0
|
||||||
|
for row := range rows {
|
||||||
|
rowNum += 1
|
||||||
|
cell, err := excelize.CoordinatesToCellName(1, rowNum)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := stream.SetRow(cell, row); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = stream.Flush()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return excelFile.Save()
|
||||||
|
}
|
29
go.mod
Normal file
29
go.mod
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
module git.nxshock.me/omc
|
||||||
|
|
||||||
|
go 1.21.3
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/dimchansky/utfbom v1.1.1
|
||||||
|
github.com/rivo/tview v0.0.0-20231115183240-7c9e464bac02
|
||||||
|
github.com/sijms/go-ora/v2 v2.7.21
|
||||||
|
github.com/xuri/excelize/v2 v2.8.0
|
||||||
|
golang.org/x/text v0.12.0
|
||||||
|
gopkg.in/ini.v1 v1.67.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gdamore/encoding v1.0.0 // indirect
|
||||||
|
github.com/gdamore/tcell/v2 v2.6.0 // indirect
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||||
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||||
|
github.com/richardlehane/mscfb v1.0.4 // indirect
|
||||||
|
github.com/richardlehane/msoleps v1.0.3 // indirect
|
||||||
|
github.com/rivo/uniseg v0.4.3 // indirect
|
||||||
|
github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca // indirect
|
||||||
|
github.com/xuri/nfp v0.0.0-20230819163627-dc951e3ffe1a // indirect
|
||||||
|
golang.org/x/crypto v0.12.0 // indirect
|
||||||
|
golang.org/x/net v0.14.0 // indirect
|
||||||
|
golang.org/x/sys v0.11.0 // indirect
|
||||||
|
golang.org/x/term v0.11.0 // indirect
|
||||||
|
)
|
92
go.sum
Normal file
92
go.sum
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
|
||||||
|
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
|
||||||
|
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||||
|
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||||
|
github.com/gdamore/tcell/v2 v2.6.0 h1:OKbluoP9VYmJwZwq/iLb4BxwKcwGthaa1YNBJIyCySg=
|
||||||
|
github.com/gdamore/tcell/v2 v2.6.0/go.mod h1:be9omFATkdr0D9qewWW3d+MEvl5dha+Etb5y65J2H8Y=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
|
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||||
|
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||||
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
|
||||||
|
github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
|
||||||
|
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||||
|
github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM=
|
||||||
|
github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||||
|
github.com/rivo/tview v0.0.0-20231115183240-7c9e464bac02 h1:UkSrnoeeuKdeNFe4ghSjZmp7tA5B1CQKnvV1By9FSYw=
|
||||||
|
github.com/rivo/tview v0.0.0-20231115183240-7c9e464bac02/go.mod h1:nVwGv4MP47T0jvlk7KuTTjjuSmrGO4JF0iaiNt4bufE=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw=
|
||||||
|
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
|
github.com/sijms/go-ora/v2 v2.7.21 h1:BbfkcgoRYanmQkHklvRFJ7v/Cil8gPSxfG6ExZrHHlY=
|
||||||
|
github.com/sijms/go-ora/v2 v2.7.21/go.mod h1:EHxlY6x7y9HAsdfumurRfTd+v8NrEOTR3Xl4FWlH6xk=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca h1:uvPMDVyP7PXMMioYdyPH+0O+Ta/UO1WFfNYMO3Wz0eg=
|
||||||
|
github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
||||||
|
github.com/xuri/excelize/v2 v2.8.0 h1:Vd4Qy809fupgp1v7X+nCS/MioeQmYVVzi495UCTqB7U=
|
||||||
|
github.com/xuri/excelize/v2 v2.8.0/go.mod h1:6iA2edBTKxKbZAa7X5bDhcCg51xdOn1Ar5sfoXRGrQg=
|
||||||
|
github.com/xuri/nfp v0.0.0-20230819163627-dc951e3ffe1a h1:Mw2VNrNNNjDtw68VsEj2+st+oCSn4Uz7vZw6TbhcV1o=
|
||||||
|
github.com/xuri/nfp v0.0.0-20230819163627-dc951e3ffe1a/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||||
|
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||||
|
golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
|
||||||
|
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
|
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||||
|
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||||
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
|
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
|
||||||
|
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||||
|
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
|
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
253
kernel.go
Normal file
253
kernel.go
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/dimchansky/utfbom"
|
||||||
|
go_ora "github.com/sijms/go-ora/v2"
|
||||||
|
"gopkg.in/ini.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Row struct {
|
||||||
|
isHeader bool
|
||||||
|
data []any
|
||||||
|
}
|
||||||
|
|
||||||
|
func launch(configFilePath, scriptFilePath string, exportFileFormat ExportFormat, encoding Encoding) error {
|
||||||
|
sqlBytes, err := readFileIgnoreBOM(scriptFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
servers, err := loadConfig(configFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
branchFieldNum := getBranchFieldNumber(string(sqlBytes))
|
||||||
|
if branchFieldNum <= 0 {
|
||||||
|
return fmt.Errorf("Некорректное значение номера поля филиала: %v", branchFieldNum)
|
||||||
|
}
|
||||||
|
|
||||||
|
rowsChan := iterateServers(servers, string(sqlBytes), branchFieldNum)
|
||||||
|
|
||||||
|
err = export(scriptFilePath, exportFileFormat, encoding, rowsChan)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func export(scriptFilePath string, exportFileFormat ExportFormat, encoding Encoding, inputRows chan Row) error {
|
||||||
|
converter, err := exportFileFormat.GetExporter(encoding)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fileName := filepath.Base(scriptFilePath)
|
||||||
|
fileName = strings.TrimSuffix(fileName, filepath.Ext(fileName))
|
||||||
|
|
||||||
|
outputRows := make(chan []any)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer close(outputRows)
|
||||||
|
|
||||||
|
gotHeader := false
|
||||||
|
rowsCache := make([][]any, 0)
|
||||||
|
rowCount := -1
|
||||||
|
|
||||||
|
for row := range inputRows {
|
||||||
|
rowCount += 1
|
||||||
|
|
||||||
|
if gotHeader {
|
||||||
|
outputRows <- row.data
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if row.isHeader {
|
||||||
|
gotHeader = true
|
||||||
|
outputRows <- row.data
|
||||||
|
for _, cachedRow := range rowsCache {
|
||||||
|
outputRows <- cachedRow
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rowsCache = append(rowsCache, row.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return converter.Convert(fileName, outputRows)
|
||||||
|
}
|
||||||
|
|
||||||
|
func iterateServers(servers []Server, sqlStr string, branchFieldNum int) chan Row {
|
||||||
|
rowsChan := make(chan Row)
|
||||||
|
|
||||||
|
slog.Info("Выгрузка начата...")
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
close(rowsChan)
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg := new(sync.WaitGroup)
|
||||||
|
wg.Add(len(servers))
|
||||||
|
|
||||||
|
for i, server := range servers {
|
||||||
|
i := i
|
||||||
|
server := server
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
db, err := sql.Open("oracle", server.Url)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Ошибка подключения к серверу", slog.String("server", server.Url), slog.Any("err", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := db.Query(sqlStr)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Ошибка выполнения запроса", slog.String("server", server.Url), slog.Any("err", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
cols, err := rows.Columns()
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Ошибка получения списка колонок", slog.String("server", server.Url), slog.Any("err", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == 0 {
|
||||||
|
rowsChan <- Row{isHeader: true, data: sliceToAnySlice[string](cols)} // Добавление заголовков
|
||||||
|
}
|
||||||
|
|
||||||
|
rowNum := 0
|
||||||
|
for rows.Next() {
|
||||||
|
pointers := make([]any, len(cols))
|
||||||
|
container := make([]any, len(cols))
|
||||||
|
for i := range pointers {
|
||||||
|
pointers[i] = &container[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rows.Scan(pointers...)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Ошибка получения строки", slog.String("server", server.Url), slog.Any("err", err))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
rowsChan <- Row{isHeader: false, data: append(append(container[:branchFieldNum-1], server.Name), container[branchFieldNum:]...)} // Добавление имени сервера
|
||||||
|
rowNum += 1
|
||||||
|
}
|
||||||
|
slog.Info("Получение строк завершено", slog.String("server", server.Name), slog.Int("rowCount", rowNum))
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}()
|
||||||
|
|
||||||
|
return rowsChan
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadConfig считывает конфиг и возвращает список параметров серверов
|
||||||
|
func loadConfig(filePath string) ([]Server, error) {
|
||||||
|
servers := make([]Server, 0)
|
||||||
|
|
||||||
|
iniBytes, err := readFileIgnoreBOM(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := ini.Load(iniBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.DeleteSection("DEFAULT")
|
||||||
|
|
||||||
|
for _, server := range cfg.SectionStrings() {
|
||||||
|
loginKey, err := cfg.Section(server).GetKey("Login")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
passwordKey, err := cfg.Section(server).GetKey("Password")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nameKey, err := cfg.Section(server).GetKey("Name")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
serv := strings.Split(server, "/")[0]
|
||||||
|
service := strings.Split(server, "/")[1]
|
||||||
|
dbUrl := go_ora.BuildUrl(serv, 1521, service, loginKey.String(), passwordKey.String(), nil)
|
||||||
|
|
||||||
|
server := Server{
|
||||||
|
Url: dbUrl,
|
||||||
|
Name: nameKey.String()}
|
||||||
|
|
||||||
|
servers = append(servers, server)
|
||||||
|
}
|
||||||
|
|
||||||
|
return servers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sliceToAnySlice[T string | any](slice []T) []any {
|
||||||
|
result := make([]any, len(slice))
|
||||||
|
for i := range result {
|
||||||
|
result[i] = slice[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func readFileIgnoreBOM(filePath string) ([]byte, error) {
|
||||||
|
f, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
b, err := io.ReadAll(utfbom.SkipOnly(f))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes.ReplaceAll(b, []byte("\r"), []byte{}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getBranchFieldNumber осуществляет определение номера колонки для подстановки
|
||||||
|
// наименования филиала. В первой строке SQL-скрипта должен быть комментарий,
|
||||||
|
// начинающийся на `// ` и содержащий только номер колонки (нумерация с 1).
|
||||||
|
func getBranchFieldNumber(sqlStr string) int {
|
||||||
|
lines := strings.Split(sqlStr, "\n")
|
||||||
|
if len(lines) == 0 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
line := lines[0]
|
||||||
|
|
||||||
|
if !strings.HasPrefix(line, "-- ") {
|
||||||
|
slog.Warn("Не указан номер колонки для вывода филиала, будет перезаписан первый столбец!")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
line = strings.TrimPrefix(line, "-- ")
|
||||||
|
fieldNum, err := strconv.Atoi(line)
|
||||||
|
if err != nil {
|
||||||
|
slog.Warn("Неверно указан номер колонки для вывода филиала, будет перезаписан первый столбец!")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return fieldNum
|
||||||
|
}
|
94
main.go
Normal file
94
main.go
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/rivo/tview"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
logger := slog.New(&Handler{os.Stderr, slog.LevelInfo})
|
||||||
|
slog.SetDefault(logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
configFilePath, scriptFilePath, exportFileFormat, encoding, err := getReportParams()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = launch(configFilePath, scriptFilePath, exportFileFormat, encoding)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Ошибка при выполнении", slog.Any("err", err))
|
||||||
|
fmt.Scanln()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getReportParams() (configFilePath, scriptFilePath string, exportFileFormat ExportFormat, encoding Encoding, err error) {
|
||||||
|
exportFileFormatStr := ""
|
||||||
|
|
||||||
|
// Список файлов с SQL-скриптами
|
||||||
|
files, _ := filepath.Glob(filepath.Join(SQL_FILES_DIR, "*.sql"))
|
||||||
|
for i := range files {
|
||||||
|
files[i] = filepath.Base(files[i])
|
||||||
|
}
|
||||||
|
// Список файлов с настройками подключения к БД
|
||||||
|
configs, _ := filepath.Glob("*.ini")
|
||||||
|
|
||||||
|
configFileDropDown := tview.NewDropDown().
|
||||||
|
SetOptions(configs, nil).
|
||||||
|
SetLabel("Файл настроек серверов").
|
||||||
|
SetCurrentOption(0)
|
||||||
|
sqlFileDropDown := tview.NewDropDown().
|
||||||
|
SetOptions(files, nil).
|
||||||
|
SetLabel("Файл SQL-скрипта").
|
||||||
|
SetCurrentOption(0)
|
||||||
|
encodingDropDown := tview.NewDropDown().
|
||||||
|
SetOptions([]string{string(EncodingWin1251), string(EncodingUtf8)}, nil).
|
||||||
|
SetLabel("Кодировка CSV-файла")
|
||||||
|
encodingDropDown.SetDisabled(true)
|
||||||
|
encodingDropDown.SetCurrentOption(0)
|
||||||
|
|
||||||
|
formatDropDown := tview.NewDropDown().
|
||||||
|
SetOptions([]string{string(ExportFormatExcel), string(ExportFormatCsv)}, func(text string, index int) {
|
||||||
|
if text == string(ExportFormatCsv) {
|
||||||
|
encodingDropDown.SetDisabled(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
encodingDropDown.SetDisabled(true)
|
||||||
|
}).
|
||||||
|
SetLabel("Формат выгрузки").SetCurrentOption(0)
|
||||||
|
|
||||||
|
form := tview.NewForm().
|
||||||
|
AddFormItem(configFileDropDown).
|
||||||
|
AddFormItem(sqlFileDropDown).
|
||||||
|
AddFormItem(formatDropDown).
|
||||||
|
AddFormItem(encodingDropDown)
|
||||||
|
form.SetTitle("Параметры").SetBorder(true)
|
||||||
|
|
||||||
|
app := tview.NewApplication()
|
||||||
|
grid := tview.NewGrid().SetRows(-1, 1).
|
||||||
|
AddItem(form, 0, 0, 1, 1, 0, 0, true).
|
||||||
|
AddItem(tview.NewButton("Запуск").SetSelectedFunc(func() {
|
||||||
|
_, scriptFilePath = sqlFileDropDown.GetCurrentOption()
|
||||||
|
_, exportFileFormatStr = formatDropDown.GetCurrentOption()
|
||||||
|
_, configFilePath = configFileDropDown.GetCurrentOption()
|
||||||
|
_, encodingStr := encodingDropDown.GetCurrentOption()
|
||||||
|
encoding = Encoding(encodingStr)
|
||||||
|
app.Stop()
|
||||||
|
}), 1, 0, 1, 1, 0, 0, false)
|
||||||
|
grid.SetBorderPadding(0, 1, 1, 1)
|
||||||
|
|
||||||
|
form.SetTitle("Параметры выгрузки").SetTitleAlign(tview.AlignLeft)
|
||||||
|
if err := app.SetRoot(grid, true).EnableMouse(true).Run(); err != nil {
|
||||||
|
return "", "", "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return configFilePath, filepath.Join(SQL_FILES_DIR, scriptFilePath), ExportFormat(exportFileFormatStr), encoding, nil
|
||||||
|
}
|
9
servers.go
Normal file
9
servers.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// Server - экземпляр сервера
|
||||||
|
type Server struct {
|
||||||
|
// Полная ссылка на БД, вкючая логин/пароль
|
||||||
|
Url string
|
||||||
|
// Наименование филиала
|
||||||
|
Name string
|
||||||
|
}
|
97
slog.go
Normal file
97
slog.go
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Мьютекс для предотвращения наложения строк из горутин
|
||||||
|
var mu = new(sync.Mutex)
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
w io.Writer
|
||||||
|
level slog.Level
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) Enabled(c context.Context, l slog.Level) bool {
|
||||||
|
return l >= h.level
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) Handle(c context.Context, r slog.Record) error {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
switch r.Level {
|
||||||
|
case slog.LevelError:
|
||||||
|
fmt.Fprintf(h.w, "• ОШИБКА: %v\n", r.Message)
|
||||||
|
|
||||||
|
r.Attrs(func(a slog.Attr) bool {
|
||||||
|
s := fmt.Sprintf(" %v=%v\n", a.Key, a.Value)
|
||||||
|
fmt.Fprint(h.w, s)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(h.w, "• %v ", r.Message)
|
||||||
|
|
||||||
|
r.Attrs(func(a slog.Attr) bool {
|
||||||
|
s := fmt.Sprintf("%v=%v ", a.Key, a.Value)
|
||||||
|
fmt.Fprint(h.w, s)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
fmt.Fprint(h.w, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*func (h *Handler) HandleColor(c context.Context, r slog.Record) error {
|
||||||
|
var yellow = color.New(color.FgYellow).SprintFunc()
|
||||||
|
|
||||||
|
switch r.Level {
|
||||||
|
case slog.LevelError:
|
||||||
|
redHi := color.New(color.FgHiRed).SprintFunc()
|
||||||
|
fmt.Fprintf(h.w, redHi("• %v\n"), r.Message)
|
||||||
|
|
||||||
|
red := color.New(color.FgRed).SprintFunc()
|
||||||
|
r.Attrs(func(a slog.Attr) bool {
|
||||||
|
s := fmt.Sprintf(" %v=%v\n", red(a.Key), a.Value)
|
||||||
|
fmt.Fprint(h.w, s)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
case slog.LevelWarn:
|
||||||
|
fmt.Fprintf(h.w, yellow("• %v\n"), r.Message)
|
||||||
|
|
||||||
|
red := color.New(color.FgRed).SprintFunc()
|
||||||
|
r.Attrs(func(a slog.Attr) bool {
|
||||||
|
s := fmt.Sprintf(" %v=%v\n", red(a.Key), yellow(a.Value))
|
||||||
|
fmt.Fprint(h.w, s)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(h.w, "• %v ", r.Message)
|
||||||
|
|
||||||
|
r.Attrs(func(a slog.Attr) bool {
|
||||||
|
s := fmt.Sprintf("%v=%v ", yellow(a.Key), a.Value)
|
||||||
|
fmt.Fprint(h.w, s)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
fmt.Fprint(h.w, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}*/
|
||||||
|
|
||||||
|
func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) WithGroup(name string) slog.Handler {
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) SetLevel(level slog.Level) {
|
||||||
|
h.level = level
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user