dmg-builder 26.0.16 → 26.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/dmg.d.ts +33 -1
- package/out/dmg.js +9 -165
- package/out/dmg.js.map +1 -1
- package/out/dmgUtil.d.ts +16 -2
- package/out/dmgUtil.js +153 -2
- package/out/dmgUtil.js.map +1 -1
- package/out/hdiuil.js +1 -1
- package/out/hdiuil.js.map +1 -1
- package/package.json +3 -4
- package/vendor/biplist/__init__.py +75 -75
- package/vendor/dmgbuild/__init__.py +5 -0
- package/vendor/dmgbuild/__main__.py +67 -0
- package/vendor/dmgbuild/badge.py +104 -78
- package/vendor/dmgbuild/colors.py +239 -229
- package/vendor/dmgbuild/core.py +945 -274
- package/vendor/dmgbuild/licensing.py +329 -0
- package/vendor/dmgbuild/resources/builtin-arrow.tiff +0 -0
- package/vendor/ds_store/__init__.py +3 -1
- package/vendor/ds_store/__main__.py +102 -0
- package/vendor/ds_store/buddy.py +142 -134
- package/vendor/ds_store/store.py +333 -322
- package/vendor/mac_alias/__init__.py +43 -23
- package/vendor/mac_alias/alias.py +334 -224
- package/vendor/mac_alias/bookmark.py +283 -278
- package/vendor/mac_alias/osx.py +406 -302
- package/vendor/mac_alias/utils.py +10 -8
- package/vendor/run_dmgbuild.py +14 -0
|
@@ -1,59 +1,38 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
#
|
|
3
1
|
# This file implements the Apple "bookmark" format, which is the replacement
|
|
4
2
|
# for the old-fashioned alias format. The details of this format were
|
|
5
3
|
# reverse engineered; some things are still not entirely clear.
|
|
6
4
|
#
|
|
7
|
-
from __future__ import unicode_literals, print_function
|
|
8
|
-
|
|
9
|
-
import struct
|
|
10
|
-
import uuid
|
|
11
5
|
import datetime
|
|
12
6
|
import os
|
|
7
|
+
import struct
|
|
13
8
|
import sys
|
|
14
|
-
import
|
|
15
|
-
|
|
16
|
-
try:
|
|
17
|
-
from urlparse import urljoin
|
|
18
|
-
except ImportError:
|
|
19
|
-
from urllib.parse import urljoin
|
|
9
|
+
import uuid
|
|
10
|
+
from urllib.parse import urljoin
|
|
20
11
|
|
|
21
|
-
if sys.platform ==
|
|
12
|
+
if sys.platform == "darwin":
|
|
22
13
|
from . import osx
|
|
23
14
|
|
|
24
|
-
|
|
25
|
-
return x.iteritems()
|
|
26
|
-
|
|
27
|
-
try:
|
|
28
|
-
unicode
|
|
29
|
-
except NameError:
|
|
30
|
-
unicode = str
|
|
31
|
-
long = int
|
|
32
|
-
xrange = range
|
|
33
|
-
def iteritems(x):
|
|
34
|
-
return x.items()
|
|
35
|
-
|
|
36
|
-
from .utils import *
|
|
15
|
+
from .utils import osx_epoch
|
|
37
16
|
|
|
38
|
-
BMK_DATA_TYPE_MASK
|
|
39
|
-
BMK_DATA_SUBTYPE_MASK =
|
|
17
|
+
BMK_DATA_TYPE_MASK = 0xFFFFFF00
|
|
18
|
+
BMK_DATA_SUBTYPE_MASK = 0x000000FF
|
|
40
19
|
|
|
41
|
-
BMK_STRING
|
|
42
|
-
BMK_DATA
|
|
43
|
-
BMK_NUMBER
|
|
44
|
-
BMK_DATE
|
|
20
|
+
BMK_STRING = 0x0100
|
|
21
|
+
BMK_DATA = 0x0200
|
|
22
|
+
BMK_NUMBER = 0x0300
|
|
23
|
+
BMK_DATE = 0x0400
|
|
45
24
|
BMK_BOOLEAN = 0x0500
|
|
46
|
-
BMK_ARRAY
|
|
47
|
-
BMK_DICT
|
|
48
|
-
BMK_UUID
|
|
49
|
-
BMK_URL
|
|
50
|
-
BMK_NULL
|
|
25
|
+
BMK_ARRAY = 0x0600
|
|
26
|
+
BMK_DICT = 0x0700
|
|
27
|
+
BMK_UUID = 0x0800
|
|
28
|
+
BMK_URL = 0x0900
|
|
29
|
+
BMK_NULL = 0x0A00
|
|
51
30
|
|
|
52
31
|
BMK_ST_ZERO = 0x0000
|
|
53
|
-
BMK_ST_ONE
|
|
32
|
+
BMK_ST_ONE = 0x0001
|
|
54
33
|
|
|
55
34
|
BMK_BOOLEAN_ST_FALSE = 0x0000
|
|
56
|
-
BMK_BOOLEAN_ST_TRUE
|
|
35
|
+
BMK_BOOLEAN_ST_TRUE = 0x0001
|
|
57
36
|
|
|
58
37
|
# Subtypes for BMK_NUMBER are really CFNumberType values
|
|
59
38
|
kCFNumberSInt8Type = 1
|
|
@@ -74,123 +53,123 @@ kCFNumberNSIntegerType = 15
|
|
|
74
53
|
kCFNumberCGFloatType = 16
|
|
75
54
|
|
|
76
55
|
# Resource property flags (from CFURLPriv.h)
|
|
77
|
-
kCFURLResourceIsRegularFile
|
|
78
|
-
kCFURLResourceIsDirectory
|
|
79
|
-
kCFURLResourceIsSymbolicLink
|
|
80
|
-
kCFURLResourceIsVolume
|
|
81
|
-
kCFURLResourceIsPackage
|
|
82
|
-
kCFURLResourceIsSystemImmutable
|
|
83
|
-
kCFURLResourceIsUserImmutable
|
|
84
|
-
kCFURLResourceIsHidden
|
|
56
|
+
kCFURLResourceIsRegularFile = 0x00000001
|
|
57
|
+
kCFURLResourceIsDirectory = 0x00000002
|
|
58
|
+
kCFURLResourceIsSymbolicLink = 0x00000004
|
|
59
|
+
kCFURLResourceIsVolume = 0x00000008
|
|
60
|
+
kCFURLResourceIsPackage = 0x00000010
|
|
61
|
+
kCFURLResourceIsSystemImmutable = 0x00000020
|
|
62
|
+
kCFURLResourceIsUserImmutable = 0x00000040
|
|
63
|
+
kCFURLResourceIsHidden = 0x00000080
|
|
85
64
|
kCFURLResourceHasHiddenExtension = 0x00000100
|
|
86
|
-
kCFURLResourceIsApplication
|
|
87
|
-
kCFURLResourceIsCompressed
|
|
65
|
+
kCFURLResourceIsApplication = 0x00000200
|
|
66
|
+
kCFURLResourceIsCompressed = 0x00000400
|
|
88
67
|
kCFURLResourceIsSystemCompressed = 0x00000400
|
|
89
|
-
kCFURLCanSetHiddenExtension
|
|
90
|
-
kCFURLResourceIsReadable
|
|
91
|
-
kCFURLResourceIsWriteable
|
|
92
|
-
kCFURLResourceIsExecutable
|
|
93
|
-
kCFURLIsAliasFile
|
|
94
|
-
kCFURLIsMountTrigger
|
|
68
|
+
kCFURLCanSetHiddenExtension = 0x00000800
|
|
69
|
+
kCFURLResourceIsReadable = 0x00001000
|
|
70
|
+
kCFURLResourceIsWriteable = 0x00002000
|
|
71
|
+
kCFURLResourceIsExecutable = 0x00004000
|
|
72
|
+
kCFURLIsAliasFile = 0x00008000
|
|
73
|
+
kCFURLIsMountTrigger = 0x00010000
|
|
95
74
|
|
|
96
75
|
# Volume property flags (from CFURLPriv.h)
|
|
97
|
-
kCFURLVolumeIsLocal
|
|
98
|
-
kCFURLVolumeIsAutomount
|
|
99
|
-
kCFURLVolumeDontBrowse
|
|
100
|
-
kCFURLVolumeIsReadOnly
|
|
101
|
-
kCFURLVolumeIsQuarantined
|
|
102
|
-
kCFURLVolumeIsEjectable
|
|
103
|
-
kCFURLVolumeIsRemovable
|
|
104
|
-
kCFURLVolumeIsInternal
|
|
105
|
-
kCFURLVolumeIsExternal
|
|
106
|
-
kCFURLVolumeIsDiskImage
|
|
107
|
-
kCFURLVolumeIsFileVault
|
|
108
|
-
kCFURLVolumeIsLocaliDiskMirror
|
|
109
|
-
kCFURLVolumeIsiPod
|
|
110
|
-
kCFURLVolumeIsiDisk
|
|
111
|
-
kCFURLVolumeIsCD
|
|
112
|
-
kCFURLVolumeIsDVD
|
|
113
|
-
kCFURLVolumeIsDeviceFileSystem
|
|
114
|
-
kCFURLVolumeSupportsPersistentIDs
|
|
115
|
-
kCFURLVolumeSupportsSearchFS
|
|
116
|
-
kCFURLVolumeSupportsExchange
|
|
76
|
+
kCFURLVolumeIsLocal = 0x1
|
|
77
|
+
kCFURLVolumeIsAutomount = 0x2
|
|
78
|
+
kCFURLVolumeDontBrowse = 0x4
|
|
79
|
+
kCFURLVolumeIsReadOnly = 0x8
|
|
80
|
+
kCFURLVolumeIsQuarantined = 0x10
|
|
81
|
+
kCFURLVolumeIsEjectable = 0x20
|
|
82
|
+
kCFURLVolumeIsRemovable = 0x40
|
|
83
|
+
kCFURLVolumeIsInternal = 0x80
|
|
84
|
+
kCFURLVolumeIsExternal = 0x100
|
|
85
|
+
kCFURLVolumeIsDiskImage = 0x200
|
|
86
|
+
kCFURLVolumeIsFileVault = 0x400
|
|
87
|
+
kCFURLVolumeIsLocaliDiskMirror = 0x800
|
|
88
|
+
kCFURLVolumeIsiPod = 0x1000
|
|
89
|
+
kCFURLVolumeIsiDisk = 0x2000
|
|
90
|
+
kCFURLVolumeIsCD = 0x4000
|
|
91
|
+
kCFURLVolumeIsDVD = 0x8000
|
|
92
|
+
kCFURLVolumeIsDeviceFileSystem = 0x10000
|
|
93
|
+
kCFURLVolumeSupportsPersistentIDs = 0x100000000
|
|
94
|
+
kCFURLVolumeSupportsSearchFS = 0x200000000
|
|
95
|
+
kCFURLVolumeSupportsExchange = 0x400000000
|
|
117
96
|
# reserved 0x800000000
|
|
118
|
-
kCFURLVolumeSupportsSymbolicLinks
|
|
119
|
-
kCFURLVolumeSupportsDenyModes
|
|
120
|
-
kCFURLVolumeSupportsCopyFile
|
|
121
|
-
kCFURLVolumeSupportsReadDirAttr
|
|
122
|
-
kCFURLVolumeSupportsJournaling
|
|
123
|
-
kCFURLVolumeSupportsRename
|
|
124
|
-
kCFURLVolumeSupportsFastStatFS
|
|
125
|
-
kCFURLVolumeSupportsCaseSensitiveNames
|
|
126
|
-
kCFURLVolumeSupportsCasePreservedNames
|
|
127
|
-
kCFURLVolumeSupportsFLock
|
|
128
|
-
kCFURLVolumeHasNoRootDirectoryTimes
|
|
129
|
-
kCFURLVolumeSupportsExtendedSecurity
|
|
130
|
-
kCFURLVolumeSupports2TBFileSize
|
|
131
|
-
kCFURLVolumeSupportsHardLinks
|
|
132
|
-
kCFURLVolumeSupportsMandatoryByteRangeLocks =
|
|
133
|
-
kCFURLVolumeSupportsPathFromID
|
|
97
|
+
kCFURLVolumeSupportsSymbolicLinks = 0x1000000000
|
|
98
|
+
kCFURLVolumeSupportsDenyModes = 0x2000000000
|
|
99
|
+
kCFURLVolumeSupportsCopyFile = 0x4000000000
|
|
100
|
+
kCFURLVolumeSupportsReadDirAttr = 0x8000000000
|
|
101
|
+
kCFURLVolumeSupportsJournaling = 0x10000000000
|
|
102
|
+
kCFURLVolumeSupportsRename = 0x20000000000
|
|
103
|
+
kCFURLVolumeSupportsFastStatFS = 0x40000000000
|
|
104
|
+
kCFURLVolumeSupportsCaseSensitiveNames = 0x80000000000
|
|
105
|
+
kCFURLVolumeSupportsCasePreservedNames = 0x100000000000
|
|
106
|
+
kCFURLVolumeSupportsFLock = 0x200000000000
|
|
107
|
+
kCFURLVolumeHasNoRootDirectoryTimes = 0x400000000000
|
|
108
|
+
kCFURLVolumeSupportsExtendedSecurity = 0x800000000000
|
|
109
|
+
kCFURLVolumeSupports2TBFileSize = 0x1000000000000
|
|
110
|
+
kCFURLVolumeSupportsHardLinks = 0x2000000000000
|
|
111
|
+
kCFURLVolumeSupportsMandatoryByteRangeLocks = 0x4000000000000
|
|
112
|
+
kCFURLVolumeSupportsPathFromID = 0x8000000000000
|
|
134
113
|
# reserved 0x10000000000000
|
|
135
|
-
kCFURLVolumeIsJournaling
|
|
136
|
-
kCFURLVolumeSupportsSparseFiles
|
|
137
|
-
kCFURLVolumeSupportsZeroRuns
|
|
138
|
-
kCFURLVolumeSupportsVolumeSizes
|
|
139
|
-
kCFURLVolumeSupportsRemoteEvents
|
|
140
|
-
kCFURLVolumeSupportsHiddenFiles
|
|
141
|
-
kCFURLVolumeSupportsDecmpFSCompression
|
|
142
|
-
kCFURLVolumeHas64BitObjectIDs
|
|
143
|
-
kCFURLVolumePropertyFlagsAll
|
|
114
|
+
kCFURLVolumeIsJournaling = 0x20000000000000
|
|
115
|
+
kCFURLVolumeSupportsSparseFiles = 0x40000000000000
|
|
116
|
+
kCFURLVolumeSupportsZeroRuns = 0x80000000000000
|
|
117
|
+
kCFURLVolumeSupportsVolumeSizes = 0x100000000000000
|
|
118
|
+
kCFURLVolumeSupportsRemoteEvents = 0x200000000000000
|
|
119
|
+
kCFURLVolumeSupportsHiddenFiles = 0x400000000000000
|
|
120
|
+
kCFURLVolumeSupportsDecmpFSCompression = 0x800000000000000
|
|
121
|
+
kCFURLVolumeHas64BitObjectIDs = 0x1000000000000000
|
|
122
|
+
kCFURLVolumePropertyFlagsAll = 0xFFFFFFFFFFFFFFFF
|
|
144
123
|
|
|
145
124
|
BMK_URL_ST_ABSOLUTE = 0x0001
|
|
146
125
|
BMK_URL_ST_RELATIVE = 0x0002
|
|
147
126
|
|
|
148
127
|
# Bookmark keys
|
|
149
|
-
kBookmarkURL
|
|
150
|
-
kBookmarkPath
|
|
151
|
-
kBookmarkCNIDPath
|
|
152
|
-
kBookmarkFileProperties
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
kBookmarkFileName
|
|
156
|
-
kBookmarkFileID
|
|
157
|
-
kBookmarkFileCreationDate
|
|
158
|
-
#
|
|
159
|
-
#
|
|
160
|
-
#
|
|
161
|
-
#
|
|
162
|
-
#
|
|
163
|
-
kBookmarkTOCPath
|
|
164
|
-
kBookmarkVolumePath
|
|
165
|
-
kBookmarkVolumeURL
|
|
166
|
-
kBookmarkVolumeName
|
|
167
|
-
kBookmarkVolumeUUID
|
|
168
|
-
kBookmarkVolumeSize
|
|
128
|
+
kBookmarkURL = 0x1003 # A URL
|
|
129
|
+
kBookmarkPath = 0x1004 # Array of path components
|
|
130
|
+
kBookmarkCNIDPath = 0x1005 # Array of CNIDs
|
|
131
|
+
kBookmarkFileProperties = (
|
|
132
|
+
0x1010 # (CFURL rp flags, CFURL rp flags asked for, 8 bytes NULL)
|
|
133
|
+
)
|
|
134
|
+
kBookmarkFileName = 0x1020
|
|
135
|
+
kBookmarkFileID = 0x1030
|
|
136
|
+
kBookmarkFileCreationDate = 0x1040
|
|
137
|
+
# = 0x1054 # ?
|
|
138
|
+
# = 0x1055 # ?
|
|
139
|
+
# = 0x1056 # ?
|
|
140
|
+
# = 0x1101 # ?
|
|
141
|
+
# = 0x1102 # ?
|
|
142
|
+
kBookmarkTOCPath = 0x2000 # A list of (TOC id, ?) pairs
|
|
143
|
+
kBookmarkVolumePath = 0x2002
|
|
144
|
+
kBookmarkVolumeURL = 0x2005
|
|
145
|
+
kBookmarkVolumeName = 0x2010
|
|
146
|
+
kBookmarkVolumeUUID = 0x2011 # Stored (perversely) as a string
|
|
147
|
+
kBookmarkVolumeSize = 0x2012
|
|
169
148
|
kBookmarkVolumeCreationDate = 0x2013
|
|
170
|
-
kBookmarkVolumeProperties
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
kBookmarkVolumeIsRoot
|
|
174
|
-
kBookmarkVolumeBookmark
|
|
175
|
-
kBookmarkVolumeMountPoint
|
|
176
|
-
#
|
|
177
|
-
kBookmarkContainingFolder
|
|
178
|
-
kBookmarkUserName
|
|
179
|
-
kBookmarkUID
|
|
180
|
-
kBookmarkWasFileReference
|
|
181
|
-
kBookmarkCreationOptions
|
|
182
|
-
kBookmarkURLLengths
|
|
183
|
-
kBookmarkDisplayName
|
|
184
|
-
kBookmarkIconData
|
|
185
|
-
kBookmarkIconRef
|
|
186
|
-
kBookmarkTypeBindingData
|
|
187
|
-
kBookmarkCreationTime
|
|
188
|
-
kBookmarkSandboxRwExtension =
|
|
189
|
-
kBookmarkSandboxRoExtension =
|
|
190
|
-
kBookmarkAliasData
|
|
149
|
+
kBookmarkVolumeProperties = (
|
|
150
|
+
0x2020 # (CFURL vp flags, CFURL vp flags asked for, 8 bytes NULL)
|
|
151
|
+
)
|
|
152
|
+
kBookmarkVolumeIsRoot = 0x2030 # True if volume is FS root
|
|
153
|
+
kBookmarkVolumeBookmark = 0x2040 # Embedded bookmark for disk image (TOC id)
|
|
154
|
+
kBookmarkVolumeMountPoint = 0x2050 # A URL
|
|
155
|
+
# = 0x2070
|
|
156
|
+
kBookmarkContainingFolder = 0xC001 # Index of containing folder in path
|
|
157
|
+
kBookmarkUserName = 0xC011 # User that created bookmark
|
|
158
|
+
kBookmarkUID = 0xC012 # UID that created bookmark
|
|
159
|
+
kBookmarkWasFileReference = 0xD001 # True if the URL was a file reference
|
|
160
|
+
kBookmarkCreationOptions = 0xD010
|
|
161
|
+
kBookmarkURLLengths = 0xE003 # See below
|
|
162
|
+
kBookmarkDisplayName = 0xF017
|
|
163
|
+
kBookmarkIconData = 0xF020
|
|
164
|
+
kBookmarkIconRef = 0xF021
|
|
165
|
+
kBookmarkTypeBindingData = 0xF022
|
|
166
|
+
kBookmarkCreationTime = 0xF030
|
|
167
|
+
kBookmarkSandboxRwExtension = 0xF080
|
|
168
|
+
kBookmarkSandboxRoExtension = 0xF081
|
|
169
|
+
kBookmarkAliasData = 0xFE00
|
|
191
170
|
|
|
192
171
|
# Alias for backwards compatibility
|
|
193
|
-
kBookmarkSecurityExtension
|
|
172
|
+
kBookmarkSecurityExtension = kBookmarkSandboxRwExtension
|
|
194
173
|
|
|
195
174
|
# kBookmarkURLLengths is an array that is set if the URL encoded by the
|
|
196
175
|
# bookmark had a base URL; in that case, each entry is the length of the
|
|
@@ -205,15 +184,16 @@ kBookmarkSecurityExtension = kBookmarkSandboxRwExtension
|
|
|
205
184
|
# would result in [1, 2, 1, 1]
|
|
206
185
|
|
|
207
186
|
|
|
208
|
-
class Data
|
|
187
|
+
class Data:
|
|
209
188
|
def __init__(self, bytedata=None):
|
|
210
189
|
#: The bytes, stored as a byte string
|
|
211
190
|
self.bytes = bytes(bytedata)
|
|
212
191
|
|
|
213
192
|
def __repr__(self):
|
|
214
|
-
return
|
|
193
|
+
return "Data(%r)" % self.bytes
|
|
215
194
|
|
|
216
|
-
|
|
195
|
+
|
|
196
|
+
class URL:
|
|
217
197
|
def __init__(self, base, rel=None):
|
|
218
198
|
if rel is not None:
|
|
219
199
|
#: The base URL, if any (a :class:`URL`)
|
|
@@ -230,13 +210,13 @@ class URL (object):
|
|
|
230
210
|
if self.base is None:
|
|
231
211
|
return self.relative
|
|
232
212
|
else:
|
|
233
|
-
base_abs = self.base.absolute
|
|
234
213
|
return urljoin(self.base.absolute, self.relative)
|
|
235
214
|
|
|
236
215
|
def __repr__(self):
|
|
237
|
-
return
|
|
216
|
+
return "URL(%r)" % self.absolute
|
|
217
|
+
|
|
238
218
|
|
|
239
|
-
class Bookmark
|
|
219
|
+
class Bookmark:
|
|
240
220
|
def __init__(self, tocs=None):
|
|
241
221
|
if tocs is None:
|
|
242
222
|
#: The TOCs for this Bookmark
|
|
@@ -248,39 +228,39 @@ class Bookmark (object):
|
|
|
248
228
|
def _get_item(cls, data, hdrsize, offset):
|
|
249
229
|
offset += hdrsize
|
|
250
230
|
if offset > len(data) - 8:
|
|
251
|
-
raise ValueError(
|
|
231
|
+
raise ValueError("Offset out of range")
|
|
252
232
|
|
|
253
|
-
length,typecode = struct.unpack(b
|
|
233
|
+
length, typecode = struct.unpack(b"<II", data[offset : offset + 8])
|
|
254
234
|
|
|
255
235
|
if len(data) - offset < 8 + length:
|
|
256
|
-
raise ValueError(
|
|
236
|
+
raise ValueError("Data item truncated")
|
|
257
237
|
|
|
258
|
-
databytes = data[offset+8:offset+8+length]
|
|
238
|
+
databytes = data[offset + 8 : offset + 8 + length]
|
|
259
239
|
|
|
260
240
|
dsubtype = typecode & BMK_DATA_SUBTYPE_MASK
|
|
261
241
|
dtype = typecode & BMK_DATA_TYPE_MASK
|
|
262
242
|
|
|
263
243
|
if dtype == BMK_STRING:
|
|
264
|
-
return databytes.decode(
|
|
244
|
+
return databytes.decode("utf-8")
|
|
265
245
|
elif dtype == BMK_DATA:
|
|
266
246
|
return Data(databytes)
|
|
267
247
|
elif dtype == BMK_NUMBER:
|
|
268
248
|
if dsubtype == kCFNumberSInt8Type:
|
|
269
249
|
return ord(databytes[0])
|
|
270
250
|
elif dsubtype == kCFNumberSInt16Type:
|
|
271
|
-
return struct.unpack(b
|
|
251
|
+
return struct.unpack(b"<h", databytes)[0]
|
|
272
252
|
elif dsubtype == kCFNumberSInt32Type:
|
|
273
|
-
return struct.unpack(b
|
|
253
|
+
return struct.unpack(b"<i", databytes)[0]
|
|
274
254
|
elif dsubtype == kCFNumberSInt64Type:
|
|
275
|
-
return struct.unpack(b
|
|
255
|
+
return struct.unpack(b"<q", databytes)[0]
|
|
276
256
|
elif dsubtype == kCFNumberFloat32Type:
|
|
277
|
-
return struct.unpack(b
|
|
257
|
+
return struct.unpack(b"<f", databytes)[0]
|
|
278
258
|
elif dsubtype == kCFNumberFloat64Type:
|
|
279
|
-
return struct.unpack(b
|
|
259
|
+
return struct.unpack(b"<d", databytes)[0]
|
|
280
260
|
elif dtype == BMK_DATE:
|
|
281
261
|
# Yes, dates really are stored as *BIG-endian* doubles; everything
|
|
282
262
|
# else is little-endian
|
|
283
|
-
secs = datetime.timedelta(seconds=struct.unpack(b
|
|
263
|
+
secs = datetime.timedelta(seconds=struct.unpack(b">d", databytes)[0])
|
|
284
264
|
return osx_epoch + secs
|
|
285
265
|
elif dtype == BMK_BOOLEAN:
|
|
286
266
|
if dsubtype == BMK_BOOLEAN_ST_TRUE:
|
|
@@ -291,22 +271,22 @@ class Bookmark (object):
|
|
|
291
271
|
return uuid.UUID(bytes=databytes)
|
|
292
272
|
elif dtype == BMK_URL:
|
|
293
273
|
if dsubtype == BMK_URL_ST_ABSOLUTE:
|
|
294
|
-
return URL(databytes.decode(
|
|
274
|
+
return URL(databytes.decode("utf-8"))
|
|
295
275
|
elif dsubtype == BMK_URL_ST_RELATIVE:
|
|
296
|
-
baseoff,reloff = struct.unpack(b
|
|
276
|
+
baseoff, reloff = struct.unpack(b"<II", databytes)
|
|
297
277
|
base = cls._get_item(data, hdrsize, baseoff)
|
|
298
278
|
rel = cls._get_item(data, hdrsize, reloff)
|
|
299
279
|
return URL(base, rel)
|
|
300
280
|
elif dtype == BMK_ARRAY:
|
|
301
281
|
result = []
|
|
302
|
-
for aoff in
|
|
303
|
-
eltoff, = struct.unpack(b
|
|
282
|
+
for aoff in range(offset + 8, offset + 8 + length, 4):
|
|
283
|
+
(eltoff,) = struct.unpack(b"<I", data[aoff : aoff + 4])
|
|
304
284
|
result.append(cls._get_item(data, hdrsize, eltoff))
|
|
305
285
|
return result
|
|
306
286
|
elif dtype == BMK_DICT:
|
|
307
287
|
result = {}
|
|
308
|
-
for eoff in
|
|
309
|
-
keyoff,valoff = struct.unpack(b
|
|
288
|
+
for eoff in range(offset + 8, offset + 8 + length, 8):
|
|
289
|
+
keyoff, valoff = struct.unpack(b"<II", data[eoff : eoff + 8])
|
|
310
290
|
key = cls._get_item(data, hdrsize, keyoff)
|
|
311
291
|
val = cls._get_item(data, hdrsize, valoff)
|
|
312
292
|
result[key] = val
|
|
@@ -314,7 +294,7 @@ class Bookmark (object):
|
|
|
314
294
|
elif dtype == BMK_NULL:
|
|
315
295
|
return None
|
|
316
296
|
|
|
317
|
-
print(
|
|
297
|
+
print("Unknown data type %08x" % typecode)
|
|
318
298
|
return (typecode, databytes)
|
|
319
299
|
|
|
320
300
|
@classmethod
|
|
@@ -322,58 +302,56 @@ class Bookmark (object):
|
|
|
322
302
|
"""Create a :class:`Bookmark` given byte data."""
|
|
323
303
|
|
|
324
304
|
if len(data) < 16:
|
|
325
|
-
raise ValueError(
|
|
305
|
+
raise ValueError("Not a bookmark file (too short)")
|
|
326
306
|
|
|
327
307
|
if isinstance(data, bytearray):
|
|
328
308
|
data = bytes(data)
|
|
329
309
|
|
|
330
|
-
magic,size,dummy,hdrsize = struct.unpack(b
|
|
310
|
+
magic, size, dummy, hdrsize = struct.unpack(b"<4sIII", data[0:16])
|
|
331
311
|
|
|
332
|
-
if magic not in (b
|
|
333
|
-
raise ValueError(
|
|
312
|
+
if magic not in (b"book", b"alis"):
|
|
313
|
+
raise ValueError("Not a bookmark file (bad magic) %r" % magic)
|
|
334
314
|
|
|
335
315
|
if hdrsize < 16:
|
|
336
|
-
raise ValueError(
|
|
316
|
+
raise ValueError("Not a bookmark file (header size too short)")
|
|
337
317
|
|
|
338
318
|
if hdrsize > size:
|
|
339
|
-
raise ValueError(
|
|
319
|
+
raise ValueError("Not a bookmark file (header size too large)")
|
|
340
320
|
|
|
341
321
|
if size != len(data):
|
|
342
|
-
raise ValueError(
|
|
322
|
+
raise ValueError("Not a bookmark file (truncated)")
|
|
343
323
|
|
|
344
|
-
tocoffset, = struct.unpack(b
|
|
324
|
+
(tocoffset,) = struct.unpack(b"<I", data[hdrsize : hdrsize + 4])
|
|
345
325
|
|
|
346
326
|
tocs = []
|
|
347
327
|
|
|
348
328
|
while tocoffset != 0:
|
|
349
329
|
tocbase = hdrsize + tocoffset
|
|
350
|
-
if tocoffset > size - hdrsize
|
|
351
|
-
|
|
352
|
-
raise ValueError('TOC offset out of range')
|
|
330
|
+
if (tocoffset > size - hdrsize) or (size - tocbase < 20):
|
|
331
|
+
raise ValueError("TOC offset out of range")
|
|
353
332
|
|
|
354
|
-
tocsize,tocmagic,tocid,nexttoc,toccount
|
|
355
|
-
|
|
356
|
-
|
|
333
|
+
(tocsize, tocmagic, tocid, nexttoc, toccount) = struct.unpack(
|
|
334
|
+
b"<IIIII", data[tocbase : tocbase + 20]
|
|
335
|
+
)
|
|
357
336
|
|
|
358
|
-
if tocmagic !=
|
|
337
|
+
if tocmagic != 0xFFFFFFFE:
|
|
359
338
|
break
|
|
360
339
|
|
|
361
340
|
tocsize += 8
|
|
362
341
|
|
|
363
342
|
if size - tocbase < tocsize:
|
|
364
|
-
raise ValueError(
|
|
343
|
+
raise ValueError("TOC truncated")
|
|
365
344
|
|
|
366
345
|
if tocsize < 12 * toccount:
|
|
367
|
-
raise ValueError(
|
|
346
|
+
raise ValueError("TOC entries overrun TOC size")
|
|
368
347
|
|
|
369
348
|
toc = {}
|
|
370
|
-
for n in
|
|
349
|
+
for n in range(0, toccount):
|
|
371
350
|
ebase = tocbase + 20 + 12 * n
|
|
372
|
-
eid,eoffset,edummy = struct.unpack(b
|
|
373
|
-
data[ebase:ebase+12])
|
|
351
|
+
eid, eoffset, edummy = struct.unpack(b"<III", data[ebase : ebase + 12])
|
|
374
352
|
|
|
375
353
|
if eid & 0x80000000:
|
|
376
|
-
eid = cls._get_item(data, hdrsize, eid &
|
|
354
|
+
eid = cls._get_item(data, hdrsize, eid & 0x7FFFFFFF)
|
|
377
355
|
|
|
378
356
|
toc[eid] = cls._get_item(data, hdrsize, eoffset)
|
|
379
357
|
|
|
@@ -384,10 +362,10 @@ class Bookmark (object):
|
|
|
384
362
|
return cls(tocs)
|
|
385
363
|
|
|
386
364
|
def __getitem__(self, key):
|
|
387
|
-
for tid,toc in self.tocs:
|
|
365
|
+
for tid, toc in self.tocs:
|
|
388
366
|
if key in toc:
|
|
389
367
|
return toc[key]
|
|
390
|
-
raise KeyError(
|
|
368
|
+
raise KeyError("Key not found")
|
|
391
369
|
|
|
392
370
|
def __setitem__(self, key, value):
|
|
393
371
|
if len(self.tocs) == 0:
|
|
@@ -397,7 +375,7 @@ class Bookmark (object):
|
|
|
397
375
|
def get(self, key, default=None):
|
|
398
376
|
"""Lookup the value for a given key, returning a default if not
|
|
399
377
|
present."""
|
|
400
|
-
for tid,toc in self.tocs:
|
|
378
|
+
for tid, toc in self.tocs:
|
|
401
379
|
if key in toc:
|
|
402
380
|
return toc[key]
|
|
403
381
|
return default
|
|
@@ -405,87 +383,89 @@ class Bookmark (object):
|
|
|
405
383
|
@classmethod
|
|
406
384
|
def _encode_item(cls, item, offset):
|
|
407
385
|
if item is True:
|
|
408
|
-
result = struct.pack(b
|
|
386
|
+
result = struct.pack(b"<II", 0, BMK_BOOLEAN | BMK_BOOLEAN_ST_TRUE)
|
|
409
387
|
elif item is False:
|
|
410
|
-
result = struct.pack(b
|
|
411
|
-
elif isinstance(item,
|
|
412
|
-
encoded = item.encode(
|
|
413
|
-
result = (
|
|
414
|
-
|
|
388
|
+
result = struct.pack(b"<II", 0, BMK_BOOLEAN | BMK_BOOLEAN_ST_FALSE)
|
|
389
|
+
elif isinstance(item, str):
|
|
390
|
+
encoded = item.encode("utf-8")
|
|
391
|
+
result = (
|
|
392
|
+
struct.pack(b"<II", len(encoded), BMK_STRING | BMK_ST_ONE) + encoded
|
|
393
|
+
)
|
|
415
394
|
elif isinstance(item, bytes):
|
|
416
|
-
result =
|
|
417
|
-
+ item)
|
|
395
|
+
result = struct.pack(b"<II", len(item), BMK_STRING | BMK_ST_ONE) + item
|
|
418
396
|
elif isinstance(item, Data):
|
|
419
|
-
result =
|
|
420
|
-
|
|
421
|
-
|
|
397
|
+
result = struct.pack(
|
|
398
|
+
b"<II", len(item.bytes), BMK_DATA | BMK_ST_ONE
|
|
399
|
+
) + bytes(item.bytes)
|
|
422
400
|
elif isinstance(item, bytearray):
|
|
423
|
-
result =
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
if item > -0x80000000 and item < 0x7fffffff:
|
|
428
|
-
result = struct.pack(b'<IIi', 4,
|
|
429
|
-
BMK_NUMBER | kCFNumberSInt32Type, item)
|
|
401
|
+
result = struct.pack(b"<II", len(item), BMK_DATA | BMK_ST_ONE) + bytes(item)
|
|
402
|
+
elif isinstance(item, int):
|
|
403
|
+
if item > -0x80000000 and item < 0x7FFFFFFF:
|
|
404
|
+
result = struct.pack(b"<IIi", 4, BMK_NUMBER | kCFNumberSInt32Type, item)
|
|
430
405
|
else:
|
|
431
|
-
result = struct.pack(b
|
|
432
|
-
BMK_NUMBER | kCFNumberSInt64Type, item)
|
|
406
|
+
result = struct.pack(b"<IIq", 8, BMK_NUMBER | kCFNumberSInt64Type, item)
|
|
433
407
|
elif isinstance(item, float):
|
|
434
|
-
result = struct.pack(b
|
|
435
|
-
BMK_NUMBER | kCFNumberFloat64Type, item)
|
|
408
|
+
result = struct.pack(b"<IId", 8, BMK_NUMBER | kCFNumberFloat64Type, item)
|
|
436
409
|
elif isinstance(item, datetime.datetime):
|
|
437
410
|
secs = item - osx_epoch
|
|
438
|
-
result = struct.pack(b
|
|
439
|
-
|
|
411
|
+
result = struct.pack(b"<II", 8, BMK_DATE | BMK_ST_ZERO) + struct.pack(
|
|
412
|
+
b">d", float(secs.total_seconds())
|
|
413
|
+
)
|
|
414
|
+
|
|
440
415
|
elif isinstance(item, uuid.UUID):
|
|
441
|
-
result = struct.pack(b
|
|
442
|
-
+ item.bytes
|
|
416
|
+
result = struct.pack(b"<II", 16, BMK_UUID | BMK_ST_ONE) + item.bytes
|
|
443
417
|
elif isinstance(item, URL):
|
|
444
418
|
if item.base:
|
|
445
419
|
baseoff = offset + 16
|
|
446
420
|
reloff, baseenc = cls._encode_item(item.base, baseoff)
|
|
447
421
|
xoffset, relenc = cls._encode_item(item.relative, reloff)
|
|
448
|
-
result = b
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
422
|
+
result = b"".join(
|
|
423
|
+
[
|
|
424
|
+
struct.pack(
|
|
425
|
+
b"<IIII", 8, BMK_URL | BMK_URL_ST_RELATIVE, baseoff, reloff
|
|
426
|
+
),
|
|
427
|
+
baseenc,
|
|
428
|
+
relenc,
|
|
429
|
+
]
|
|
430
|
+
)
|
|
453
431
|
else:
|
|
454
|
-
encoded = item.relative.encode(
|
|
455
|
-
result =
|
|
456
|
-
|
|
432
|
+
encoded = item.relative.encode("utf-8")
|
|
433
|
+
result = (
|
|
434
|
+
struct.pack(b"<II", len(encoded), BMK_URL | BMK_URL_ST_ABSOLUTE)
|
|
435
|
+
+ encoded
|
|
436
|
+
)
|
|
457
437
|
elif isinstance(item, list):
|
|
458
438
|
ioffset = offset + 8 + len(item) * 4
|
|
459
|
-
result = [struct.pack(b
|
|
439
|
+
result = [struct.pack(b"<II", len(item) * 4, BMK_ARRAY | BMK_ST_ONE)]
|
|
460
440
|
enc = []
|
|
461
441
|
for elt in item:
|
|
462
|
-
result.append(struct.pack(b
|
|
442
|
+
result.append(struct.pack(b"<I", ioffset))
|
|
463
443
|
ioffset, ienc = cls._encode_item(elt, ioffset)
|
|
464
444
|
enc.append(ienc)
|
|
465
|
-
result = b
|
|
445
|
+
result = b"".join(result + enc)
|
|
466
446
|
elif isinstance(item, dict):
|
|
467
447
|
ioffset = offset + 8 + len(item) * 8
|
|
468
|
-
result = [struct.pack(b
|
|
448
|
+
result = [struct.pack(b"<II", len(item) * 8, BMK_DICT | BMK_ST_ONE)]
|
|
469
449
|
enc = []
|
|
470
|
-
for k,v in
|
|
471
|
-
result.append(struct.pack(b
|
|
450
|
+
for k, v in item.items():
|
|
451
|
+
result.append(struct.pack(b"<I", ioffset))
|
|
472
452
|
ioffset, ienc = cls._encode_item(k, ioffset)
|
|
473
453
|
enc.append(ienc)
|
|
474
|
-
result.append(struct.pack(b
|
|
454
|
+
result.append(struct.pack(b"<I", ioffset))
|
|
475
455
|
ioffset, ienc = cls._encode_item(v, ioffset)
|
|
476
456
|
enc.append(ienc)
|
|
477
|
-
result = b
|
|
457
|
+
result = b"".join(result + enc)
|
|
478
458
|
elif item is None:
|
|
479
|
-
result = struct.pack(b
|
|
459
|
+
result = struct.pack(b"<II", 0, BMK_NULL | BMK_ST_ONE)
|
|
480
460
|
else:
|
|
481
|
-
raise ValueError(
|
|
461
|
+
raise ValueError("Unknown item type when encoding: %s" % item)
|
|
482
462
|
|
|
483
463
|
offset += len(result)
|
|
484
464
|
|
|
485
465
|
# Pad to a multiple of 4 bytes
|
|
486
466
|
if offset & 3:
|
|
487
467
|
extra = 4 - (offset & 3)
|
|
488
|
-
result += b
|
|
468
|
+
result += b"\0" * extra
|
|
489
469
|
offset += extra
|
|
490
470
|
|
|
491
471
|
return (offset, result)
|
|
@@ -498,11 +478,11 @@ class Bookmark (object):
|
|
|
498
478
|
offset = 4 # For the offset to the first TOC
|
|
499
479
|
|
|
500
480
|
# Generate the data and build the TOCs
|
|
501
|
-
for tid,toc in self.tocs:
|
|
481
|
+
for tid, toc in self.tocs:
|
|
502
482
|
entries = []
|
|
503
483
|
|
|
504
|
-
for k,v in
|
|
505
|
-
if isinstance(k,
|
|
484
|
+
for k, v in toc.items():
|
|
485
|
+
if isinstance(k, str):
|
|
506
486
|
noffset = offset
|
|
507
487
|
voffset, enc = self._encode_item(k, offset)
|
|
508
488
|
result.append(enc)
|
|
@@ -518,39 +498,52 @@ class Bookmark (object):
|
|
|
518
498
|
# binary search to find data
|
|
519
499
|
entries.sort()
|
|
520
500
|
|
|
521
|
-
tocs.append(
|
|
522
|
-
|
|
501
|
+
tocs.append(
|
|
502
|
+
(tid, b"".join([struct.pack(b"<III", k, o, 0) for k, o in entries]))
|
|
503
|
+
)
|
|
523
504
|
|
|
524
505
|
first_toc_offset = offset
|
|
525
506
|
|
|
526
507
|
# Now generate the TOC headers
|
|
527
|
-
for ndx,toc in enumerate(tocs):
|
|
508
|
+
for ndx, toc in enumerate(tocs):
|
|
528
509
|
tid, data = toc
|
|
529
510
|
if ndx == len(tocs) - 1:
|
|
530
511
|
next_offset = 0
|
|
531
512
|
else:
|
|
532
513
|
next_offset = offset + 20 + len(data)
|
|
533
514
|
|
|
534
|
-
result.append(
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
515
|
+
result.append(
|
|
516
|
+
struct.pack(
|
|
517
|
+
b"<IIIII",
|
|
518
|
+
len(data) - 8,
|
|
519
|
+
0xFFFFFFFE,
|
|
520
|
+
tid,
|
|
521
|
+
next_offset,
|
|
522
|
+
len(data) // 12,
|
|
523
|
+
)
|
|
524
|
+
)
|
|
539
525
|
result.append(data)
|
|
540
526
|
|
|
541
527
|
offset += 20 + len(data)
|
|
542
528
|
|
|
543
529
|
# Finally, add the header (and the first TOC offset, which isn't part
|
|
544
530
|
# of the header, but goes just after it)
|
|
545
|
-
header = struct.pack(
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
531
|
+
header = struct.pack(
|
|
532
|
+
b"<4sIIIQQQQI",
|
|
533
|
+
b"book",
|
|
534
|
+
offset + 48,
|
|
535
|
+
0x10040000,
|
|
536
|
+
48,
|
|
537
|
+
0,
|
|
538
|
+
0,
|
|
539
|
+
0,
|
|
540
|
+
0,
|
|
541
|
+
first_toc_offset,
|
|
542
|
+
)
|
|
550
543
|
|
|
551
544
|
result.insert(0, header)
|
|
552
545
|
|
|
553
|
-
return b
|
|
546
|
+
return b"".join(result)
|
|
554
547
|
|
|
555
548
|
@classmethod
|
|
556
549
|
def for_file(cls, path):
|
|
@@ -558,14 +551,16 @@ class Bookmark (object):
|
|
|
558
551
|
|
|
559
552
|
# Find the filesystem
|
|
560
553
|
st = osx.statfs(path)
|
|
561
|
-
vol_path = st.f_mntonname.decode(
|
|
554
|
+
vol_path = st.f_mntonname.decode("utf-8")
|
|
562
555
|
|
|
563
556
|
# Grab its attributes
|
|
564
|
-
attrs = [
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
557
|
+
attrs = [
|
|
558
|
+
osx.ATTR_CMN_CRTIME,
|
|
559
|
+
osx.ATTR_VOL_SIZE | osx.ATTR_VOL_NAME | osx.ATTR_VOL_UUID,
|
|
560
|
+
0,
|
|
561
|
+
0,
|
|
562
|
+
0,
|
|
563
|
+
]
|
|
569
564
|
volinfo = osx.getattrlist(vol_path, attrs, 0)
|
|
570
565
|
|
|
571
566
|
vol_crtime = volinfo[0]
|
|
@@ -574,9 +569,13 @@ class Bookmark (object):
|
|
|
574
569
|
vol_uuid = volinfo[3]
|
|
575
570
|
|
|
576
571
|
# Also grab various attributes of the file
|
|
577
|
-
attrs = [
|
|
578
|
-
|
|
579
|
-
|
|
572
|
+
attrs = [
|
|
573
|
+
(osx.ATTR_CMN_OBJTYPE | osx.ATTR_CMN_CRTIME | osx.ATTR_CMN_FILEID),
|
|
574
|
+
0,
|
|
575
|
+
0,
|
|
576
|
+
0,
|
|
577
|
+
0,
|
|
578
|
+
]
|
|
580
579
|
info = osx.getattrlist(path, attrs, osx.FSOPT_NOFOLLOW)
|
|
581
580
|
|
|
582
581
|
cnid = info[2]
|
|
@@ -603,7 +602,7 @@ class Bookmark (object):
|
|
|
603
602
|
head, tail = os.path.split(head)
|
|
604
603
|
dirname = os.path.join(curdir, dirname)
|
|
605
604
|
|
|
606
|
-
foldername = os.path.basename(dirname)
|
|
605
|
+
# ?? foldername = os.path.basename(dirname)
|
|
607
606
|
|
|
608
607
|
rel_path = os.path.relpath(path, vol_path)
|
|
609
608
|
|
|
@@ -627,9 +626,15 @@ class Bookmark (object):
|
|
|
627
626
|
|
|
628
627
|
url_lengths = [relcount, len(name_path) - relcount]
|
|
629
628
|
|
|
630
|
-
fileprops = Data(struct.pack(b
|
|
631
|
-
volprops = Data(
|
|
632
|
-
|
|
629
|
+
fileprops = Data(struct.pack(b"<QQQ", flags, 0x0F, 0))
|
|
630
|
+
volprops = Data(
|
|
631
|
+
struct.pack(
|
|
632
|
+
b"<QQQ",
|
|
633
|
+
0x81 | kCFURLVolumeSupportsPersistentIDs,
|
|
634
|
+
0x13EF | kCFURLVolumeSupportsPersistentIDs,
|
|
635
|
+
0,
|
|
636
|
+
)
|
|
637
|
+
)
|
|
633
638
|
|
|
634
639
|
toc = {
|
|
635
640
|
kBookmarkPath: name_path,
|
|
@@ -638,8 +643,8 @@ class Bookmark (object):
|
|
|
638
643
|
kBookmarkFileProperties: fileprops,
|
|
639
644
|
kBookmarkContainingFolder: len(name_path) - 2,
|
|
640
645
|
kBookmarkVolumePath: vol_path,
|
|
641
|
-
kBookmarkVolumeIsRoot: vol_path ==
|
|
642
|
-
kBookmarkVolumeURL: URL(
|
|
646
|
+
kBookmarkVolumeIsRoot: vol_path == "/",
|
|
647
|
+
kBookmarkVolumeURL: URL("file://" + vol_path),
|
|
643
648
|
kBookmarkVolumeName: vol_name,
|
|
644
649
|
kBookmarkVolumeSize: vol_size,
|
|
645
650
|
kBookmarkVolumeCreationDate: vol_crtime,
|
|
@@ -647,7 +652,7 @@ class Bookmark (object):
|
|
|
647
652
|
kBookmarkVolumeProperties: volprops,
|
|
648
653
|
kBookmarkCreationOptions: 512,
|
|
649
654
|
kBookmarkWasFileReference: True,
|
|
650
|
-
kBookmarkUserName:
|
|
655
|
+
kBookmarkUserName: "unknown",
|
|
651
656
|
kBookmarkUID: 99,
|
|
652
657
|
}
|
|
653
658
|
|
|
@@ -657,16 +662,16 @@ class Bookmark (object):
|
|
|
657
662
|
return Bookmark([(1, toc)])
|
|
658
663
|
|
|
659
664
|
def __repr__(self):
|
|
660
|
-
result = [
|
|
661
|
-
for tid,toc in self.tocs:
|
|
662
|
-
result.append(
|
|
663
|
-
for k,v in
|
|
664
|
-
if isinstance(k,
|
|
665
|
+
result = ["Bookmark(["]
|
|
666
|
+
for tid, toc in self.tocs:
|
|
667
|
+
result.append("(0x%x, {\n" % tid)
|
|
668
|
+
for k, v in toc.items():
|
|
669
|
+
if isinstance(k, str):
|
|
665
670
|
kf = repr(k)
|
|
666
671
|
else:
|
|
667
|
-
kf =
|
|
668
|
-
result.append(
|
|
669
|
-
result.append(
|
|
670
|
-
result.append(
|
|
672
|
+
kf = "0x%04x" % k
|
|
673
|
+
result.append(f" {kf}: {v!r}\n")
|
|
674
|
+
result.append("}),\n")
|
|
675
|
+
result.append("])")
|
|
671
676
|
|
|
672
|
-
return
|
|
677
|
+
return "".join(result)
|