@@ -2183,312 +2183,6 @@ def _compile(file, optimize=-1):
2183
2183
return (fname , archivename )
2184
2184
2185
2185
2186
- def _parents (path ):
2187
- """
2188
- Given a path with elements separated by
2189
- posixpath.sep, generate all parents of that path.
2190
-
2191
- >>> list(_parents('b/d'))
2192
- ['b']
2193
- >>> list(_parents('/b/d/'))
2194
- ['/b']
2195
- >>> list(_parents('b/d/f/'))
2196
- ['b/d', 'b']
2197
- >>> list(_parents('b'))
2198
- []
2199
- >>> list(_parents(''))
2200
- []
2201
- """
2202
- return itertools .islice (_ancestry (path ), 1 , None )
2203
-
2204
-
2205
- def _ancestry (path ):
2206
- """
2207
- Given a path with elements separated by
2208
- posixpath.sep, generate all elements of that path
2209
-
2210
- >>> list(_ancestry('b/d'))
2211
- ['b/d', 'b']
2212
- >>> list(_ancestry('/b/d/'))
2213
- ['/b/d', '/b']
2214
- >>> list(_ancestry('b/d/f/'))
2215
- ['b/d/f', 'b/d', 'b']
2216
- >>> list(_ancestry('b'))
2217
- ['b']
2218
- >>> list(_ancestry(''))
2219
- []
2220
- """
2221
- path = path .rstrip (posixpath .sep )
2222
- while path and path != posixpath .sep :
2223
- yield path
2224
- path , tail = posixpath .split (path )
2225
-
2226
-
2227
- _dedupe = dict .fromkeys
2228
- """Deduplicate an iterable in original order"""
2229
-
2230
-
2231
- def _difference (minuend , subtrahend ):
2232
- """
2233
- Return items in minuend not in subtrahend, retaining order
2234
- with O(1) lookup.
2235
- """
2236
- return itertools .filterfalse (set (subtrahend ).__contains__ , minuend )
2237
-
2238
-
2239
- class CompleteDirs (ZipFile ):
2240
- """
2241
- A ZipFile subclass that ensures that implied directories
2242
- are always included in the namelist.
2243
- """
2244
-
2245
- @staticmethod
2246
- def _implied_dirs (names ):
2247
- parents = itertools .chain .from_iterable (map (_parents , names ))
2248
- as_dirs = (p + posixpath .sep for p in parents )
2249
- return _dedupe (_difference (as_dirs , names ))
2250
-
2251
- def namelist (self ):
2252
- names = super (CompleteDirs , self ).namelist ()
2253
- return names + list (self ._implied_dirs (names ))
2254
-
2255
- def _name_set (self ):
2256
- return set (self .namelist ())
2257
-
2258
- def resolve_dir (self , name ):
2259
- """
2260
- If the name represents a directory, return that name
2261
- as a directory (with the trailing slash).
2262
- """
2263
- names = self ._name_set ()
2264
- dirname = name + '/'
2265
- dir_match = name not in names and dirname in names
2266
- return dirname if dir_match else name
2267
-
2268
- @classmethod
2269
- def make (cls , source ):
2270
- """
2271
- Given a source (filename or zipfile), return an
2272
- appropriate CompleteDirs subclass.
2273
- """
2274
- if isinstance (source , CompleteDirs ):
2275
- return source
2276
-
2277
- if not isinstance (source , ZipFile ):
2278
- return cls (source )
2279
-
2280
- # Only allow for FastLookup when supplied zipfile is read-only
2281
- if 'r' not in source .mode :
2282
- cls = CompleteDirs
2283
-
2284
- source .__class__ = cls
2285
- return source
2286
-
2287
-
2288
- class FastLookup (CompleteDirs ):
2289
- """
2290
- ZipFile subclass to ensure implicit
2291
- dirs exist and are resolved rapidly.
2292
- """
2293
-
2294
- def namelist (self ):
2295
- with contextlib .suppress (AttributeError ):
2296
- return self .__names
2297
- self .__names = super (FastLookup , self ).namelist ()
2298
- return self .__names
2299
-
2300
- def _name_set (self ):
2301
- with contextlib .suppress (AttributeError ):
2302
- return self .__lookup
2303
- self .__lookup = super (FastLookup , self )._name_set ()
2304
- return self .__lookup
2305
-
2306
-
2307
- class Path :
2308
- """
2309
- A pathlib-compatible interface for zip files.
2310
-
2311
- Consider a zip file with this structure::
2312
-
2313
- .
2314
- ├── a.txt
2315
- └── b
2316
- ├── c.txt
2317
- └── d
2318
- └── e.txt
2319
-
2320
- >>> data = io.BytesIO()
2321
- >>> zf = ZipFile(data, 'w')
2322
- >>> zf.writestr('a.txt', 'content of a')
2323
- >>> zf.writestr('b/c.txt', 'content of c')
2324
- >>> zf.writestr('b/d/e.txt', 'content of e')
2325
- >>> zf.filename = 'mem/abcde.zip'
2326
-
2327
- Path accepts the zipfile object itself or a filename
2328
-
2329
- >>> root = Path(zf)
2330
-
2331
- From there, several path operations are available.
2332
-
2333
- Directory iteration (including the zip file itself):
2334
-
2335
- >>> a, b = root.iterdir()
2336
- >>> a
2337
- Path('mem/abcde.zip', 'a.txt')
2338
- >>> b
2339
- Path('mem/abcde.zip', 'b/')
2340
-
2341
- name property:
2342
-
2343
- >>> b.name
2344
- 'b'
2345
-
2346
- join with divide operator:
2347
-
2348
- >>> c = b / 'c.txt'
2349
- >>> c
2350
- Path('mem/abcde.zip', 'b/c.txt')
2351
- >>> c.name
2352
- 'c.txt'
2353
-
2354
- Read text:
2355
-
2356
- >>> c.read_text()
2357
- 'content of c'
2358
-
2359
- existence:
2360
-
2361
- >>> c.exists()
2362
- True
2363
- >>> (b / 'missing.txt').exists()
2364
- False
2365
-
2366
- Coercion to string:
2367
-
2368
- >>> import os
2369
- >>> str(c).replace(os.sep, posixpath.sep)
2370
- 'mem/abcde.zip/b/c.txt'
2371
-
2372
- At the root, ``name``, ``filename``, and ``parent``
2373
- resolve to the zipfile. Note these attributes are not
2374
- valid and will raise a ``ValueError`` if the zipfile
2375
- has no filename.
2376
-
2377
- >>> root.name
2378
- 'abcde.zip'
2379
- >>> str(root.filename).replace(os.sep, posixpath.sep)
2380
- 'mem/abcde.zip'
2381
- >>> str(root.parent)
2382
- 'mem'
2383
- """
2384
-
2385
- __repr = "{self.__class__.__name__}({self.root.filename!r}, {self.at!r})"
2386
-
2387
- def __init__ (self , root , at = "" ):
2388
- """
2389
- Construct a Path from a ZipFile or filename.
2390
-
2391
- Note: When the source is an existing ZipFile object,
2392
- its type (__class__) will be mutated to a
2393
- specialized type. If the caller wishes to retain the
2394
- original type, the caller should either create a
2395
- separate ZipFile object or pass a filename.
2396
- """
2397
- self .root = FastLookup .make (root )
2398
- self .at = at
2399
-
2400
- def open (self , mode = 'r' , * args , pwd = None , ** kwargs ):
2401
- """
2402
- Open this entry as text or binary following the semantics
2403
- of ``pathlib.Path.open()`` by passing arguments through
2404
- to io.TextIOWrapper().
2405
- """
2406
- if self .is_dir ():
2407
- raise IsADirectoryError (self )
2408
- zip_mode = mode [0 ]
2409
- if not self .exists () and zip_mode == 'r' :
2410
- raise FileNotFoundError (self )
2411
- stream = self .root .open (self .at , zip_mode , pwd = pwd )
2412
- if 'b' in mode :
2413
- if args or kwargs :
2414
- raise ValueError ("encoding args invalid for binary operation" )
2415
- return stream
2416
- else :
2417
- kwargs ["encoding" ] = io .text_encoding (kwargs .get ("encoding" ))
2418
- return io .TextIOWrapper (stream , * args , ** kwargs )
2419
-
2420
- @property
2421
- def name (self ):
2422
- return pathlib .Path (self .at ).name or self .filename .name
2423
-
2424
- @property
2425
- def suffix (self ):
2426
- return pathlib .Path (self .at ).suffix or self .filename .suffix
2427
-
2428
- @property
2429
- def suffixes (self ):
2430
- return pathlib .Path (self .at ).suffixes or self .filename .suffixes
2431
-
2432
- @property
2433
- def stem (self ):
2434
- return pathlib .Path (self .at ).stem or self .filename .stem
2435
-
2436
- @property
2437
- def filename (self ):
2438
- return pathlib .Path (self .root .filename ).joinpath (self .at )
2439
-
2440
- def read_text (self , * args , ** kwargs ):
2441
- kwargs ["encoding" ] = io .text_encoding (kwargs .get ("encoding" ))
2442
- with self .open ('r' , * args , ** kwargs ) as strm :
2443
- return strm .read ()
2444
-
2445
- def read_bytes (self ):
2446
- with self .open ('rb' ) as strm :
2447
- return strm .read ()
2448
-
2449
- def _is_child (self , path ):
2450
- return posixpath .dirname (path .at .rstrip ("/" )) == self .at .rstrip ("/" )
2451
-
2452
- def _next (self , at ):
2453
- return self .__class__ (self .root , at )
2454
-
2455
- def is_dir (self ):
2456
- return not self .at or self .at .endswith ("/" )
2457
-
2458
- def is_file (self ):
2459
- return self .exists () and not self .is_dir ()
2460
-
2461
- def exists (self ):
2462
- return self .at in self .root ._name_set ()
2463
-
2464
- def iterdir (self ):
2465
- if not self .is_dir ():
2466
- raise ValueError ("Can't listdir a file" )
2467
- subs = map (self ._next , self .root .namelist ())
2468
- return filter (self ._is_child , subs )
2469
-
2470
- def __str__ (self ):
2471
- return posixpath .join (self .root .filename , self .at )
2472
-
2473
- def __repr__ (self ):
2474
- return self .__repr .format (self = self )
2475
-
2476
- def joinpath (self , * other ):
2477
- next = posixpath .join (self .at , * other )
2478
- return self ._next (self .root .resolve_dir (next ))
2479
-
2480
- __truediv__ = joinpath
2481
-
2482
- @property
2483
- def parent (self ):
2484
- if not self .at :
2485
- return self .filename .parent
2486
- parent_at = posixpath .dirname (self .at .rstrip ('/' ))
2487
- if parent_at :
2488
- parent_at += '/'
2489
- return self ._next (parent_at )
2490
-
2491
-
2492
2186
def main (args = None ):
2493
2187
import argparse
2494
2188
0 commit comments