else
This commit is contained in:
parent
6e64483523
commit
0896ba96e6
20 changed files with 1826 additions and 1269 deletions
|
@ -9,10 +9,24 @@ import os
|
||||||
from voussoirkit import bytestring
|
from voussoirkit import bytestring
|
||||||
|
|
||||||
|
|
||||||
|
# 128 bits
|
||||||
BLOCK_SIZE = 32
|
BLOCK_SIZE = 32
|
||||||
|
KEY_SIZE = 32
|
||||||
SEEK_END = 2
|
SEEK_END = 2
|
||||||
|
|
||||||
|
def decrypt_data(aes, data):
|
||||||
|
data = aes.decrypt(data)
|
||||||
|
pad_byte = data[-1:]
|
||||||
|
data = data.rstrip(pad_byte)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def encrypt_data(aes, data):
|
||||||
|
pad_byte = (data[-1] + 1) % 256
|
||||||
|
pad_length = BLOCK_SIZE - (len(data) % BLOCK_SIZE)
|
||||||
|
data += (bytes([pad_byte]) * pad_length)
|
||||||
|
data = aes.encrypt(data)
|
||||||
|
return data
|
||||||
|
|
||||||
def decrypt_file(aes, input_handle, output_handle):
|
def decrypt_file(aes, input_handle, output_handle):
|
||||||
current_pos = input_handle.tell()
|
current_pos = input_handle.tell()
|
||||||
input_size = input_handle.seek(0, SEEK_END) - current_pos
|
input_size = input_handle.seek(0, SEEK_END) - current_pos
|
||||||
|
@ -42,8 +56,7 @@ def encrypt_file(aes, input_handle, output_handle):
|
||||||
last_byte = chunk[-1]
|
last_byte = chunk[-1]
|
||||||
if len(chunk) < BLOCK_SIZE:
|
if len(chunk) < BLOCK_SIZE:
|
||||||
pad_byte = (last_byte + 1) % 256
|
pad_byte = (last_byte + 1) % 256
|
||||||
pad_byte = chr(pad_byte)
|
pad_byte = bytes([pad_byte])
|
||||||
pad_byte = pad_byte.encode('ascii')
|
|
||||||
chunk += pad_byte * (BLOCK_SIZE - len(chunk))
|
chunk += pad_byte * (BLOCK_SIZE - len(chunk))
|
||||||
done = True
|
done = True
|
||||||
bytes_read += len(chunk)
|
bytes_read += len(chunk)
|
||||||
|
@ -60,7 +73,7 @@ def encrypt_argparse(args):
|
||||||
input_handle = open(args.input, 'rb')
|
input_handle = open(args.input, 'rb')
|
||||||
output_handle = open(args.output, 'wb')
|
output_handle = open(args.output, 'wb')
|
||||||
|
|
||||||
password = hashit(args.password, 32)
|
password = hashit(args.password)
|
||||||
initialization_vector = os.urandom(16)
|
initialization_vector = os.urandom(16)
|
||||||
aes = AES.new(password, mode=3, IV=initialization_vector)
|
aes = AES.new(password, mode=3, IV=initialization_vector)
|
||||||
output_handle.write(initialization_vector)
|
output_handle.write(initialization_vector)
|
||||||
|
@ -71,15 +84,14 @@ def decrypt_argparse(args):
|
||||||
input_handle = open(args.input, 'rb')
|
input_handle = open(args.input, 'rb')
|
||||||
output_handle = open(args.output, 'wb')
|
output_handle = open(args.output, 'wb')
|
||||||
|
|
||||||
password = hashit(args.password, 32)
|
password = hashit(args.password)
|
||||||
initialization_vector = input_handle.read(16)
|
initialization_vector = input_handle.read(16)
|
||||||
aes = AES.new(password, mode=3, IV=initialization_vector)
|
aes = AES.new(password, mode=3, IV=initialization_vector)
|
||||||
decrypt_file(aes, input_handle, output_handle)
|
decrypt_file(aes, input_handle, output_handle)
|
||||||
|
|
||||||
def hashit(text, length=None):
|
def hashit(text):
|
||||||
h = hashlib.sha512(text.encode('utf-8')).hexdigest()
|
h = hashlib.sha512(text.encode('utf-8')).hexdigest()
|
||||||
if length is not None:
|
h = h[:BLOCK_SIZE]
|
||||||
h = h[:length]
|
|
||||||
return h
|
return h
|
||||||
|
|
||||||
def main(argv):
|
def main(argv):
|
||||||
|
|
|
@ -23,7 +23,7 @@ FILENAME_BADCHARS = '*?"<>|\r'
|
||||||
|
|
||||||
last_request = 0
|
last_request = 0
|
||||||
CHUNKSIZE = 4 * bytestring.KIBIBYTE
|
CHUNKSIZE = 4 * bytestring.KIBIBYTE
|
||||||
TIMEOUT = 600
|
TIMEOUT = 60
|
||||||
TEMP_EXTENSION = '.downloadytemp'
|
TEMP_EXTENSION = '.downloadytemp'
|
||||||
|
|
||||||
PRINT_LIMITER = ratelimiter.Ratelimiter(allowance=5, mode='reject')
|
PRINT_LIMITER = ratelimiter.Ratelimiter(allowance=5, mode='reject')
|
||||||
|
@ -35,11 +35,12 @@ def download_file(
|
||||||
auth=None,
|
auth=None,
|
||||||
bytespersecond=None,
|
bytespersecond=None,
|
||||||
callback_progress=None,
|
callback_progress=None,
|
||||||
|
do_head=True,
|
||||||
headers=None,
|
headers=None,
|
||||||
overwrite=False,
|
overwrite=False,
|
||||||
raise_for_undersized=True,
|
raise_for_undersized=True,
|
||||||
|
timeout=None,
|
||||||
verbose=False,
|
verbose=False,
|
||||||
**get_kwargs
|
|
||||||
):
|
):
|
||||||
headers = headers or {}
|
headers = headers or {}
|
||||||
|
|
||||||
|
@ -61,13 +62,20 @@ def download_file(
|
||||||
localname,
|
localname,
|
||||||
auth=auth,
|
auth=auth,
|
||||||
bytespersecond=bytespersecond,
|
bytespersecond=bytespersecond,
|
||||||
|
callback_progress=callback_progress,
|
||||||
|
do_head=do_head,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
overwrite=overwrite,
|
overwrite=overwrite,
|
||||||
|
raise_for_undersized=raise_for_undersized,
|
||||||
|
timeout=timeout,
|
||||||
)
|
)
|
||||||
#print(plan)
|
#print(plan)
|
||||||
if plan is None:
|
if plan is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
return download_plan(plan)
|
||||||
|
|
||||||
|
def download_plan(plan):
|
||||||
localname = plan['download_into']
|
localname = plan['download_into']
|
||||||
directory = os.path.split(localname)[0]
|
directory = os.path.split(localname)[0]
|
||||||
if directory != '':
|
if directory != '':
|
||||||
|
@ -77,7 +85,7 @@ def download_file(
|
||||||
file_handle.seek(plan['seek_to'])
|
file_handle.seek(plan['seek_to'])
|
||||||
|
|
||||||
if plan['header_range_min'] is not None:
|
if plan['header_range_min'] is not None:
|
||||||
headers['range'] = 'bytes={min}-{max}'.format(
|
plan['headers']['range'] = 'bytes={min}-{max}'.format(
|
||||||
min=plan['header_range_min'],
|
min=plan['header_range_min'],
|
||||||
max=plan['header_range_max'],
|
max=plan['header_range_max'],
|
||||||
)
|
)
|
||||||
|
@ -89,7 +97,20 @@ def download_file(
|
||||||
else:
|
else:
|
||||||
bytes_downloaded = 0
|
bytes_downloaded = 0
|
||||||
|
|
||||||
download_stream = request('get', url, stream=True, headers=headers, auth=auth, **get_kwargs)
|
download_stream = request(
|
||||||
|
'get',
|
||||||
|
plan['url'],
|
||||||
|
stream=True,
|
||||||
|
auth=plan['auth'],
|
||||||
|
headers=plan['headers'],
|
||||||
|
timeout=plan['timeout'],
|
||||||
|
)
|
||||||
|
|
||||||
|
if plan['remote_total_bytes'] is None:
|
||||||
|
# Since we didn't do a head, let's fill this in now.
|
||||||
|
plan['remote_total_bytes'] = int(download_stream.headers.get('Content-Length', 0))
|
||||||
|
|
||||||
|
callback_progress = plan['callback_progress']
|
||||||
if callback_progress is not None:
|
if callback_progress is not None:
|
||||||
callback_progress = callback_progress(plan['remote_total_bytes'])
|
callback_progress = callback_progress(plan['remote_total_bytes'])
|
||||||
|
|
||||||
|
@ -108,7 +129,7 @@ def download_file(
|
||||||
if os.devnull not in [localname, plan['real_localname']]:
|
if os.devnull not in [localname, plan['real_localname']]:
|
||||||
localsize = os.path.getsize(localname)
|
localsize = os.path.getsize(localname)
|
||||||
undersized = plan['plan_type'] != 'partial' and localsize < plan['remote_total_bytes']
|
undersized = plan['plan_type'] != 'partial' and localsize < plan['remote_total_bytes']
|
||||||
if raise_for_undersized and undersized:
|
if plan['raise_for_undersized'] and undersized:
|
||||||
message = 'File does not contain expected number of bytes. Received {size} / {total}'
|
message = 'File does not contain expected number of bytes. Received {size} / {total}'
|
||||||
message = message.format(size=localsize, total=plan['remote_total_bytes'])
|
message = message.format(size=localsize, total=plan['remote_total_bytes'])
|
||||||
raise Exception(message)
|
raise Exception(message)
|
||||||
|
@ -121,12 +142,17 @@ def download_file(
|
||||||
def prepare_plan(
|
def prepare_plan(
|
||||||
url,
|
url,
|
||||||
localname,
|
localname,
|
||||||
auth,
|
auth=None,
|
||||||
bytespersecond,
|
bytespersecond=None,
|
||||||
headers,
|
callback_progress=None,
|
||||||
overwrite,
|
do_head=True,
|
||||||
|
headers=None,
|
||||||
|
overwrite=False,
|
||||||
|
raise_for_undersized=True,
|
||||||
|
timeout=TIMEOUT,
|
||||||
):
|
):
|
||||||
# Chapter 1: File existence
|
# Chapter 1: File existence
|
||||||
|
headers = headers or {}
|
||||||
user_provided_range = 'range' in headers
|
user_provided_range = 'range' in headers
|
||||||
real_localname = localname
|
real_localname = localname
|
||||||
temp_localname = localname + TEMP_EXTENSION
|
temp_localname = localname + TEMP_EXTENSION
|
||||||
|
@ -163,21 +189,34 @@ def prepare_plan(
|
||||||
temp_headers = headers
|
temp_headers = headers
|
||||||
temp_headers.update({'range': 'bytes=0-'})
|
temp_headers.update({'range': 'bytes=0-'})
|
||||||
|
|
||||||
|
if do_head:
|
||||||
# I'm using a GET instead of an actual HEAD here because some servers respond
|
# I'm using a GET instead of an actual HEAD here because some servers respond
|
||||||
# differently, even though they're not supposed to.
|
# differently, even though they're not supposed to.
|
||||||
head = request('get', url, stream=True, headers=temp_headers, auth=auth)
|
head = request('get', url, stream=True, headers=temp_headers, auth=auth)
|
||||||
remote_total_bytes = int(head.headers.get('content-length', 0))
|
remote_total_bytes = int(head.headers.get('content-length', 0))
|
||||||
server_respects_range = (head.status_code == 206 and 'content-range' in head.headers)
|
server_respects_range = (head.status_code == 206 and 'content-range' in head.headers)
|
||||||
head.connection.close()
|
head.connection.close()
|
||||||
|
else:
|
||||||
|
remote_total_bytes = None
|
||||||
|
server_respects_range = False
|
||||||
|
|
||||||
if user_provided_range and not server_respects_range:
|
if user_provided_range and not server_respects_range:
|
||||||
|
if not do_head:
|
||||||
|
raise Exception('Cannot determine range support without the head request')
|
||||||
|
else:
|
||||||
raise Exception('Server did not respect your range header')
|
raise Exception('Server did not respect your range header')
|
||||||
|
|
||||||
# Chapter 5: Plan definitions
|
# Chapter 5: Plan definitions
|
||||||
plan_base = {
|
plan_base = {
|
||||||
|
'url': url,
|
||||||
|
'auth': auth,
|
||||||
|
'callback_progress': callback_progress,
|
||||||
'limiter': limiter,
|
'limiter': limiter,
|
||||||
|
'headers': headers,
|
||||||
'real_localname': real_localname,
|
'real_localname': real_localname,
|
||||||
|
'raise_for_undersized': raise_for_undersized,
|
||||||
'remote_total_bytes': remote_total_bytes,
|
'remote_total_bytes': remote_total_bytes,
|
||||||
|
'timeout': timeout,
|
||||||
}
|
}
|
||||||
plan_fulldownload = dict(
|
plan_fulldownload = dict(
|
||||||
plan_base,
|
plan_base,
|
||||||
|
@ -373,8 +412,10 @@ def download_argparse(args):
|
||||||
localname=args.localname,
|
localname=args.localname,
|
||||||
bytespersecond=bytespersecond,
|
bytespersecond=bytespersecond,
|
||||||
callback_progress=callback,
|
callback_progress=callback,
|
||||||
|
do_head=args.no_head is False,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
overwrite=args.overwrite,
|
overwrite=args.overwrite,
|
||||||
|
timeout=int(args.timeout),
|
||||||
verbose=True,
|
verbose=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -388,6 +429,8 @@ if __name__ == '__main__':
|
||||||
parser.add_argument('-bps', '--bytespersecond', dest='bytespersecond', default=None)
|
parser.add_argument('-bps', '--bytespersecond', dest='bytespersecond', default=None)
|
||||||
parser.add_argument('-ow', '--overwrite', dest='overwrite', action='store_true')
|
parser.add_argument('-ow', '--overwrite', dest='overwrite', action='store_true')
|
||||||
parser.add_argument('-r', '--range', dest='range', default=None)
|
parser.add_argument('-r', '--range', dest='range', default=None)
|
||||||
|
parser.add_argument('--timeout', dest='timeout', default=TIMEOUT)
|
||||||
|
parser.add_argument('--no-head', dest='no_head', action='store_true')
|
||||||
parser.set_defaults(func=download_argparse)
|
parser.set_defaults(func=download_argparse)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
import glob
|
import glob
|
||||||
import os
|
import os
|
||||||
|
|
||||||
class Path:
|
class Path:
|
||||||
'''
|
'''
|
||||||
I started to use pathlib.Path, but it was too much of a pain.
|
I started to use pathlib.Path, but it was too much of a pain.
|
||||||
'''
|
'''
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
|
if isinstance(path, Path):
|
||||||
|
self.absolute_path = path.absolute_path
|
||||||
|
else:
|
||||||
path = os.path.normpath(path)
|
path = os.path.normpath(path)
|
||||||
path = os.path.abspath(path)
|
path = os.path.abspath(path)
|
||||||
self.absolute_path = path
|
self.absolute_path = path
|
||||||
|
|
||||||
def __contains__(self, other):
|
def __contains__(self, other):
|
||||||
this = os.path.normcase(self.absolute_path)
|
return other.normcase.startswith(self.normcase)
|
||||||
that = os.path.normcase(other.absolute_path)
|
|
||||||
return that.startswith(this)
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if not hasattr(other, 'absolute_path'):
|
if not hasattr(other, 'absolute_path'):
|
||||||
return False
|
return False
|
||||||
this = os.path.normcase(self.absolute_path)
|
return self.normcase == other.normcase
|
||||||
that = os.path.normcase(other.absolute_path)
|
|
||||||
return this == that
|
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return hash(os.path.normcase(self.absolute_path))
|
return hash(os.path.normcase(self.absolute_path))
|
||||||
|
@ -35,6 +35,10 @@ class Path:
|
||||||
self.absolute_path = get_path_casing(self.absolute_path)
|
self.absolute_path = get_path_casing(self.absolute_path)
|
||||||
return self.absolute_path
|
return self.absolute_path
|
||||||
|
|
||||||
|
@property
|
||||||
|
def depth(self):
|
||||||
|
return len(self.absolute_path.split(os.sep))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def exists(self):
|
def exists(self):
|
||||||
return os.path.exists(self.absolute_path)
|
return os.path.exists(self.absolute_path)
|
||||||
|
@ -51,6 +55,10 @@ class Path:
|
||||||
def is_link(self):
|
def is_link(self):
|
||||||
return os.path.islink(self.absolute_path)
|
return os.path.islink(self.absolute_path)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def normcase(self):
|
||||||
|
return os.path.normcase(self.absolute_path)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def parent(self):
|
def parent(self):
|
||||||
parent = os.path.dirname(self.absolute_path)
|
parent = os.path.dirname(self.absolute_path)
|
||||||
|
@ -59,10 +67,21 @@ class Path:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def relative_path(self):
|
def relative_path(self):
|
||||||
relative = self.absolute_path
|
cwd = Path(os.getcwd())
|
||||||
relative = relative.replace(os.getcwd(), '')
|
self.correct_case()
|
||||||
relative = relative.lstrip(os.sep)
|
if self.absolute_path == cwd:
|
||||||
return relative
|
return '.'
|
||||||
|
|
||||||
|
if self in cwd:
|
||||||
|
return self.absolute_path.replace(cwd.absolute_path, '.')
|
||||||
|
|
||||||
|
common = common_path([os.getcwd(), self.absolute_path], fallback=None)
|
||||||
|
if common is None:
|
||||||
|
return self.absolute_path
|
||||||
|
backsteps = cwd.depth - common.depth
|
||||||
|
backsteps = os.sep.join('..' for x in range(backsteps))
|
||||||
|
print('hi')
|
||||||
|
return self.absolute_path.replace(common.absolute_path, backsteps)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def size(self):
|
def size(self):
|
||||||
|
@ -80,6 +99,34 @@ class Path:
|
||||||
return Path(os.path.join(self.absolute_path, basename))
|
return Path(os.path.join(self.absolute_path, basename))
|
||||||
|
|
||||||
|
|
||||||
|
def common_path(paths, fallback):
|
||||||
|
'''
|
||||||
|
Given a list of file paths, determine the deepest path which all
|
||||||
|
have in common.
|
||||||
|
'''
|
||||||
|
if isinstance(paths, (str, Path)):
|
||||||
|
raise TypeError('`paths` must be a collection')
|
||||||
|
paths = [Path(f) for f in paths]
|
||||||
|
|
||||||
|
if len(paths) == 0:
|
||||||
|
raise ValueError('Empty list')
|
||||||
|
|
||||||
|
if hasattr(paths, 'pop'):
|
||||||
|
model = paths.pop()
|
||||||
|
else:
|
||||||
|
model = paths[0]
|
||||||
|
paths = paths[1:]
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if all(f in model for f in paths):
|
||||||
|
return model
|
||||||
|
parent = model.parent
|
||||||
|
if parent == model:
|
||||||
|
# We just processed the root, and now we're stuck at the root.
|
||||||
|
# Which means there was no common path.
|
||||||
|
return fallback
|
||||||
|
model = parent
|
||||||
|
|
||||||
def get_path_casing(path):
|
def get_path_casing(path):
|
||||||
'''
|
'''
|
||||||
Take what is perhaps incorrectly cased input and get the path's actual
|
Take what is perhaps incorrectly cased input and get the path's actual
|
||||||
|
@ -89,7 +136,20 @@ def get_path_casing(path):
|
||||||
Ethan Furman http://stackoverflow.com/a/7133137/5430534
|
Ethan Furman http://stackoverflow.com/a/7133137/5430534
|
||||||
xvorsx http://stackoverflow.com/a/14742779/5430534
|
xvorsx http://stackoverflow.com/a/14742779/5430534
|
||||||
'''
|
'''
|
||||||
if isinstance(path, Path):
|
if not isinstance(path, Path):
|
||||||
|
path = Path(path)
|
||||||
|
|
||||||
|
# Nonexistent paths don't glob correctly. If the input is a nonexistent
|
||||||
|
# subpath of an existing path, we have to glob the existing portion first,
|
||||||
|
# and then attach the fake portion again at the end.
|
||||||
|
input_path = path
|
||||||
|
while not path.exists:
|
||||||
|
parent = path.parent
|
||||||
|
if path == parent:
|
||||||
|
# We're stuck at a fake root.
|
||||||
|
return input_path.absolute_path
|
||||||
|
path = parent
|
||||||
|
|
||||||
path = path.absolute_path
|
path = path.absolute_path
|
||||||
|
|
||||||
(drive, subpath) = os.path.splitdrive(path)
|
(drive, subpath) = os.path.splitdrive(path)
|
||||||
|
@ -99,11 +159,17 @@ def get_path_casing(path):
|
||||||
pattern = [glob_patternize(piece) for piece in subpath.split(os.sep)]
|
pattern = [glob_patternize(piece) for piece in subpath.split(os.sep)]
|
||||||
pattern = os.sep.join(pattern)
|
pattern = os.sep.join(pattern)
|
||||||
pattern = drive + os.sep + pattern
|
pattern = drive + os.sep + pattern
|
||||||
#print(pattern)
|
|
||||||
try:
|
try:
|
||||||
return glob.glob(pattern)[0]
|
cased = glob.glob(pattern)[0]
|
||||||
|
imaginary_portion = input_path.normcase
|
||||||
|
real_portion = os.path.normcase(cased)
|
||||||
|
imaginary_portion = imaginary_portion.replace(real_portion, '')
|
||||||
|
imaginary_portion = imaginary_portion.lstrip(os.sep)
|
||||||
|
cased = os.path.join(cased, imaginary_portion)
|
||||||
|
return cased
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return path
|
return input_path
|
||||||
|
|
||||||
def glob_patternize(piece):
|
def glob_patternize(piece):
|
||||||
'''
|
'''
|
||||||
|
@ -111,10 +177,15 @@ def glob_patternize(piece):
|
||||||
correct path name, while guaranteeing that the only result will be the correct path.
|
correct path name, while guaranteeing that the only result will be the correct path.
|
||||||
|
|
||||||
Special cases are:
|
Special cases are:
|
||||||
!, because in glob syntax, [!x] tells glob to look for paths that don't contain
|
`!`
|
||||||
"x". [!] is invalid syntax, so we pick the first non-! character to put
|
because in glob syntax, [!x] tells glob to look for paths that don't contain
|
||||||
in the brackets.
|
"x", and [!] is invalid syntax.
|
||||||
[, because this starts a capture group
|
`[`, `]`
|
||||||
|
because this starts a glob capture group
|
||||||
|
|
||||||
|
so we pick the first non-special character to put in the brackets.
|
||||||
|
If the path consists entirely of these special characters, then the
|
||||||
|
casing doesn't need to be corrected anyway.
|
||||||
'''
|
'''
|
||||||
piece = glob.escape(piece)
|
piece = glob.escape(piece)
|
||||||
for character in piece:
|
for character in piece:
|
||||||
|
@ -124,3 +195,13 @@ def glob_patternize(piece):
|
||||||
piece = piece.replace(character, replacement, 1)
|
piece = piece.replace(character, replacement, 1)
|
||||||
break
|
break
|
||||||
return piece
|
return piece
|
||||||
|
|
||||||
|
def normalize_sep(path):
|
||||||
|
for char in ('\\', '/'):
|
||||||
|
if char != os.sep:
|
||||||
|
path = path.replace(char, os.sep)
|
||||||
|
path = path.rstrip(os.sep)
|
||||||
|
return path
|
||||||
|
|
||||||
|
def system_root():
|
||||||
|
return os.path.abspath(os.sep)
|
||||||
|
|
18
Safeprint/safeprint.py
Normal file
18
Safeprint/safeprint.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
'''
|
||||||
|
This function is slow and ugly, but I need a way to safely print unicode strings
|
||||||
|
on systems that don't support it without crippling those who do.
|
||||||
|
'''
|
||||||
|
def safeprint(text, file_handle=None):
|
||||||
|
for character in text:
|
||||||
|
try:
|
||||||
|
if file_handle:
|
||||||
|
file_handle.write(character)
|
||||||
|
else:
|
||||||
|
print(character, end='', flush=False)
|
||||||
|
except UnicodeError:
|
||||||
|
if file_handle:
|
||||||
|
file_handle.write('?')
|
||||||
|
else:
|
||||||
|
print('?', end='', flush=False)
|
||||||
|
if not file_handle:
|
||||||
|
print()
|
|
@ -290,7 +290,7 @@ def generate_random_filename(original_filename='', length=8):
|
||||||
return identifier
|
return identifier
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
server = ThreadedServer(('', 32768), RequestHandler)
|
server = ThreadedServer(('', int(sys.argv[1] or 32768)), RequestHandler)
|
||||||
print('server starting')
|
print('server starting')
|
||||||
server.serve_forever()
|
server.serve_forever()
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
22
Templates/timetest.py
Normal file
22
Templates/timetest.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import time
|
||||||
|
|
||||||
|
LOOPS = 10000
|
||||||
|
|
||||||
|
def func_1():
|
||||||
|
pass
|
||||||
|
|
||||||
|
def func_2():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
start = time.time()
|
||||||
|
for x in range(LOOPS):
|
||||||
|
func_1()
|
||||||
|
end = time.time()
|
||||||
|
print('v1', end - start)
|
||||||
|
|
||||||
|
start = time.time()
|
||||||
|
for x in range(LOOPS):
|
||||||
|
func_2()
|
||||||
|
end = time.time()
|
||||||
|
print('v2', end - start)
|
|
@ -36,6 +36,10 @@ def threaded_dl(urls, thread_count, filename_format=None):
|
||||||
if filename_format is None:
|
if filename_format is None:
|
||||||
filename_format = '{now}_{index}_{basename}'
|
filename_format = '{now}_{index}_{basename}'
|
||||||
filename_format = filename_format.replace('{index}', '{index:0%0dd}' % index_digits)
|
filename_format = filename_format.replace('{index}', '{index:0%0dd}' % index_digits)
|
||||||
|
if '{' not in filename_format and len(urls) > 1:
|
||||||
|
filename_format += '_{index}'
|
||||||
|
if '{extension}' not in filename_format:
|
||||||
|
filename_format += '{extension}'
|
||||||
now = int(time.time())
|
now = int(time.time())
|
||||||
for (index, url) in enumerate(urls):
|
for (index, url) in enumerate(urls):
|
||||||
while len(threads) == thread_count:
|
while len(threads) == thread_count:
|
||||||
|
|
|
@ -16,6 +16,8 @@ import random
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from voussoirkit import safeprint
|
||||||
|
|
||||||
|
|
||||||
def brename(transformation):
|
def brename(transformation):
|
||||||
old = os.listdir()
|
old = os.listdir()
|
||||||
|
@ -46,7 +48,7 @@ def loop(pairs, dry=False):
|
||||||
line = '{old}\n{new}\n'
|
line = '{old}\n{new}\n'
|
||||||
line = line.format(old=x, new=y)
|
line = line.format(old=x, new=y)
|
||||||
#print(line.encode('utf-8'))
|
#print(line.encode('utf-8'))
|
||||||
print(line.encode('ascii', 'replace').decode())
|
safeprint.safeprint(line)
|
||||||
has_content = True
|
has_content = True
|
||||||
else:
|
else:
|
||||||
os.rename(x, y)
|
os.rename(x, y)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import glob
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from voussoirkit import safeprint
|
||||||
from voussoirkit import spinal
|
from voussoirkit import spinal
|
||||||
|
|
||||||
filepattern = sys.argv[1]
|
filepattern = sys.argv[1]
|
||||||
|
@ -31,5 +32,5 @@ for filename in spinal.walk_generator():
|
||||||
pass
|
pass
|
||||||
if matches:
|
if matches:
|
||||||
print(filename)
|
print(filename)
|
||||||
print('\n'.join(matches).encode('ascii', 'replace').decode())
|
safeprint.safeprint('\n'.join(matches))
|
||||||
print()
|
print()
|
||||||
|
|
|
@ -5,14 +5,16 @@ import glob
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from voussoirkit import safeprint
|
||||||
|
|
||||||
def touch(glob_pattern):
|
def touch(glob_pattern):
|
||||||
filenames = glob.glob(glob_pattern)
|
filenames = glob.glob(glob_pattern)
|
||||||
if len(filenames) == 0:
|
if len(filenames) == 0:
|
||||||
print(glob_pattern.encode('ascii', 'replace').decode())
|
safeprint.safeprint(glob_pattern)
|
||||||
open(glob_pattern, 'a').close()
|
open(glob_pattern, 'a').close()
|
||||||
else:
|
else:
|
||||||
for filename in filenames:
|
for filename in filenames:
|
||||||
print(filename.encode('ascii', 'replace').decode())
|
safeprint.safeprint(filename)
|
||||||
os.utime(filename)
|
os.utime(filename)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
BIN
VoxelSphereGenerator/dot_corner.png
Normal file
BIN
VoxelSphereGenerator/dot_corner.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 168 B |
BIN
VoxelSphereGenerator/dot_highlight.png
Normal file
BIN
VoxelSphereGenerator/dot_highlight.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 168 B |
BIN
VoxelSphereGenerator/dot_normal.png
Normal file
BIN
VoxelSphereGenerator/dot_normal.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 168 B |
274
VoxelSphereGenerator/voxelspheregenerator.py
Normal file
274
VoxelSphereGenerator/voxelspheregenerator.py
Normal file
|
@ -0,0 +1,274 @@
|
||||||
|
import argparse
|
||||||
|
import math
|
||||||
|
import PIL.Image
|
||||||
|
import PIL.ImageDraw
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def choose_guideline_style(guideline_mod):
|
||||||
|
if guideline_mod % 16 == 0:
|
||||||
|
return ('#1f32ff', 3)
|
||||||
|
if guideline_mod % 8 == 0:
|
||||||
|
return ('#80f783', 2)
|
||||||
|
if guideline_mod % 4 == 0:
|
||||||
|
return ('#f4bffb', 1)
|
||||||
|
|
||||||
|
def voxelspheregenerator(WIDTH, HEIGH, DEPTH, WALL_THICKNESS=None):
|
||||||
|
def in_ellipsoid(x, y, z, rad_x, rad_y, rad_z, center_x=None, center_y=None, center_z=None):
|
||||||
|
'''
|
||||||
|
Given a point (x, y, z), return whether that point lies inside the
|
||||||
|
ellipsoid defined by (x/a)^2 + (y/b)^2 + (z/c)^2 = 1
|
||||||
|
'''
|
||||||
|
if center_x is None: center_x = rad_x
|
||||||
|
if center_y is None: center_y = rad_y
|
||||||
|
if center_z is None: center_z = rad_z
|
||||||
|
#print(x, y, z, rad_x, rad_y, rad_z, center_x, center_y, center_z)
|
||||||
|
x = ((x - center_x) / rad_x) ** 2
|
||||||
|
y = ((y - center_y) / rad_y) ** 2
|
||||||
|
z = ((z - center_z) / rad_z) ** 2
|
||||||
|
distance = x + y + z
|
||||||
|
#print(distance)
|
||||||
|
return distance < 1
|
||||||
|
|
||||||
|
ODD_W = WIDTH % 2 == 1
|
||||||
|
ODD_H = HEIGH % 2 == 1
|
||||||
|
ODD_D = DEPTH % 2 == 1
|
||||||
|
|
||||||
|
RAD_X = WIDTH / 2
|
||||||
|
RAD_Y = HEIGH / 2
|
||||||
|
RAD_Z = DEPTH / 2
|
||||||
|
|
||||||
|
if WALL_THICKNESS:
|
||||||
|
INNER_RAD_X = RAD_X - WALL_THICKNESS
|
||||||
|
INNER_RAD_Y = RAD_Y - WALL_THICKNESS
|
||||||
|
INNER_RAD_Z = RAD_Z - WALL_THICKNESS
|
||||||
|
|
||||||
|
X_CENTER = {WIDTH // 2} if ODD_W else {WIDTH // 2, (WIDTH // 2) - 1}
|
||||||
|
Y_CENTER = {HEIGH // 2} if ODD_H else {HEIGH // 2, (HEIGH // 2) - 1}
|
||||||
|
Z_CENTER = {DEPTH // 2} if ODD_D else {DEPTH // 2, (DEPTH // 2) - 1}
|
||||||
|
|
||||||
|
layer_digits = len(str(DEPTH))
|
||||||
|
filename_form = '{w}x{h}x{d}w{wall}-{{layer:0{digits}}}.png'
|
||||||
|
filename_form = filename_form.format(
|
||||||
|
w=WIDTH,
|
||||||
|
h=HEIGH,
|
||||||
|
d=DEPTH,
|
||||||
|
wall=WALL_THICKNESS if WALL_THICKNESS else 0,
|
||||||
|
digits=layer_digits,
|
||||||
|
)
|
||||||
|
|
||||||
|
dot_highlight = PIL.Image.open('dot_highlight.png')
|
||||||
|
dot_normal = PIL.Image.open('dot_normal.png')
|
||||||
|
dot_corner = PIL.Image.open('dot_corner.png')
|
||||||
|
pixel_scale = dot_highlight.size[0]
|
||||||
|
|
||||||
|
# Space between each pixel
|
||||||
|
PIXEL_MARGIN = 7
|
||||||
|
|
||||||
|
# Space between the pixel area and the canvas
|
||||||
|
PIXELSPACE_MARGIN = 20
|
||||||
|
|
||||||
|
# Space between the canvas area and the image edge
|
||||||
|
CANVAS_MARGIN = 20
|
||||||
|
|
||||||
|
LABEL_HEIGH = 20
|
||||||
|
FINAL_IMAGE_SCALE = 1
|
||||||
|
|
||||||
|
PIXELSPACE_WIDTH = (WIDTH * pixel_scale) + ((WIDTH - 1) * PIXEL_MARGIN)
|
||||||
|
PIXELSPACE_HEIGH = (HEIGH * pixel_scale) + ((HEIGH - 1) * PIXEL_MARGIN)
|
||||||
|
|
||||||
|
CANVAS_WIDTH = PIXELSPACE_WIDTH + (2 * PIXELSPACE_MARGIN)
|
||||||
|
CANVAS_HEIGH = PIXELSPACE_HEIGH + (2 * PIXELSPACE_MARGIN)
|
||||||
|
|
||||||
|
IMAGE_WIDTH = CANVAS_WIDTH + (2 * CANVAS_MARGIN)
|
||||||
|
IMAGE_HEIGH = CANVAS_HEIGH + (2 * CANVAS_MARGIN) + LABEL_HEIGH
|
||||||
|
|
||||||
|
CANVAS_START_X = CANVAS_MARGIN
|
||||||
|
CANVAS_START_Y = CANVAS_MARGIN
|
||||||
|
CANVAS_END_X = CANVAS_START_X + CANVAS_WIDTH
|
||||||
|
CANVAS_END_Y = CANVAS_START_Y + CANVAS_HEIGH
|
||||||
|
|
||||||
|
PIXELSPACE_START_X = CANVAS_START_X + PIXELSPACE_MARGIN
|
||||||
|
PIXELSPACE_START_Y = CANVAS_START_Y + PIXELSPACE_MARGIN
|
||||||
|
PIXELSPACE_END_X = PIXELSPACE_START_X + PIXELSPACE_WIDTH
|
||||||
|
PIXELSPACE_END_Y = PIXELSPACE_START_Y + PIXELSPACE_HEIGH
|
||||||
|
|
||||||
|
GUIDELINE_MOD_X = math.ceil(RAD_X)
|
||||||
|
GUIDELINE_MOD_Y = math.ceil(RAD_Y)
|
||||||
|
|
||||||
|
def pixel_coord(x, y):
|
||||||
|
x = PIXELSPACE_START_X + (x * pixel_scale) + (x * PIXEL_MARGIN)
|
||||||
|
y = PIXELSPACE_START_Y + (y * pixel_scale) + (y * PIXEL_MARGIN)
|
||||||
|
return (x, y)
|
||||||
|
|
||||||
|
def make_layer_matrix(z):
|
||||||
|
layer_matrix = [[None for y in range(math.ceil(RAD_Y))] for x in range(math.ceil(RAD_X))]
|
||||||
|
|
||||||
|
# Generate the upper left corner.
|
||||||
|
furthest_x = RAD_X
|
||||||
|
furthest_y = RAD_Y
|
||||||
|
for y in range(math.ceil(RAD_Y)):
|
||||||
|
for x in range(math.ceil(RAD_X)):
|
||||||
|
ux = x + 0.5
|
||||||
|
uy = y + 0.5
|
||||||
|
uz = z + 0.5
|
||||||
|
|
||||||
|
within = in_ellipsoid(ux, uy, uz, RAD_X, RAD_Y, RAD_Z)
|
||||||
|
if WALL_THICKNESS:
|
||||||
|
in_hole = in_ellipsoid(
|
||||||
|
ux, uy, uz,
|
||||||
|
INNER_RAD_X, INNER_RAD_Y, INNER_RAD_Z,
|
||||||
|
RAD_X, RAD_Y, RAD_Z
|
||||||
|
)
|
||||||
|
within = within and not in_hole
|
||||||
|
if within:
|
||||||
|
if x in X_CENTER or y in Y_CENTER:
|
||||||
|
if z in Z_CENTER:
|
||||||
|
dot = dot_normal
|
||||||
|
else:
|
||||||
|
dot = dot_highlight
|
||||||
|
else:
|
||||||
|
if z in Z_CENTER:
|
||||||
|
dot = dot_highlight
|
||||||
|
else:
|
||||||
|
dot = dot_normal
|
||||||
|
layer_matrix[x][y] = dot
|
||||||
|
furthest_x = min(x, furthest_x)
|
||||||
|
furthest_y = min(y, furthest_y)
|
||||||
|
#layer_image.paste(dot, box=(pixel_coord_x, pixel_coord_y))
|
||||||
|
|
||||||
|
# Mark the corner pieces
|
||||||
|
for y in range(furthest_y, math.ceil(RAD_Y-1)):
|
||||||
|
for x in range(furthest_x, math.ceil(RAD_X-1)):
|
||||||
|
is_corner = (
|
||||||
|
layer_matrix[x][y] is not None and
|
||||||
|
layer_matrix[x-1][y+1] is not None and
|
||||||
|
layer_matrix[x+1][y-1] is not None and
|
||||||
|
(
|
||||||
|
# Outer corners
|
||||||
|
(layer_matrix[x][y-1] is None and layer_matrix[x-1][y] is None) or
|
||||||
|
# Inner corners, if hollow
|
||||||
|
(layer_matrix[x][y+1] is None and layer_matrix[x+1][y] is None)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if is_corner:
|
||||||
|
layer_matrix[x][y] = dot_corner
|
||||||
|
|
||||||
|
return layer_matrix
|
||||||
|
|
||||||
|
def make_layer_image(layer_matrix):
|
||||||
|
layer_image = PIL.Image.new('RGBA', size=(IMAGE_WIDTH, IMAGE_HEIGH), color=(0, 0, 0, 0))
|
||||||
|
draw = PIL.ImageDraw.ImageDraw(layer_image)
|
||||||
|
|
||||||
|
# Plot.
|
||||||
|
for y in range(math.ceil(RAD_Y)):
|
||||||
|
for x in range(math.ceil(RAD_X)):
|
||||||
|
right_x = (WIDTH - 1) - x
|
||||||
|
bottom_y = (HEIGH - 1) - y
|
||||||
|
if layer_matrix[x][y] is not None:
|
||||||
|
layer_image.paste(layer_matrix[x][y], box=pixel_coord(x, y))
|
||||||
|
layer_image.paste(layer_matrix[x][y], box=pixel_coord(right_x, y))
|
||||||
|
layer_image.paste(layer_matrix[x][y], box=pixel_coord(x, bottom_y))
|
||||||
|
layer_image.paste(layer_matrix[x][y], box=pixel_coord(right_x, bottom_y))
|
||||||
|
|
||||||
|
# To draw the guidelines, start from
|
||||||
|
for x in range(GUIDELINE_MOD_X % 4, WIDTH + 4, 4):
|
||||||
|
# Vertical guideline
|
||||||
|
as_if = GUIDELINE_MOD_X - x
|
||||||
|
#print(x, as_if)
|
||||||
|
line_x = PIXELSPACE_START_X + (x * pixel_scale) + (x * PIXEL_MARGIN)
|
||||||
|
line_x = line_x - PIXEL_MARGIN + (PIXEL_MARGIN // 2)
|
||||||
|
if line_x >= PIXELSPACE_END_X:
|
||||||
|
continue
|
||||||
|
(color, width) = choose_guideline_style(as_if)
|
||||||
|
draw.line((line_x, CANVAS_START_Y, line_x, CANVAS_END_Y - 1), fill=color, width=width)
|
||||||
|
draw.text((line_x, CANVAS_END_X), str(x), fill='#000')
|
||||||
|
|
||||||
|
for y in range(GUIDELINE_MOD_Y % 4, HEIGH + 4, 4):
|
||||||
|
# Horizontal guideline
|
||||||
|
as_if = GUIDELINE_MOD_Y - y
|
||||||
|
#print(y, as_if)
|
||||||
|
line_y = PIXELSPACE_START_Y + (y * pixel_scale) + (y * PIXEL_MARGIN)
|
||||||
|
line_y = line_y - PIXEL_MARGIN + (PIXEL_MARGIN // 2)
|
||||||
|
if line_y >= PIXELSPACE_END_Y:
|
||||||
|
continue
|
||||||
|
(color, width) = choose_guideline_style(as_if)
|
||||||
|
draw.line((CANVAS_START_X, line_y, CANVAS_END_X - 1, line_y), fill=color, width=width)
|
||||||
|
draw.text((CANVAS_END_X, line_y), str(y), fill='#000')
|
||||||
|
|
||||||
|
draw.rectangle((CANVAS_START_X, CANVAS_START_Y, CANVAS_END_X - 1, CANVAS_END_Y - 1), outline='#000')
|
||||||
|
draw.text((CANVAS_START_X, IMAGE_HEIGH - LABEL_HEIGH), layer_filename, fill='#000')
|
||||||
|
print(layer_filename)
|
||||||
|
if FINAL_IMAGE_SCALE != 1:
|
||||||
|
layer_image = layer_image.resize((FINAL_IMAGE_SCALE * IMAGE_WIDTH, FINAL_IMAGE_SCALE * IMAGE_HEIGH))
|
||||||
|
|
||||||
|
return layer_image
|
||||||
|
|
||||||
|
layer_matrices = []
|
||||||
|
for z in range(DEPTH):
|
||||||
|
if z < math.ceil(RAD_Z):
|
||||||
|
layer_matrix = make_layer_matrix(z)
|
||||||
|
layer_matrices.append(layer_matrix)
|
||||||
|
else:
|
||||||
|
layer_matrix = layer_matrices[(DEPTH - 1) - z]
|
||||||
|
layer_filename = filename_form.format(layer=z)
|
||||||
|
layer_image = make_layer_image(layer_matrix)
|
||||||
|
layer_image.save(layer_filename)
|
||||||
|
|
||||||
|
|
||||||
|
# Copy to the upper right corner.
|
||||||
|
#for y in range(math.ceil(RAD_Y)):
|
||||||
|
# #print(y)
|
||||||
|
# for x in range(math.ceil(RAD_X), WIDTH):
|
||||||
|
# #print(x, '==', (WIDTH-1) - x)
|
||||||
|
# mapped_x = (WIDTH - 1) - x
|
||||||
|
# layer_matrix[x][y] = layer_matrix[mapped_x][y]
|
||||||
|
|
||||||
|
# Copy to the lower semicircle.
|
||||||
|
#for y in range(math.ceil(RAD_Y), HEIGH):
|
||||||
|
# #print(y)
|
||||||
|
# for x in range(WIDTH):
|
||||||
|
# #print(y, '==', (HEIGH-1) - y)
|
||||||
|
# mapped_y = (HEIGH-1) - y
|
||||||
|
# layer_matrix[x][y] = layer_matrix[x][mapped_y]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#break
|
||||||
|
#layer_matrix = [['▓' if dot == dot_highlight else '░' if dot == dot_normal else ' 'for dot in sublist] for sublist in layer_matrix]
|
||||||
|
#layer_matrix = [''.join(sublist) for sublist in layer_matrix]
|
||||||
|
#layer_matrix = '\n'.join(layer_matrix)
|
||||||
|
#print(layer_matrix)
|
||||||
|
#print()
|
||||||
|
|
||||||
|
|
||||||
|
def voxelsphere_argparse(args):
|
||||||
|
height_depth_match = bool(args.height) == bool(args.depth)
|
||||||
|
if not height_depth_match:
|
||||||
|
raise ValueError('Must provide both or neither of height+depth. Not just one.')
|
||||||
|
|
||||||
|
if (args.height is args.depth is None):
|
||||||
|
args.height = args.width
|
||||||
|
args.depth = args.width
|
||||||
|
|
||||||
|
voxelspheregenerator(
|
||||||
|
int(args.width),
|
||||||
|
int(args.height),
|
||||||
|
int(args.depth),
|
||||||
|
WALL_THICKNESS=int(args.wall_thickness) if args.wall_thickness else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
|
||||||
|
parser.add_argument('width')
|
||||||
|
parser.add_argument('height', nargs='?', default=None)
|
||||||
|
parser.add_argument('depth', nargs='?', default=None)
|
||||||
|
parser.add_argument('--wall', dest='wall_thickness', default=None)
|
||||||
|
parser.set_defaults(func=voxelsphere_argparse)
|
||||||
|
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
args.func(args)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main(sys.argv[1:])
|
3
WebstreamZip/README.md
Normal file
3
WebstreamZip/README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
PLEASE DON'T USE THIS
|
||||||
|
|
||||||
|
Use [python-zipstream](https://github.com/allanlei/python-zipstream) instead.
|
Binary file not shown.
|
@ -1,7 +0,0 @@
|
||||||
import webstreamzip
|
|
||||||
|
|
||||||
g = webstreamzip.stream_zip({'C:\\git\\else\\readme.md':'michael.md'})
|
|
||||||
|
|
||||||
x = open('test.zip', 'wb')
|
|
||||||
for chunk in g:
|
|
||||||
x.write(chunk)
|
|
|
@ -10,6 +10,7 @@ PATHS = [
|
||||||
'C:\\git\\else\\Pathclass\\pathclass.py',
|
'C:\\git\\else\\Pathclass\\pathclass.py',
|
||||||
'C:\\git\\else\\Ratelimiter\\ratelimiter.py',
|
'C:\\git\\else\\Ratelimiter\\ratelimiter.py',
|
||||||
'C:\\git\\else\\RateMeter\\ratemeter.py',
|
'C:\\git\\else\\RateMeter\\ratemeter.py',
|
||||||
|
'C:\\git\\else\\Safeprint\\safeprint.py',
|
||||||
'C:\\git\\else\\SpinalTap\\spinal.py',
|
'C:\\git\\else\\SpinalTap\\spinal.py',
|
||||||
'C:\\git\\else\\WebstreamZip\\webstreamzip.py',
|
'C:\\git\\else\\WebstreamZip\\webstreamzip.py',
|
||||||
]
|
]
|
||||||
|
@ -40,7 +41,7 @@ import setuptools
|
||||||
setuptools.setup(
|
setuptools.setup(
|
||||||
author='voussoir',
|
author='voussoir',
|
||||||
name='{package}',
|
name='{package}',
|
||||||
version='0.0.3',
|
version='0.0.4',
|
||||||
description='',
|
description='',
|
||||||
py_modules=[{py_modules}],
|
py_modules=[{py_modules}],
|
||||||
)
|
)
|
||||||
|
|
Binary file not shown.
Loading…
Reference in a new issue