s3_cmd_bin 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +28 -0
- data/Rakefile +1 -0
- data/lib/s3_cmd_bin/version.rb +3 -0
- data/lib/s3_cmd_bin.rb +15 -0
- data/resources/ChangeLog +1462 -0
- data/resources/INSTALL +97 -0
- data/resources/LICENSE +339 -0
- data/resources/MANIFEST.in +2 -0
- data/resources/Makefile +4 -0
- data/resources/NEWS +234 -0
- data/resources/README +342 -0
- data/resources/S3/ACL.py +224 -0
- data/resources/S3/ACL.pyc +0 -0
- data/resources/S3/AccessLog.py +92 -0
- data/resources/S3/AccessLog.pyc +0 -0
- data/resources/S3/BidirMap.py +42 -0
- data/resources/S3/BidirMap.pyc +0 -0
- data/resources/S3/CloudFront.py +773 -0
- data/resources/S3/CloudFront.pyc +0 -0
- data/resources/S3/Config.py +294 -0
- data/resources/S3/Config.pyc +0 -0
- data/resources/S3/ConnMan.py +71 -0
- data/resources/S3/ConnMan.pyc +0 -0
- data/resources/S3/Exceptions.py +88 -0
- data/resources/S3/Exceptions.pyc +0 -0
- data/resources/S3/FileDict.py +53 -0
- data/resources/S3/FileDict.pyc +0 -0
- data/resources/S3/FileLists.py +517 -0
- data/resources/S3/FileLists.pyc +0 -0
- data/resources/S3/HashCache.py +53 -0
- data/resources/S3/HashCache.pyc +0 -0
- data/resources/S3/MultiPart.py +137 -0
- data/resources/S3/MultiPart.pyc +0 -0
- data/resources/S3/PkgInfo.py +14 -0
- data/resources/S3/PkgInfo.pyc +0 -0
- data/resources/S3/Progress.py +173 -0
- data/resources/S3/Progress.pyc +0 -0
- data/resources/S3/S3.py +979 -0
- data/resources/S3/S3.pyc +0 -0
- data/resources/S3/S3Uri.py +223 -0
- data/resources/S3/S3Uri.pyc +0 -0
- data/resources/S3/SimpleDB.py +178 -0
- data/resources/S3/SortedDict.py +66 -0
- data/resources/S3/SortedDict.pyc +0 -0
- data/resources/S3/Utils.py +462 -0
- data/resources/S3/Utils.pyc +0 -0
- data/resources/S3/__init__.py +0 -0
- data/resources/S3/__init__.pyc +0 -0
- data/resources/TODO +52 -0
- data/resources/artwork/AtomicClockRadio.ttf +0 -0
- data/resources/artwork/TypeRa.ttf +0 -0
- data/resources/artwork/site-top-full-size.xcf +0 -0
- data/resources/artwork/site-top-label-download.png +0 -0
- data/resources/artwork/site-top-label-s3cmd.png +0 -0
- data/resources/artwork/site-top-label-s3sync.png +0 -0
- data/resources/artwork/site-top-s3tools-logo.png +0 -0
- data/resources/artwork/site-top.jpg +0 -0
- data/resources/artwork/site-top.png +0 -0
- data/resources/artwork/site-top.xcf +0 -0
- data/resources/format-manpage.pl +196 -0
- data/resources/magic +63 -0
- data/resources/run-tests.py +537 -0
- data/resources/s3cmd +2116 -0
- data/resources/s3cmd.1 +435 -0
- data/resources/s3db +55 -0
- data/resources/setup.cfg +2 -0
- data/resources/setup.py +80 -0
- data/resources/testsuite.tar.gz +0 -0
- data/resources/upload-to-sf.sh +7 -0
- data/s3_cmd_bin.gemspec +23 -0
- metadata +152 -0
data/resources/S3/S3.pyc
ADDED
Binary file
|
@@ -0,0 +1,223 @@
|
|
1
|
+
## Amazon S3 manager
|
2
|
+
## Author: Michal Ludvig <michal@logix.cz>
|
3
|
+
## http://www.logix.cz/michal
|
4
|
+
## License: GPL Version 2
|
5
|
+
|
6
|
+
import os
|
7
|
+
import re
|
8
|
+
import sys
|
9
|
+
from BidirMap import BidirMap
|
10
|
+
from logging import debug
|
11
|
+
import S3
|
12
|
+
from Utils import unicodise, check_bucket_name_dns_conformity
|
13
|
+
import Config
|
14
|
+
|
15
|
+
class S3Uri(object):
|
16
|
+
type = None
|
17
|
+
_subclasses = None
|
18
|
+
|
19
|
+
def __new__(self, string):
|
20
|
+
if not self._subclasses:
|
21
|
+
## Generate a list of all subclasses of S3Uri
|
22
|
+
self._subclasses = []
|
23
|
+
dict = sys.modules[__name__].__dict__
|
24
|
+
for something in dict:
|
25
|
+
if type(dict[something]) is not type(self):
|
26
|
+
continue
|
27
|
+
if issubclass(dict[something], self) and dict[something] != self:
|
28
|
+
self._subclasses.append(dict[something])
|
29
|
+
for subclass in self._subclasses:
|
30
|
+
try:
|
31
|
+
instance = object.__new__(subclass)
|
32
|
+
instance.__init__(string)
|
33
|
+
return instance
|
34
|
+
except ValueError, e:
|
35
|
+
continue
|
36
|
+
raise ValueError("%s: not a recognized URI" % string)
|
37
|
+
|
38
|
+
def __str__(self):
|
39
|
+
return self.uri()
|
40
|
+
|
41
|
+
def __unicode__(self):
|
42
|
+
return self.uri()
|
43
|
+
|
44
|
+
def __repr__(self):
|
45
|
+
return "<%s: %s>" % (self.__class__.__name__, self.__unicode__())
|
46
|
+
|
47
|
+
def public_url(self):
|
48
|
+
raise ValueError("This S3 URI does not have Anonymous URL representation")
|
49
|
+
|
50
|
+
def basename(self):
|
51
|
+
return self.__unicode__().split("/")[-1]
|
52
|
+
|
53
|
+
class S3UriS3(S3Uri):
|
54
|
+
type = "s3"
|
55
|
+
_re = re.compile("^s3://([^/]+)/?(.*)", re.IGNORECASE)
|
56
|
+
def __init__(self, string):
|
57
|
+
match = self._re.match(string)
|
58
|
+
if not match:
|
59
|
+
raise ValueError("%s: not a S3 URI" % string)
|
60
|
+
groups = match.groups()
|
61
|
+
self._bucket = groups[0]
|
62
|
+
self._object = unicodise(groups[1])
|
63
|
+
|
64
|
+
def bucket(self):
|
65
|
+
return self._bucket
|
66
|
+
|
67
|
+
def object(self):
|
68
|
+
return self._object
|
69
|
+
|
70
|
+
def has_bucket(self):
|
71
|
+
return bool(self._bucket)
|
72
|
+
|
73
|
+
def has_object(self):
|
74
|
+
return bool(self._object)
|
75
|
+
|
76
|
+
def uri(self):
|
77
|
+
return "/".join(["s3:/", self._bucket, self._object])
|
78
|
+
|
79
|
+
def is_dns_compatible(self):
|
80
|
+
return check_bucket_name_dns_conformity(self._bucket)
|
81
|
+
|
82
|
+
def public_url(self):
|
83
|
+
if self.is_dns_compatible():
|
84
|
+
return "http://%s.%s/%s" % (self._bucket, Config.Config().host_base, self._object)
|
85
|
+
else:
|
86
|
+
return "http://%s/%s/%s" % (self._bucket, Config.Config().host_base, self._object)
|
87
|
+
|
88
|
+
def host_name(self):
|
89
|
+
if self.is_dns_compatible():
|
90
|
+
return "%s.s3.amazonaws.com" % (self._bucket)
|
91
|
+
else:
|
92
|
+
return "s3.amazonaws.com"
|
93
|
+
|
94
|
+
@staticmethod
|
95
|
+
def compose_uri(bucket, object = ""):
|
96
|
+
return "s3://%s/%s" % (bucket, object)
|
97
|
+
|
98
|
+
@staticmethod
|
99
|
+
def httpurl_to_s3uri(http_url):
|
100
|
+
m=re.match("(https?://)?([^/]+)/?(.*)", http_url, re.IGNORECASE)
|
101
|
+
hostname, object = m.groups()[1:]
|
102
|
+
hostname = hostname.lower()
|
103
|
+
if hostname == "s3.amazonaws.com":
|
104
|
+
## old-style url: http://s3.amazonaws.com/bucket/object
|
105
|
+
if object.count("/") == 0:
|
106
|
+
## no object given
|
107
|
+
bucket = object
|
108
|
+
object = ""
|
109
|
+
else:
|
110
|
+
## bucket/object
|
111
|
+
bucket, object = object.split("/", 1)
|
112
|
+
elif hostname.endswith(".s3.amazonaws.com"):
|
113
|
+
## new-style url: http://bucket.s3.amazonaws.com/object
|
114
|
+
bucket = hostname[:-(len(".s3.amazonaws.com"))]
|
115
|
+
else:
|
116
|
+
raise ValueError("Unable to parse URL: %s" % http_url)
|
117
|
+
return S3Uri("s3://%(bucket)s/%(object)s" % {
|
118
|
+
'bucket' : bucket,
|
119
|
+
'object' : object })
|
120
|
+
|
121
|
+
class S3UriS3FS(S3Uri):
|
122
|
+
type = "s3fs"
|
123
|
+
_re = re.compile("^s3fs://([^/]*)/?(.*)", re.IGNORECASE)
|
124
|
+
def __init__(self, string):
|
125
|
+
match = self._re.match(string)
|
126
|
+
if not match:
|
127
|
+
raise ValueError("%s: not a S3fs URI" % string)
|
128
|
+
groups = match.groups()
|
129
|
+
self._fsname = groups[0]
|
130
|
+
self._path = unicodise(groups[1]).split("/")
|
131
|
+
|
132
|
+
def fsname(self):
|
133
|
+
return self._fsname
|
134
|
+
|
135
|
+
def path(self):
|
136
|
+
return "/".join(self._path)
|
137
|
+
|
138
|
+
def uri(self):
|
139
|
+
return "/".join(["s3fs:/", self._fsname, self.path()])
|
140
|
+
|
141
|
+
class S3UriFile(S3Uri):
|
142
|
+
type = "file"
|
143
|
+
_re = re.compile("^(\w+://)?(.*)")
|
144
|
+
def __init__(self, string):
|
145
|
+
match = self._re.match(string)
|
146
|
+
groups = match.groups()
|
147
|
+
if groups[0] not in (None, "file://"):
|
148
|
+
raise ValueError("%s: not a file:// URI" % string)
|
149
|
+
self._path = unicodise(groups[1]).split("/")
|
150
|
+
|
151
|
+
def path(self):
|
152
|
+
return "/".join(self._path)
|
153
|
+
|
154
|
+
def uri(self):
|
155
|
+
return "/".join(["file:/", self.path()])
|
156
|
+
|
157
|
+
def isdir(self):
|
158
|
+
return os.path.isdir(self.path())
|
159
|
+
|
160
|
+
def dirname(self):
|
161
|
+
return os.path.dirname(self.path())
|
162
|
+
|
163
|
+
class S3UriCloudFront(S3Uri):
|
164
|
+
type = "cf"
|
165
|
+
_re = re.compile("^cf://([^/]*)/*(.*)", re.IGNORECASE)
|
166
|
+
def __init__(self, string):
|
167
|
+
match = self._re.match(string)
|
168
|
+
if not match:
|
169
|
+
raise ValueError("%s: not a CloudFront URI" % string)
|
170
|
+
groups = match.groups()
|
171
|
+
self._dist_id = groups[0]
|
172
|
+
self._request_id = groups[1] != "/" and groups[1] or None
|
173
|
+
|
174
|
+
def dist_id(self):
|
175
|
+
return self._dist_id
|
176
|
+
|
177
|
+
def request_id(self):
|
178
|
+
return self._request_id
|
179
|
+
|
180
|
+
def uri(self):
|
181
|
+
uri = "cf://" + self.dist_id()
|
182
|
+
if self.request_id():
|
183
|
+
uri += "/" + self.request_id()
|
184
|
+
return uri
|
185
|
+
|
186
|
+
if __name__ == "__main__":
|
187
|
+
uri = S3Uri("s3://bucket/object")
|
188
|
+
print "type() =", type(uri)
|
189
|
+
print "uri =", uri
|
190
|
+
print "uri.type=", uri.type
|
191
|
+
print "bucket =", uri.bucket()
|
192
|
+
print "object =", uri.object()
|
193
|
+
print
|
194
|
+
|
195
|
+
uri = S3Uri("s3://bucket")
|
196
|
+
print "type() =", type(uri)
|
197
|
+
print "uri =", uri
|
198
|
+
print "uri.type=", uri.type
|
199
|
+
print "bucket =", uri.bucket()
|
200
|
+
print
|
201
|
+
|
202
|
+
uri = S3Uri("s3fs://filesystem1/path/to/remote/file.txt")
|
203
|
+
print "type() =", type(uri)
|
204
|
+
print "uri =", uri
|
205
|
+
print "uri.type=", uri.type
|
206
|
+
print "path =", uri.path()
|
207
|
+
print
|
208
|
+
|
209
|
+
uri = S3Uri("/path/to/local/file.txt")
|
210
|
+
print "type() =", type(uri)
|
211
|
+
print "uri =", uri
|
212
|
+
print "uri.type=", uri.type
|
213
|
+
print "path =", uri.path()
|
214
|
+
print
|
215
|
+
|
216
|
+
uri = S3Uri("cf://1234567890ABCD/")
|
217
|
+
print "type() =", type(uri)
|
218
|
+
print "uri =", uri
|
219
|
+
print "uri.type=", uri.type
|
220
|
+
print "dist_id =", uri.dist_id()
|
221
|
+
print
|
222
|
+
|
223
|
+
# vim:et:ts=4:sts=4:ai
|
Binary file
|
@@ -0,0 +1,178 @@
|
|
1
|
+
## Amazon SimpleDB library
|
2
|
+
## Author: Michal Ludvig <michal@logix.cz>
|
3
|
+
## http://www.logix.cz/michal
|
4
|
+
## License: GPL Version 2
|
5
|
+
|
6
|
+
"""
|
7
|
+
Low-level class for working with Amazon SimpleDB
|
8
|
+
"""
|
9
|
+
|
10
|
+
import time
|
11
|
+
import urllib
|
12
|
+
import base64
|
13
|
+
import hmac
|
14
|
+
import sha
|
15
|
+
import httplib
|
16
|
+
from logging import debug, info, warning, error
|
17
|
+
|
18
|
+
from Utils import convertTupleListToDict
|
19
|
+
from SortedDict import SortedDict
|
20
|
+
from Exceptions import *
|
21
|
+
|
22
|
+
class SimpleDB(object):
|
23
|
+
# API Version
|
24
|
+
# See http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/
|
25
|
+
Version = "2007-11-07"
|
26
|
+
SignatureVersion = 1
|
27
|
+
|
28
|
+
def __init__(self, config):
|
29
|
+
self.config = config
|
30
|
+
|
31
|
+
## ------------------------------------------------
|
32
|
+
## Methods implementing SimpleDB API
|
33
|
+
## ------------------------------------------------
|
34
|
+
|
35
|
+
def ListDomains(self, MaxNumberOfDomains = 100):
|
36
|
+
'''
|
37
|
+
Lists all domains associated with our Access Key. Returns
|
38
|
+
domain names up to the limit set by MaxNumberOfDomains.
|
39
|
+
'''
|
40
|
+
parameters = SortedDict()
|
41
|
+
parameters['MaxNumberOfDomains'] = MaxNumberOfDomains
|
42
|
+
return self.send_request("ListDomains", DomainName = None, parameters = parameters)
|
43
|
+
|
44
|
+
def CreateDomain(self, DomainName):
|
45
|
+
return self.send_request("CreateDomain", DomainName = DomainName)
|
46
|
+
|
47
|
+
def DeleteDomain(self, DomainName):
|
48
|
+
return self.send_request("DeleteDomain", DomainName = DomainName)
|
49
|
+
|
50
|
+
def PutAttributes(self, DomainName, ItemName, Attributes):
|
51
|
+
parameters = SortedDict()
|
52
|
+
parameters['ItemName'] = ItemName
|
53
|
+
seq = 0
|
54
|
+
for attrib in Attributes:
|
55
|
+
if type(Attributes[attrib]) == type(list()):
|
56
|
+
for value in Attributes[attrib]:
|
57
|
+
parameters['Attribute.%d.Name' % seq] = attrib
|
58
|
+
parameters['Attribute.%d.Value' % seq] = unicode(value)
|
59
|
+
seq += 1
|
60
|
+
else:
|
61
|
+
parameters['Attribute.%d.Name' % seq] = attrib
|
62
|
+
parameters['Attribute.%d.Value' % seq] = unicode(Attributes[attrib])
|
63
|
+
seq += 1
|
64
|
+
## TODO:
|
65
|
+
## - support for Attribute.N.Replace
|
66
|
+
## - support for multiple values for one attribute
|
67
|
+
return self.send_request("PutAttributes", DomainName = DomainName, parameters = parameters)
|
68
|
+
|
69
|
+
def GetAttributes(self, DomainName, ItemName, Attributes = []):
|
70
|
+
parameters = SortedDict()
|
71
|
+
parameters['ItemName'] = ItemName
|
72
|
+
seq = 0
|
73
|
+
for attrib in Attributes:
|
74
|
+
parameters['AttributeName.%d' % seq] = attrib
|
75
|
+
seq += 1
|
76
|
+
return self.send_request("GetAttributes", DomainName = DomainName, parameters = parameters)
|
77
|
+
|
78
|
+
def DeleteAttributes(self, DomainName, ItemName, Attributes = {}):
|
79
|
+
"""
|
80
|
+
Remove specified Attributes from ItemName.
|
81
|
+
Attributes parameter can be either:
|
82
|
+
- not specified, in which case the whole Item is removed
|
83
|
+
- list, e.g. ['Attr1', 'Attr2'] in which case these parameters are removed
|
84
|
+
- dict, e.g. {'Attr' : 'One', 'Attr' : 'Two'} in which case the
|
85
|
+
specified values are removed from multi-value attributes.
|
86
|
+
"""
|
87
|
+
parameters = SortedDict()
|
88
|
+
parameters['ItemName'] = ItemName
|
89
|
+
seq = 0
|
90
|
+
for attrib in Attributes:
|
91
|
+
parameters['Attribute.%d.Name' % seq] = attrib
|
92
|
+
if type(Attributes) == type(dict()):
|
93
|
+
parameters['Attribute.%d.Value' % seq] = unicode(Attributes[attrib])
|
94
|
+
seq += 1
|
95
|
+
return self.send_request("DeleteAttributes", DomainName = DomainName, parameters = parameters)
|
96
|
+
|
97
|
+
def Query(self, DomainName, QueryExpression = None, MaxNumberOfItems = None, NextToken = None):
|
98
|
+
parameters = SortedDict()
|
99
|
+
if QueryExpression:
|
100
|
+
parameters['QueryExpression'] = QueryExpression
|
101
|
+
if MaxNumberOfItems:
|
102
|
+
parameters['MaxNumberOfItems'] = MaxNumberOfItems
|
103
|
+
if NextToken:
|
104
|
+
parameters['NextToken'] = NextToken
|
105
|
+
return self.send_request("Query", DomainName = DomainName, parameters = parameters)
|
106
|
+
## Handle NextToken? Or maybe not - let the upper level do it
|
107
|
+
|
108
|
+
## ------------------------------------------------
|
109
|
+
## Low-level methods for handling SimpleDB requests
|
110
|
+
## ------------------------------------------------
|
111
|
+
|
112
|
+
def send_request(self, *args, **kwargs):
|
113
|
+
request = self.create_request(*args, **kwargs)
|
114
|
+
#debug("Request: %s" % repr(request))
|
115
|
+
conn = self.get_connection()
|
116
|
+
conn.request("GET", self.format_uri(request['uri_params']))
|
117
|
+
http_response = conn.getresponse()
|
118
|
+
response = {}
|
119
|
+
response["status"] = http_response.status
|
120
|
+
response["reason"] = http_response.reason
|
121
|
+
response["headers"] = convertTupleListToDict(http_response.getheaders())
|
122
|
+
response["data"] = http_response.read()
|
123
|
+
conn.close()
|
124
|
+
|
125
|
+
if response["status"] < 200 or response["status"] > 299:
|
126
|
+
debug("Response: " + str(response))
|
127
|
+
raise S3Error(response)
|
128
|
+
|
129
|
+
return response
|
130
|
+
|
131
|
+
def create_request(self, Action, DomainName, parameters = None):
|
132
|
+
if not parameters:
|
133
|
+
parameters = SortedDict()
|
134
|
+
if len(self.config.access_token) > 0:
|
135
|
+
self.config.refresh_role()
|
136
|
+
parameters['Signature']=self.config.access_token
|
137
|
+
parameters['AWSAccessKeyId'] = self.config.access_key
|
138
|
+
parameters['Version'] = self.Version
|
139
|
+
parameters['SignatureVersion'] = self.SignatureVersion
|
140
|
+
parameters['Action'] = Action
|
141
|
+
parameters['Timestamp'] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
142
|
+
if DomainName:
|
143
|
+
parameters['DomainName'] = DomainName
|
144
|
+
parameters['Signature'] = self.sign_request(parameters)
|
145
|
+
parameters.keys_return_lowercase = False
|
146
|
+
uri_params = urllib.urlencode(parameters)
|
147
|
+
request = {}
|
148
|
+
request['uri_params'] = uri_params
|
149
|
+
request['parameters'] = parameters
|
150
|
+
return request
|
151
|
+
|
152
|
+
def sign_request(self, parameters):
|
153
|
+
h = ""
|
154
|
+
parameters.keys_sort_lowercase = True
|
155
|
+
parameters.keys_return_lowercase = False
|
156
|
+
for key in parameters:
|
157
|
+
h += "%s%s" % (key, parameters[key])
|
158
|
+
#debug("SignRequest: %s" % h)
|
159
|
+
return base64.encodestring(hmac.new(self.config.secret_key, h, sha).digest()).strip()
|
160
|
+
|
161
|
+
def get_connection(self):
|
162
|
+
if self.config.proxy_host != "":
|
163
|
+
return httplib.HTTPConnection(self.config.proxy_host, self.config.proxy_port)
|
164
|
+
else:
|
165
|
+
if self.config.use_https:
|
166
|
+
return httplib.HTTPSConnection(self.config.simpledb_host)
|
167
|
+
else:
|
168
|
+
return httplib.HTTPConnection(self.config.simpledb_host)
|
169
|
+
|
170
|
+
def format_uri(self, uri_params):
|
171
|
+
if self.config.proxy_host != "":
|
172
|
+
uri = "http://%s/?%s" % (self.config.simpledb_host, uri_params)
|
173
|
+
else:
|
174
|
+
uri = "/?%s" % uri_params
|
175
|
+
#debug('format_uri(): ' + uri)
|
176
|
+
return uri
|
177
|
+
|
178
|
+
# vim:et:ts=4:sts=4:ai
|
@@ -0,0 +1,66 @@
|
|
1
|
+
## Amazon S3 manager
|
2
|
+
## Author: Michal Ludvig <michal@logix.cz>
|
3
|
+
## http://www.logix.cz/michal
|
4
|
+
## License: GPL Version 2
|
5
|
+
|
6
|
+
from BidirMap import BidirMap
|
7
|
+
import Utils
|
8
|
+
|
9
|
+
class SortedDictIterator(object):
|
10
|
+
def __init__(self, sorted_dict, keys):
|
11
|
+
self.sorted_dict = sorted_dict
|
12
|
+
self.keys = keys
|
13
|
+
|
14
|
+
def next(self):
|
15
|
+
try:
|
16
|
+
return self.keys.pop(0)
|
17
|
+
except IndexError:
|
18
|
+
raise StopIteration
|
19
|
+
|
20
|
+
class SortedDict(dict):
|
21
|
+
def __init__(self, mapping = {}, ignore_case = True, **kwargs):
|
22
|
+
"""
|
23
|
+
WARNING: SortedDict() with ignore_case==True will
|
24
|
+
drop entries differing only in capitalisation!
|
25
|
+
Eg: SortedDict({'auckland':1, 'Auckland':2}).keys() => ['Auckland']
|
26
|
+
With ignore_case==False it's all right
|
27
|
+
"""
|
28
|
+
dict.__init__(self, mapping, **kwargs)
|
29
|
+
self.ignore_case = ignore_case
|
30
|
+
|
31
|
+
def keys(self):
|
32
|
+
keys = dict.keys(self)
|
33
|
+
if self.ignore_case:
|
34
|
+
# Translation map
|
35
|
+
xlat_map = BidirMap()
|
36
|
+
for key in keys:
|
37
|
+
xlat_map[key.lower()] = key
|
38
|
+
# Lowercase keys
|
39
|
+
lc_keys = xlat_map.keys()
|
40
|
+
lc_keys.sort()
|
41
|
+
return [xlat_map[k] for k in lc_keys]
|
42
|
+
else:
|
43
|
+
keys.sort()
|
44
|
+
return keys
|
45
|
+
|
46
|
+
def __iter__(self):
|
47
|
+
return SortedDictIterator(self, self.keys())
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
if __name__ == "__main__":
|
52
|
+
d = { 'AWS' : 1, 'Action' : 2, 'america' : 3, 'Auckland' : 4, 'America' : 5 }
|
53
|
+
sd = SortedDict(d)
|
54
|
+
print "Wanted: Action, america, Auckland, AWS, [ignore case]"
|
55
|
+
print "Got: ",
|
56
|
+
for key in sd:
|
57
|
+
print "%s," % key,
|
58
|
+
print " [used: __iter__()]"
|
59
|
+
d = SortedDict(d, ignore_case = False)
|
60
|
+
print "Wanted: AWS, Action, Auckland, america, [case sensitive]"
|
61
|
+
print "Got: ",
|
62
|
+
for key in d.keys():
|
63
|
+
print "%s," % key,
|
64
|
+
print " [used: keys()]"
|
65
|
+
|
66
|
+
# vim:et:ts=4:sts=4:ai
|
Binary file
|