Merge branch 'release/1.0'
This commit is contained in:
commit
14539d89fa
11
.vscode/settings.json
vendored
11
.vscode/settings.json
vendored
@ -4,23 +4,34 @@
|
||||
"Версионирование",
|
||||
"версионируется",
|
||||
"Alek",
|
||||
"Aleksei",
|
||||
"Badiaev",
|
||||
"Childs",
|
||||
"choco",
|
||||
"drawio",
|
||||
"errcheck",
|
||||
"gocov",
|
||||
"GOPATH",
|
||||
"honnef",
|
||||
"INSTALLDIR",
|
||||
"jstemmer",
|
||||
"kisielk",
|
||||
"matm",
|
||||
"mousesoft",
|
||||
"mxfile",
|
||||
"nxfile",
|
||||
"outdir",
|
||||
"outext",
|
||||
"sashamelentyev",
|
||||
"shlibs",
|
||||
"STARTMENUSHORTCUT",
|
||||
"staticcheck",
|
||||
"stretchr",
|
||||
"subdir",
|
||||
"svvg",
|
||||
"tamerh",
|
||||
"usestdlibvars",
|
||||
"wdir",
|
||||
"xmlparser"
|
||||
]
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
Формат основан на [Keep a Changelog](https://keepachangelog.com/ru/1.1.0/),
|
||||
и этот проект придерживается [Semantic Versioning](https://semver.org/lang/ru/).
|
||||
|
||||
## [0.1] - Не опубликовано
|
||||
## [1.0] - Не опубликовано
|
||||
|
||||
- **Добавлено**
|
||||
- Все параметры передаются программе `drawio` из пакета `drawio-desktop` без
|
||||
|
75
build/ci/Jenkinsfile
vendored
75
build/ci/Jenkinsfile
vendored
@ -30,35 +30,35 @@ pipeline {
|
||||
'''
|
||||
}
|
||||
}
|
||||
// stage('Build Windows') {
|
||||
// agent{ label 'windows' }
|
||||
// environment {
|
||||
// GO_OPT = ' '
|
||||
// CGO_ENABLED = 0
|
||||
// }
|
||||
// steps {
|
||||
// echo "***** BUILD ${PROJECT_NAME} on Windows *****"
|
||||
// cleanWs(disableDeferredWipeout: true, deleteDirs: true)
|
||||
// catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') {
|
||||
// checkout scm
|
||||
// bat 'make clean vendor build dist pkg-msi'
|
||||
// }
|
||||
// }
|
||||
// post {
|
||||
// always {
|
||||
// script {
|
||||
// if (getContext(hudson.FilePath)) {
|
||||
// archiveArtifacts (
|
||||
// artifacts: 'out/*.zip,out/*.msi',
|
||||
// allowEmptyArchive: true,
|
||||
// fingerprint: true,
|
||||
// onlyIfSuccessful: true,
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
stage('Build Windows') {
|
||||
agent{ label 'windows' }
|
||||
environment {
|
||||
GO_OPT = ' '
|
||||
CGO_ENABLED = 0
|
||||
}
|
||||
steps {
|
||||
echo "***** BUILD ${PROJECT_NAME} on Windows *****"
|
||||
cleanWs(disableDeferredWipeout: true, deleteDirs: true)
|
||||
catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') {
|
||||
checkout scm
|
||||
bat 'make clean vendor build dist'
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
script {
|
||||
if (getContext(hudson.FilePath)) {
|
||||
archiveArtifacts (
|
||||
artifacts: 'out/*.zip,out/*.msi',
|
||||
allowEmptyArchive: true,
|
||||
fingerprint: true,
|
||||
onlyIfSuccessful: true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Test') {
|
||||
@ -117,15 +117,16 @@ pipeline {
|
||||
steps {
|
||||
echo "***** RELEASE ${PROJECT_NAME} for Linux *****"
|
||||
sh '''#!/bin/bash
|
||||
make build dist
|
||||
make build package
|
||||
find -O1 dist/ -name '*.changes' -exec dput mousesoft \\{\\} \\;
|
||||
'''
|
||||
script {
|
||||
msUploadFilesToRelease(
|
||||
"${PROJECT_OWNER}",
|
||||
"${PROJECT_ID}",
|
||||
RELEASE_ID,
|
||||
['dist/*.tar.gz', 'out/doc/*.pdf'],
|
||||
)
|
||||
// msUploadFilesToRelease(
|
||||
// "${PROJECT_OWNER}",
|
||||
// "${PROJECT_ID}",
|
||||
// RELEASE_ID,
|
||||
// ['dist/*.deb'],
|
||||
// )
|
||||
if( currentBuild.currentResult == 'SUCCESS' ) {
|
||||
currentBuild.keepLog = true
|
||||
}
|
||||
@ -143,7 +144,7 @@ pipeline {
|
||||
}
|
||||
steps {
|
||||
echo "***** RELEASE ${PROJECT_NAME} for Windows *****"
|
||||
bat 'make build dist pkg-msi'
|
||||
bat 'make build package'
|
||||
script {
|
||||
msUploadFilesToRelease(
|
||||
"${PROJECT_OWNER}",
|
||||
|
5
build/package/debian/changelog
Normal file
5
build/package/debian/changelog
Normal file
@ -0,0 +1,5 @@
|
||||
drawio-export (0.1) UNRELEASED; urgency=low
|
||||
|
||||
* Initial Release.
|
||||
|
||||
-- Aleksei Badiaev <aleksei.badiaev@mousesoft.ru> Sun, 9 Apr 2023 21:45:22 +0700
|
8
build/package/debian/control
Normal file
8
build/package/debian/control
Normal file
@ -0,0 +1,8 @@
|
||||
Source: drawio-export
|
||||
Maintainer: Aleksei Badiaev <aleksei.badiaev@mousesoft.ru>
|
||||
|
||||
Package: drawio-export
|
||||
Section: graphics
|
||||
Priority: optional
|
||||
Architecture: amd64
|
||||
Description: Export Draw.io diagrams using drawio-desktop.
|
36
build/package/msi/templates/LicenseAgreementDlg_HK.wxs
Normal file
36
build/package/msi/templates/LicenseAgreementDlg_HK.wxs
Normal file
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<Fragment>
|
||||
<UI>
|
||||
<Dialog Id="LicenseAgreementDlg_HK" Width="370" Height="270" Title="!(loc.LicenseAgreementDlg_Title)">
|
||||
<Control Id="LicenseAcceptedCheckBox" Type="CheckBox" X="20" Y="207" Width="330" Height="18" CheckBoxValue="1" Property="LicenseAccepted"
|
||||
Text="!(loc.LicenseAgreementDlgLicenseAcceptedCheckBox)" />
|
||||
<Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="!(loc.WixUIBack)" />
|
||||
<Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="!(loc.WixUINext)">
|
||||
<Publish Event="SpawnWaitDialog" Value="WaitForCostingDlg">CostingComplete = 1</Publish>
|
||||
<Condition Action="disable"><![CDATA[LicenseAccepted <> "1"]]></Condition>
|
||||
<Condition Action="enable">LicenseAccepted = "1"</Condition>
|
||||
</Control>
|
||||
<Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="!(loc.WixUICancel)">
|
||||
<Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
|
||||
</Control>
|
||||
<Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="!(loc.LicenseAgreementDlgBannerBitmap)" />
|
||||
<Control Id="LicenseText" Type="ScrollableText" X="20" Y="60" Width="330" Height="140" Sunken="yes" TabSkip="no">
|
||||
|
||||
{{if gt (.License | len) 0}}
|
||||
<Text SourceFile="{{.License}}" />
|
||||
{{end}}
|
||||
|
||||
</Control>
|
||||
<Control Id="Print" Type="PushButton" X="112" Y="243" Width="56" Height="17" Text="!(loc.WixUIPrint)">
|
||||
<Publish Event="DoAction" Value="WixUIPrintEula">1</Publish>
|
||||
</Control>
|
||||
<Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
|
||||
<Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
|
||||
<Control Id="Description" Type="Text" X="25" Y="23" Width="340" Height="15" Transparent="yes" NoPrefix="yes" Text="!(loc.LicenseAgreementDlgDescription)" />
|
||||
<Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes" Text="!(loc.LicenseAgreementDlgTitle)" />
|
||||
</Dialog>
|
||||
</UI>
|
||||
</Fragment>
|
||||
</Wix>
|
60
build/package/msi/templates/WixUI_HK.wxs
Normal file
60
build/package/msi/templates/WixUI_HK.wxs
Normal file
@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<Fragment>
|
||||
|
||||
<UI Id="WixUI_HK">
|
||||
<TextStyle Id="WixUI_Font_Normal" FaceName="Tahoma" Size="8" />
|
||||
<TextStyle Id="WixUI_Font_Bigger" FaceName="Tahoma" Size="12" />
|
||||
<TextStyle Id="WixUI_Font_Title" FaceName="Tahoma" Size="9" Bold="yes" />
|
||||
|
||||
<Property Id="DefaultUIFont" Value="WixUI_Font_Normal" />
|
||||
<Property Id="WixUI_Mode" Value="InstallDir" />
|
||||
|
||||
<DialogRef Id="BrowseDlg" />
|
||||
<DialogRef Id="DiskCostDlg" />
|
||||
<DialogRef Id="ErrorDlg" />
|
||||
<DialogRef Id="FatalError" />
|
||||
<DialogRef Id="FilesInUse" />
|
||||
<DialogRef Id="MsiRMFilesInUse" />
|
||||
<DialogRef Id="PrepareDlg" />
|
||||
<DialogRef Id="ProgressDlg" />
|
||||
<DialogRef Id="ResumeDlg" />
|
||||
<DialogRef Id="UserExit" />
|
||||
|
||||
<!-- Make sure to include custom dialogs in the installer database via a DialogRef command,
|
||||
especially if they are not included explicitly in the publish chain below -->
|
||||
<DialogRef Id="LicenseAgreementDlg_HK"/>
|
||||
|
||||
<Publish Dialog="BrowseDlg" Control="OK" Event="DoAction" Value="WixUIValidatePath" Order="3">1</Publish>
|
||||
<Publish Dialog="BrowseDlg" Control="OK" Event="SpawnDialog" Value="InvalidDirDlg" Order="4"><![CDATA[WIXUI_INSTALLDIR_VALID<>"1"]]></Publish>
|
||||
|
||||
<Publish Dialog="ExitDialog" Control="Finish" Event="EndDialog" Value="Return" Order="999">1</Publish>
|
||||
|
||||
<Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="{{if gt (.License | len) 0}}LicenseAgreementDlg_HK{{else}}InstallDirDlg{{end}}">NOT Installed</Publish>
|
||||
<Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg">Installed AND PATCH</Publish>
|
||||
|
||||
<Publish Dialog="LicenseAgreementDlg_HK" Control="Back" Event="NewDialog" Value="WelcomeDlg">1</Publish>
|
||||
<Publish Dialog="LicenseAgreementDlg_HK" Control="Next" Event="NewDialog" Value="InstallDirDlg">LicenseAccepted = "1"</Publish>
|
||||
|
||||
<Publish Dialog="InstallDirDlg" Control="Back" Event="NewDialog" Value="{{if gt (.License | len) 0}}LicenseAgreementDlg_HK{{else}}WelcomeDlg{{end}}">1</Publish>
|
||||
<Publish Dialog="InstallDirDlg" Control="Next" Event="SetTargetPath" Value="[WIXUI_INSTALLDIR]" Order="1">1</Publish>
|
||||
<Publish Dialog="InstallDirDlg" Control="Next" Event="DoAction" Value="WixUIValidatePath" Order="2">NOT WIXUI_DONTVALIDATEPATH</Publish>
|
||||
<Publish Dialog="InstallDirDlg" Control="Next" Event="SpawnDialog" Value="InvalidDirDlg" Order="3"><![CDATA[NOT WIXUI_DONTVALIDATEPATH AND WIXUI_INSTALLDIR_VALID<>"1"]]></Publish>
|
||||
<Publish Dialog="InstallDirDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg" Order="4">WIXUI_DONTVALIDATEPATH OR WIXUI_INSTALLDIR_VALID="1"</Publish>
|
||||
|
||||
<Publish Dialog="InstallDirDlg" Control="ChangeFolder" Property="_BrowseProperty" Value="[WIXUI_INSTALLDIR]" Order="1">1</Publish>
|
||||
<Publish Dialog="InstallDirDlg" Control="ChangeFolder" Event="SpawnDialog" Value="BrowseDlg" Order="2">1</Publish>
|
||||
|
||||
<Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="InstallDirDlg">NOT Installed</Publish>
|
||||
<Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="MaintenanceTypeDlg">Installed</Publish>
|
||||
|
||||
<Publish Dialog="MaintenanceWelcomeDlg" Control="Next" Event="NewDialog" Value="MaintenanceTypeDlg">1</Publish>
|
||||
|
||||
<Publish Dialog="MaintenanceTypeDlg" Control="RepairButton" Event="NewDialog" Value="VerifyReadyDlg">1</Publish>
|
||||
<Publish Dialog="MaintenanceTypeDlg" Control="RemoveButton" Event="NewDialog" Value="VerifyReadyDlg">1</Publish>
|
||||
<Publish Dialog="MaintenanceTypeDlg" Control="Back" Event="NewDialog" Value="MaintenanceWelcomeDlg">1</Publish>
|
||||
</UI>
|
||||
|
||||
<UIRef Id="WixUI_Common" />
|
||||
</Fragment>
|
||||
</Wix>
|
9
build/package/msi/templates/choco/LICENSE.txt
Normal file
9
build/package/msi/templates/choco/LICENSE.txt
Normal file
@ -0,0 +1,9 @@
|
||||
From: {{.Choco.LicenseURL}}
|
||||
|
||||
LICENSE
|
||||
|
||||
{{if gt (.License | len) 0}}
|
||||
{{.License | cat}}
|
||||
{{else if gt (.Choco.LicenseURL | len) 0}}
|
||||
{{.Choco.LicenseURL | download}}
|
||||
{{end}}
|
10
build/package/msi/templates/choco/VERIFICATION.txt
Normal file
10
build/package/msi/templates/choco/VERIFICATION.txt
Normal file
@ -0,0 +1,10 @@
|
||||
VERIFICATION
|
||||
|
||||
To check the checksum of this package, extract the msi file contained into it,
|
||||
then run
|
||||
|
||||
checksum.exe {{.Choco.MsiFile}} -t=sha256
|
||||
|
||||
The result must match
|
||||
|
||||
{{.Choco.MsiSum | upper}}
|
7
build/package/msi/templates/choco/chocolateyInstall.ps1
Normal file
7
build/package/msi/templates/choco/chocolateyInstall.ps1
Normal file
@ -0,0 +1,7 @@
|
||||
$packageName = '{{.Choco.ID}}'
|
||||
$fileType = 'msi'
|
||||
$silentArgs = '/quiet';
|
||||
$scriptPath = $(Split-Path $MyInvocation.MyCommand.Path);
|
||||
$fileFullPath = Join-Path $scriptPath '{{.Choco.MsiFile}}';
|
||||
|
||||
Install-ChocolateyInstallPackage $packageName $fileType $silentArgs $fileFullPath -checksum '{{.Choco.MsiSum}}' -checksumType = 'sha256'
|
@ -0,0 +1,6 @@
|
||||
$packageName = "{{.Choco.ID}}";
|
||||
$fileType = 'msi';
|
||||
$scriptPath = $(Split-Path $MyInvocation.MyCommand.Path);
|
||||
$fileFullPath = Join-Path $scriptPath '{{.Choco.MsiFile}}';
|
||||
|
||||
Uninstall-ChocolateyPackage $packageName $fileType "$fileFullPath /q"
|
38
build/package/msi/templates/choco/pkg.nuspec
Normal file
38
build/package/msi/templates/choco/pkg.nuspec
Normal file
@ -0,0 +1,38 @@
|
||||
<?xml version="1.0"?>
|
||||
<package xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||
<metadata>
|
||||
<id>{{.Choco.ID}}</id>
|
||||
<title>{{.Choco.Title}}</title>
|
||||
<version>{{.Version.MSI}}</version>
|
||||
<authors>{{.Choco.Authors}}</authors>
|
||||
<owners>{{.Choco.Owners}}</owners>
|
||||
<description>{{.Choco.Description}}</description>
|
||||
{{if gt (.Choco.ProjectURL | len) 0}}
|
||||
<projectUrl>{{.Choco.ProjectURL}}</projectUrl>
|
||||
{{end}}
|
||||
{{if gt (.Choco.Tags | len) 0}}
|
||||
<tags>{{.Choco.Tags}}</tags>
|
||||
{{end}}
|
||||
{{if gt (.Choco.LicenseURL | len) 0}}
|
||||
<licenseUrl>{{.Choco.LicenseURL}}</licenseUrl>
|
||||
{{end}}
|
||||
{{if gt (.Choco.IconURL | len) 0}}
|
||||
<iconUrl>{{.Choco.IconURL}}</iconUrl>
|
||||
{{end}}
|
||||
{{if gt (.Choco.ChangeLog | len) 0}}
|
||||
<releaseNotes>{{.Choco.ChangeLog}}</releaseNotes>
|
||||
{{end}}
|
||||
{{if .Choco.RequireLicense}}
|
||||
<requireLicenseAcceptance>true</requireLicenseAcceptance>
|
||||
{{else}}
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
{{end}}
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="{{.Choco.BuildDir}}\chocolateyInstall.ps1" target="tools" />
|
||||
<file src="{{.Choco.BuildDir}}\chocolateyUninstall.ps1" target="tools" />
|
||||
<file src="{{.Choco.BuildDir}}\{{.Choco.MsiFile}}" target="tools" />
|
||||
<file src="{{.Choco.BuildDir}}\LICENSE.txt" target="tools" />
|
||||
<file src="{{.Choco.BuildDir}}\VERIFICATION.txt" target="tools" />
|
||||
</files>
|
||||
</package>
|
212
build/package/msi/templates/product.wxs
Normal file
212
build/package/msi/templates/product.wxs
Normal file
@ -0,0 +1,212 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<?if $(sys.BUILDARCH)="x86"?>
|
||||
<?define Program_Files="ProgramFilesFolder"?>
|
||||
<?elseif $(sys.BUILDARCH)="x64"?>
|
||||
<?define Program_Files="ProgramFiles64Folder"?>
|
||||
<?else?>
|
||||
<?error Unsupported value of sys.BUILDARCH=$(sys.BUILDARCH)?>
|
||||
<?endif?>
|
||||
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
|
||||
<Product Id="*" UpgradeCode="{{.UpgradeCode}}"
|
||||
Name="{{.Product}}"
|
||||
Version="{{.Version.MSI}}"
|
||||
Manufacturer="{{.Company}}"
|
||||
Language="1033">
|
||||
|
||||
<Package InstallerVersion="200" Compressed="yes" Description="{{.Product}} {{.Version.Display}}"
|
||||
Comments="This installs {{.Product}} {{.Version.Display}}" InstallScope="perMachine"/>
|
||||
|
||||
<MediaTemplate EmbedCab="yes" {{if gt (.Compression | len) 0}}CompressionLevel="{{.Compression}}"{{end}}/>
|
||||
|
||||
<MajorUpgrade DowngradeErrorMessage="A newer version of this software is already installed."/>
|
||||
|
||||
{{if gt (.Banner | len) 0 }} <WixVariable Id="WixUIBannerBmp" Value="{{.Banner}}"/> {{end}}
|
||||
{{if gt (.Dialog | len) 0 }} <WixVariable Id="WixUIDialogBmp" Value="{{.Dialog}}"/> {{end}}
|
||||
|
||||
{{if gt (.Icon | len) 0 }}
|
||||
<Icon Id="Installer.Ico" SourceFile="{{.Icon}}"/>
|
||||
<Property Id="ARPPRODUCTICON" Value="Installer.Ico"/>
|
||||
{{end}}
|
||||
<!-- Need to customize the Add/remove program list entry, set the automatically created one to SystemComponent to hide it then create another one. -->
|
||||
<Property Id="ARPSYSTEMCOMPONENT" Value="1"/>
|
||||
|
||||
{{range $i, $p := .Properties}}
|
||||
<Property Id="{{$p.ID}}" {{if $p.Value}}Value="{{$p.Value}}"{{end}} {{if not $p.Registry}}Secure="yes"{{end}}>
|
||||
{{if $p.Registry}}
|
||||
<RegistrySearch Id="{{$p.ID}}Search" Root="{{$p.Registry.Root}}" Key="{{$p.Registry.Key}}"
|
||||
{{if gt ($p.Registry.Name | len) 0}} Name="{{$p.Registry.Name}}" {{end}} Type="raw"/>
|
||||
{{end}}
|
||||
</Property>
|
||||
{{end}}
|
||||
{{range $i, $c := .Conditions}}
|
||||
<Condition Message="{{$c.Message}}"><![CDATA[{{$c.Condition}}]]></Condition>
|
||||
{{end}}
|
||||
|
||||
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||
|
||||
<Directory Id="$(var.Program_Files)">
|
||||
<Directory Id="INSTALLDIR" Name="{{.Product}}">
|
||||
{{define "FILES"}}
|
||||
{{range $f := .}}
|
||||
<Component
|
||||
Id="ApplicationFiles{{$f.ID}}"
|
||||
Guid="*"
|
||||
Permanent="{{if $f.Permanent}}yes{{else}}no{{end}}"
|
||||
NeverOverwrite="{{if $f.NeverOverwrite}}yes{{else}}no{{end}}">
|
||||
|
||||
<File Id="ApplicationFile{{$f.ID}}" Source="{{$f.Path}}"/>
|
||||
{{if $f.Service}}
|
||||
<ServiceInstall Id="ServiceInstall{{$f.ID}}" Type="ownProcess" Name="{{$f.Service.Name}}" Start="{{$f.Service.Start}}" Account="LocalSystem" ErrorControl="normal"
|
||||
{{if gt ($f.Service.DisplayName | len) 0}} DisplayName="{{$f.Service.DisplayName}}" {{end}}
|
||||
{{if gt ($f.Service.Description | len) 0}} Description="{{$f.Service.Description}}" {{end}}
|
||||
{{if gt ($f.Service.Arguments | len) 0}} Arguments="{{$f.Service.Arguments}}" {{end}}>
|
||||
{{range $d := $f.Service.Dependencies}}
|
||||
<ServiceDependency Id="{{$d}}"/>
|
||||
{{end}}
|
||||
{{if $f.Service.Delayed}}
|
||||
<ServiceConfig DelayedAutoStart="yes" OnInstall="yes" OnReinstall ="yes"/>
|
||||
{{end}}
|
||||
</ServiceInstall>
|
||||
<ServiceControl Id="ServiceControl{{$f.ID}}" Name="{{$f.Service.Name}}" Start="install" Stop="both" Remove="uninstall"/>
|
||||
{{end}}
|
||||
</Component>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{template "FILES" .Directory.Files}}
|
||||
{{define "DIRECTORIES"}}
|
||||
{{range $d := .}}
|
||||
<Directory Id="ApplicationDirectory{{$d.ID}}" Name="{{$d.Name}}">
|
||||
{{template "FILES" $d.Files}}
|
||||
{{template "DIRECTORIES" $d.Directories}}
|
||||
</Directory>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{template "DIRECTORIES" .Directory.Directories}}
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
{{range $i, $e := .Environments}}
|
||||
<Component Id="Environments{{$i}}" Guid="*">
|
||||
<Environment Id="Environment{{$i}}" Name="{{$e.Name}}" Value="{{$e.Value}}" Permanent="{{$e.Permanent}}" Part="{{$e.Part}}" Action="{{$e.Action}}" System="{{$e.System}}"/>
|
||||
<RegistryValue Root="HKLM" Key="Software\[Manufacturer]\[ProductName]" Name="envvar{{$i}}" Type="integer" Value="1" KeyPath="yes"/>
|
||||
{{if gt ($e.Condition | len) 0}}<Condition><![CDATA[{{$e.Condition}}]]></Condition>{{end}}
|
||||
</Component>
|
||||
{{end}}
|
||||
|
||||
{{range $i, $r := .Registries}}
|
||||
<Component Id="RegistryEntries{{$i}}" Guid="*">
|
||||
<RegistryKey Root="{{$r.Root}}" Key="{{$r.Key}}">
|
||||
{{range $j, $v := $r.Values}}
|
||||
<RegistryValue Type="{{$v.Type}}" {{if gt ($v.Name | len) 0}} Name="{{$v.Name}}" {{end}} Value="{{$v.Value}}" {{if eq $i 0}}{{if eq $j 0}} KeyPath="yes" {{end}}{{end}}/>
|
||||
{{end}}
|
||||
</RegistryKey>
|
||||
{{if gt ($r.Condition | len) 0}}<Condition><![CDATA[{{$r.Condition}}]]></Condition>{{end}}
|
||||
</Component>
|
||||
{{end}}
|
||||
<Component Id="RegistryEntriesARP" Guid="*">
|
||||
<RegistryKey Root="HKLM" Key="Software\Microsoft\Windows\CurrentVersion\Uninstall\[ProductName]">
|
||||
<RegistryValue Type="string" Name="AuthorizedCDFPrefix" Value=""/>
|
||||
<RegistryValue Type="string" Name="Comments" Value="{{.Info.Comments}}"/>
|
||||
<RegistryValue Type="string" Name="Contact" Value="{{.Info.Contact}}"/>
|
||||
{{if gt (.Icon | len) 0 }}
|
||||
<RegistryValue Type="string" Name="DisplayIcon" Value="%SystemRoot%\Installer\[ProductCode]\Installer.Ico"/>
|
||||
{{end}}
|
||||
<RegistryValue Type="string" Name="DisplayName" Value="[ProductName]" KeyPath="yes"/>
|
||||
<RegistryValue Type="string" Name="DisplayVersion" Value="{{.Version.Display}}"/>
|
||||
<RegistryValue Type="integer" Name="EstimatedSize" Value="{{.Info.Size}}"/>
|
||||
<RegistryValue Type="string" Name="HelpLink" Value="{{.Info.HelpLink}}"/>
|
||||
<RegistryValue Type="string" Name="HelpTelephone" Value="{{.Info.SupportTelephone}}"/>
|
||||
<RegistryValue Type="string" Name="InstallDate" Value="[Date]"/>
|
||||
<RegistryValue Type="string" Name="InstallLocation" Value="[INSTALLDIR]"/>
|
||||
<RegistryValue Type="string" Name="InstallSource" Value="[SourceDir]"/>
|
||||
<RegistryValue Type="integer" Name="Language" Value="[ProductLanguage]"/>
|
||||
<RegistryValue Type="expandable" Name="ModifyPath" Value="MsiExec.exe /I[ProductCode]"/>
|
||||
<RegistryValue Type="string" Name="Publisher" Value="{{.Company}}"/>
|
||||
<RegistryValue Type="string" Name="Readme" Value="{{.Info.Readme}}"/>
|
||||
<RegistryValue Type="expandable" Name="UninstallString" Value="MsiExec.exe /I[ProductCode]"/>
|
||||
<RegistryValue Type="string" Name="URLInfoAbout" Value="{{.Info.SupportLink}}"/>
|
||||
<RegistryValue Type="string" Name="URLUpdateInfo" Value="{{.Info.UpdateInfoLink}}"/>
|
||||
<RegistryValue Type="integer" Name="Version" Value="{{.Version.Hex}}"/>
|
||||
</RegistryKey>
|
||||
</Component>
|
||||
|
||||
<Directory Id="ProgramMenuFolder"/>
|
||||
<Directory Id="DesktopFolder"/>
|
||||
|
||||
{{range $i, $s := .Shortcuts}}
|
||||
<Component Id="ApplicationShortcuts{{$i}}" Guid="*">
|
||||
<Shortcut Id="ApplicationShortcut{{$i}}" Name="{{$s.Name}}" Description="{{$s.Description}}" Target="{{$s.Target}}" WorkingDirectory="{{$s.WDir}}"
|
||||
Directory={{if eq $s.Location "program"}}"ProgramMenuFolder"{{else}}"DesktopFolder"{{end}}
|
||||
{{if gt ($s.Arguments | len) 0}}Arguments="{{$s.Arguments}}"{{end}}>
|
||||
{{if gt ($s.Icon | len) 0}}<Icon Id="Icon{{$i}}" SourceFile="{{$s.Icon}}"/>{{end}}
|
||||
{{range $j, $p := $s.Properties}}<ShortcutProperty Key="{{$p.Key}}" Value="{{$p.Value}}"/>{{end}}
|
||||
</Shortcut>
|
||||
{{if gt ($s.Condition | len) 0}}<Condition><![CDATA[{{$s.Condition}}]]></Condition>{{end}}
|
||||
<RegistryValue Root="HKCU" Key="Software\[Manufacturer]\[ProductName]" Name="shortcut{{$i}}" Type="integer" Value="1" KeyPath="yes"/>
|
||||
</Component>
|
||||
{{end}}
|
||||
|
||||
</Directory>
|
||||
|
||||
{{range $i, $h := .Hooks}}
|
||||
<SetProperty Action="SetCustomExec{{$i}}" {{if eq $h.Execute "immediate"}} Id="WixQuietExecCmdLine" {{else}} Id="CustomExec{{$i}}" {{end}} Value="{{$h.CookedCommand}}" Before="CustomExec{{$i}}" Sequence="execute"/>
|
||||
<CustomAction Id="CustomExec{{$i}}" BinaryKey="WixCA" DllEntry="WixQuietExec" Execute="{{$h.Execute}}" Impersonate="{{$h.Impersonate}}" {{if gt ($h.Return | len) 0}} Return="{{$h.Return}}" {{end}}/>
|
||||
{{end}}
|
||||
<InstallExecuteSequence>
|
||||
{{range $i, $h := .Hooks}}
|
||||
<Custom Action="CustomExec{{$i}}" {{if eq $h.When "install"}} After="InstallFiles" {{else if eq $h.Execute "immediate"}} Before="InstallValidate" {{else}} After="InstallInitialize" {{end}}>
|
||||
{{if eq $h.When "install"}}
|
||||
<![CDATA[NOT Installed AND NOT REMOVE{{if gt ($h.Condition | len) 0}} AND ({{$h.Condition}}){{end}}]]>
|
||||
{{else if eq $h.When "uninstall"}}
|
||||
<![CDATA[REMOVE{{if gt ($h.Condition | len) 0}} AND ({{$h.Condition}}){{end}}]]>
|
||||
{{else if gt ($h.Condition | len) 0 }}
|
||||
<![CDATA[{{$h.Condition}}]]>
|
||||
{{end}}
|
||||
</Custom>
|
||||
{{end}}
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<Feature Id="DefaultFeature" Level="1">
|
||||
{{range $i, $e := .Environments}}
|
||||
<ComponentRef Id="Environments{{$i}}"/>
|
||||
{{end}}
|
||||
{{$id := 0}}
|
||||
{{define "FILESREF"}}
|
||||
{{range $f := .}}
|
||||
<ComponentRef Id="ApplicationFiles{{$f.ID}}"/>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{template "FILESREF" .Directory.Files}}
|
||||
{{define "DIRECTORIESREF"}}
|
||||
{{range $d := .}}
|
||||
{{template "FILESREF" $d.Files}}
|
||||
{{template "DIRECTORIESREF" $d.Directories}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{template "DIRECTORIESREF" .Directory.Directories}}
|
||||
{{range $i, $r := .Registries}}
|
||||
<ComponentRef Id="RegistryEntries{{$i}}"/>
|
||||
{{end}}
|
||||
<ComponentRef Id="RegistryEntriesARP"/>
|
||||
{{range $i, $e := .Shortcuts}}
|
||||
<ComponentRef Id="ApplicationShortcuts{{$i}}"/>
|
||||
{{end}}
|
||||
</Feature>
|
||||
|
||||
<UI>
|
||||
<UIRef Id="WixUI_ErrorProgressText"/>
|
||||
<!-- Define the installer UI -->
|
||||
<UIRef Id="WixUI_HK"/>
|
||||
</UI>
|
||||
|
||||
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLDIR" />
|
||||
|
||||
<!-- this should help to propagate env var changes -->
|
||||
<CustomActionRef Id="WixBroadcastEnvironmentChange" />
|
||||
|
||||
</Product>
|
||||
|
||||
</Wix>
|
37
build/package/msi/wix.json
Normal file
37
build/package/msi/wix.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"product": "Draw.io Export",
|
||||
"company": "MouseSoft",
|
||||
"license": "tmp\\LICENSE.rtf",
|
||||
"info": {
|
||||
"comments": "",
|
||||
"contact": "Aleksei Badiaev <aleksei.badiaev@mousesoft.ru>",
|
||||
"help-link": "http://mousesoft.ru/help",
|
||||
"support-telephone": "555-123456789",
|
||||
"support-link": "http://mousesoft.ru/support",
|
||||
"update-info-link": "http://mousesoft.ru/update",
|
||||
"readme": "[INSTALLDIR]readme.txt"
|
||||
},
|
||||
"upgrade-code": "a2313bf4-d6ef-11ed-9abd-071668ee52fd",
|
||||
"files": [
|
||||
{
|
||||
"path": "out/bin/drawio-export.exe"
|
||||
}
|
||||
],
|
||||
"shortcuts": [
|
||||
{
|
||||
"name": "MouseSoft Draw.io Export",
|
||||
"description": "Export Draw.io diagrams using drawio-desktop",
|
||||
"location": "program",
|
||||
"target": "[INSTALLDIR]drawio-export.exe",
|
||||
"wdir": "INSTALLDIR",
|
||||
"condition": "STARTMENUSHORTCUT ~= \"yes\""
|
||||
}
|
||||
],
|
||||
"choco": {},
|
||||
"properties": [
|
||||
{
|
||||
"id": "STARTMENUSHORTCUT",
|
||||
"value": "yes"
|
||||
}
|
||||
]
|
||||
}
|
@ -1,7 +1,31 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.mousesoft.ru/ms/drawio-exporter/pkg/drawio"
|
||||
)
|
||||
|
||||
var version string // Версия приложения
|
||||
|
||||
func main() {
|
||||
fmt.Println("Draw.io Exporter")
|
||||
flag.Parse()
|
||||
if flagHelp {
|
||||
flag.Usage()
|
||||
os.Exit(0)
|
||||
}
|
||||
if flagVersion {
|
||||
fmt.Println("Draw.io Export cli util", version)
|
||||
os.Exit(0)
|
||||
}
|
||||
if flag.NArg() < 1 {
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
exporter := drawio.NewWithOptions(&opts)
|
||||
if err := exporter.Export(flag.Args()...); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error:", err)
|
||||
}
|
||||
}
|
||||
|
75
cmd/drawio-export/options.go
Normal file
75
cmd/drawio-export/options.go
Normal file
@ -0,0 +1,75 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
|
||||
"git.mousesoft.ru/ms/drawio-exporter/pkg/drawio"
|
||||
)
|
||||
|
||||
var (
|
||||
flagHelp bool // Вывести справку о приложении и выйти
|
||||
flagVersion bool // Вывести информацию о версии приложения и выйти
|
||||
opts = drawio.Options{} // Аргументы командной строки приложения
|
||||
)
|
||||
|
||||
func init() {
|
||||
// version
|
||||
flag.BoolVar(&flagVersion, "V", false, "Prints version information")
|
||||
flag.BoolVar(&flagVersion, "version", false, "Prints version information")
|
||||
// Application
|
||||
flag.StringVar(&opts.Application, "A", "", "Draw.io Desktop Application")
|
||||
flag.StringVar(&opts.Application, "application", "", "Draw.io Desktop Application")
|
||||
// Output
|
||||
flag.StringVar(&opts.Output, "o", "", "Exported folder name [default: export]")
|
||||
flag.StringVar(&opts.Output, "output", "", "Exported folder name [default: export]")
|
||||
// Format
|
||||
flag.Var(&opts.Format, "f",
|
||||
"Exported format [default: pdf] [possible values: pdf, png, jpg, svg, vsdx, xml]")
|
||||
flag.Var(&opts.Format, "format",
|
||||
"Exported format [default: pdf] [possible values: pdf, png, jpg, svg, vsdx, xml]")
|
||||
// Recursive
|
||||
flag.BoolVar(&opts.Recursive, "r", false,
|
||||
"For a folder input, recursively convert all files in sub-folders also")
|
||||
flag.BoolVar(&opts.Recursive, "recursive", false,
|
||||
"For a folder input, recursively convert all files in sub-folders also")
|
||||
// RemovePageSuffix
|
||||
flag.BoolVar(&opts.RemovePageSuffix, "remove-page-suffix", false,
|
||||
"Remove page suffix when possible (in case of single page file")
|
||||
// Quality
|
||||
flag.UintVar(&opts.Quality, "q", 0, "Output image quality for JPEG [default: 90]")
|
||||
flag.UintVar(&opts.Quality, "quality", 0, "Output image quality for JPEG [default: 90]")
|
||||
// Transparent
|
||||
flag.BoolVar(&opts.Transparent, "t", false, "Set transparent background for PNG")
|
||||
flag.BoolVar(&opts.Transparent, "transparent", false, "Set transparent background for PNG")
|
||||
// EmbedDiagram
|
||||
flag.BoolVar(&opts.EmbedDiagram, "e", false,
|
||||
"Includes a copy of the diagram for PDF, PNG, or SVG")
|
||||
flag.BoolVar(&opts.EmbedDiagram, "embed-diagram", false,
|
||||
"Includes a copy of the diagram for PDF, PNG, or SVG")
|
||||
// EmbedSvgImages
|
||||
flag.BoolVar(&opts.EmbedSvgImages, "embed-svg-images", false, "Embed Images in SVG file")
|
||||
// Border
|
||||
flag.UintVar(&opts.Border, "b", 0,
|
||||
"Sets the border width around the diagram [default: 0]")
|
||||
flag.UintVar(&opts.Border, "border", 0,
|
||||
"Sets the border width around the diagram [default: 0]")
|
||||
// Scale
|
||||
flag.UintVar(&opts.Scale, "s", 0, "Scales the diagram size")
|
||||
flag.UintVar(&opts.Scale, "scale", 0, "Scales the diagram size")
|
||||
// Width
|
||||
flag.UintVar(&opts.Width, "width", 0,
|
||||
"Fits the generated image/pdf into the specified width, preserves aspect ratio")
|
||||
// Height
|
||||
flag.UintVar(&opts.Height, "height", 0,
|
||||
"Fits the generated image/pdf into the specified height, preserves aspect ratio")
|
||||
// Crop
|
||||
flag.BoolVar(&opts.Crop, "crop", false, "Crops PDF to diagram size")
|
||||
// Uncompressed
|
||||
flag.BoolVar(&opts.Uncompressed, "u", false, "Uncompressed XML output")
|
||||
flag.BoolVar(&opts.Uncompressed, "uncompressed", false, "Uncompressed XML output")
|
||||
// EnablePlugins
|
||||
flag.BoolVar(&opts.EnablePlugins, "enable-plugins", false, "Enable Plugins")
|
||||
// help
|
||||
flag.BoolVar(&flagHelp, "h", false, "Prints help information")
|
||||
flag.BoolVar(&flagHelp, "help", false, "Prints help information")
|
||||
}
|
58
makefile
58
makefile
@ -1,7 +1,8 @@
|
||||
# drawio-exporter makefile
|
||||
# ========================
|
||||
|
||||
PROJECT_NAME := drawio-exporter
|
||||
PROJECT_ID := drawio-export
|
||||
BIN_SUFFIX :=
|
||||
|
||||
TMPDIR ?= $(CURDIR)/tmp
|
||||
OUTDIR ?= $(CURDIR)/out
|
||||
@ -28,6 +29,8 @@ MSI_FILE := $(PROJECT_NAME)_$(VERSION)_$(DIST_KIND).msi
|
||||
DIST_EXT := .zip
|
||||
DIST_OPTS := -a -cf
|
||||
ECHO_CMD := echo -e
|
||||
MSI_VERSION := $(shell echo $(VERSION_NUMBER) | sed -e 's/-.*//')
|
||||
BIN_SUFFIX := .exe
|
||||
else
|
||||
PLATFORM := $(shell uname -s | tr '[:upper:]' '[:lower:]')
|
||||
DIST_SUFFIX := $(PLATFORM)-$(DIST_KIND)
|
||||
@ -44,7 +47,7 @@ WHITE := $(shell tput -Txterm setaf 7)
|
||||
CYAN := $(shell tput -Txterm setaf 6)
|
||||
RESET := $(shell tput -Txterm sgr0)
|
||||
|
||||
.PHONY: all version version-number test build dist vendor
|
||||
.PHONY: all version version-number test build dist vendor package pkg-deb
|
||||
|
||||
version: ## Version of the project to be built
|
||||
@echo $(VERSION)
|
||||
@ -72,7 +75,7 @@ dist: ## Create all distro packages
|
||||
tar $(DIST_OPTS) "$(OUTDIR)/$(DIST_FILE)" -C "$(OUTDIR)" bin
|
||||
@$(ECHO_CMD) "Dist\t\t${GREEN}[OK]${RESET}"
|
||||
|
||||
vendor: ## Copy of all packages needed to support builds and tests in the vendor directory.
|
||||
vendor: ## Copy of all packages needed to support builds and tests in the vendor directory
|
||||
$(GOCMD) mod vendor
|
||||
@echo "Vendor\t\t${GREEN}[OK]${RESET}"
|
||||
|
||||
@ -82,7 +85,56 @@ clean: ## Remove build related files
|
||||
@rm -fr dist/
|
||||
@$(ECHO_CMD) "Clean\t\t${GREEN}[OK]${RESET}"
|
||||
|
||||
## Package:
|
||||
|
||||
ifeq ($(OS),Windows_NT)
|
||||
PACKAGE_TARGETS := pkg-msi
|
||||
else
|
||||
PACKAGE_TARGETS := pkg-deb
|
||||
endif
|
||||
|
||||
package: $(PACKAGE_TARGETS) ## Build all available packages
|
||||
@$(ECHO_CMD) "Package\t\t${GREEN}[OK]${RESET}"
|
||||
|
||||
ifeq ($(OS),Windows_NT)
|
||||
pkg-msi: ## Create MSI package
|
||||
@rm -f $(OUTDIR)/$(MSI_FILE)
|
||||
@mkdir -p $(TMPDIR)
|
||||
go-msi to-rtf --src LICENSE.txt --out $(TMPDIR)/LICENSE.rtf -e
|
||||
go-msi generate-templates --path build/package/msi/wix.json \
|
||||
--src build/package/msi/templates --out $(TMPDIR) \
|
||||
--version $(MSI_VERSION) --display $(VERSION) --license tmp/LICENSE.rtf
|
||||
go-msi gen-wix-cmd --path build/package/msi/wix.json --src tmp \
|
||||
--out $(TMPDIR) --arch amd64 --msi $(TMPDIR)/$(MSI_FILE) \
|
||||
--version $(MSI_VERSION) --display $(VERSION)
|
||||
@rm -f wix.dynamic.json
|
||||
cd $(TMPDIR) && ./build.bat
|
||||
mv $(TMPDIR)/$(MSI_FILE) $(OUTDIR)/
|
||||
@$(ECHO_CMD) "MSI package\t${GREEN}[OK]${RESET}"
|
||||
else
|
||||
DEB_NAME := $(PROJECT_ID)_$(VERSION_NUMBER)-1_amd64
|
||||
|
||||
pkg-deb: ## Build debian package
|
||||
@mkdir -p $(TMPDIR)/$(DEB_NAME)/usr/bin
|
||||
@mkdir -p $(TMPDIR)/$(DEB_NAME)/debian
|
||||
@mkdir -p $(TMPDIR)/$(DEB_NAME)/DEBIAN
|
||||
@cp -a $(BINDIR)/* $(TMPDIR)/$(DEB_NAME)/usr/bin/
|
||||
dpkg-gencontrol -v1.0-1 \
|
||||
-c$(CURDIR)/build/package/debian/control \
|
||||
-lbuild/package/debian/changelog \
|
||||
-f$(TMPDIR)/$(DEB_NAME)/debian/files -Ptmp/$(DEB_NAME)
|
||||
dpkg-deb --build --root-owner-group $(TMPDIR)/$(DEB_NAME)
|
||||
dpkg-genchanges --build=binary \
|
||||
-c$(CURDIR)/build/package/debian/control \
|
||||
-lbuild/package/debian/changelog \
|
||||
-f$(TMPDIR)/$(DEB_NAME)/debian/files \
|
||||
-u$(TMPDIR) -O$(OUTDIR)/$(DEB_NAME).changes
|
||||
@mv $(TMPDIR)/*.deb $(OUTDIR)/
|
||||
@$(ECHO_CMD) "pkg-deb\t\t${GREEN}[OK]${RESET}"
|
||||
endif
|
||||
|
||||
## Test:
|
||||
|
||||
test: ## Run the tests of the project
|
||||
ifeq ($(EXPORT_RESULT), true)
|
||||
@mkdir -p $(OUTDIR)
|
||||
|
20
pkg/drawio/execution.go
Normal file
20
pkg/drawio/execution.go
Normal file
@ -0,0 +1,20 @@
|
||||
package drawio
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// Последовательный запуск команд в ОС
|
||||
func RunSequence(command ...*exec.Cmd) error {
|
||||
var (
|
||||
errs = []error{}
|
||||
err error
|
||||
)
|
||||
for _, cmd := range command {
|
||||
if err = cmd.Run(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}
|
@ -2,12 +2,168 @@ package drawio
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
xmlparser "github.com/tamerh/xml-stream-parser"
|
||||
)
|
||||
|
||||
// Создать нового экспортёра диаграмм
|
||||
func New(opt ...Option) Exporter {
|
||||
options := &Options{
|
||||
Application: "drawio",
|
||||
Output: "export",
|
||||
Format: Format("pdf"),
|
||||
}
|
||||
for _, opt := range opt {
|
||||
opt.apply(options)
|
||||
}
|
||||
return NewWithOptions(options)
|
||||
}
|
||||
|
||||
// Создать нового экспортёра с параметрами
|
||||
func NewWithOptions(opts *Options) Exporter {
|
||||
exp := Exporter{opts: opts}
|
||||
if opts.openFile == nil {
|
||||
exp.openFile = func(name string) (io.ReadCloser, error) {
|
||||
return os.Open(name)
|
||||
}
|
||||
} else {
|
||||
exp.openFile = opts.openFile
|
||||
}
|
||||
if opts.readDir == nil {
|
||||
exp.readDir = func(name string) ([]os.DirEntry, error) {
|
||||
return os.ReadDir(name)
|
||||
}
|
||||
} else {
|
||||
exp.readDir = opts.readDir
|
||||
}
|
||||
return exp
|
||||
}
|
||||
|
||||
// Экспортёр диаграмм
|
||||
type Exporter struct {
|
||||
opts *Options // Параметры экспортёра диаграмм
|
||||
openFile OpenFileFunc // Открыть файл
|
||||
readDir ReadDirFunc // Прочитать папку на диске
|
||||
}
|
||||
|
||||
// Экспорт диаграмм из указанный файлов или папок
|
||||
func (exp Exporter) Export(fileOrDir ...string) error {
|
||||
var (
|
||||
commands = []*exec.Cmd{}
|
||||
err error
|
||||
errs = []error{}
|
||||
args = exp.opts.Args()
|
||||
info fs.FileInfo
|
||||
)
|
||||
for _, item := range fileOrDir {
|
||||
if info, err = os.Stat(item); err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
var addCommands []*exec.Cmd
|
||||
if info.IsDir() {
|
||||
addCommands, err = exp.ExportDir(item, args)
|
||||
} else {
|
||||
addCommands, err = exp.ExportFile(item, args)
|
||||
}
|
||||
commands = append(commands, addCommands...)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
if err = RunSequence(commands...); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
// Экспорт диаграмм из всех файлов в папке
|
||||
func (exp Exporter) ExportDir(
|
||||
dirPath string, args []string, subDir ...string,
|
||||
) ([]*exec.Cmd, error) {
|
||||
var (
|
||||
commands = []*exec.Cmd{}
|
||||
err error
|
||||
entries []fs.DirEntry
|
||||
errs []error
|
||||
)
|
||||
if entries, err = exp.readDir(dirPath); err != nil {
|
||||
return commands, err
|
||||
}
|
||||
for _, entry := range entries {
|
||||
name := entry.Name()
|
||||
var addCommands []*exec.Cmd
|
||||
if entry.IsDir() {
|
||||
if exp.opts.Recursive {
|
||||
deep := len(subDir)
|
||||
outDir := make([]string, deep+1)
|
||||
copy(outDir, subDir)
|
||||
outDir[deep] = name
|
||||
addCommands, err = exp.ExportDir(path.Join(dirPath, name), args, outDir...)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
addCommands, err = exp.ExportFile(path.Join(dirPath, name), args, subDir...)
|
||||
}
|
||||
commands = append(commands, addCommands...)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return commands, errors.Join(errs...)
|
||||
}
|
||||
|
||||
// Экспорт файла с диаграммами
|
||||
func (exp Exporter) ExportFile(
|
||||
filePath string, args []string, subDir ...string,
|
||||
) ([]*exec.Cmd, error) {
|
||||
var (
|
||||
commands = []*exec.Cmd{}
|
||||
err error
|
||||
file io.ReadCloser
|
||||
diagrams []string
|
||||
)
|
||||
if file, err = exp.openFile(filePath); err != nil {
|
||||
return commands, err
|
||||
}
|
||||
defer file.Close()
|
||||
if diagrams, err = Diagrams(file); err != nil {
|
||||
return commands, err
|
||||
}
|
||||
outDirs := make([]string, 1, len(subDir)+1)
|
||||
outDirs[0] = exp.opts.OutDir()
|
||||
outDirs = append(outDirs, subDir...)
|
||||
output := path.Join(outDirs...)
|
||||
for i, name := range diagrams {
|
||||
outName := strings.TrimSuffix(path.Base(filePath), path.Ext(filePath))
|
||||
if !exp.opts.RemovePageSuffix || len(diagrams) > 1 {
|
||||
outName += "-" + name
|
||||
}
|
||||
outName += exp.opts.OutExt()
|
||||
drawioArgs := make([]string, len(args), len(args)+4)
|
||||
copy(drawioArgs, args)
|
||||
drawioArgs = append(drawioArgs,
|
||||
"--page-index", strconv.Itoa(i),
|
||||
"--output", path.Join(output, outName),
|
||||
"--export", filePath,
|
||||
)
|
||||
cmd := exec.Command(exp.opts.App(), drawioArgs...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
commands = append(commands, cmd)
|
||||
}
|
||||
return commands, err
|
||||
}
|
||||
|
||||
// Разбирает данные и возвращает срез имён диаграмм в них
|
||||
func Diagrams(reader io.Reader) ([]string, error) {
|
||||
var (
|
||||
@ -21,7 +177,6 @@ func Diagrams(reader io.Reader) ([]string, error) {
|
||||
if xml.Err != nil {
|
||||
return result, xml.Err
|
||||
}
|
||||
fmt.Println(xml.Name)
|
||||
if items, ok := xml.Childs["diagram"]; ok {
|
||||
for _, item := range items {
|
||||
result = append(result, item.Attrs["name"])
|
||||
|
@ -1,22 +1,126 @@
|
||||
package drawio_test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.mousesoft.ru/ms/drawio-exporter/pkg/drawio"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Команда оболочки ОС
|
||||
type command struct {
|
||||
cmd string // Команда
|
||||
args []string // Аргументы команды
|
||||
}
|
||||
|
||||
// Файл с диаграммами
|
||||
type source struct {
|
||||
output string // Папка вывода для ExportFile
|
||||
data string // Данные файла с диаграммами
|
||||
diagrams []string // Срез имён диаграмм
|
||||
commands []command // Ожидаемые команды экспорта диаграмм
|
||||
}
|
||||
|
||||
// Элемент папки с файлами
|
||||
type dirEntry struct {
|
||||
name string // Имя элемента
|
||||
isDir bool // Истина, если элемент является папкой
|
||||
}
|
||||
|
||||
var (
|
||||
_ os.DirEntry = (*dirEntry)(nil)
|
||||
_ os.FileInfo = (*dirEntry)(nil)
|
||||
)
|
||||
|
||||
func (entry dirEntry) Name() string {
|
||||
return entry.name
|
||||
}
|
||||
|
||||
func (entry dirEntry) IsDir() bool {
|
||||
return entry.isDir
|
||||
}
|
||||
|
||||
func (entry dirEntry) Type() fs.FileMode {
|
||||
if entry.isDir {
|
||||
return fs.ModeDir
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (entry dirEntry) Info() (fs.FileInfo, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (entry dirEntry) Size() int64 {
|
||||
return 100
|
||||
}
|
||||
|
||||
func (entry dirEntry) Mode() fs.FileMode {
|
||||
return entry.Type()
|
||||
}
|
||||
|
||||
func (entry dirEntry) ModTime() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
func (entry dirEntry) Sys() any {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Конвертация команд в информацию о командах
|
||||
func cmd2info(commands []*exec.Cmd) []command {
|
||||
cmdInfo := make([]command, len(commands))
|
||||
for i, cmd := range commands {
|
||||
cmdInfo[i] = command{
|
||||
cmd: cmd.Path,
|
||||
args: make([]string, len(cmd.Args)),
|
||||
}
|
||||
copy(cmdInfo[i].args, cmd.Args)
|
||||
}
|
||||
return cmdInfo
|
||||
}
|
||||
|
||||
type exportTest struct {
|
||||
name string // Наименование теста
|
||||
dirs map[string][]os.DirEntry // Папки файлов с диаграммами
|
||||
files map[string]source // Файлы с диаграммами
|
||||
}
|
||||
|
||||
// Информация об ожидаемых командах при экспорте папки
|
||||
func (test exportTest) dirCommands(dir string, recursive bool) []command {
|
||||
commands := make([]command, 0, 4)
|
||||
if tc, ok := test.dirs[dir]; ok {
|
||||
for _, entry := range tc {
|
||||
path := path.Join(dir, entry.Name())
|
||||
if entry.IsDir() && recursive {
|
||||
commands = append(commands, test.dirCommands(path, true)...)
|
||||
} else {
|
||||
commands = append(commands, test.files[path].commands...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return commands
|
||||
}
|
||||
|
||||
// Тестовые данные
|
||||
var testData = []struct {
|
||||
name string // Наименование теста
|
||||
source string // Данные файла с диаграммами
|
||||
diagrams []string // Срез имён диаграмм
|
||||
}{
|
||||
var testData = []exportTest{
|
||||
{
|
||||
name: "positive case",
|
||||
source: `<mxfile host="test">
|
||||
dirs: map[string][]os.DirEntry{
|
||||
"source": {
|
||||
dirEntry{name: "diagrams.drawio", isDir: false},
|
||||
},
|
||||
},
|
||||
files: map[string]source{
|
||||
"source/diagrams.drawio": {
|
||||
data: `<mxfile host="test">
|
||||
<diagram name="1" id="01">
|
||||
<mxGraphModel page="1">
|
||||
</mxGraphModel>
|
||||
@ -30,11 +134,78 @@ var testData = []struct {
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>`,
|
||||
diagrams: []string{"1", "2", "3"},
|
||||
diagrams: []string{"1", "2", "3"},
|
||||
commands: []command{
|
||||
{
|
||||
cmd: "/usr/bin/drawio",
|
||||
args: []string{
|
||||
"drawio", "--page-index", "0",
|
||||
"--output", "export/diagrams-1.pdf",
|
||||
"--export", "source/diagrams.drawio",
|
||||
},
|
||||
},
|
||||
{
|
||||
cmd: "/usr/bin/drawio",
|
||||
args: []string{
|
||||
"drawio", "--page-index", "1",
|
||||
"--output", "export/diagrams-2.pdf",
|
||||
"--export", "source/diagrams.drawio",
|
||||
},
|
||||
},
|
||||
{
|
||||
cmd: "/usr/bin/drawio",
|
||||
args: []string{
|
||||
"drawio", "--page-index", "2",
|
||||
"--output", "export/diagrams-3.pdf",
|
||||
"--export", "source/diagrams.drawio",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid source",
|
||||
source: `<nxfile host="test">
|
||||
dirs: map[string][]os.DirEntry{
|
||||
"source": {
|
||||
dirEntry{name: "diagrams.drawio", isDir: false},
|
||||
},
|
||||
},
|
||||
files: map[string]source{
|
||||
"source/diagrams.drawio": {
|
||||
data: `<nxfile host="test">
|
||||
<diagram name="1" id="01">
|
||||
<mxGraphModel page="1">
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
<diagram name="2" id="02">
|
||||
<mxGraphModel page="1">
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
<diagram name="3" id="03">
|
||||
<mxGraphModel page="1">
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</nxfile>`,
|
||||
diagrams: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nested dirs",
|
||||
dirs: map[string][]os.DirEntry{
|
||||
"source": {
|
||||
dirEntry{name: "diagrams.drawio", isDir: false},
|
||||
dirEntry{name: "subdir", isDir: true},
|
||||
dirEntry{name: "additional.xml", isDir: false},
|
||||
},
|
||||
"source/subdir": {
|
||||
dirEntry{name: "Вложенные диаграммы.drawio", isDir: false},
|
||||
},
|
||||
},
|
||||
files: map[string]source{
|
||||
"source/diagrams.drawio": {
|
||||
data: `<mxfile host="test">
|
||||
<diagram name="1" id="01">
|
||||
<mxGraphModel page="1">
|
||||
</mxGraphModel>
|
||||
@ -47,17 +218,193 @@ var testData = []struct {
|
||||
<mxGraphModel page="1">
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</nxfile>`,
|
||||
diagrams: []string{},
|
||||
</mxfile>`,
|
||||
diagrams: []string{"1", "2", "3"},
|
||||
commands: []command{
|
||||
{
|
||||
cmd: "/usr/bin/drawio",
|
||||
args: []string{
|
||||
"drawio", "--page-index", "0",
|
||||
"--output", "export/diagrams-1.pdf",
|
||||
"--export", "source/diagrams.drawio",
|
||||
},
|
||||
},
|
||||
{
|
||||
cmd: "/usr/bin/drawio",
|
||||
args: []string{
|
||||
"drawio", "--page-index", "1",
|
||||
"--output", "export/diagrams-2.pdf",
|
||||
"--export", "source/diagrams.drawio",
|
||||
},
|
||||
},
|
||||
{
|
||||
cmd: "/usr/bin/drawio",
|
||||
args: []string{
|
||||
"drawio", "--page-index", "2",
|
||||
"--output", "export/diagrams-3.pdf",
|
||||
"--export", "source/diagrams.drawio",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"source/additional.xml": {
|
||||
data: `<mxfile host="test">
|
||||
<diagram name="Один" id="01">
|
||||
<mxGraphModel page="1">
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
<diagram name="Два" id="02">
|
||||
<mxGraphModel page="1">
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>`,
|
||||
diagrams: []string{"Один", "Два"},
|
||||
commands: []command{
|
||||
{
|
||||
cmd: "/usr/bin/drawio",
|
||||
args: []string{
|
||||
"drawio", "--page-index", "0",
|
||||
"--output", "export/additional-Один.pdf",
|
||||
"--export", "source/additional.xml",
|
||||
},
|
||||
},
|
||||
{
|
||||
cmd: "/usr/bin/drawio",
|
||||
args: []string{
|
||||
"drawio", "--page-index", "1",
|
||||
"--output", "export/additional-Два.pdf",
|
||||
"--export", "source/additional.xml",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"source/subdir/Вложенные диаграммы.drawio": {
|
||||
output: "export/subdir",
|
||||
data: `<mxfile host="test">
|
||||
<diagram name="Первая диаграмма" id="01">
|
||||
<mxGraphModel page="1">
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
<diagram name="Вторая диаграмма" id="02">
|
||||
<mxGraphModel page="1">
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>`,
|
||||
diagrams: []string{"Первая диаграмма", "Вторая диаграмма"},
|
||||
commands: []command{
|
||||
{
|
||||
cmd: "/usr/bin/drawio",
|
||||
args: []string{
|
||||
"drawio", "--page-index", "0",
|
||||
"--output", "export/subdir/Вложенные диаграммы-Первая диаграмма.pdf",
|
||||
"--export", "source/subdir/Вложенные диаграммы.drawio",
|
||||
},
|
||||
},
|
||||
{
|
||||
cmd: "/usr/bin/drawio",
|
||||
args: []string{
|
||||
"drawio", "--page-index", "1",
|
||||
"--output", "export/subdir/Вложенные диаграммы-Вторая диаграмма.pdf",
|
||||
"--export", "source/subdir/Вложенные диаграммы.drawio",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestDiagrams(t *testing.T) {
|
||||
for _, test := range testData {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
diagrams, err := drawio.Diagrams(strings.NewReader(test.source))
|
||||
assert.NoError(t, err)
|
||||
assert.ElementsMatch(t, test.diagrams, diagrams)
|
||||
for filePath, source := range test.files {
|
||||
t.Run(filePath, func(t *testing.T) {
|
||||
diagrams, err := drawio.Diagrams(strings.NewReader(source.data))
|
||||
assert.NoError(t, err)
|
||||
assert.ElementsMatch(t, source.diagrams, diagrams)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExportFile(t *testing.T) {
|
||||
for _, test := range testData {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
for filePath, source := range test.files {
|
||||
var openOpt = drawio.WithNop()
|
||||
if len(source.output) > 0 {
|
||||
openOpt = drawio.WithOutput(source.output)
|
||||
}
|
||||
t.Run(filePath, func(t *testing.T) {
|
||||
var (
|
||||
openFileCalls = []string{}
|
||||
exp = drawio.New(
|
||||
drawio.WithOpenFile(func(s string) (io.ReadCloser, error) {
|
||||
openFileCalls = append(openFileCalls, s)
|
||||
return io.NopCloser(strings.NewReader(source.data)), nil
|
||||
}),
|
||||
openOpt,
|
||||
)
|
||||
)
|
||||
commands, err := exp.ExportFile(filePath, []string{})
|
||||
assert.NoError(t, err)
|
||||
if assert.Equal(t, 1, len(openFileCalls)) {
|
||||
assert.Equal(t, filePath, openFileCalls[0])
|
||||
}
|
||||
assert.ElementsMatch(t, source.commands, cmd2info(commands))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExportDir(t *testing.T) {
|
||||
for _, recursive := range []bool{false, true} {
|
||||
var (
|
||||
name string
|
||||
recursiveOption drawio.Option
|
||||
)
|
||||
if recursive {
|
||||
name = "recursive"
|
||||
recursiveOption = drawio.WithRecursive()
|
||||
} else {
|
||||
name = "non-recursive"
|
||||
recursiveOption = drawio.WithNop()
|
||||
}
|
||||
t.Run(name, func(t *testing.T) {
|
||||
for _, test := range testData {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var (
|
||||
openFileCalls = []string{}
|
||||
exp = drawio.New(
|
||||
drawio.WithOpenFile(func(s string) (io.ReadCloser, error) {
|
||||
openFileCalls = append(openFileCalls, s)
|
||||
return io.NopCloser(
|
||||
strings.NewReader(test.files[s].data),
|
||||
), nil
|
||||
}),
|
||||
drawio.WithReadDir(func(s string) ([]os.DirEntry, error) {
|
||||
return test.dirs[s], nil
|
||||
}),
|
||||
recursiveOption,
|
||||
)
|
||||
)
|
||||
for dir := range test.dirs {
|
||||
pathDir, _ := path.Split(dir)
|
||||
if len(pathDir) > 0 {
|
||||
continue
|
||||
}
|
||||
t.Run(dir, func(t *testing.T) {
|
||||
openFileCalls = openFileCalls[:0]
|
||||
commands, err := exp.ExportDir(dir, []string{})
|
||||
assert.NoError(t, err)
|
||||
assert.ElementsMatch(t,
|
||||
test.dirCommands(dir, recursive), cmd2info(commands))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
421
pkg/drawio/options.go
Normal file
421
pkg/drawio/options.go
Normal file
@ -0,0 +1,421 @@
|
||||
package drawio
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"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)
|
||||
}
|
||||
|
||||
// Проверка эквивалентности ошибок
|
||||
func (e ErrUnsupportedFormat) Is(target error) bool {
|
||||
switch t := target.(type) {
|
||||
case ErrUnsupportedFormat:
|
||||
return t.Format == e.Format
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var _ error = (*ErrUnsupportedFormat)(nil) // Проверка реализации интерфейса
|
||||
|
||||
type Format string // Формат экспортированного файла
|
||||
|
||||
const (
|
||||
PDF Format = "pdf"
|
||||
PNG Format = "png"
|
||||
JPG Format = "jpg"
|
||||
SVG Format = "svg"
|
||||
VSDX Format = "vsdx"
|
||||
XML Format = "xml"
|
||||
)
|
||||
|
||||
// Строковое представление формата
|
||||
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}
|
||||
}
|
||||
|
||||
// Список всех поддерживаемых форматов экспорта
|
||||
func SupportedFormats() []Format {
|
||||
return []Format{"pdf", "png", "jpg", "svg", "vsdx", "xml"}
|
||||
}
|
||||
|
||||
var _ flag.Value = (*Format)(nil) // Поверка реализации интерфейса
|
||||
|
||||
// Расширение файла заданного формата
|
||||
func (f Format) ext() string {
|
||||
return "." + string(f)
|
||||
}
|
||||
|
||||
// Параметры экспортёра диаграмм
|
||||
type Options struct {
|
||||
Application string // Путь к приложению drawio
|
||||
Output string // Путь к папке с экспортированными файлами
|
||||
Format Format // Формат экспортированных файлов
|
||||
Recursive bool // Рекурсивно сканировать вложенные папки с файлами
|
||||
RemovePageSuffix bool // Удалять суффикс страницы, если это возможно
|
||||
Quality uint // Качество экспортированного изображения (только для JPEG)
|
||||
Transparent bool // Прозрачный фона для PNG
|
||||
// Включать копию диаграммы в экспортированный файл для PDF, PNG и SVG
|
||||
EmbedDiagram bool
|
||||
EmbedSvgImages bool // Встраивать изображения в файл формата SVG
|
||||
Border uint // Ширина рамки вокруг диаграмм
|
||||
Scale uint // Масштаб в процентах размера экспортированных диаграмм
|
||||
Width uint // Ширина экспортированной диаграммы с сохранением масштаба
|
||||
Height uint // Высота экспортированной диаграммы с сохранением масштаба
|
||||
Crop bool // Обрезать результирующий PDF до размера диаграммы
|
||||
Uncompressed bool // Выводить несжатый XML
|
||||
EnablePlugins bool // Включить подключаемые модули
|
||||
openFile OpenFileFunc // Открыть файл
|
||||
readDir ReadDirFunc // Прочитать папку на диске
|
||||
}
|
||||
|
||||
// Функция открытия файла
|
||||
type OpenFileFunc func(string) (io.ReadCloser, error)
|
||||
|
||||
// Функция чтения папки на диске
|
||||
type ReadDirFunc func(string) ([]os.DirEntry, error)
|
||||
|
||||
// Путь к приложению drawio
|
||||
func (opts Options) App() string {
|
||||
app := opts.Application
|
||||
if len(app) == 0 {
|
||||
app = "drawio"
|
||||
}
|
||||
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()
|
||||
}
|
||||
|
||||
// Аргументы командной строки drawio
|
||||
func (opts Options) Args() []string {
|
||||
args := []string{}
|
||||
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)
|
||||
}
|
||||
|
||||
// Путь к папке с экспортированными файлами
|
||||
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)
|
||||
func WithQuality(q uint) Option {
|
||||
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
|
||||
}
|
||||
|
||||
// Ширина рамки вокруг диаграмм
|
||||
func WithBorder(border uint) Option {
|
||||
return optionBorder(border)
|
||||
}
|
||||
|
||||
type optionBorder uint
|
||||
|
||||
var _ Option = (*optionBorder)(nil) // Проверка реализации интерфейса
|
||||
|
||||
func (opt optionBorder) apply(opts *Options) {
|
||||
opts.Border = uint(opt)
|
||||
}
|
||||
|
||||
// Масштаб в процентах размера экспортированных диаграмм
|
||||
func WithScale(scale uint) Option {
|
||||
return optionScale(scale)
|
||||
}
|
||||
|
||||
type optionScale uint
|
||||
|
||||
var _ Option = (*optionScale)(nil) // Проверка реализации интерфейса
|
||||
|
||||
func (opt optionScale) apply(opts *Options) {
|
||||
opts.Scale = uint(opt)
|
||||
}
|
||||
|
||||
// Ширина экспортированной диаграммы с сохранением масштаба
|
||||
func WithWidth(width uint) Option {
|
||||
return optionWidth(width)
|
||||
}
|
||||
|
||||
type optionWidth uint
|
||||
|
||||
var _ Option = (*optionWidth)(nil) // Проверка реализации интерфейса
|
||||
|
||||
func (opt optionWidth) apply(opts *Options) {
|
||||
opts.Width = uint(opt)
|
||||
}
|
||||
|
||||
// Высота экспортированной диаграммы с сохранением масштаба
|
||||
func WithHeight(height uint) Option {
|
||||
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
|
||||
}
|
||||
|
||||
// Открыть файл
|
||||
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) {
|
||||
// Не требуется действий, так как это пустой параметр
|
||||
}
|
105
pkg/drawio/options_internal_test.go
Normal file
105
pkg/drawio/options_internal_test.go
Normal file
@ -0,0 +1,105 @@
|
||||
package drawio
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestOptions(t *testing.T) {
|
||||
testData := []struct {
|
||||
name string // Наименование теста
|
||||
opts []Option // Параметры
|
||||
app string // Путь к приложению drawio
|
||||
outdir string // Путь к папке с экспортированными файлами
|
||||
outext string // Расширение экспортированных файлов
|
||||
args []string // Аргументы командной строки drawio
|
||||
}{
|
||||
{
|
||||
name: "default",
|
||||
opts: []Option{},
|
||||
app: "drawio",
|
||||
outdir: "export",
|
||||
outext: ".pdf",
|
||||
args: []string{},
|
||||
},
|
||||
{
|
||||
name: "svg with all options",
|
||||
opts: []Option{
|
||||
WithAppPath("/usr/local/bin/drawio"),
|
||||
WithOutput("/tmp/images/"),
|
||||
WithFormat(SVG),
|
||||
WithRecursive(),
|
||||
WithRemovePageSuffix(),
|
||||
WithQuality(100),
|
||||
WithTransparent(),
|
||||
WithEmbedDiagram(),
|
||||
WithEmbedSvgImages(),
|
||||
WithBorder(1),
|
||||
WithScale(120),
|
||||
WithWidth(800),
|
||||
WithHeight(600),
|
||||
WithCrop(),
|
||||
WithUncompressed(),
|
||||
WithEnablePlugins(),
|
||||
},
|
||||
app: "/usr/local/bin/drawio",
|
||||
outdir: "/tmp/images/",
|
||||
outext: ".svg",
|
||||
args: []string{
|
||||
"--format", "svg",
|
||||
"--quality", "100",
|
||||
"--transparent",
|
||||
"--embed-diagram",
|
||||
"--embed-svg-images",
|
||||
"--border", "1",
|
||||
"--scale", "120",
|
||||
"--width", "800",
|
||||
"--height", "600",
|
||||
"--crop",
|
||||
"--uncompressed",
|
||||
"--enable-plugins",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range testData {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
options := Options{}
|
||||
for _, opt := range test.opts {
|
||||
opt.apply(&options)
|
||||
}
|
||||
assert.Equal(t, test.app, options.App())
|
||||
assert.Equal(t, test.outdir, options.OutDir())
|
||||
assert.Equal(t, test.outext, options.OutExt())
|
||||
assert.ElementsMatch(t, test.args, options.Args())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Тестовая функция открытия файла
|
||||
func openFile(name string) (io.ReadCloser, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func TestOptionsOpenFileFunc(t *testing.T) {
|
||||
opts := New(WithOpenFile(openFile))
|
||||
assert.Equal(t,
|
||||
reflect.ValueOf(openFile).Pointer(),
|
||||
reflect.ValueOf(opts.openFile).Pointer(),
|
||||
)
|
||||
}
|
||||
|
||||
func readDir(name string) ([]os.DirEntry, error) {
|
||||
return []os.DirEntry{}, nil
|
||||
}
|
||||
|
||||
func TestOptionsReadDirFunc(t *testing.T) {
|
||||
opts := New(WithReadDir(readDir))
|
||||
assert.Equal(t,
|
||||
reflect.ValueOf(readDir).Pointer(),
|
||||
reflect.ValueOf(opts.readDir).Pointer(),
|
||||
)
|
||||
}
|
76
pkg/drawio/options_test.go
Normal file
76
pkg/drawio/options_test.go
Normal file
@ -0,0 +1,76 @@
|
||||
package drawio_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.mousesoft.ru/ms/drawio-exporter/pkg/drawio"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFormat(t *testing.T) {
|
||||
testData := []struct {
|
||||
name string
|
||||
err error
|
||||
format drawio.Format
|
||||
ext string
|
||||
}{
|
||||
{
|
||||
name: "pdf",
|
||||
err: nil,
|
||||
format: drawio.PDF,
|
||||
ext: ".pdf",
|
||||
},
|
||||
{
|
||||
name: "png",
|
||||
err: nil,
|
||||
format: drawio.PNG,
|
||||
ext: ".png",
|
||||
},
|
||||
{
|
||||
name: "jpg",
|
||||
err: nil,
|
||||
format: drawio.JPG,
|
||||
ext: ".jpg",
|
||||
},
|
||||
{
|
||||
name: "svg",
|
||||
err: nil,
|
||||
format: drawio.SVG,
|
||||
ext: ".svg",
|
||||
},
|
||||
{
|
||||
name: "vsdx",
|
||||
err: nil,
|
||||
format: drawio.VSDX,
|
||||
ext: ".vsdx",
|
||||
},
|
||||
{
|
||||
name: "xml",
|
||||
err: nil,
|
||||
format: drawio.XML,
|
||||
ext: ".xml",
|
||||
},
|
||||
{
|
||||
name: "svvg",
|
||||
err: drawio.ErrUnsupportedFormat{"svvg"},
|
||||
format: drawio.Format(""),
|
||||
ext: "",
|
||||
},
|
||||
}
|
||||
for _, test := range testData {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var (
|
||||
v drawio.Format
|
||||
err error
|
||||
)
|
||||
err = (&v).Set(test.name)
|
||||
if test.err == nil {
|
||||
assert.Equal(t, test.name, test.format.String())
|
||||
assert.NoError(t, err)
|
||||
} else {
|
||||
assert.ErrorIs(t, err, test.err)
|
||||
assert.ErrorContains(t, err, test.err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user