2023-04-09 21:08:35 +07:00
|
|
|
|
package drawio
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"flag"
|
|
|
|
|
"fmt"
|
2023-04-12 22:37:54 +07:00
|
|
|
|
"io"
|
|
|
|
|
"os"
|
2023-04-09 21:08:35 +07:00
|
|
|
|
"strconv"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Интерфейс параметра экспортёра диаграмм
|
|
|
|
|
type Option interface {
|
|
|
|
|
apply(opts *Options) // Применить параметр к хранилищу параметров
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ошибка "неподдерживаемый формат"
|
|
|
|
|
type ErrUnsupportedFormat struct {
|
|
|
|
|
Format string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Описание ошибки
|
|
|
|
|
func (e ErrUnsupportedFormat) Error() string {
|
|
|
|
|
return fmt.Sprintf("unsupported format: '%s'", e.Format)
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-12 22:37:54 +07:00
|
|
|
|
// Проверка эквивалентности ошибок
|
|
|
|
|
func (e ErrUnsupportedFormat) Is(target error) bool {
|
|
|
|
|
switch t := target.(type) {
|
|
|
|
|
case ErrUnsupportedFormat:
|
|
|
|
|
return t.Format == e.Format
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-09 21:08:35 +07:00
|
|
|
|
var _ error = (*ErrUnsupportedFormat)(nil) // Проверка реализации интерфейса
|
|
|
|
|
|
|
|
|
|
type Format string // Формат экспортированного файла
|
|
|
|
|
|
2023-04-12 22:37:54 +07:00
|
|
|
|
const (
|
|
|
|
|
PDF Format = "pdf"
|
|
|
|
|
PNG Format = "png"
|
|
|
|
|
JPG Format = "jpg"
|
|
|
|
|
SVG Format = "svg"
|
|
|
|
|
VSDX Format = "vsdx"
|
|
|
|
|
XML Format = "xml"
|
|
|
|
|
)
|
|
|
|
|
|
2023-04-09 21:08:35 +07:00
|
|
|
|
// Строковое представление формата
|
|
|
|
|
func (f Format) String() string {
|
|
|
|
|
return string(f)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Загрузка формата из строки
|
|
|
|
|
func (f *Format) Set(str string) error {
|
|
|
|
|
for _, fmt := range SupportedFormats() {
|
|
|
|
|
if str == string(fmt) {
|
|
|
|
|
*f = Format(str)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return &ErrUnsupportedFormat{str}
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-12 22:37:54 +07:00
|
|
|
|
// Список всех поддерживаемых форматов экспорта
|
|
|
|
|
func SupportedFormats() []Format {
|
|
|
|
|
return []Format{"pdf", "png", "jpg", "svg", "vsdx", "xml"}
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-09 21:08:35 +07:00
|
|
|
|
var _ flag.Value = (*Format)(nil) // Поверка реализации интерфейса
|
|
|
|
|
|
|
|
|
|
// Расширение файла заданного формата
|
|
|
|
|
func (f Format) ext() string {
|
|
|
|
|
return "." + string(f)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Параметры экспортёра диаграмм
|
|
|
|
|
type Options struct {
|
|
|
|
|
Application string // Путь к приложению drawio
|
2023-04-13 19:34:27 +07:00
|
|
|
|
EnableXvfb bool // Запускать drawio внутри xvfb-run
|
2023-04-09 21:08:35 +07:00
|
|
|
|
Output string // Путь к папке с экспортированными файлами
|
|
|
|
|
Format Format // Формат экспортированных файлов
|
|
|
|
|
Recursive bool // Рекурсивно сканировать вложенные папки с файлами
|
|
|
|
|
RemovePageSuffix bool // Удалять суффикс страницы, если это возможно
|
|
|
|
|
Quality uint // Качество экспортированного изображения (только для JPEG)
|
|
|
|
|
Transparent bool // Прозрачный фона для PNG
|
|
|
|
|
// Включать копию диаграммы в экспортированный файл для PDF, PNG и SVG
|
|
|
|
|
EmbedDiagram bool
|
2023-04-12 22:37:54 +07:00
|
|
|
|
EmbedSvgImages bool // Встраивать изображения в файл формата SVG
|
|
|
|
|
Border uint // Ширина рамки вокруг диаграмм
|
|
|
|
|
Scale uint // Масштаб в процентах размера экспортированных диаграмм
|
|
|
|
|
Width uint // Ширина экспортированной диаграммы с сохранением масштаба
|
|
|
|
|
Height uint // Высота экспортированной диаграммы с сохранением масштаба
|
|
|
|
|
Crop bool // Обрезать результирующий PDF до размера диаграммы
|
|
|
|
|
Uncompressed bool // Выводить несжатый XML
|
|
|
|
|
EnablePlugins bool // Включить подключаемые модули
|
|
|
|
|
openFile OpenFileFunc // Открыть файл
|
|
|
|
|
readDir ReadDirFunc // Прочитать папку на диске
|
2023-04-09 21:08:35 +07:00
|
|
|
|
}
|
|
|
|
|
|
2023-04-12 22:37:54 +07:00
|
|
|
|
// Функция открытия файла
|
|
|
|
|
type OpenFileFunc func(string) (io.ReadCloser, error)
|
|
|
|
|
|
|
|
|
|
// Функция чтения папки на диске
|
|
|
|
|
type ReadDirFunc func(string) ([]os.DirEntry, error)
|
|
|
|
|
|
2023-04-09 21:08:35 +07:00
|
|
|
|
// Путь к приложению drawio
|
|
|
|
|
func (opts Options) App() string {
|
2023-04-13 19:34:27 +07:00
|
|
|
|
if opts.EnableXvfb {
|
2023-04-13 20:11:30 +07:00
|
|
|
|
return "xvfb-run"
|
2023-04-13 19:34:27 +07:00
|
|
|
|
}
|
2023-04-09 21:08:35 +07:00
|
|
|
|
app := opts.Application
|
|
|
|
|
if len(app) == 0 {
|
2023-04-13 19:34:27 +07:00
|
|
|
|
return "drawio"
|
2023-04-09 21:08:35 +07:00
|
|
|
|
}
|
|
|
|
|
return app
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Путь к папке с экспортированными файлами
|
|
|
|
|
func (opts Options) OutDir() string {
|
|
|
|
|
out := opts.Output
|
|
|
|
|
if len(out) == 0 {
|
|
|
|
|
out = "export"
|
|
|
|
|
}
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Расширение экспортированных файлов
|
|
|
|
|
func (opts Options) OutExt() string {
|
|
|
|
|
fmt := opts.Format
|
|
|
|
|
if len(fmt) == 0 {
|
|
|
|
|
fmt = Format("pdf")
|
|
|
|
|
}
|
|
|
|
|
return fmt.ext()
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-12 22:37:54 +07:00
|
|
|
|
// Аргументы командной строки drawio
|
2023-04-09 21:08:35 +07:00
|
|
|
|
func (opts Options) Args() []string {
|
|
|
|
|
args := []string{}
|
2023-04-13 19:34:27 +07:00
|
|
|
|
if opts.EnableXvfb {
|
|
|
|
|
app := "drawio"
|
|
|
|
|
if len(opts.Application) > 0 {
|
|
|
|
|
app = opts.Application
|
|
|
|
|
}
|
|
|
|
|
args = append(args, "-a", "-l", app)
|
|
|
|
|
}
|
2023-04-09 21:08:35 +07:00
|
|
|
|
if len(opts.Format) > 0 {
|
|
|
|
|
args = append(args, "--format", string(opts.Format))
|
|
|
|
|
}
|
|
|
|
|
if opts.Quality != 0 {
|
|
|
|
|
args = append(args, "--quality", strconv.Itoa(int(opts.Quality)))
|
|
|
|
|
}
|
|
|
|
|
if opts.Transparent {
|
|
|
|
|
args = append(args, "--transparent")
|
|
|
|
|
}
|
|
|
|
|
if opts.EmbedDiagram {
|
|
|
|
|
args = append(args, "--embed-diagram")
|
|
|
|
|
}
|
|
|
|
|
if opts.EmbedSvgImages {
|
|
|
|
|
args = append(args, "--embed-svg-images")
|
|
|
|
|
}
|
|
|
|
|
if opts.Border != 0 {
|
|
|
|
|
args = append(args, "--border", strconv.Itoa(int(opts.Border)))
|
|
|
|
|
}
|
|
|
|
|
if opts.Scale != 0 {
|
|
|
|
|
args = append(args, "--scale", strconv.Itoa(int(opts.Scale)))
|
|
|
|
|
}
|
|
|
|
|
if opts.Width != 0 {
|
|
|
|
|
args = append(args, "--width", strconv.Itoa(int(opts.Width)))
|
|
|
|
|
}
|
|
|
|
|
if opts.Height != 0 {
|
|
|
|
|
args = append(args, "--height", strconv.Itoa(int(opts.Height)))
|
|
|
|
|
}
|
|
|
|
|
if opts.Crop {
|
|
|
|
|
args = append(args, "--crop")
|
|
|
|
|
}
|
|
|
|
|
if opts.Uncompressed {
|
|
|
|
|
args = append(args, "--uncompressed")
|
|
|
|
|
}
|
|
|
|
|
if opts.EnablePlugins {
|
|
|
|
|
args = append(args, "--enable-plugins")
|
|
|
|
|
}
|
|
|
|
|
return args
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Путь к приложению drawio
|
|
|
|
|
func WithAppPath(path string) Option {
|
|
|
|
|
return optionApplication(path)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type optionApplication string
|
|
|
|
|
|
|
|
|
|
var _ Option = (*optionApplication)(nil) // Проверка реализации интерфейса
|
|
|
|
|
|
|
|
|
|
func (opt optionApplication) apply(opts *Options) {
|
|
|
|
|
opts.Application = string(opt)
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-13 19:34:27 +07:00
|
|
|
|
// Запускать drawio внутри xvfb-run
|
|
|
|
|
func WithXvfb() Option {
|
|
|
|
|
return optionXvfb{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type optionXvfb struct{}
|
|
|
|
|
|
|
|
|
|
var _ Option = optionXvfb{}
|
|
|
|
|
|
|
|
|
|
func (opt optionXvfb) apply(opts *Options) {
|
|
|
|
|
opts.EnableXvfb = true
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-09 21:08:35 +07:00
|
|
|
|
// Путь к папке с экспортированными файлами
|
|
|
|
|
func WithOutput(path string) Option {
|
|
|
|
|
return optionOutput(path)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type optionOutput string
|
|
|
|
|
|
|
|
|
|
var _ Option = (*optionOutput)(nil) // Проверка реализации интерфейса
|
|
|
|
|
|
|
|
|
|
func (opt optionOutput) apply(opts *Options) {
|
|
|
|
|
opts.Output = string(opt)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Формат экспортированных файлов
|
|
|
|
|
func WithFormat(format Format) Option {
|
|
|
|
|
return format
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var _ Option = (*Format)(nil) // Проверка реализации интерфейса
|
|
|
|
|
|
|
|
|
|
func (opt Format) apply(opts *Options) {
|
|
|
|
|
opts.Format = opt
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Рекурсивно сканировать вложенные папки с файлами
|
|
|
|
|
func WithRecursive() Option {
|
|
|
|
|
return optionRecursive{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type optionRecursive struct{}
|
|
|
|
|
|
|
|
|
|
var _ Option = optionRecursive{} // Проверка реализации интерфейса
|
|
|
|
|
|
|
|
|
|
func (opt optionRecursive) apply(opts *Options) {
|
|
|
|
|
opts.Recursive = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Удалять суффикс страницы, если это возможно
|
|
|
|
|
func WithRemovePageSuffix() Option {
|
|
|
|
|
return optionRemovePageSuffix{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type optionRemovePageSuffix struct{}
|
|
|
|
|
|
|
|
|
|
var _ Option = optionRemovePageSuffix{} // Проверка реализации интерфейса
|
|
|
|
|
|
|
|
|
|
func (opt optionRemovePageSuffix) apply(opts *Options) {
|
|
|
|
|
opts.RemovePageSuffix = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Качество экспортированного изображения (только для JPEG)
|
2023-04-12 22:37:54 +07:00
|
|
|
|
func WithQuality(q uint) Option {
|
2023-04-09 21:08:35 +07:00
|
|
|
|
return optionQuality(q)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type optionQuality uint
|
|
|
|
|
|
|
|
|
|
var _ Option = (*optionQuality)(nil) // Проверка реализации интерфейса
|
|
|
|
|
|
|
|
|
|
func (opt optionQuality) apply(opts *Options) {
|
|
|
|
|
opts.Quality = uint(opt)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Прозрачный фона для PNG
|
|
|
|
|
func WithTransparent() Option {
|
|
|
|
|
return optionTransparent{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type optionTransparent struct{}
|
|
|
|
|
|
|
|
|
|
var _ Option = optionTransparent{} // Проверка реализации интерфейса
|
|
|
|
|
|
|
|
|
|
func (opt optionTransparent) apply(opts *Options) {
|
|
|
|
|
opts.Transparent = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Включать копию диаграммы в экспортированный файл для PDF, PNG и SVG
|
|
|
|
|
func WithEmbedDiagram() Option {
|
|
|
|
|
return optionEmbedDiagram{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type optionEmbedDiagram struct{}
|
|
|
|
|
|
|
|
|
|
var _ Option = optionEmbedDiagram{} // Проверка реализации интерфейса
|
|
|
|
|
|
|
|
|
|
func (opt optionEmbedDiagram) apply(opts *Options) {
|
|
|
|
|
opts.EmbedDiagram = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Встраивать изображения в файл формата SVG
|
|
|
|
|
func WithEmbedSvgImages() Option {
|
|
|
|
|
return optionEmbedSvgImages{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type optionEmbedSvgImages struct{}
|
|
|
|
|
|
|
|
|
|
var _ Option = optionEmbedSvgImages{} // Проверка реализации интерфейса
|
|
|
|
|
|
|
|
|
|
func (opt optionEmbedSvgImages) apply(opts *Options) {
|
|
|
|
|
opts.EmbedSvgImages = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ширина рамки вокруг диаграмм
|
2023-04-12 22:37:54 +07:00
|
|
|
|
func WithBorder(border uint) Option {
|
2023-04-09 21:08:35 +07:00
|
|
|
|
return optionBorder(border)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type optionBorder uint
|
|
|
|
|
|
|
|
|
|
var _ Option = (*optionBorder)(nil) // Проверка реализации интерфейса
|
|
|
|
|
|
|
|
|
|
func (opt optionBorder) apply(opts *Options) {
|
|
|
|
|
opts.Border = uint(opt)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Масштаб в процентах размера экспортированных диаграмм
|
2023-04-12 22:37:54 +07:00
|
|
|
|
func WithScale(scale uint) Option {
|
2023-04-09 21:08:35 +07:00
|
|
|
|
return optionScale(scale)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type optionScale uint
|
|
|
|
|
|
|
|
|
|
var _ Option = (*optionScale)(nil) // Проверка реализации интерфейса
|
|
|
|
|
|
|
|
|
|
func (opt optionScale) apply(opts *Options) {
|
|
|
|
|
opts.Scale = uint(opt)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ширина экспортированной диаграммы с сохранением масштаба
|
2023-04-12 22:37:54 +07:00
|
|
|
|
func WithWidth(width uint) Option {
|
2023-04-09 21:08:35 +07:00
|
|
|
|
return optionWidth(width)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type optionWidth uint
|
|
|
|
|
|
|
|
|
|
var _ Option = (*optionWidth)(nil) // Проверка реализации интерфейса
|
|
|
|
|
|
|
|
|
|
func (opt optionWidth) apply(opts *Options) {
|
|
|
|
|
opts.Width = uint(opt)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Высота экспортированной диаграммы с сохранением масштаба
|
2023-04-12 22:37:54 +07:00
|
|
|
|
func WithHeight(height uint) Option {
|
2023-04-09 21:08:35 +07:00
|
|
|
|
return optionHeight(height)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type optionHeight uint
|
|
|
|
|
|
|
|
|
|
var _ Option = (*optionHeight)(nil) // Проверка реализации интерфейса
|
|
|
|
|
|
|
|
|
|
func (opt optionHeight) apply(opts *Options) {
|
|
|
|
|
opts.Height = uint(opt)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Обрезать результирующий PDF до размера диаграммы
|
|
|
|
|
func WithCrop() Option {
|
|
|
|
|
return optionCrop{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type optionCrop struct{}
|
|
|
|
|
|
|
|
|
|
var _ Option = optionCrop{} // Проверка реализации интерфейса
|
|
|
|
|
|
|
|
|
|
func (opt optionCrop) apply(opts *Options) {
|
|
|
|
|
opts.Crop = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Выводить несжатый XML
|
|
|
|
|
func WithUncompressed() Option {
|
|
|
|
|
return optionUncompressed{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type optionUncompressed struct{}
|
|
|
|
|
|
|
|
|
|
var _ Option = optionUncompressed{} // Проверка реализации интерфейса
|
|
|
|
|
|
|
|
|
|
func (opt optionUncompressed) apply(opts *Options) {
|
|
|
|
|
opts.Uncompressed = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Включить подключаемые модули
|
|
|
|
|
func WithEnablePlugins() Option {
|
|
|
|
|
return optionEnablePlugins{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type optionEnablePlugins struct{}
|
|
|
|
|
|
|
|
|
|
var _ Option = optionEnablePlugins{} // Проверка реализации интерфейса
|
|
|
|
|
|
|
|
|
|
func (opt optionEnablePlugins) apply(opts *Options) {
|
|
|
|
|
opts.EnablePlugins = true
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-12 22:37:54 +07:00
|
|
|
|
// Открыть файл
|
|
|
|
|
func WithOpenFile(openFile OpenFileFunc) Option {
|
|
|
|
|
return optionOpenFile{openFile}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type optionOpenFile struct {
|
|
|
|
|
openFile OpenFileFunc
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var _ Option = optionOpenFile{} // Проверка реализации интерфейса
|
|
|
|
|
|
|
|
|
|
func (opt optionOpenFile) apply(opts *Options) {
|
|
|
|
|
opts.openFile = opt.openFile
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Прочитать папку на диске
|
|
|
|
|
func WithReadDir(readDir ReadDirFunc) Option {
|
|
|
|
|
return optionReadDir{readDir}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type optionReadDir struct {
|
|
|
|
|
readDir ReadDirFunc
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var _ Option = optionReadDir{} // Проверка реализации интерфейса
|
|
|
|
|
|
|
|
|
|
func (opt optionReadDir) apply(opts *Options) {
|
|
|
|
|
opts.readDir = opt.readDir
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Пустой параметр
|
|
|
|
|
func WithNop() Option {
|
|
|
|
|
return optionNop{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type optionNop struct{}
|
|
|
|
|
|
|
|
|
|
var _ Option = optionNop{}
|
|
|
|
|
|
|
|
|
|
func (opt optionNop) apply(opts *Options) {
|
|
|
|
|
// Не требуется действий, так как это пустой параметр
|
2023-04-09 21:08:35 +07:00
|
|
|
|
}
|