mac-alias 1.0.0__tar.gz → 2.0.0__tar.gz
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.
- {mac_alias-1.0.0 → mac_alias-2.0.0}/PKG-INFO +10 -6
- mac_alias-2.0.0/README.rst +11 -0
- mac_alias-2.0.0/mac_alias/__init__.py +27 -0
- {mac_alias-1.0.0 → mac_alias-2.0.0}/mac_alias/alias.py +12 -11
- mac_alias-2.0.0/mac_alias/bookmark.py +647 -0
- {mac_alias-1.0.0 → mac_alias-2.0.0}/mac_alias/osx.py +1 -1
- {mac_alias-1.0.0 → mac_alias-2.0.0}/mac_alias/utils.py +1 -0
- {mac_alias-1.0.0 → mac_alias-2.0.0}/mac_alias.egg-info/PKG-INFO +10 -6
- {mac_alias-1.0.0 → mac_alias-2.0.0}/mac_alias.egg-info/SOURCES.txt +2 -0
- {mac_alias-1.0.0 → mac_alias-2.0.0}/setup.py +5 -10
- mac_alias-1.0.0/mac_alias/__init__.py +0 -14
- {mac_alias-1.0.0 → mac_alias-2.0.0}/mac_alias.egg-info/dependency_links.txt +0 -0
- {mac_alias-1.0.0 → mac_alias-2.0.0}/mac_alias.egg-info/top_level.txt +0 -0
- {mac_alias-1.0.0 → mac_alias-2.0.0}/setup.cfg +0 -0
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
Metadata-Version: 1.1
|
|
2
2
|
Name: mac_alias
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: Generate/parse Mac OS Alias records from Python
|
|
5
5
|
Home-page: http://alastairs-place.net/projects/mac_alias
|
|
6
6
|
Author: Alastair Houghton
|
|
7
7
|
Author-email: alastair@alastairs-place.net
|
|
8
8
|
License: MIT License
|
|
9
|
-
Description:
|
|
10
|
-
|
|
9
|
+
Description: ``mac_alias`` lets you generate or read binary Alias and Bookmark records
|
|
10
|
+
from Python code.
|
|
11
|
+
|
|
11
12
|
While it is written in pure Python, some OS X specific code is required to
|
|
12
|
-
generate a proper Alias record for a given file, so this module
|
|
13
|
-
is not portable to other platforms.
|
|
13
|
+
generate a proper Alias or Bookmark record for a given file, so this module
|
|
14
|
+
currently is not portable to other platforms.
|
|
14
15
|
|
|
15
16
|
Credit is due to Wim Lewis <wiml@hhhh.org>, whose work on reverse engineering
|
|
16
|
-
the alias format was extremely useful in writing this code.
|
|
17
|
+
the alias format was extremely useful in writing this code. Alastair Houghton
|
|
18
|
+
<alastair@alastairs-place.net> was responsible for reverse engineering the
|
|
19
|
+
bookmark format.
|
|
20
|
+
|
|
17
21
|
Platform: darwin
|
|
18
22
|
Classifier: Development Status :: 5 - Production/Stable
|
|
19
23
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
``mac_alias`` lets you generate or read binary Alias and Bookmark records
|
|
2
|
+
from Python code.
|
|
3
|
+
|
|
4
|
+
While it is written in pure Python, some OS X specific code is required to
|
|
5
|
+
generate a proper Alias or Bookmark record for a given file, so this module
|
|
6
|
+
currently is not portable to other platforms.
|
|
7
|
+
|
|
8
|
+
Credit is due to Wim Lewis <wiml@hhhh.org>, whose work on reverse engineering
|
|
9
|
+
the alias format was extremely useful in writing this code. Alastair Houghton
|
|
10
|
+
<alastair@alastairs-place.net> was responsible for reverse engineering the
|
|
11
|
+
bookmark format.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from .alias import *
|
|
2
|
+
from .bookmark import *
|
|
3
|
+
|
|
4
|
+
__all__ = [ 'ALIAS_KIND_FILE', 'ALIAS_KIND_FOLDER',
|
|
5
|
+
'ALIAS_HFS_VOLUME_SIGNATURE',
|
|
6
|
+
'ALIAS_FIXED_DISK', 'ALIAS_NETWORK_DISK', 'ALIAS_400KB_FLOPPY_DISK',
|
|
7
|
+
'ALIAS_800KB_FLOPPY_DISK', 'ALIAS_1_44MB_FLOPPY_DISK',
|
|
8
|
+
'ALIAS_EJECTABLE_DISK',
|
|
9
|
+
'ALIAS_NO_CNID',
|
|
10
|
+
'kBookmarkPath', 'kBookmarkCNIDPath', 'kBookmarkFileProperties',
|
|
11
|
+
'kBookmarkFileName', 'kBookmarkFileID', 'kBookmarkFileCreationDate',
|
|
12
|
+
'kBookmarkTOCPath', 'kBookmarkVolumePath',
|
|
13
|
+
'kBookmarkVolumeURL', 'kBookmarkVolumeName', 'kBookmarkVolumeUUID',
|
|
14
|
+
'kBookmarkVolumeSize', 'kBookmarkVolumeCreationDate',
|
|
15
|
+
'kBookmarkVolumeProperties', 'kBookmarkContainingFolder',
|
|
16
|
+
'kBookmarkUserName', 'kBookmarkUID', 'kBookmarkWasFileReference',
|
|
17
|
+
'kBookmarkCreationOptions', 'kBookmarkURLLengths',
|
|
18
|
+
'kBookmarkSecurityExtension',
|
|
19
|
+
'AppleShareInfo',
|
|
20
|
+
'VolumeInfo',
|
|
21
|
+
'TargetInfo',
|
|
22
|
+
'Alias',
|
|
23
|
+
'Bookmark',
|
|
24
|
+
'Data',
|
|
25
|
+
'URL' ]
|
|
26
|
+
|
|
27
|
+
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
from __future__ import unicode_literals
|
|
3
|
+
from __future__ import division
|
|
3
4
|
|
|
4
5
|
import struct
|
|
5
6
|
import datetime
|
|
@@ -138,28 +139,28 @@ class TargetInfo (object):
|
|
|
138
139
|
self.type_code = type_code
|
|
139
140
|
|
|
140
141
|
#: The depth of the alias? Always seems to be -1 on OS X.
|
|
141
|
-
self.levels_from =
|
|
142
|
+
self.levels_from = levels_from
|
|
142
143
|
|
|
143
144
|
#: The depth of the target? Always seems to be -1 on OS X.
|
|
144
|
-
self.levels_to =
|
|
145
|
+
self.levels_to = levels_to
|
|
145
146
|
|
|
146
147
|
#: The (POSIX) name of the target's containing folder. *(optional)*
|
|
147
|
-
self.folder_name =
|
|
148
|
+
self.folder_name = folder_name
|
|
148
149
|
|
|
149
150
|
#: The path from the volume root as a sequence of CNIDs. *(optional)*
|
|
150
|
-
self.cnid_path =
|
|
151
|
+
self.cnid_path = cnid_path
|
|
151
152
|
|
|
152
153
|
#: The Carbon path of the target *(optional)*
|
|
153
|
-
self.carbon_path =
|
|
154
|
+
self.carbon_path = carbon_path
|
|
154
155
|
|
|
155
156
|
#: The POSIX path of the target relative to the volume root. Note
|
|
156
157
|
#: that this may or may not have a leading '/' character, but it is
|
|
157
158
|
#: always relative to the containing volume. *(optional)*
|
|
158
|
-
self.posix_path =
|
|
159
|
+
self.posix_path = posix_path
|
|
159
160
|
|
|
160
161
|
#: If the path points into a user's home folder, the number of folders
|
|
161
162
|
#: deep that we go before we get to that home folder. *(optional)*
|
|
162
|
-
self.user_home_prefix_len =
|
|
163
|
+
self.user_home_prefix_len = user_home_prefix_len
|
|
163
164
|
|
|
164
165
|
def __repr__(self):
|
|
165
166
|
args = ['kind', 'filename', 'folder_cnid', 'cnid', 'creation_date',
|
|
@@ -262,7 +263,7 @@ class Alias (object):
|
|
|
262
263
|
if tag == TAG_CARBON_FOLDER_NAME:
|
|
263
264
|
alias.target.folder_name = value.replace('/',':')
|
|
264
265
|
elif tag == TAG_CNID_PATH:
|
|
265
|
-
alias.target.cnid_path = struct.unpack(b'>%uI' % (length
|
|
266
|
+
alias.target.cnid_path = struct.unpack(b'>%uI' % (length // 4),
|
|
266
267
|
value)
|
|
267
268
|
elif tag == TAG_CARBON_PATH:
|
|
268
269
|
alias.target.carbon_path = value
|
|
@@ -482,7 +483,7 @@ class Alias (object):
|
|
|
482
483
|
if len(ai.server) & 1:
|
|
483
484
|
b.write(b'\0')
|
|
484
485
|
if ai.username:
|
|
485
|
-
b.write(struct.pack(b'>hh',
|
|
486
|
+
b.write(struct.pack(b'>hh', TAG_APPLESHARE_USERNAME,
|
|
486
487
|
len(ai.username)))
|
|
487
488
|
b.write(ai.username)
|
|
488
489
|
if len(ai.username) & 1:
|
|
@@ -513,13 +514,13 @@ class Alias (object):
|
|
|
513
514
|
utf16 = self.target.filename.replace(':','/').encode('utf-16-be')
|
|
514
515
|
b.write(struct.pack(b'>hhh', TAG_UNICODE_FILENAME,
|
|
515
516
|
len(utf16) + 2,
|
|
516
|
-
len(utf16)
|
|
517
|
+
len(utf16) // 2))
|
|
517
518
|
b.write(utf16)
|
|
518
519
|
|
|
519
520
|
utf16 = self.volume.name.replace(':','/').encode('utf-16-be')
|
|
520
521
|
b.write(struct.pack(b'>hhh', TAG_UNICODE_VOLUME_NAME,
|
|
521
522
|
len(utf16) + 2,
|
|
522
|
-
len(utf16)
|
|
523
|
+
len(utf16) // 2))
|
|
523
524
|
b.write(utf16)
|
|
524
525
|
|
|
525
526
|
if self.target.posix_path:
|
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# This file implements the Apple "bookmark" format, which is the replacement
|
|
4
|
+
# for the old-fashioned alias format. The details of this format were
|
|
5
|
+
# reverse engineered; some things are still not entirely clear.
|
|
6
|
+
#
|
|
7
|
+
from __future__ import unicode_literals
|
|
8
|
+
|
|
9
|
+
import struct
|
|
10
|
+
import uuid
|
|
11
|
+
import datetime
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
import pprint
|
|
15
|
+
|
|
16
|
+
from urlparse import urljoin
|
|
17
|
+
|
|
18
|
+
if sys.platform == 'darwin':
|
|
19
|
+
from . import osx
|
|
20
|
+
|
|
21
|
+
from .utils import *
|
|
22
|
+
|
|
23
|
+
BMK_DATA_TYPE_MASK = 0xffffff00
|
|
24
|
+
BMK_DATA_SUBTYPE_MASK = 0x000000ff
|
|
25
|
+
|
|
26
|
+
BMK_STRING = 0x0100
|
|
27
|
+
BMK_DATA = 0x0200
|
|
28
|
+
BMK_NUMBER = 0x0300
|
|
29
|
+
BMK_DATE = 0x0400
|
|
30
|
+
BMK_BOOLEAN = 0x0500
|
|
31
|
+
BMK_ARRAY = 0x0600
|
|
32
|
+
BMK_DICT = 0x0700
|
|
33
|
+
BMK_UUID = 0x0800
|
|
34
|
+
BMK_URL = 0x0900
|
|
35
|
+
BMK_NULL = 0x0a00
|
|
36
|
+
|
|
37
|
+
BMK_ST_ZERO = 0x0000
|
|
38
|
+
BMK_ST_ONE = 0x0001
|
|
39
|
+
|
|
40
|
+
BMK_BOOLEAN_ST_FALSE = 0x0000
|
|
41
|
+
BMK_BOOLEAN_ST_TRUE = 0x0001
|
|
42
|
+
|
|
43
|
+
# Subtypes for BMK_NUMBER are really CFNumberType values
|
|
44
|
+
kCFNumberSInt8Type = 1
|
|
45
|
+
kCFNumberSInt16Type = 2
|
|
46
|
+
kCFNumberSInt32Type = 3
|
|
47
|
+
kCFNumberSInt64Type = 4
|
|
48
|
+
kCFNumberFloat32Type = 5
|
|
49
|
+
kCFNumberFloat64Type = 6
|
|
50
|
+
kCFNumberCharType = 7
|
|
51
|
+
kCFNumberShortType = 8
|
|
52
|
+
kCFNumberIntType = 9
|
|
53
|
+
kCFNumberLongType = 10
|
|
54
|
+
kCFNumberLongLongType = 11
|
|
55
|
+
kCFNumberFloatType = 12
|
|
56
|
+
kCFNumberDoubleType = 13
|
|
57
|
+
kCFNumberCFIndexType = 14
|
|
58
|
+
kCFNumberNSIntegerType = 15
|
|
59
|
+
kCFNumberCGFloatType = 16
|
|
60
|
+
|
|
61
|
+
# Resource property flags (from CFURLPriv.h)
|
|
62
|
+
kCFURLResourceIsRegularFile = 0x00000001
|
|
63
|
+
kCFURLResourceIsDirectory = 0x00000002
|
|
64
|
+
kCFURLResourceIsSymbolicLink = 0x00000004
|
|
65
|
+
kCFURLResourceIsVolume = 0x00000008
|
|
66
|
+
kCFURLResourceIsPackage = 0x00000010
|
|
67
|
+
kCFURLResourceIsSystemImmutable = 0x00000020
|
|
68
|
+
kCFURLResourceIsUserImmutable = 0x00000040
|
|
69
|
+
kCFURLResourceIsHidden = 0x00000080
|
|
70
|
+
kCFURLResourceHasHiddenExtension = 0x00000100
|
|
71
|
+
kCFURLResourceIsApplication = 0x00000200
|
|
72
|
+
kCFURLResourceIsCompressed = 0x00000400
|
|
73
|
+
kCFURLResourceIsSystemCompressed = 0x00000400
|
|
74
|
+
kCFURLCanSetHiddenExtension = 0x00000800
|
|
75
|
+
kCFURLResourceIsReadable = 0x00001000
|
|
76
|
+
kCFURLResourceIsWriteable = 0x00002000
|
|
77
|
+
kCFURLResourceIsExecutable = 0x00004000
|
|
78
|
+
kCFURLIsAliasFile = 0x00008000
|
|
79
|
+
kCFURLIsMountTrigger = 0x00010000
|
|
80
|
+
|
|
81
|
+
# Volume property flags (from CFURLPriv.h)
|
|
82
|
+
kCFURLVolumeIsLocal = 0x1 #
|
|
83
|
+
kCFURLVolumeIsAutomount = 0x2 #
|
|
84
|
+
kCFURLVolumeDontBrowse = 0x4 #
|
|
85
|
+
kCFURLVolumeIsReadOnly = 0x8 #
|
|
86
|
+
kCFURLVolumeIsQuarantined = 0x10
|
|
87
|
+
kCFURLVolumeIsEjectable = 0x20 #
|
|
88
|
+
kCFURLVolumeIsRemovable = 0x40 #
|
|
89
|
+
kCFURLVolumeIsInternal = 0x80 #
|
|
90
|
+
kCFURLVolumeIsExternal = 0x100 #
|
|
91
|
+
kCFURLVolumeIsDiskImage = 0x200 #
|
|
92
|
+
kCFURLVolumeIsFileVault = 0x400
|
|
93
|
+
kCFURLVolumeIsLocaliDiskMirror = 0x800
|
|
94
|
+
kCFURLVolumeIsiPod = 0x1000 #
|
|
95
|
+
kCFURLVolumeIsiDisk = 0x2000
|
|
96
|
+
kCFURLVolumeIsCD = 0x4000
|
|
97
|
+
kCFURLVolumeIsDVD = 0x8000
|
|
98
|
+
kCFURLVolumeIsDeviceFileSystem = 0x10000
|
|
99
|
+
kCFURLVolumeSupportsPersistentIDs = 0x100000000
|
|
100
|
+
kCFURLVolumeSupportsSearchFS = 0x200000000
|
|
101
|
+
kCFURLVolumeSupportsExchange = 0x400000000
|
|
102
|
+
# reserved 0x800000000
|
|
103
|
+
kCFURLVolumeSupportsSymbolicLinks = 0x1000000000
|
|
104
|
+
kCFURLVolumeSupportsDenyModes = 0x2000000000
|
|
105
|
+
kCFURLVolumeSupportsCopyFile = 0x4000000000
|
|
106
|
+
kCFURLVolumeSupportsReadDirAttr = 0x8000000000
|
|
107
|
+
kCFURLVolumeSupportsJournaling = 0x10000000000
|
|
108
|
+
kCFURLVolumeSupportsRename = 0x20000000000
|
|
109
|
+
kCFURLVolumeSupportsFastStatFS = 0x40000000000
|
|
110
|
+
kCFURLVolumeSupportsCaseSensitiveNames = 0x80000000000
|
|
111
|
+
kCFURLVolumeSupportsCasePreservedNames = 0x100000000000
|
|
112
|
+
kCFURLVolumeSupportsFLock = 0x200000000000
|
|
113
|
+
kCFURLVolumeHasNoRootDirectoryTimes = 0x400000000000
|
|
114
|
+
kCFURLVolumeSupportsExtendedSecurity = 0x800000000000
|
|
115
|
+
kCFURLVolumeSupports2TBFileSize = 0x1000000000000
|
|
116
|
+
kCFURLVolumeSupportsHardLinks = 0x2000000000000
|
|
117
|
+
kCFURLVolumeSupportsMandatoryByteRangeLocks = 0x4000000000000
|
|
118
|
+
kCFURLVolumeSupportsPathFromID = 0x8000000000000
|
|
119
|
+
# reserved 0x10000000000000
|
|
120
|
+
kCFURLVolumeIsJournaling = 0x20000000000000
|
|
121
|
+
kCFURLVolumeSupportsSparseFiles = 0x40000000000000
|
|
122
|
+
kCFURLVolumeSupportsZeroRuns = 0x80000000000000
|
|
123
|
+
kCFURLVolumeSupportsVolumeSizes = 0x100000000000000
|
|
124
|
+
kCFURLVolumeSupportsRemoteEvents = 0x200000000000000
|
|
125
|
+
kCFURLVolumeSupportsHiddenFiles = 0x400000000000000
|
|
126
|
+
kCFURLVolumeSupportsDecmpFSCompression = 0x800000000000000
|
|
127
|
+
kCFURLVolumeHas64BitObjectIDs = 0x1000000000000000
|
|
128
|
+
kCFURLVolumePropertyFlagsAll = 0xffffffffffffffff
|
|
129
|
+
|
|
130
|
+
BMK_URL_ST_ABSOLUTE = 0x0001
|
|
131
|
+
BMK_URL_ST_RELATIVE = 0x0002
|
|
132
|
+
|
|
133
|
+
# Bookmark keys
|
|
134
|
+
# = 0x1003
|
|
135
|
+
kBookmarkPath = 0x1004 # Array of path components
|
|
136
|
+
kBookmarkCNIDPath = 0x1005 # Array of CNIDs
|
|
137
|
+
kBookmarkFileProperties = 0x1010 # (CFURL rp flags,
|
|
138
|
+
# CFURL rp flags asked for,
|
|
139
|
+
# 8 bytes NULL)
|
|
140
|
+
kBookmarkFileName = 0x1020
|
|
141
|
+
kBookmarkFileID = 0x1030
|
|
142
|
+
kBookmarkFileCreationDate = 0x1040
|
|
143
|
+
# = 0x1054 # ?
|
|
144
|
+
# = 0x1055 # ?
|
|
145
|
+
# = 0x1056 # ?
|
|
146
|
+
# = 0x1101 # ?
|
|
147
|
+
# = 0x1102 # ?
|
|
148
|
+
kBookmarkTOCPath = 0x2000 # A list of (TOC id, ?) pairs
|
|
149
|
+
kBookmarkVolumePath = 0x2002
|
|
150
|
+
kBookmarkVolumeURL = 0x2005
|
|
151
|
+
kBookmarkVolumeName = 0x2010
|
|
152
|
+
kBookmarkVolumeUUID = 0x2011 # Stored (perversely) as a string
|
|
153
|
+
kBookmarkVolumeSize = 0x2012
|
|
154
|
+
kBookmarkVolumeCreationDate = 0x2013
|
|
155
|
+
kBookmarkVolumeProperties = 0x2020 # (CFURL vp flags,
|
|
156
|
+
# CFURL vp flags asked for,
|
|
157
|
+
# 8 bytes NULL)
|
|
158
|
+
kBookmarkVolumeIsRoot = 0x2030 # True if volume is FS root
|
|
159
|
+
kBookmarkVolumeBookmark = 0x2040 # Embedded bookmark for disk image (TOC id)
|
|
160
|
+
kBookmarkVolumeMountPoint = 0x2050 # A URL
|
|
161
|
+
# = 0x2070
|
|
162
|
+
kBookmarkContainingFolder = 0xc001 # Index of containing folder in path
|
|
163
|
+
kBookmarkUserName = 0xc011 # User that created bookmark
|
|
164
|
+
kBookmarkUID = 0xc012 # UID that created bookmark
|
|
165
|
+
kBookmarkWasFileReference = 0xd001 # True if the URL was a file reference
|
|
166
|
+
kBookmarkCreationOptions = 0xd010
|
|
167
|
+
kBookmarkURLLengths = 0xe003 # See below
|
|
168
|
+
# = 0xf017 # Localized name?
|
|
169
|
+
# = 0xf022
|
|
170
|
+
kBookmarkSecurityExtension = 0xf080
|
|
171
|
+
# = 0xf081
|
|
172
|
+
|
|
173
|
+
# kBookmarkURLLengths is an array that is set if the URL encoded by the
|
|
174
|
+
# bookmark had a base URL; in that case, each entry is the length of the
|
|
175
|
+
# base URL in question. Thus a URL
|
|
176
|
+
#
|
|
177
|
+
# file:///foo/bar/baz blam/blat.html
|
|
178
|
+
#
|
|
179
|
+
# will result in [3, 2], while the URL
|
|
180
|
+
#
|
|
181
|
+
# file:///foo bar/baz blam blat.html
|
|
182
|
+
#
|
|
183
|
+
# would result in [1, 2, 1, 1]
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class Data (object):
|
|
187
|
+
def __init__(self, bytedata=None):
|
|
188
|
+
#: The bytes, stored as a byte string
|
|
189
|
+
self.bytes = bytes(bytedata)
|
|
190
|
+
|
|
191
|
+
def __repr__(self):
|
|
192
|
+
return 'Data(%r)' % self.bytes
|
|
193
|
+
|
|
194
|
+
class URL (object):
|
|
195
|
+
def __init__(self, base, rel=None):
|
|
196
|
+
if rel is not None:
|
|
197
|
+
#: The base URL, if any (a :class:`URL`)
|
|
198
|
+
self.base = base
|
|
199
|
+
#: The rest of the URL (a string)
|
|
200
|
+
self.relative = rel
|
|
201
|
+
else:
|
|
202
|
+
self.base = None
|
|
203
|
+
self.relative = base
|
|
204
|
+
|
|
205
|
+
@property
|
|
206
|
+
def absolute(self):
|
|
207
|
+
"""Return an absolute URL."""
|
|
208
|
+
if self.base is None:
|
|
209
|
+
return self.relative
|
|
210
|
+
else:
|
|
211
|
+
base_abs = self.base.absolute
|
|
212
|
+
return urljoin(self.base.absolute, self.relative)
|
|
213
|
+
|
|
214
|
+
def __repr__(self):
|
|
215
|
+
return 'URL(%r)' % self.absolute
|
|
216
|
+
|
|
217
|
+
class Bookmark (object):
|
|
218
|
+
def __init__(self, tocs=None):
|
|
219
|
+
if tocs is None:
|
|
220
|
+
#: The TOCs for this Bookmark
|
|
221
|
+
self.tocs = []
|
|
222
|
+
else:
|
|
223
|
+
self.tocs = tocs
|
|
224
|
+
|
|
225
|
+
@classmethod
|
|
226
|
+
def _get_item(cls, data, hdrsize, offset):
|
|
227
|
+
offset += hdrsize
|
|
228
|
+
if offset > len(data) - 8:
|
|
229
|
+
raise ValueError('Offset out of range')
|
|
230
|
+
|
|
231
|
+
length,typecode = struct.unpack(b'<II', data[offset:offset+8])
|
|
232
|
+
|
|
233
|
+
if len(data) - offset < 8 + length:
|
|
234
|
+
raise ValueError('Data item truncated')
|
|
235
|
+
|
|
236
|
+
databytes = data[offset+8:offset+8+length]
|
|
237
|
+
|
|
238
|
+
dsubtype = typecode & BMK_DATA_SUBTYPE_MASK
|
|
239
|
+
dtype = typecode & BMK_DATA_TYPE_MASK
|
|
240
|
+
|
|
241
|
+
if dtype == BMK_STRING:
|
|
242
|
+
return databytes.decode('utf-8')
|
|
243
|
+
elif dtype == BMK_DATA:
|
|
244
|
+
return Data(databytes)
|
|
245
|
+
elif dtype == BMK_NUMBER:
|
|
246
|
+
if dsubtype == kCFNumberSInt8Type:
|
|
247
|
+
return ord(databytes[0])
|
|
248
|
+
elif dsubtype == kCFNumberSInt16Type:
|
|
249
|
+
return struct.unpack(b'<h', databytes)[0]
|
|
250
|
+
elif dsubtype == kCFNumberSInt32Type:
|
|
251
|
+
return struct.unpack(b'<i', databytes)[0]
|
|
252
|
+
elif dsubtype == kCFNumberSInt64Type:
|
|
253
|
+
return struct.unpack(b'<q', databytes)[0]
|
|
254
|
+
elif dsubtype == kCFNumberFloat32Type:
|
|
255
|
+
return struct.unpack(b'<f', databytes)[0]
|
|
256
|
+
elif dsubtype == kCFNumberFloat64Type:
|
|
257
|
+
return struct.unpack(b'<d', databytes)[0]
|
|
258
|
+
elif dtype == BMK_DATE:
|
|
259
|
+
# Yes, dates really are stored as *BIG-endian* doubles; everything
|
|
260
|
+
# else is little-endian
|
|
261
|
+
secs = datetime.timedelta(seconds=struct.unpack(b'>d', databytes)[0])
|
|
262
|
+
return osx_epoch + secs
|
|
263
|
+
elif dtype == BMK_BOOLEAN:
|
|
264
|
+
if dsubtype == BMK_BOOLEAN_ST_TRUE:
|
|
265
|
+
return True
|
|
266
|
+
elif dsubtype == BMK_BOOLEAN_ST_FALSE:
|
|
267
|
+
return False
|
|
268
|
+
elif dtype == BMK_UUID:
|
|
269
|
+
return uuid.UUID(bytes=databytes)
|
|
270
|
+
elif dtype == BMK_URL:
|
|
271
|
+
if dsubtype == BMK_URL_ST_ABSOLUTE:
|
|
272
|
+
return URL(databytes.decode('utf-8'))
|
|
273
|
+
elif dsubtype == BMK_URL_ST_RELATIVE:
|
|
274
|
+
baseoff,reloff = struct.unpack(b'<II', databytes)
|
|
275
|
+
base = cls._get_item(data, hdrsize, baseoff)
|
|
276
|
+
rel = cls._get_item(data, hdrsize, reloff)
|
|
277
|
+
return URL(base, rel)
|
|
278
|
+
elif dtype == BMK_ARRAY:
|
|
279
|
+
result = []
|
|
280
|
+
for aoff in xrange(offset+8,offset+8+length,4):
|
|
281
|
+
eltoff, = struct.unpack(b'<I', data[aoff:aoff+4])
|
|
282
|
+
result.append(cls._get_item(data, hdrsize, eltoff))
|
|
283
|
+
return result
|
|
284
|
+
elif dtype == BMK_DICT:
|
|
285
|
+
result = {}
|
|
286
|
+
for eoff in xrange(offset+8,offset+8+length,8):
|
|
287
|
+
keyoff,valoff = struct.unpack(b'<II', data[eoff:eoff+8])
|
|
288
|
+
key = cls._get_item(data, hdrsize, keyoff)
|
|
289
|
+
val = cls._get_item(data, hdrsize, valoff)
|
|
290
|
+
result[key] = val
|
|
291
|
+
return result
|
|
292
|
+
elif dtype == BMK_NULL:
|
|
293
|
+
return None
|
|
294
|
+
|
|
295
|
+
print 'Unknown data type %08x' % typecode
|
|
296
|
+
return (typecode, databytes)
|
|
297
|
+
|
|
298
|
+
@classmethod
|
|
299
|
+
def from_bytes(cls, data):
|
|
300
|
+
"""Create a :class:`Bookmark` given byte data."""
|
|
301
|
+
|
|
302
|
+
if len(data) < 16:
|
|
303
|
+
raise ValueError('Not a bookmark file (too short)')
|
|
304
|
+
|
|
305
|
+
magic,size,dummy,hdrsize = struct.unpack(b'<4sIII', data[0:16])
|
|
306
|
+
|
|
307
|
+
if magic != 'book':
|
|
308
|
+
raise ValueError('Not a bookmark file (bad magic)')
|
|
309
|
+
|
|
310
|
+
if hdrsize < 16:
|
|
311
|
+
raise ValueError('Not a bookmark file (header size too short)')
|
|
312
|
+
|
|
313
|
+
if hdrsize > size:
|
|
314
|
+
raise ValueError('Not a bookmark file (header size too large)')
|
|
315
|
+
|
|
316
|
+
if size != len(data):
|
|
317
|
+
raise ValueError('Not a bookmark file (truncated)')
|
|
318
|
+
|
|
319
|
+
tocoffset, = struct.unpack(b'<I', data[hdrsize:hdrsize+4])
|
|
320
|
+
|
|
321
|
+
tocs = []
|
|
322
|
+
|
|
323
|
+
while tocoffset != 0:
|
|
324
|
+
tocbase = hdrsize + tocoffset
|
|
325
|
+
if tocoffset > size - hdrsize \
|
|
326
|
+
or size - tocbase < 20:
|
|
327
|
+
raise ValueError('TOC offset out of range')
|
|
328
|
+
|
|
329
|
+
tocsize,tocmagic,tocid,nexttoc,toccount \
|
|
330
|
+
= struct.unpack(b'<IIIII',
|
|
331
|
+
data[tocbase:tocbase+20])
|
|
332
|
+
|
|
333
|
+
if tocmagic != 0xfffffffe:
|
|
334
|
+
break
|
|
335
|
+
|
|
336
|
+
tocsize += 8
|
|
337
|
+
|
|
338
|
+
if size - tocbase < tocsize:
|
|
339
|
+
raise ValueError('TOC truncated')
|
|
340
|
+
|
|
341
|
+
if tocsize < 12 * toccount:
|
|
342
|
+
raise ValueError('TOC entries overrun TOC size')
|
|
343
|
+
|
|
344
|
+
toc = {}
|
|
345
|
+
for n in xrange(0,toccount):
|
|
346
|
+
ebase = tocbase + 20 + 12 * n
|
|
347
|
+
eid,eoffset,edummy = struct.unpack(b'<III',
|
|
348
|
+
data[ebase:ebase+12])
|
|
349
|
+
|
|
350
|
+
if eid & 0x80000000:
|
|
351
|
+
eid = cls._get_item(data, hdrsize, eid & 0x7fffffff)
|
|
352
|
+
|
|
353
|
+
toc[eid] = cls._get_item(data, hdrsize, eoffset)
|
|
354
|
+
|
|
355
|
+
tocs.append((tocid, toc))
|
|
356
|
+
|
|
357
|
+
tocoffset = nexttoc
|
|
358
|
+
|
|
359
|
+
return cls(tocs)
|
|
360
|
+
|
|
361
|
+
def __getitem__(self, key):
|
|
362
|
+
for tid,toc in self.tocs:
|
|
363
|
+
if key in toc:
|
|
364
|
+
return toc[key]
|
|
365
|
+
raise KeyError('Key not found')
|
|
366
|
+
|
|
367
|
+
def __setitem__(self, key, value):
|
|
368
|
+
if len(self.tocs) == 0:
|
|
369
|
+
self.tocs = [(1, {})]
|
|
370
|
+
self.tocs[0][1][key] = value
|
|
371
|
+
|
|
372
|
+
def get(self, key, default=None):
|
|
373
|
+
"""Lookup the value for a given key, returning a default if not
|
|
374
|
+
present."""
|
|
375
|
+
for tid,toc in self.tocs:
|
|
376
|
+
if key in toc:
|
|
377
|
+
return toc[key]
|
|
378
|
+
return default
|
|
379
|
+
|
|
380
|
+
@classmethod
|
|
381
|
+
def _encode_item(cls, item, offset):
|
|
382
|
+
if item is True:
|
|
383
|
+
result = struct.pack(b'<II', 0, BMK_BOOLEAN | BMK_BOOLEAN_ST_TRUE)
|
|
384
|
+
elif item is False:
|
|
385
|
+
result = struct.pack(b'<II', 0, BMK_BOOLEAN | BMK_BOOLEAN_ST_FALSE)
|
|
386
|
+
elif isinstance(item, unicode):
|
|
387
|
+
encoded = item.encode('utf-8')
|
|
388
|
+
result = (struct.pack(b'<II', len(encoded), BMK_STRING | BMK_ST_ONE)
|
|
389
|
+
+ encoded)
|
|
390
|
+
elif isinstance(item, bytes):
|
|
391
|
+
result = (struct.pack(b'<II', len(item), BMK_STRING | BMK_ST_ONE)
|
|
392
|
+
+ item)
|
|
393
|
+
elif isinstance(item, Data):
|
|
394
|
+
result = (struct.pack(b'<II', len(item.bytes),
|
|
395
|
+
BMK_DATA | BMK_ST_ONE)
|
|
396
|
+
+ bytes(item.bytes))
|
|
397
|
+
elif isinstance(item, bytearray):
|
|
398
|
+
result = (struct.pack(b'<II', len(item),
|
|
399
|
+
BMK_DATA | BMK_ST_ONE)
|
|
400
|
+
+ bytes(item))
|
|
401
|
+
elif isinstance(item, int) or isinstance(item, long):
|
|
402
|
+
if item > -0x80000000 and item < 0x7fffffff:
|
|
403
|
+
result = struct.pack(b'<IIi', 4,
|
|
404
|
+
BMK_NUMBER | kCFNumberSInt32Type, item)
|
|
405
|
+
else:
|
|
406
|
+
result = struct.pack(b'<IIq', 8,
|
|
407
|
+
BMK_NUMBER | kCFNumberSInt64Type, item)
|
|
408
|
+
elif isinstance(item, float):
|
|
409
|
+
result = struct.pack(b'<IId', 8,
|
|
410
|
+
BMK_NUMBER | kCFNumberFloat64Type, item)
|
|
411
|
+
elif isinstance(item, datetime.datetime):
|
|
412
|
+
secs = item - osx_epoch
|
|
413
|
+
result = struct.pack(b'<II', 8, BMK_DATE | BMK_ST_ZERO) \
|
|
414
|
+
+ struct.pack(b'>d', float(secs.total_seconds()))
|
|
415
|
+
elif isinstance(item, uuid.UUID):
|
|
416
|
+
result = struct.pack(b'<II', 16, BMK_UUID | BMK_ST_ONE) \
|
|
417
|
+
+ item.bytes
|
|
418
|
+
elif isinstance(item, URL):
|
|
419
|
+
if item.base:
|
|
420
|
+
baseoff = offset + 16
|
|
421
|
+
reloff, baseenc = cls._encode_item(item.base, baseoff)
|
|
422
|
+
xoffset, relenc = cls._encode_item(item.relative, reloff)
|
|
423
|
+
result = b''.join([
|
|
424
|
+
struct.pack(b'<IIII', 8, BMK_URL | BMK_URL_ST_RELATIVE,
|
|
425
|
+
baseoff, reloff),
|
|
426
|
+
baseenc,
|
|
427
|
+
relenc])
|
|
428
|
+
else:
|
|
429
|
+
encoded = item.relative.encode('utf-8')
|
|
430
|
+
result = struct.pack(b'<II', len(encoded),
|
|
431
|
+
BMK_URL | BMK_URL_ST_ABSOLUTE) + encoded
|
|
432
|
+
elif isinstance(item, list):
|
|
433
|
+
ioffset = offset + 8 + len(item) * 4
|
|
434
|
+
result = [struct.pack(b'<II', len(item) * 4, BMK_ARRAY | BMK_ST_ONE)]
|
|
435
|
+
enc = []
|
|
436
|
+
for elt in item:
|
|
437
|
+
result.append(struct.pack(b'<I', ioffset))
|
|
438
|
+
ioffset, ienc = cls._encode_item(elt, ioffset)
|
|
439
|
+
enc.append(ienc)
|
|
440
|
+
result = b''.join(result + enc)
|
|
441
|
+
elif isinstance(item, dict):
|
|
442
|
+
ioffset = offset + 8 + len(item) * 8
|
|
443
|
+
result = [struct.pack(b'<II', len(item) * 8, BMK_DICT | BMK_ST_ONE)]
|
|
444
|
+
enc = []
|
|
445
|
+
for k,v in item.iteritems():
|
|
446
|
+
result.append(struct.pack(b'<I', ioffset))
|
|
447
|
+
ioffset, ienc = cls._encode_item(k, ioffset)
|
|
448
|
+
enc.append(ienc)
|
|
449
|
+
result.append(struct.pack(b'<I', ioffset))
|
|
450
|
+
ioffset, ienc = cls._encode_item(v, ioffset)
|
|
451
|
+
enc.append(ienc)
|
|
452
|
+
result = b''.join(result + enc)
|
|
453
|
+
elif item is None:
|
|
454
|
+
result = struct.pack(b'<II', 0, BMK_NULL | BMK_ST_ONE)
|
|
455
|
+
else:
|
|
456
|
+
raise ValueError('Unknown item type when encoding: %s' % item)
|
|
457
|
+
|
|
458
|
+
offset += len(result)
|
|
459
|
+
|
|
460
|
+
# Pad to a multiple of 4 bytes
|
|
461
|
+
if offset & 3:
|
|
462
|
+
extra = 4 - (offset & 3)
|
|
463
|
+
result += b'\0' * extra
|
|
464
|
+
offset += extra
|
|
465
|
+
|
|
466
|
+
return (offset, result)
|
|
467
|
+
|
|
468
|
+
def to_bytes(self):
|
|
469
|
+
"""Convert this :class:`Bookmark` to a byte representation."""
|
|
470
|
+
|
|
471
|
+
result = []
|
|
472
|
+
tocs = []
|
|
473
|
+
offset = 4 # For the offset to the first TOC
|
|
474
|
+
|
|
475
|
+
# Generate the data and build the TOCs
|
|
476
|
+
for tid,toc in self.tocs:
|
|
477
|
+
entries = []
|
|
478
|
+
|
|
479
|
+
for k,v in toc.iteritems():
|
|
480
|
+
if isinstance(k, basestring):
|
|
481
|
+
noffset = offset
|
|
482
|
+
voffset, enc = self._encode_item(k, offset)
|
|
483
|
+
result.append(enc)
|
|
484
|
+
offset, enc = self._encode_item(v, voffset)
|
|
485
|
+
result.append(enc)
|
|
486
|
+
entries.append((noffset | 0x80000000, voffset))
|
|
487
|
+
else:
|
|
488
|
+
entries.append((k, offset))
|
|
489
|
+
offset, enc = self._encode_item(v, offset)
|
|
490
|
+
result.append(enc)
|
|
491
|
+
|
|
492
|
+
# TOC entries must be sorted - CoreServicesInternal does a
|
|
493
|
+
# binary search to find data
|
|
494
|
+
entries.sort()
|
|
495
|
+
|
|
496
|
+
tocs.append((tid, b''.join([struct.pack('<III',k,o,0)
|
|
497
|
+
for k,o in entries])))
|
|
498
|
+
|
|
499
|
+
first_toc_offset = offset
|
|
500
|
+
|
|
501
|
+
# Now generate the TOC headers
|
|
502
|
+
for ndx,toc in enumerate(tocs):
|
|
503
|
+
tid, data = toc
|
|
504
|
+
if ndx == len(tocs) - 1:
|
|
505
|
+
next_offset = 0
|
|
506
|
+
else:
|
|
507
|
+
next_offset = offset + 20 + len(data)
|
|
508
|
+
|
|
509
|
+
result.append(struct.pack(b'<IIIII', len(data) - 8,
|
|
510
|
+
0xfffffffe,
|
|
511
|
+
tid,
|
|
512
|
+
next_offset,
|
|
513
|
+
len(data) / 12))
|
|
514
|
+
result.append(data)
|
|
515
|
+
|
|
516
|
+
offset += 20 + len(data)
|
|
517
|
+
|
|
518
|
+
# Finally, add the header (and the first TOC offset, which isn't part
|
|
519
|
+
# of the header, but goes just after it)
|
|
520
|
+
header = struct.pack(b'<4sIIIQQQQI', b'book',
|
|
521
|
+
offset + 48,
|
|
522
|
+
0x10040000,
|
|
523
|
+
48,
|
|
524
|
+
0, 0, 0, 0, first_toc_offset)
|
|
525
|
+
|
|
526
|
+
result.insert(0, header)
|
|
527
|
+
|
|
528
|
+
return b''.join(result)
|
|
529
|
+
|
|
530
|
+
@classmethod
|
|
531
|
+
def for_file(cls, path):
|
|
532
|
+
"""Construct a :class:`Bookmark` for a given file."""
|
|
533
|
+
|
|
534
|
+
# Find the filesystem
|
|
535
|
+
st = osx.statfs(path)
|
|
536
|
+
vol_path = st.f_mntonname
|
|
537
|
+
|
|
538
|
+
# Grab its attributes
|
|
539
|
+
attrs = [osx.ATTR_CMN_CRTIME,
|
|
540
|
+
osx.ATTR_VOL_SIZE
|
|
541
|
+
| osx.ATTR_VOL_NAME
|
|
542
|
+
| osx.ATTR_VOL_UUID,
|
|
543
|
+
0, 0, 0]
|
|
544
|
+
volinfo = osx.getattrlist(vol_path, attrs, 0)
|
|
545
|
+
|
|
546
|
+
vol_crtime = volinfo[0]
|
|
547
|
+
vol_size = volinfo[1]
|
|
548
|
+
vol_name = volinfo[2]
|
|
549
|
+
vol_uuid = volinfo[3]
|
|
550
|
+
|
|
551
|
+
# Also grab various attributes of the file
|
|
552
|
+
attrs = [(osx.ATTR_CMN_OBJTYPE
|
|
553
|
+
| osx.ATTR_CMN_CRTIME
|
|
554
|
+
| osx.ATTR_CMN_FILEID), 0, 0, 0, 0]
|
|
555
|
+
info = osx.getattrlist(path, attrs, osx.FSOPT_NOFOLLOW)
|
|
556
|
+
|
|
557
|
+
cnid = info[2]
|
|
558
|
+
crtime = info[1]
|
|
559
|
+
|
|
560
|
+
if info[0] == osx.VREG:
|
|
561
|
+
flags = kCFURLResourceIsRegularFile
|
|
562
|
+
elif info[0] == osx.VDIR:
|
|
563
|
+
flags = kCFURLResourceIsDirectory
|
|
564
|
+
elif info[0] == osx.VLNK:
|
|
565
|
+
flags = kCFURLResourceIsSymbolicLink
|
|
566
|
+
else:
|
|
567
|
+
flags = kCFURLResourceIsRegularFile
|
|
568
|
+
|
|
569
|
+
dirname, filename = os.path.split(path)
|
|
570
|
+
|
|
571
|
+
relcount = 0
|
|
572
|
+
if not os.path.isabs(dirname):
|
|
573
|
+
curdir = os.getcwd()
|
|
574
|
+
head, tail = os.path.split(curdir)
|
|
575
|
+
relcount = 0
|
|
576
|
+
while head and tail:
|
|
577
|
+
relcount += 1
|
|
578
|
+
head, tail = os.path.split(head)
|
|
579
|
+
dirname = os.path.join(curdir, dirname)
|
|
580
|
+
|
|
581
|
+
foldername = os.path.basename(dirname)
|
|
582
|
+
|
|
583
|
+
rel_path = os.path.relpath(path, vol_path)
|
|
584
|
+
|
|
585
|
+
# Build the path arrays
|
|
586
|
+
name_path = []
|
|
587
|
+
cnid_path = []
|
|
588
|
+
head, tail = os.path.split(rel_path)
|
|
589
|
+
if not tail:
|
|
590
|
+
head, tail = os.path.split(head)
|
|
591
|
+
while head or tail:
|
|
592
|
+
if head:
|
|
593
|
+
attrs = [osx.ATTR_CMN_FILEID, 0, 0, 0, 0]
|
|
594
|
+
info = osx.getattrlist(os.path.join(vol_path, head), attrs, 0)
|
|
595
|
+
cnid_path.insert(0, info[0])
|
|
596
|
+
head, tail = os.path.split(head)
|
|
597
|
+
name_path.insert(0, tail)
|
|
598
|
+
else:
|
|
599
|
+
head, tail = os.path.split(head)
|
|
600
|
+
name_path.append(filename)
|
|
601
|
+
cnid_path.append(cnid)
|
|
602
|
+
|
|
603
|
+
url_lengths = [relcount, len(name_path) - relcount]
|
|
604
|
+
|
|
605
|
+
fileprops = Data(struct.pack(b'<QQQ', flags, 0x0f, 0))
|
|
606
|
+
volprops = Data(struct.pack(b'<QQQ', 0x81 | kCFURLVolumeSupportsPersistentIDs,
|
|
607
|
+
0x13ef | kCFURLVolumeSupportsPersistentIDs, 0))
|
|
608
|
+
|
|
609
|
+
toc = {
|
|
610
|
+
kBookmarkPath: name_path,
|
|
611
|
+
kBookmarkCNIDPath: cnid_path,
|
|
612
|
+
kBookmarkFileCreationDate: crtime,
|
|
613
|
+
kBookmarkFileProperties: fileprops,
|
|
614
|
+
kBookmarkContainingFolder: len(name_path) - 2,
|
|
615
|
+
kBookmarkVolumePath: vol_path,
|
|
616
|
+
kBookmarkVolumeIsRoot: vol_path == '/',
|
|
617
|
+
kBookmarkVolumeURL: URL('file://' + vol_path),
|
|
618
|
+
kBookmarkVolumeName: vol_name,
|
|
619
|
+
kBookmarkVolumeSize: vol_size,
|
|
620
|
+
kBookmarkVolumeCreationDate: vol_crtime,
|
|
621
|
+
kBookmarkVolumeUUID: str(vol_uuid).upper(),
|
|
622
|
+
kBookmarkVolumeProperties: volprops,
|
|
623
|
+
kBookmarkCreationOptions: 512,
|
|
624
|
+
kBookmarkWasFileReference: True,
|
|
625
|
+
kBookmarkUserName: 'alastair',
|
|
626
|
+
kBookmarkUID: 501,
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
if relcount:
|
|
630
|
+
toc[kBookmarkURLLengths] = url_lengths
|
|
631
|
+
|
|
632
|
+
return Bookmark([(1, toc)])
|
|
633
|
+
|
|
634
|
+
def __repr__(self):
|
|
635
|
+
result = ['Bookmark([']
|
|
636
|
+
for tid,toc in self.tocs:
|
|
637
|
+
result.append('(0x%x, {\n' % tid)
|
|
638
|
+
for k,v in toc.iteritems():
|
|
639
|
+
if isinstance(k, basestring):
|
|
640
|
+
kf = repr(k)
|
|
641
|
+
else:
|
|
642
|
+
kf = '0x%04x' % k
|
|
643
|
+
result.append(' %s: %r\n' % (kf, v))
|
|
644
|
+
result.append('}),\n')
|
|
645
|
+
result.append('])')
|
|
646
|
+
|
|
647
|
+
return ''.join(result)
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
Metadata-Version: 1.1
|
|
2
2
|
Name: mac-alias
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: Generate/parse Mac OS Alias records from Python
|
|
5
5
|
Home-page: http://alastairs-place.net/projects/mac_alias
|
|
6
6
|
Author: Alastair Houghton
|
|
7
7
|
Author-email: alastair@alastairs-place.net
|
|
8
8
|
License: MIT License
|
|
9
|
-
Description:
|
|
10
|
-
|
|
9
|
+
Description: ``mac_alias`` lets you generate or read binary Alias and Bookmark records
|
|
10
|
+
from Python code.
|
|
11
|
+
|
|
11
12
|
While it is written in pure Python, some OS X specific code is required to
|
|
12
|
-
generate a proper Alias record for a given file, so this module
|
|
13
|
-
is not portable to other platforms.
|
|
13
|
+
generate a proper Alias or Bookmark record for a given file, so this module
|
|
14
|
+
currently is not portable to other platforms.
|
|
14
15
|
|
|
15
16
|
Credit is due to Wim Lewis <wiml@hhhh.org>, whose work on reverse engineering
|
|
16
|
-
the alias format was extremely useful in writing this code.
|
|
17
|
+
the alias format was extremely useful in writing this code. Alastair Houghton
|
|
18
|
+
<alastair@alastairs-place.net> was responsible for reverse engineering the
|
|
19
|
+
bookmark format.
|
|
20
|
+
|
|
17
21
|
Platform: darwin
|
|
18
22
|
Classifier: Development Status :: 5 - Production/Stable
|
|
19
23
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
from setuptools import setup
|
|
3
3
|
|
|
4
|
+
with open('README.rst', 'r') as f:
|
|
5
|
+
longdesc = f.read().decode('utf-8')
|
|
6
|
+
|
|
4
7
|
setup(name='mac_alias',
|
|
5
|
-
version='
|
|
8
|
+
version='2.0.0',
|
|
6
9
|
description='Generate/parse Mac OS Alias records from Python',
|
|
7
|
-
long_description=
|
|
8
|
-
``mac_alias`` lets you generate or read binary Alias records from Python code.
|
|
9
|
-
While it is written in pure Python, some OS X specific code is required to
|
|
10
|
-
generate a proper Alias record for a given file, so this module currently
|
|
11
|
-
is not portable to other platforms.
|
|
12
|
-
|
|
13
|
-
Credit is due to Wim Lewis <wiml@hhhh.org>, whose work on reverse engineering
|
|
14
|
-
the alias format was extremely useful in writing this code.""",
|
|
10
|
+
long_description=longdesc,
|
|
15
11
|
author='Alastair Houghton',
|
|
16
12
|
author_email='alastair@alastairs-place.net',
|
|
17
13
|
url='http://alastairs-place.net/projects/mac_alias',
|
|
@@ -24,4 +20,3 @@ the alias format was extremely useful in writing this code.""",
|
|
|
24
20
|
'Topic :: Desktop Environment',
|
|
25
21
|
'Topic :: Software Development :: Libraries :: Python Modules'],
|
|
26
22
|
provides=['mac_alias'])
|
|
27
|
-
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
from .alias import *
|
|
2
|
-
|
|
3
|
-
__all__ = [ 'ALIAS_KIND_FILE', 'ALIAS_KIND_FOLDER',
|
|
4
|
-
'ALIAS_HFS_VOLUME_SIGNATURE',
|
|
5
|
-
'ALIAS_FIXED_DISK', 'ALIAS_NETWORK_DISK', 'ALIAS_400KB_FLOPPY_DISK',
|
|
6
|
-
'ALIAS_800KB_FLOPPY_DISK', 'ALIAS_1_44MB_FLOPPY_DISK',
|
|
7
|
-
'ALIAS_EJECTABLE_DISK',
|
|
8
|
-
'ALIAS_NO_CNID',
|
|
9
|
-
'AppleShareInfo',
|
|
10
|
-
'VolumeInfo',
|
|
11
|
-
'TargetInfo',
|
|
12
|
-
'Alias' ]
|
|
13
|
-
|
|
14
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|