Автоматическая сборка пакетов Debian (#4)
All checks were successful
drawio-export/pipeline/head This commit looks good

This PR closes #2

Reviewed-on: #4
This commit is contained in:
Алексей Бадяев 2023-04-12 22:37:54 +07:00
parent 78624641e4
commit 8d18d7291e
Signed by: Git
GPG Key ID: D2668F5B91D30623
21 changed files with 1292 additions and 149 deletions

11
.vscode/settings.json vendored
View File

@ -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"
]
}

View File

@ -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
View File

@ -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}",

View 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

View 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.

View 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>

View 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>

View 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}}

View 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}}

View 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'

View File

@ -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"

View 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>

View 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>

View 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"
}
]
}

View File

@ -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
View 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...)
}

View File

@ -3,7 +3,6 @@ package drawio
import (
"bufio"
"errors"
"fmt"
"io"
"io/fs"
"os"
@ -30,39 +29,141 @@ func New(opt ...Option) Exporter {
// Создать нового экспортёра с параметрами
func NewWithOptions(opts *Options) Exporter {
return Exporter{opts: opts}
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
opts *Options // Параметры экспортёра диаграмм
openFile OpenFileFunc // Открыть файл
readDir ReadDirFunc // Прочитать папку на диске
}
// Экспорт диаграмм из указанный файлов или папок
func (exp Exporter) Export(fileOrDir ...string) error {
var (
err error
errs = []error{}
args = exp.opts.Args()
info fs.FileInfo
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() {
err = exp.exportDir(item, args)
addCommands, err = exp.ExportDir(item, args)
} else {
err = exp.exportFile(item, args)
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 (
@ -84,72 +185,3 @@ func Diagrams(reader io.Reader) ([]string, error) {
}
return result, nil
}
// Экспорт диаграмм из всех файлов в папке
func (exp Exporter) exportDir(dirPath string, args []string, subDir ...string) error {
var (
entries []fs.DirEntry
err error
errs []error
)
if entries, err = os.ReadDir(dirPath); err != nil {
return err
}
for _, entry := range entries {
name := entry.Name()
outDir := make([]string, len(subDir), len(subDir)+1)
outDir = append(outDir, name)
if entry.IsDir() && exp.opts.Recursive {
err = exp.exportDir(path.Join(dirPath, name), args, outDir...)
} else {
err = exp.exportFile(path.Join(dirPath, name), args, outDir...)
}
if err != nil {
errs = append(errs, err)
}
}
return errors.Join(errs...)
}
// Экспорт файла с диаграммами
func (exp Exporter) exportFile(filePath string, args []string, subDir ...string) error {
var (
file *os.File
diagrams []string
err error
)
if file, err = os.Open(filePath); err != nil {
return err
}
defer file.Close()
if diagrams, err = Diagrams(file); err != nil {
return err
}
outDirs := make([]string, 1, len(subDir)+1)
outDirs[0] = exp.opts.Output
outDirs = append(outDirs, subDir...)
output := path.Join(outDirs...)
errs := []error{}
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
fmt.Println("Run command:", cmd.Path, cmd.Args)
if err = cmd.Run(); err != nil {
errs = append(errs, err)
}
}
return errors.Join(errs...)
}

View File

@ -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))
})
}
})
}
})
}
}

View File

@ -3,6 +3,8 @@ package drawio
import (
"flag"
"fmt"
"io"
"os"
"strconv"
)
@ -21,10 +23,28 @@ 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)
@ -41,6 +61,11 @@ func (f *Format) Set(str string) error {
return &ErrUnsupportedFormat{str}
}
// Список всех поддерживаемых форматов экспорта
func SupportedFormats() []Format {
return []Format{"pdf", "png", "jpg", "svg", "vsdx", "xml"}
}
var _ flag.Value = (*Format)(nil) // Поверка реализации интерфейса
// Расширение файла заданного формата
@ -59,16 +84,24 @@ type Options struct {
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 // Включить подключаемые модули
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
@ -96,7 +129,7 @@ func (opts Options) OutExt() string {
return fmt.ext()
}
// Формирование аргументов командной строки
// Аргументы командной строки drawio
func (opts Options) Args() []string {
args := []string{}
if len(opts.Format) > 0 {
@ -202,7 +235,7 @@ func (opt optionRemovePageSuffix) apply(opts *Options) {
}
// Качество экспортированного изображения (только для JPEG)
func WithQuality(q int) Option {
func WithQuality(q uint) Option {
return optionQuality(q)
}
@ -254,7 +287,7 @@ func (opt optionEmbedSvgImages) apply(opts *Options) {
}
// Ширина рамки вокруг диаграмм
func WithBorder(border int) Option {
func WithBorder(border uint) Option {
return optionBorder(border)
}
@ -267,7 +300,7 @@ func (opt optionBorder) apply(opts *Options) {
}
// Масштаб в процентах размера экспортированных диаграмм
func WithScale(scale int) Option {
func WithScale(scale uint) Option {
return optionScale(scale)
}
@ -280,7 +313,7 @@ func (opt optionScale) apply(opts *Options) {
}
// Ширина экспортированной диаграммы с сохранением масштаба
func WithWidth(width int) Option {
func WithWidth(width uint) Option {
return optionWidth(width)
}
@ -293,7 +326,7 @@ func (opt optionWidth) apply(opts *Options) {
}
// Высота экспортированной диаграммы с сохранением масштаба
func WithHeight(height int) Option {
func WithHeight(height uint) Option {
return optionHeight(height)
}
@ -344,7 +377,45 @@ func (opt optionEnablePlugins) apply(opts *Options) {
opts.EnablePlugins = true
}
// Список всех поддерживаемых форматов экспорта
func SupportedFormats() []Format {
return []Format{"pdf", "png", "jpg", "svg", "vsdx", "xml"}
// Открыть файл
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) {
// Не требуется действий, так как это пустой параметр
}

View 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(),
)
}

View 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())
}
})
}
}