chore(package): add dmgbuild helper package to execute dmgbuild

This commit is contained in:
2021-05-27 01:40:53 +01:00
parent 72d0254772
commit 55f35e1146
7 changed files with 1272 additions and 0 deletions

67
pkg/dmgbuild/dmgbuild.go Normal file
View File

@@ -0,0 +1,67 @@
package dmgbuild
import (
"context"
"fmt"
"os"
"os/exec"
"strings"
"github.com/hashicorp/go-hclog"
)
func Build(ctx context.Context, settings *Settings) error {
logger := hclog.NewNullLogger()
if settings.Logger == nil {
logger = settings.Logger
}
if !strings.HasSuffix(logger.Name(), "dmgbuild") {
logger = logger.Named("dmgbuild")
}
if settings == nil {
return fmt.Errorf("no settings provided")
}
_, err := os.Stat(settings.Filename)
if !os.IsNotExist(err) {
return fmt.Errorf("output dmg exists: %s", settings.Filename)
}
baseCmd := settings.Command
if baseCmd == "" {
path, err2 := exec.LookPath("dmgbuild")
if err2 != nil {
return err2
}
baseCmd = path
}
file, err := settings.TempFile()
if err != nil {
return err
}
defer os.Remove(file)
args := []string{"-s", file, settings.VolumeName, settings.Filename}
if logger.IsDebug() {
content, err := os.ReadFile(file)
if err != nil {
return err
}
logger.Debug("using settings", file, string(content))
logger.Debug("executing", "command", baseCmd, "args", args)
}
cmd := exec.CommandContext(ctx, baseCmd, args...)
if settings.Stdout != nil {
cmd.Stdout = settings.Stdout
}
if settings.Stderr != nil {
cmd.Stderr = settings.Stderr
}
return cmd.Run()
}

85
pkg/dmgbuild/icon_view.go Normal file
View File

@@ -0,0 +1,85 @@
package dmgbuild
import "fmt"
type arrageOrder string
//nolint:golint
var (
NameOrder arrageOrder = "name"
DateModifiedOrder arrageOrder = "date-modified"
DateCreatedOrder arrageOrder = "date-created"
DateAddedOrder arrageOrder = "date-added"
DateLastOpenedOrder arrageOrder = "date-last-opened"
SizeOrder arrageOrder = "size"
KindOrder arrageOrder = "kind"
LabelOrder arrageOrder = "label"
)
type labelPosition string
//nolint:golint
var (
LabelBottom labelPosition = "bottom"
LabelRight labelPosition = "right"
)
type IconView struct {
ArrangeBy arrageOrder
GridOffsetX int
GridOffsetY int
GridSpacing float32
ScrollPosX float32
ScrollPosY float32
LabelPosition labelPosition
IconSize float32
TextSize float32
}
func NewIconView() IconView {
return IconView{
GridOffsetX: 0,
GridOffsetY: 0,
GridSpacing: 100,
ScrollPosX: 0.0,
ScrollPosY: 0.0,
LabelPosition: LabelBottom,
IconSize: 128,
TextSize: 16,
}
}
func (s *IconView) Render() []string {
r := []string{}
if s.ArrangeBy != "" {
r = append(r, "arrange_by = "+pyStr(string(s.ArrangeBy))+"\n")
}
if s.GridOffsetX > 0 || s.GridOffsetY > 0 {
r = append(r, fmt.Sprintf(
"grid_offset = (%d, %d)\n",
s.GridOffsetX, s.GridOffsetY,
))
}
if s.GridSpacing > 0 {
r = append(r, fmt.Sprintf("grid_spacing = %.2f\n", s.GridSpacing))
}
if s.ScrollPosX > 0 || s.ScrollPosY > 0 {
r = append(r, fmt.Sprintf(
"scroll_position = (%.2f, %.2f)\n",
s.ScrollPosX, s.ScrollPosY,
))
}
if s.LabelPosition != "" {
r = append(r, "label_position = "+pyStr(string(s.LabelPosition))+"\n")
}
if s.IconSize > 0 {
r = append(r, fmt.Sprintf("icon_size = %.2f\n", s.IconSize))
}
if s.TextSize > 0 {
r = append(r, fmt.Sprintf("text_size = %.2f\n", s.TextSize))
}
return r
}

183
pkg/dmgbuild/license.go Normal file
View File

@@ -0,0 +1,183 @@
package dmgbuild
import (
"fmt"
"sort"
"strings"
)
type locale string
//nolint:golint
var (
LocaleAfZA locale = "af_ZA"
LocaleAr locale = "ar"
LocaleBeBY locale = "be_BY"
LocaleBgBG locale = "bg_BG"
LocaleBn locale = "bn"
LocaleBo locale = "bo"
LocaleBr locale = "br"
LocaleCaES locale = "ca_ES"
LocaleCsCZ locale = "cs_CZ"
LocaleCy locale = "cy"
LocaleDaDK locale = "da_DK"
LocaleDeAT locale = "de_AT"
LocaleDeCH locale = "de_CH"
LocaleDeDE locale = "de_DE"
LocaleDzBT locale = "dz_BT"
LocaleElCY locale = "el_CY"
LocaleElGR locale = "el_GR"
LocaleEnAU locale = "en_AU"
LocaleEnCA locale = "en_CA"
LocaleEnGB locale = "en_GB"
LocaleEnIE locale = "en_IE"
LocaleEnSG locale = "en_SG"
LocaleEnUS locale = "en_US"
LocaleEo locale = "eo"
LocaleEs419 locale = "es_419"
LocaleEsES locale = "es_ES"
LocaleEtEE locale = "et_EE"
LocaleFaIR locale = "fa_IR"
LocaleFiFI locale = "fi_FI"
LocaleFoFO locale = "fo_FO"
LocaleFr001 locale = "fr_001"
LocaleFrBE locale = "fr_BE"
LocaleFrCA locale = "fr_CA"
LocaleFrCH locale = "fr_CH"
LocaleFrFR locale = "fr_FR"
LocaleGaLatgIE locale = "ga-Latg_IE"
LocaleGaIE locale = "ga_IE"
LocaleGd locale = "gd"
LocaleGrc locale = "grc"
LocaleGuIN locale = "gu_IN"
LocaleGv locale = "gv"
LocaleHeIL locale = "he_IL"
LocaleHiIN locale = "hi_IN"
LocaleHrHR locale = "hr_HR"
LocaleHuHU locale = "hu_HU"
LocaleHyAM locale = "hy_AM"
LocaleIsIS locale = "is_IS"
LocaleItCH locale = "it_CH"
LocaleItIT locale = "it_IT"
LocaleIuCA locale = "iu_CA"
LocaleJaJP locale = "ja_JP"
LocaleKaGE locale = "ka_GE"
LocaleKl locale = "kl"
LocaleKoKR locale = "ko_KR"
LocaleLtLT locale = "lt_LT"
LocaleLvLV locale = "lv_LV"
LocaleMkMK locale = "mk_MK"
LocaleMrIN locale = "mr_IN"
LocaleMtMT locale = "mt_MT"
LocaleNbNO locale = "nb_NO"
LocaleNeNP locale = "ne_NP"
LocaleNlBE locale = "nl_BE"
LocaleNlNL locale = "nl_NL"
LocaleNnNO locale = "nn_NO"
LocalePa locale = "pa"
LocalePlPL locale = "pl_PL"
LocalePtBR locale = "pt_BR"
LocalePtPT locale = "pt_PT"
LocaleRoRO locale = "ro_RO"
LocaleRuRU locale = "ru_RU"
LocaleSe locale = "se"
LocaleSkSK locale = "sk_SK"
LocaleSlSI locale = "sl_SI"
LocaleSrRS locale = "sr_RS"
LocaleSvSE locale = "sv_SE"
LocaleThTH locale = "th_TH"
LocaleToTO locale = "to_TO"
LocaleTrTR locale = "tr_TR"
LocaleUkUA locale = "uk_UA"
LocaleUrIN locale = "ur_IN"
LocaleUrPK locale = "ur_PK"
LocaleUzUZ locale = "uz_UZ"
LocaleViVN locale = "vi_VN"
LocaleZhCN locale = "zh_CN"
LocaleZhTW locale = "zh_TW"
)
type Buttons struct {
LanguageName string
Agree string
Disagree string
Print string
Save string
Message string
}
type License struct {
DefaultLanguage locale
Licenses map[locale]string
Buttons map[locale]Buttons
}
func NewLicense() License {
return License{}
}
func (s *License) Render() []string {
var l []string
if s.DefaultLanguage != "" {
l = append(l,
"\"default-language\": "+pyStr(string(s.DefaultLanguage)),
)
}
if len(s.Licenses) > 0 {
var items []string
for k, v := range s.Licenses {
items = append(items, fmt.Sprintf(
"%s: %s", pyStr(string(k)), pyMStr(v),
))
}
sort.SliceStable(items, func(i, j int) bool {
return items[i] < items[j]
})
l = append(l,
"\"licenses\": {\n "+
strings.Join(items, ",\n ")+
"\n }",
)
}
if len(s.Buttons) > 0 {
var items []string
for k, v := range s.Buttons {
items = append(items, fmt.Sprintf(
"%s: (\n"+
" %s,\n"+
" %s,\n"+
" %s,\n"+
" %s,\n"+
" %s,\n"+
" %s\n"+
" )",
pyStr(string(k)),
pyStr(v.LanguageName),
pyStr(v.Agree),
pyStr(v.Disagree),
pyStr(v.Print),
pyStr(v.Save),
pyStr(v.Message),
))
}
sort.SliceStable(items, func(i, j int) bool {
return items[i] < items[j]
})
l = append(l,
"\"buttons\": {\n "+
strings.Join(items, ",\n ")+
"\n }",
)
}
if len(l) == 0 {
return []string{}
}
return []string{
"license = {\n " + strings.Join(l, ",\n ") + "\n}\n",
}
}

154
pkg/dmgbuild/list_view.go Normal file
View File

@@ -0,0 +1,154 @@
package dmgbuild
import (
"fmt"
"sort"
"strings"
)
type listColumn string
//nolint:golint
var (
NameColumn listColumn = "name"
DateModifiedColumn listColumn = "date-modified"
DateCreatedColumn listColumn = "date-created"
DateAddedColumn listColumn = "date-added"
DateLastOpenedColumn listColumn = "date-last-opened"
SizeColumn listColumn = "size"
KindColumn listColumn = "kind"
LabelColumn listColumn = "label"
VersionColumn listColumn = "version"
CommentsColumn listColumn = "comments"
)
type direction string
//nolint:golint
var (
Ascending direction = "ascending"
Descending direction = "descending"
)
type ListView struct {
SortBy listColumn
ScrollPosX int
ScrollPosY int
IconSize float32
TextSize float32
UseRelativeDates bool
CalculateAllSizes bool
Columns []listColumn
ColumnWidths map[listColumn]int
ColumnSortDirections map[listColumn]direction
}
func NewListView() ListView {
return ListView{
SortBy: NameColumn,
IconSize: 16,
TextSize: 12,
UseRelativeDates: true,
Columns: []listColumn{
NameColumn,
DateModifiedColumn,
SizeColumn,
KindColumn,
DateAddedColumn,
},
ColumnWidths: map[listColumn]int{
(NameColumn): 300,
(DateModifiedColumn): 181,
(DateCreatedColumn): 181,
(DateAddedColumn): 181,
(DateLastOpenedColumn): 181,
(SizeColumn): 97,
(KindColumn): 115,
(LabelColumn): 100,
(VersionColumn): 75,
(CommentsColumn): 300,
},
ColumnSortDirections: map[listColumn]direction{
(NameColumn): Ascending,
(DateModifiedColumn): Descending,
(DateCreatedColumn): Descending,
(DateAddedColumn): Descending,
(DateLastOpenedColumn): Descending,
(SizeColumn): Descending,
(KindColumn): Ascending,
(LabelColumn): Ascending,
(VersionColumn): Ascending,
(CommentsColumn): Ascending,
},
}
}
func (s *ListView) Render() []string {
r := []string{}
if s.SortBy != "" {
r = append(r, "list_sort_by = "+pyStr(string(s.SortBy))+"\n")
}
if s.ScrollPosX > 0 || s.ScrollPosY > 0 {
r = append(r, fmt.Sprintf(
"list_scroll_position = (%d, %d)\n",
s.ScrollPosX, s.ScrollPosY,
))
}
if s.IconSize > 0 {
r = append(r, fmt.Sprintf("list_icon_size = %.2f\n", s.IconSize))
}
if s.TextSize > 0 {
r = append(r, fmt.Sprintf("list_text_size = %.2f\n", s.TextSize))
}
r = append(r, "list_use_relative_dates = "+pyBool(s.UseRelativeDates)+"\n")
r = append(
r, "list_calculate_all_sizes = "+pyBool(s.CalculateAllSizes)+"\n",
)
if len(s.Columns) > 0 {
var cols []string
for _, col := range s.Columns {
cols = append(cols, pyStr(string(col)))
}
r = append(r,
"list_columns = [\n "+strings.Join(cols, ",\n ")+"\n]\n",
)
}
if len(s.ColumnWidths) > 0 {
var cols []string
for col, w := range s.ColumnWidths {
cols = append(cols, fmt.Sprintf(
"%s: %d", pyStr(string(col)), w,
))
}
sort.SliceStable(cols, func(i, j int) bool {
return cols[i] < cols[j]
})
r = append(r,
"list_column_widths = {\n "+
strings.Join(cols, ",\n ")+
"\n}\n",
)
}
if len(s.ColumnSortDirections) > 0 {
var cols []string
for col, direction := range s.ColumnSortDirections {
cols = append(cols, fmt.Sprintf(
"%s: %s", pyStr(string(col)), pyStr(string(direction)),
))
}
sort.SliceStable(cols, func(i, j int) bool {
return cols[i] < cols[j]
})
r = append(r,
"list_column_sort_directions = {\n "+
strings.Join(cols, ",\n ")+
"\n}\n",
)
}
return r
}

256
pkg/dmgbuild/settings.go Normal file
View File

@@ -0,0 +1,256 @@
package dmgbuild
import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/hashicorp/go-hclog"
)
type format string
//nolint:golint
var (
UDROFormat format = "UDRO" // Read-only
UDCOFormat format = "UDCO" // Compressed (ADC)
UDZOFormat format = "UDZO" // Compressed (gzip)
UDBZFormat format = "UDBZ" // Compressed (bzip2)
UFBIFormat format = "UFBI" // Entire device
IPODFormat format = "IPOD" // iPod image
UDxxFormat format = "UDxx" // UDIF stub
UDSBFormat format = "UDSB" // Sparse bundle
UDSPFormat format = "UDSP" // Sparse
UDRWFormat format = "UDRW" // Read/write
UDTOFormat format = "UDTO" // DVD/CD master
DC42Format format = "DC42" // Disk Copy 4.2
RdWrFormat format = "RdWr" // NDIF read/write
RdxxFormat format = "Rdxx" // NDIF read-only
ROCoFormat format = "ROCo" // NDIF Compressed
RkenFormat format = "Rken" // NDIF Compressed (KenCode)
)
type File struct {
Path string
PosX int
PosY int
Hidden bool
HideExtension bool
}
type Symlink struct {
Name string
Target string
PosX int
PosY int
Hidden bool
HideExtension bool
}
type Settings struct {
// Command can be set to a custom dmgbuild executable path. If not set,
// the first "dmgbuild" executable within PATH will be used.
Command string
// Stdout will be set as STDOUT target for dmgbuild execution if not nil.
Stdout io.Writer
// Stderr will be set as STDERR target for dmgbuild execution if not nil.
Stderr io.Writer
// Logger allows logging details of dmbuild process.
Logger hclog.Logger
// dmgbuild settings
Filename string
VolumeName string
Format format
Size string
CompressionLevel int
Files []*File
Symlinks []*Symlink
Icon string
BadgeIcon string
Window Window
IconView IconView
ListView ListView
License License
}
func NewSettings() *Settings {
return &Settings{
Format: UDZOFormat,
CompressionLevel: 9,
Window: NewWindow(),
IconView: NewIconView(),
ListView: NewListView(),
License: NewLicense(),
}
}
//nolint:funlen,gocyclo
// Render returns a string slice where each string is a separate settings
// statement.
func (s *Settings) Render() ([]string, error) {
r := []string{
"# -*- coding: utf-8 -*-\n",
"from __future__ import unicode_literals\n",
}
if s.Filename != "" {
r = append(r, "filename = "+pyStr(s.Filename)+"\n")
}
if s.VolumeName != "" {
r = append(r, "volume_name = "+pyStr(s.VolumeName)+"\n")
}
if s.Format != "" {
r = append(r, "format = "+pyStr(string(s.Format))+"\n")
}
if s.CompressionLevel != 0 {
r = append(r, fmt.Sprintf(
"compression_level = %d\n", s.CompressionLevel,
))
}
if s.Size != "" {
r = append(r, "size = "+pyStr(s.Size)+"\n")
}
var files []string
var symlinks []string
var hide []string
var hideExt []string
var iconLoc []string
if len(s.Files) > 0 {
for _, f := range s.Files {
files = append(files, pyStr(f.Path))
name := filepath.Base(f.Path)
if f.PosX > 0 || f.PosY > 0 {
iconLoc = append(iconLoc,
fmt.Sprintf("%s: (%d, %d)", pyStr(name), f.PosX, f.PosY),
)
}
if f.Hidden {
hide = append(hide, pyStr(filepath.Base(f.Path)))
}
if f.HideExtension {
hideExt = append(hideExt, pyStr(filepath.Base(f.Path)))
}
}
}
if len(s.Symlinks) > 0 {
for _, l := range s.Symlinks {
symlinks = append(symlinks, pyStr(l.Name)+": "+pyStr(l.Target))
if l.PosX > 0 || l.PosY > 0 {
iconLoc = append(iconLoc,
fmt.Sprintf("%s: (%d, %d)", pyStr(l.Name), l.PosX, l.PosY),
)
}
if l.Hidden {
hide = append(hide, pyStr(l.Name))
}
if l.HideExtension {
hideExt = append(hideExt, pyStr(l.Name))
}
}
}
if len(files) > 0 {
r = append(r,
"files = [\n "+strings.Join(files, ",\n ")+"\n]\n",
)
}
if len(symlinks) > 0 {
r = append(r,
"symlinks = {\n "+strings.Join(symlinks, ",\n ")+"\n}\n",
)
}
if len(hide) > 0 {
r = append(r,
"hide = [\n "+strings.Join(hide, ",\n ")+"\n]\n",
)
}
if len(hideExt) > 0 {
r = append(r,
"hide_extensions = [\n "+strings.Join(hideExt, ",\n ")+
"\n]\n",
)
}
if len(iconLoc) > 0 {
r = append(r,
"icon_locations = {\n "+strings.Join(iconLoc, ",\n ")+"\n}\n",
)
}
if s.Icon != "" {
r = append(r, "icon = "+pyStr(s.Icon)+"\n")
}
if s.BadgeIcon != "" {
r = append(r, "badge_icon = "+pyStr(s.BadgeIcon)+"\n")
}
r = append(r, s.Window.Render()...)
r = append(r, s.IconView.Render()...)
r = append(r, s.ListView.Render()...)
r = append(r, s.License.Render()...)
return r, nil
}
func (s *Settings) Write(w io.Writer) error {
out, err := s.Render()
if err != nil {
return err
}
for _, o := range out {
_, err := w.Write([]byte(o))
if err != nil {
return err
}
}
return nil
}
func (s *Settings) TempFile() (string, error) {
f, err := os.CreateTemp("", "*.dmgbuild.settings.py")
if err != nil {
return "", err
}
defer f.Close()
err = s.Write(f)
if err != nil {
return "", err
}
return f.Name(), nil
}
func pyStr(s string) string {
s = strings.ReplaceAll(s, `\`, `\\`)
s = strings.ReplaceAll(s, `"`, `\"`)
s = strings.ReplaceAll(s, "\r", `\r`)
s = strings.ReplaceAll(s, "\n", `\n`)
return `"` + s + `"`
}
func pyMStr(s string) string {
s = strings.ReplaceAll(s, `\`, `\\`)
s = strings.ReplaceAll(s, `"`, `\"`)
return `"""` + s + `"""`
}
func pyBool(v bool) string {
if v {
return "True"
}
return "False"
}

View File

@@ -0,0 +1,444 @@
package dmgbuild
import (
"bytes"
"strings"
"testing"
"github.com/jimeh/undent"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSettings_Write(t *testing.T) {
test := []struct {
name string
entitlements *Settings
want string
}{
{
name: "empty",
entitlements: &Settings{},
want: undent.String(`
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
show_status_bar = False
show_tab_view = False
show_toolbar = False
show_pathbar = False
show_sidebar = False
show_icon_preview = False
show_item_info = False
include_icon_view_settings = False
include_list_view_settings = False
list_use_relative_dates = False
list_calculate_all_sizes = False`,
),
},
{
name: "full",
entitlements: &Settings{
Filename: "/builds/Emacs.2021-05-25.f4dc646.master.dmg",
VolumeName: "Emacs.2021-05-25.f4dc646.master",
Format: UDBZFormat,
CompressionLevel: 8,
Size: "100m",
Files: []*File{
{
Path: "/builds/Emacs.app",
PosX: 200,
PosY: 200,
},
{
Path: "/builds/README.rtf",
PosX: 200,
PosY: 300,
HideExtension: true,
},
{
Path: "/builds/hide-me.png",
Hidden: true,
},
},
Symlinks: []*Symlink{
{
Name: "Applications",
Target: "/Applications",
PosX: 400,
PosY: 400,
},
{
Name: "QuickLook",
Target: "/Library/QuickLook",
PosX: 500,
PosY: 400,
Hidden: true,
},
{
Name: "System",
Target: "/System",
HideExtension: true,
},
},
Icon: "/opt/misc/assets/volIcon.icns",
BadgeIcon: "/builds/Emacs.app/Contents/Resources/Icon.icns",
Window: Window{
PoxX: 200,
PosY: 250,
Width: 680,
Height: 446,
Background: "/opt/misc/assets/bg.tif",
ShowStatusBar: true,
ShowTabView: true,
ShowToolbar: true,
ShowPathbar: true,
ShowSidebar: true,
SidebarWidth: 165,
DefaultView: list,
ShowIconPreview: true,
ShowItemInfo: true,
IncludeIconViewSettings: true,
IncludeListViewSettings: true,
},
IconView: IconView{
ArrangeBy: NameOrder,
GridOffsetX: 42,
GridOffsetY: 43,
GridSpacing: 44.5,
ScrollPosX: 4.5,
ScrollPosY: 5.5,
LabelPosition: LabelBottom,
IconSize: 160,
TextSize: 15,
},
ListView: ListView{
SortBy: NameColumn,
ScrollPosX: 7,
ScrollPosY: 8,
IconSize: 16,
TextSize: 12,
UseRelativeDates: true,
CalculateAllSizes: true,
Columns: []listColumn{
NameColumn,
DateModifiedColumn,
DateCreatedColumn,
DateAddedColumn,
DateLastOpenedColumn,
SizeColumn,
KindColumn,
LabelColumn,
VersionColumn,
CommentsColumn,
},
ColumnWidths: map[listColumn]int{
(NameColumn): 300,
(DateModifiedColumn): 181,
(DateCreatedColumn): 181,
(DateAddedColumn): 181,
(DateLastOpenedColumn): 181,
(SizeColumn): 97,
(KindColumn): 115,
(LabelColumn): 100,
(VersionColumn): 75,
(CommentsColumn): 300,
},
ColumnSortDirections: map[listColumn]direction{
(NameColumn): Ascending,
(DateModifiedColumn): Descending,
(DateCreatedColumn): Descending,
(DateAddedColumn): Descending,
(DateLastOpenedColumn): Descending,
(SizeColumn): Descending,
(KindColumn): Ascending,
(LabelColumn): Ascending,
(VersionColumn): Ascending,
(CommentsColumn): Ascending,
},
},
License: License{
DefaultLanguage: LocaleEnUS,
Licenses: map[locale]string{
//nolint:lll
(LocaleEnGB): undent.String(`
{\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820
{\fonttbl\f0\fnil\fcharset0 Helvetica-Bold;\f1\fnil\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;}
{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;}
\paperw11905\paperh16837\margl1133\margr1133\margb1133\margt1133
\deftab720
\pard\pardeftab720\sa160\partightenfactor0
\f0\b\fs60 \cf2 \expnd0\expndtw0\kerning0
\up0 \nosupersub \ulnone \outl0\strokewidth0 \strokec2 Test License\
\pard\pardeftab720\sa160\partightenfactor0
\fs36 \cf2 \strokec2 What is this?\
\pard\pardeftab720\sa160\partightenfactor0
\f1\b0\fs22 \cf2 \strokec2 This is the English license. It says what you are allowed to do with this software.\
\
}`,
),
//nolint:lll
(LocaleSe): undent.String(`
{\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820
{\fonttbl\f0\fnil\fcharset0 Helvetica-Bold;\f1\fnil\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;}
{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;}
\paperw11905\paperh16837\margl1133\margr1133\margb1133\margt1133
\deftab720
\pard\pardeftab720\sa160\partightenfactor0
\f0\b\fs60 \cf2 \expnd0\expndtw0\kerning0
\up0 \nosupersub \ulnone \outl0\strokewidth0 \strokec2 Test License\
\pard\pardeftab720\sa160\partightenfactor0
\fs36 \cf2 \strokec2 What is this?\
\pard\pardeftab720\sa160\partightenfactor0
\f1\b0\fs22 \cf2 \strokec2 Detta är den engelska licensen. Det står vad du får göra med den här programvaran.\
\
}`,
),
},
Buttons: map[locale]Buttons{
(LocaleEnGB): {
LanguageName: "English",
Agree: "Agree",
Disagree: "Disagree",
Print: "Print",
Save: "Save",
Message: "If you agree with the terms of this " +
"license, press \"Agree\" to install the " +
"software. If you do not agree, press " +
"\"Disagree\".",
},
(LocaleSe): {
LanguageName: "Svenska",
Agree: "Godkänn",
Disagree: "Håller inte med",
Print: "Skriv ut",
Save: "Spara",
Message: "Om du godkänner villkoren i denna " +
"licens, tryck på \"Godkänn\" för att " +
"installera programvaran. Om du inte håller " +
"med, tryck på \"Håller inte med\".",
},
},
},
},
//nolint:lll
want: undent.String(`
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
filename = "/builds/Emacs.2021-05-25.f4dc646.master.dmg"
volume_name = "Emacs.2021-05-25.f4dc646.master"
format = "UDBZ"
compression_level = 8
size = "100m"
files = [
"/builds/Emacs.app",
"/builds/README.rtf",
"/builds/hide-me.png"
]
symlinks = {
"Applications": "/Applications",
"QuickLook": "/Library/QuickLook",
"System": "/System"
}
hide = [
"hide-me.png",
"QuickLook"
]
hide_extensions = [
"README.rtf",
"System"
]
icon_locations = {
"Emacs.app": (200, 200),
"README.rtf": (200, 300),
"Applications": (400, 400),
"QuickLook": (500, 400)
}
icon = "/opt/misc/assets/volIcon.icns"
badge_icon = "/builds/Emacs.app/Contents/Resources/Icon.icns"
background = "/opt/misc/assets/bg.tif"
show_status_bar = True
show_tab_view = True
show_toolbar = True
show_pathbar = True
show_sidebar = True
sidebar_width = 165
default_view = "list-view"
window_rect = ((200, 250), (680, 446))
show_icon_preview = True
show_item_info = True
include_icon_view_settings = True
include_list_view_settings = True
arrange_by = "name"
grid_offset = (42, 43)
grid_spacing = 44.50
scroll_position = (4.50, 5.50)
label_position = "bottom"
icon_size = 160.00
text_size = 15.00
list_sort_by = "name"
list_scroll_position = (7, 8)
list_icon_size = 16.00
list_text_size = 12.00
list_use_relative_dates = True
list_calculate_all_sizes = True
list_columns = [
"name",
"date-modified",
"date-created",
"date-added",
"date-last-opened",
"size",
"kind",
"label",
"version",
"comments"
]
list_column_widths = {
"comments": 300,
"date-added": 181,
"date-created": 181,
"date-last-opened": 181,
"date-modified": 181,
"kind": 115,
"label": 100,
"name": 300,
"size": 97,
"version": 75
}
list_column_sort_directions = {
"comments": "ascending",
"date-added": "descending",
"date-created": "descending",
"date-last-opened": "descending",
"date-modified": "descending",
"kind": "ascending",
"label": "ascending",
"name": "ascending",
"size": "descending",
"version": "ascending"
}
license = {
"default-language": "en_US",
"licenses": {
"en_GB": """{\\rtf1\\ansi\\ansicpg1252\\cocoartf1504\\cocoasubrtf820
{\\fonttbl\\f0\\fnil\\fcharset0 Helvetica-Bold;\\f1\\fnil\\fcharset0 Helvetica;}
{\\colortbl;\\red255\\green255\\blue255;\\red0\\green0\\blue0;}
{\\*\\expandedcolortbl;;\\cssrgb\\c0\\c0\\c0;}
\\paperw11905\\paperh16837\\margl1133\\margr1133\\margb1133\\margt1133
\\deftab720
\\pard\\pardeftab720\\sa160\\partightenfactor0
\\f0\\b\\fs60 \\cf2 \\expnd0\\expndtw0\\kerning0
\\up0 \\nosupersub \\ulnone \\outl0\\strokewidth0 \\strokec2 Test License\\
\\pard\\pardeftab720\\sa160\\partightenfactor0
\\fs36 \\cf2 \\strokec2 What is this?\\
\\pard\\pardeftab720\\sa160\\partightenfactor0
\\f1\\b0\\fs22 \\cf2 \\strokec2 This is the English license. It says what you are allowed to do with this software.\\
\\
}""",
"se": """{\\rtf1\\ansi\\ansicpg1252\\cocoartf1504\\cocoasubrtf820
{\\fonttbl\\f0\\fnil\\fcharset0 Helvetica-Bold;\\f1\\fnil\\fcharset0 Helvetica;}
{\\colortbl;\\red255\\green255\\blue255;\\red0\\green0\\blue0;}
{\\*\\expandedcolortbl;;\\cssrgb\\c0\\c0\\c0;}
\\paperw11905\\paperh16837\\margl1133\\margr1133\\margb1133\\margt1133
\\deftab720
\\pard\\pardeftab720\\sa160\\partightenfactor0
\\f0\\b\\fs60 \\cf2 \\expnd0\\expndtw0\\kerning0
\\up0 \\nosupersub \\ulnone \\outl0\\strokewidth0 \\strokec2 Test License\\
\\pard\\pardeftab720\\sa160\\partightenfactor0
\\fs36 \\cf2 \\strokec2 What is this?\\
\\pard\\pardeftab720\\sa160\\partightenfactor0
\\f1\\b0\\fs22 \\cf2 \\strokec2 Detta är den engelska licensen. Det står vad du får göra med den här programvaran.\\
\\
}"""
},
"buttons": {
"en_GB": (
"English",
"Agree",
"Disagree",
"Print",
"Save",
"If you agree with the terms of this license, press \"Agree\" to install the software. If you do not agree, press \"Disagree\"."
),
"se": (
"Svenska",
"Godkänn",
"Håller inte med",
"Skriv ut",
"Spara",
"Om du godkänner villkoren i denna licens, tryck på \"Godkänn\" för att installera programvaran. Om du inte håller med, tryck på \"Håller inte med\"."
)
}
}`,
),
},
}
for _, tt := range test {
t.Run(tt.name, func(t *testing.T) {
var buf bytes.Buffer
err := tt.entitlements.Write(&buf)
require.NoError(t, err)
assert.Equal(t, tt.want, strings.TrimSpace(buf.String()))
})
}
}
func Test_pyStr(t *testing.T) {
tests := []struct {
name string
s string
want string
}{
{
name: "empty",
s: "",
want: `""`,
},
{
name: "regular string",
s: "foo-bar nope :)",
want: `"foo-bar nope :)"`,
},
{
name: "with single quotes",
s: "john's lost 'flip-flop'",
want: `"john's lost 'flip-flop'"`,
},
{
name: "with double quotes",
s: `john has lost a "flip-flop"`,
want: `"john has lost a \"flip-flop\""`,
},
{
name: "with backslashes",
s: `C:\path\to\file.txt`,
want: `"C:\\path\\to\\file.txt"`,
},
{
name: "with line-feed",
s: "hello\nworld",
want: `"hello\nworld"`,
},
{
name: "with carriage return",
s: "hello\rworld",
want: `"hello\rworld"`,
},
{
name: "with backslashes, single and double quotes",
s: `john's "lost" C:\path\to\file.txt`,
want: `"john's \"lost\" C:\\path\\to\\file.txt"`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := pyStr(tt.s)
assert.Equal(t, tt.want, got)
})
}
}

83
pkg/dmgbuild/window.go Normal file
View File

@@ -0,0 +1,83 @@
package dmgbuild
import "fmt"
type view string
//nolint:golint
var (
Icon view = "icon-view"
list view = "list-view"
Column view = "column-view"
Coverflow view = "coverflow"
)
type Window struct {
PoxX int
PosY int
Width int
Height int
Background string
ShowStatusBar bool
ShowTabView bool
ShowToolbar bool
ShowPathbar bool
ShowSidebar bool
SidebarWidth int
DefaultView view
ShowIconPreview bool
ShowItemInfo bool
IncludeIconViewSettings bool
IncludeListViewSettings bool
}
func NewWindow() Window {
return Window{
PoxX: 100,
PosY: 150,
Width: 640,
Height: 280,
Background: "builtin-arrow",
DefaultView: Icon,
}
}
func (s *Window) Render() []string {
r := []string{}
if s.Background != "" {
r = append(r, "background = "+pyStr(s.Background)+"\n")
}
r = append(r, "show_status_bar = "+pyBool(s.ShowStatusBar)+"\n")
r = append(r, "show_tab_view = "+pyBool(s.ShowTabView)+"\n")
r = append(r, "show_toolbar = "+pyBool(s.ShowToolbar)+"\n")
r = append(r, "show_pathbar = "+pyBool(s.ShowPathbar)+"\n")
r = append(r, "show_sidebar = "+pyBool(s.ShowSidebar)+"\n")
if s.SidebarWidth > 0 {
r = append(r, fmt.Sprintf(
"sidebar_width = %d\n", s.SidebarWidth,
))
}
if s.DefaultView != "" {
r = append(r, "default_view = "+pyStr(string(s.DefaultView))+"\n")
}
if s.Width > 0 && s.Height > 0 {
r = append(r, fmt.Sprintf(
"window_rect = ((%d, %d), (%d, %d))\n",
s.PoxX, s.PosY, s.Width, s.Height,
))
}
r = append(r, "show_icon_preview = "+pyBool(s.ShowIconPreview)+"\n")
r = append(r, "show_item_info = "+pyBool(s.ShowIconPreview)+"\n")
r = append(
r, "include_icon_view_settings = "+pyBool(s.ShowIconPreview)+"\n",
)
r = append(
r, "include_list_view_settings = "+pyBool(s.ShowIconPreview)+"\n",
)
return r
}