istr-python 0.0.0__py3-none-any.whl
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.
- istr/__init__.py +0 -0
- istr/install istr.py +219 -0
- istr/istr.py +352 -0
- istr_python-0.0.0.dist-info/METADATA +123 -0
- istr_python-0.0.0.dist-info/RECORD +7 -0
- istr_python-0.0.0.dist-info/WHEEL +5 -0
- istr_python-0.0.0.dist-info/top_level.txt +1 -0
istr/__init__.py
ADDED
|
File without changes
|
istr/install istr.py
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
from __future__ import print_function
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import site
|
|
5
|
+
import shutil
|
|
6
|
+
import hashlib
|
|
7
|
+
import base64
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import configparser
|
|
10
|
+
import six
|
|
11
|
+
from six.moves import urllib
|
|
12
|
+
|
|
13
|
+
# import urllib.request
|
|
14
|
+
# import urllib.error
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _install(files, url=None):
|
|
18
|
+
"""
|
|
19
|
+
install one file package from GitHub or current directory
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
files : list
|
|
24
|
+
files to be installed
|
|
25
|
+
the first item (files[0]) will be used as the name of the package''
|
|
26
|
+
optional files should be preceded with an exclamation mark (!)
|
|
27
|
+
|
|
28
|
+
url : str
|
|
29
|
+
url of the location of the GitHub repository
|
|
30
|
+
this will start usually with https://raw.githubusercontent.com/ and end with /master/
|
|
31
|
+
if omitted, the files will be copied from the current directory (not GitHub)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
info : Info instance
|
|
37
|
+
info.package : name of the package installed
|
|
38
|
+
info.path : name where the package is installed in the site-packages
|
|
39
|
+
info.version : version of the package (obtained from <package>.py)
|
|
40
|
+
info.files_copied : list of copied files
|
|
41
|
+
|
|
42
|
+
Notes
|
|
43
|
+
-----
|
|
44
|
+
The program automatically makes the required __init__.py file (unless given in files) and
|
|
45
|
+
<package><version>.dist-info folder with the usual files METADATA, INSTALLER and RECORDS.
|
|
46
|
+
As the setup.py is not run, the METADATA is very limited, i.e. is contains just name and version.
|
|
47
|
+
|
|
48
|
+
If a __init__.py is in files that file will be used.
|
|
49
|
+
Otherwise, an __init__/py file will be generated. In thet case, if a __version__ = statement
|
|
50
|
+
is found in the source file, the __version__ will be included in that __init__.py file.
|
|
51
|
+
|
|
52
|
+
Version history
|
|
53
|
+
---------------
|
|
54
|
+
version 1.0.5 2020-06-24
|
|
55
|
+
Bug with removing the dist-info of packages starting with the same name fixed.
|
|
56
|
+
|
|
57
|
+
version 1.0.4 2020-03-29
|
|
58
|
+
Linux and ios versions now search in sys.path for site-packages,
|
|
59
|
+
whereas other platforms now use site.getsitepackages().
|
|
60
|
+
This is to avoid installation in a roaming directory on Windows.
|
|
61
|
+
|
|
62
|
+
version 1.0.2 2020-03-07
|
|
63
|
+
modified several open calls to be compatible with Python < 3.6
|
|
64
|
+
multipe installation for Pythonista removed. Now installs only in site-packages
|
|
65
|
+
|
|
66
|
+
version 1.0.1 2020-03-06
|
|
67
|
+
now uses urllib instead of requests to avoid non standard libraries
|
|
68
|
+
installation for Pythonista improved
|
|
69
|
+
|
|
70
|
+
version 1.0.0 2020-03-04
|
|
71
|
+
initial version
|
|
72
|
+
|
|
73
|
+
(c)2020 Ruud van der Ham - www.salabim.org
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
class Info:
|
|
77
|
+
version = "?"
|
|
78
|
+
package = "?"
|
|
79
|
+
path = "?"
|
|
80
|
+
files_copied = []
|
|
81
|
+
|
|
82
|
+
info = Info()
|
|
83
|
+
Pythonista = sys.platform == "ios"
|
|
84
|
+
if not files:
|
|
85
|
+
raise ValueError("no files specified")
|
|
86
|
+
if files[0][0] == "!":
|
|
87
|
+
raise ValueError("first item in files (sourcefile) may not be optional")
|
|
88
|
+
package = Path(files[0]).stem
|
|
89
|
+
sourcefile = files[0]
|
|
90
|
+
|
|
91
|
+
file_contents = {}
|
|
92
|
+
for file in files:
|
|
93
|
+
optional = file[0] == "!"
|
|
94
|
+
if optional:
|
|
95
|
+
file = file[1:]
|
|
96
|
+
|
|
97
|
+
if url:
|
|
98
|
+
try:
|
|
99
|
+
with urllib.request.urlopen(url + file) as response:
|
|
100
|
+
page = response.read()
|
|
101
|
+
|
|
102
|
+
file_contents[file] = page
|
|
103
|
+
exists = True
|
|
104
|
+
except urllib.error.URLError:
|
|
105
|
+
exists = False
|
|
106
|
+
|
|
107
|
+
else:
|
|
108
|
+
exists = Path(file).is_file()
|
|
109
|
+
if exists:
|
|
110
|
+
with open(file, "rb") as f:
|
|
111
|
+
file_contents[file] = f.read()
|
|
112
|
+
|
|
113
|
+
if (not exists) and (not optional):
|
|
114
|
+
raise FileNotFoundError(file + " not found. Nothing installed.")
|
|
115
|
+
|
|
116
|
+
version = "unknown"
|
|
117
|
+
for line in file_contents[sourcefile].decode("utf-8").split("\n"):
|
|
118
|
+
line_split = line.split("__version__ =")
|
|
119
|
+
if len(line_split) > 1:
|
|
120
|
+
raw_version = line_split[-1].strip(" '\"")
|
|
121
|
+
version = ""
|
|
122
|
+
for c in raw_version:
|
|
123
|
+
if c in "0123456789-.":
|
|
124
|
+
version += c
|
|
125
|
+
else:
|
|
126
|
+
break
|
|
127
|
+
break
|
|
128
|
+
|
|
129
|
+
info.files_copied = list(file_contents.keys())
|
|
130
|
+
info.package = package
|
|
131
|
+
info.version = version
|
|
132
|
+
|
|
133
|
+
file = "__init__.py"
|
|
134
|
+
if file not in file_contents:
|
|
135
|
+
file_contents[file] = ("from ." + package + " import *\n").encode()
|
|
136
|
+
if version != "unknown":
|
|
137
|
+
file_contents[file] += ("from ." + package + " import __version__\n").encode()
|
|
138
|
+
if sys.platform.startswith("linux") or (sys.platform == "ios"):
|
|
139
|
+
search_in = sys.path
|
|
140
|
+
else:
|
|
141
|
+
search_in = site.getsitepackages()
|
|
142
|
+
|
|
143
|
+
for f in search_in:
|
|
144
|
+
sitepackages_path = Path(f)
|
|
145
|
+
if sitepackages_path.name == "site-packages" and sitepackages_path.is_dir():
|
|
146
|
+
break
|
|
147
|
+
else:
|
|
148
|
+
raise ModuleNotFoundError("can't find the site-packages folder")
|
|
149
|
+
|
|
150
|
+
path = sitepackages_path / package
|
|
151
|
+
info.path = str(path)
|
|
152
|
+
|
|
153
|
+
if path.is_file():
|
|
154
|
+
path.unlink()
|
|
155
|
+
|
|
156
|
+
if not path.is_dir():
|
|
157
|
+
path.mkdir()
|
|
158
|
+
|
|
159
|
+
for file, contents in file_contents.items():
|
|
160
|
+
with (path / file).open("wb") as f:
|
|
161
|
+
f.write(contents)
|
|
162
|
+
|
|
163
|
+
if Pythonista:
|
|
164
|
+
pypi_packages = sitepackages_path / ".pypi_packages"
|
|
165
|
+
config = configparser.ConfigParser()
|
|
166
|
+
config.read(pypi_packages)
|
|
167
|
+
config[package] = {}
|
|
168
|
+
config[package]["url"] = "pypi"
|
|
169
|
+
config[package]["version"] = version
|
|
170
|
+
config[package]["summary"] = ""
|
|
171
|
+
config[package]["files"] = path.as_posix()
|
|
172
|
+
config[package]["dependency"] = ""
|
|
173
|
+
with pypi_packages.open("w") as f:
|
|
174
|
+
config.write(f)
|
|
175
|
+
else:
|
|
176
|
+
for entry in sitepackages_path.glob("*"):
|
|
177
|
+
if entry.is_dir():
|
|
178
|
+
if entry.stem.startswith(package + "-") and entry.suffix == ".dist-info":
|
|
179
|
+
shutil.rmtree(str(entry))
|
|
180
|
+
path_distinfo = Path(str(path) + "-" + version + ".dist-info")
|
|
181
|
+
if not path_distinfo.is_dir():
|
|
182
|
+
path_distinfo.mkdir()
|
|
183
|
+
with open(str(path_distinfo / "METADATA"), "w") as f: # make a dummy METADATA file
|
|
184
|
+
f.write("Name: " + package + "\n")
|
|
185
|
+
f.write("Version: " + version + "\n")
|
|
186
|
+
|
|
187
|
+
with open(str(path_distinfo / "INSTALLER"), "w") as f: # make a dummy METADATA file
|
|
188
|
+
f.write("github\n")
|
|
189
|
+
with open(str(path_distinfo / "RECORD"), "w") as f:
|
|
190
|
+
pass # just to create the file to be recorded
|
|
191
|
+
|
|
192
|
+
with open(str(path_distinfo / "RECORD"), "w") as record_file:
|
|
193
|
+
for p in (path, path_distinfo):
|
|
194
|
+
for file in p.glob("**/*"):
|
|
195
|
+
if file.is_file():
|
|
196
|
+
name = file.relative_to(sitepackages_path).as_posix() # make sure we have slashes
|
|
197
|
+
record_file.write(name + ",")
|
|
198
|
+
|
|
199
|
+
if (file.stem == "RECORD" and p == path_distinfo) or ("__pycache__" in name.lower()):
|
|
200
|
+
record_file.write(",")
|
|
201
|
+
else:
|
|
202
|
+
with file.open("rb") as f:
|
|
203
|
+
file_contents = f.read()
|
|
204
|
+
hash = "sha256=" + base64.urlsafe_b64encode(hashlib.sha256(file_contents).digest()).decode("latin1").rstrip("=")
|
|
205
|
+
# hash calculation derived from wheel.py in pip
|
|
206
|
+
|
|
207
|
+
length = str(len(file_contents))
|
|
208
|
+
record_file.write(hash + "," + length)
|
|
209
|
+
|
|
210
|
+
record_file.write("\n")
|
|
211
|
+
|
|
212
|
+
return info
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
if __name__ == "__main__":
|
|
216
|
+
info = _install(files="istr.py !changelog.txt".split())
|
|
217
|
+
print(info.package + " " + info.version + " successfully installed in " + info.path)
|
|
218
|
+
print("files copied: ", ", ".join(info.files_copied))
|
|
219
|
+
|
istr/istr.py
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
from functools import partial
|
|
2
|
+
import math
|
|
3
|
+
import contextlib
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class istr(str):
|
|
7
|
+
"""
|
|
8
|
+
istr object
|
|
9
|
+
|
|
10
|
+
Parameters
|
|
11
|
+
----------
|
|
12
|
+
value : any
|
|
13
|
+
value to store
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
_format = ""
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def toint(cls, value):
|
|
22
|
+
if isinstance(value, cls):
|
|
23
|
+
return value.asint
|
|
24
|
+
if isinstance(value, str):
|
|
25
|
+
if value == "":
|
|
26
|
+
return 0
|
|
27
|
+
return int(value)
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def check_format(cls, format):
|
|
31
|
+
if format is None:
|
|
32
|
+
return cls._format
|
|
33
|
+
if not (isinstance(format, str) and all(x in "0123456789" for x in format)):
|
|
34
|
+
raise ValueError(f"{repr(format)} is incorrect format")
|
|
35
|
+
return format
|
|
36
|
+
|
|
37
|
+
def __new__(cls, value=""):
|
|
38
|
+
if isinstance(value, range):
|
|
39
|
+
return cls.range(value.start, value.stop, value.step)
|
|
40
|
+
if not isinstance(value, str) and hasattr(value, "__iter__"):
|
|
41
|
+
if hasattr(value, "__next__") or type(value) == range:
|
|
42
|
+
return map(partial(cls), value)
|
|
43
|
+
return type(value)(map(partial(cls), value))
|
|
44
|
+
|
|
45
|
+
if cls._format == "" or value == "":
|
|
46
|
+
return super().__new__(cls, str(value))
|
|
47
|
+
else:
|
|
48
|
+
return super().__new__(cls, f"{cls.toint(value):{cls._format}}")
|
|
49
|
+
|
|
50
|
+
def __init__(self, value=""):
|
|
51
|
+
self.asint = self.toint(value)
|
|
52
|
+
super().__init__()
|
|
53
|
+
|
|
54
|
+
def __hash__(self):
|
|
55
|
+
return hash((self.__class__, self.asint))
|
|
56
|
+
|
|
57
|
+
def __eq__(self, other):
|
|
58
|
+
if isinstance(other, self.__class__):
|
|
59
|
+
return self.asint == other.asint
|
|
60
|
+
if isinstance(other, str):
|
|
61
|
+
return super().__eq__(other)
|
|
62
|
+
try:
|
|
63
|
+
return self.asint == self.toint(other)
|
|
64
|
+
except Exception:
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
def __ne__(self, other):
|
|
68
|
+
return not self == other
|
|
69
|
+
|
|
70
|
+
def __contains__(self, other):
|
|
71
|
+
return super().__contains__(str(other))
|
|
72
|
+
|
|
73
|
+
def __repr__(self):
|
|
74
|
+
return f"{self.__class__.__name__}({super().__repr__()})"
|
|
75
|
+
|
|
76
|
+
def __le__(self, other):
|
|
77
|
+
return self.asint <= self.toint(other)
|
|
78
|
+
|
|
79
|
+
def __lt__(self, other):
|
|
80
|
+
return self.asint < self.toint(other)
|
|
81
|
+
|
|
82
|
+
def __ge__(self, other):
|
|
83
|
+
return self.asint >= self.toint(other)
|
|
84
|
+
|
|
85
|
+
def __gt__(self, other):
|
|
86
|
+
return self.asint > self.toint(other)
|
|
87
|
+
|
|
88
|
+
def __add__(self, other):
|
|
89
|
+
return self.__class__(self.asint + self.toint(other))
|
|
90
|
+
|
|
91
|
+
def __sub__(self, other):
|
|
92
|
+
return self.__class__(self.asint - self.toint(other))
|
|
93
|
+
|
|
94
|
+
def __mul__(self, other):
|
|
95
|
+
return self.__class__(self.asint * self.toint(other))
|
|
96
|
+
|
|
97
|
+
def __floordiv__(self, other):
|
|
98
|
+
return self.__class__(self.asint // self.toint(other))
|
|
99
|
+
|
|
100
|
+
def __rfloordiv__(self, other):
|
|
101
|
+
return self.__class__(self.toint(other) // self.asint)
|
|
102
|
+
|
|
103
|
+
def __truediv__(self, other):
|
|
104
|
+
return self.__class__(self.asint // self.toint(other))
|
|
105
|
+
|
|
106
|
+
def __rtruediv__(self, other):
|
|
107
|
+
return self.__class__(self.toint(other) // self.asint)
|
|
108
|
+
|
|
109
|
+
def __pow__(self, other):
|
|
110
|
+
return self.__class__(self.asint ** self.toint(other))
|
|
111
|
+
|
|
112
|
+
def __rpow__(self, other):
|
|
113
|
+
return self.__class__(self.toint(other) ** self.asint)
|
|
114
|
+
|
|
115
|
+
def __radd__(self, other):
|
|
116
|
+
return self.__class__(self.toint(other) + self.asint)
|
|
117
|
+
|
|
118
|
+
def __rsub__(self, other):
|
|
119
|
+
return self.__class__(self.toint(other) - self.asint)
|
|
120
|
+
|
|
121
|
+
def __rmul__(self, other):
|
|
122
|
+
return self.__class__(self.toint(other) * self.asint)
|
|
123
|
+
|
|
124
|
+
def __mod__(self, other):
|
|
125
|
+
return self.__class__(self.asint % self.toint(other))
|
|
126
|
+
|
|
127
|
+
def __rmod__(self, other):
|
|
128
|
+
return self.__class__(self.toint(other) % self.asint)
|
|
129
|
+
|
|
130
|
+
def __or__(self, other):
|
|
131
|
+
return self.__class__("".join((self, self.__class__(other))))
|
|
132
|
+
|
|
133
|
+
def __ror__(self, other):
|
|
134
|
+
return self.__class__("".join((self.__class__(other), self)))
|
|
135
|
+
|
|
136
|
+
def __int__(self):
|
|
137
|
+
return int(self.asint)
|
|
138
|
+
|
|
139
|
+
def __round__(self):
|
|
140
|
+
return self.__class__(round(self.asint))
|
|
141
|
+
|
|
142
|
+
def __trunc__(self):
|
|
143
|
+
return self.__class__(math.trunc(self.asint))
|
|
144
|
+
|
|
145
|
+
def __floor__(self):
|
|
146
|
+
return self.__class__(math.floor(self.asint))
|
|
147
|
+
|
|
148
|
+
def __ceil__(self):
|
|
149
|
+
return self.__class__(math.ceil(self.asint))
|
|
150
|
+
|
|
151
|
+
def __matmul__(self, other):
|
|
152
|
+
return self.__class__(super().__mul__(other))
|
|
153
|
+
|
|
154
|
+
def __rmatmul__(self, other):
|
|
155
|
+
return self.__class__(super().__rmul__(other))
|
|
156
|
+
|
|
157
|
+
def __divmod__(self, other):
|
|
158
|
+
return self.__class__(divmod(self.asint, self.toint(other)))
|
|
159
|
+
|
|
160
|
+
def __rdivmod__(self, other):
|
|
161
|
+
return self.__class__(divmod(self.toint(other), self.asint))
|
|
162
|
+
|
|
163
|
+
def __neg__(self):
|
|
164
|
+
return self.__class__(-self.asint)
|
|
165
|
+
|
|
166
|
+
def __pos__(self):
|
|
167
|
+
return self
|
|
168
|
+
|
|
169
|
+
def __abs__(self):
|
|
170
|
+
return self.__class__(abs(self.asint))
|
|
171
|
+
|
|
172
|
+
def is_even(self):
|
|
173
|
+
return self.asint % 2 == 0
|
|
174
|
+
|
|
175
|
+
def is_odd(self):
|
|
176
|
+
return self.asint % 2 == 1
|
|
177
|
+
|
|
178
|
+
def join(self, iterable):
|
|
179
|
+
s = super().join(iterable)
|
|
180
|
+
return self.__class__(s)
|
|
181
|
+
|
|
182
|
+
@classmethod
|
|
183
|
+
def enumerate(cls, iterable, start=0):
|
|
184
|
+
for i, value in enumerate(iterable, start):
|
|
185
|
+
yield cls(i), value
|
|
186
|
+
|
|
187
|
+
@classmethod
|
|
188
|
+
@contextlib.contextmanager
|
|
189
|
+
def format(cls, format):
|
|
190
|
+
saved_format = cls._format
|
|
191
|
+
cls._format = cls.check_format(format)
|
|
192
|
+
yield
|
|
193
|
+
cls._format = saved_format
|
|
194
|
+
|
|
195
|
+
@classmethod
|
|
196
|
+
def default_format(cls, format=None):
|
|
197
|
+
if format is not None:
|
|
198
|
+
cls._format = cls.check_format(format)
|
|
199
|
+
return cls._format
|
|
200
|
+
|
|
201
|
+
@classmethod
|
|
202
|
+
class range:
|
|
203
|
+
"""
|
|
204
|
+
based on https://codereview.stackexchange.com/questions/229073/pure-python-range-implementation
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
def __init__(self, cls, start, stop=None, step=1):
|
|
208
|
+
if stop is None:
|
|
209
|
+
start, stop = 0, start
|
|
210
|
+
self.start, self.stop, self.step = (int(obj) for obj in (start, stop, step))
|
|
211
|
+
if step == 0:
|
|
212
|
+
raise ValueError("range() arg 3 must not be zero")
|
|
213
|
+
if self.step < 0:
|
|
214
|
+
step_sign = -1
|
|
215
|
+
else:
|
|
216
|
+
step_sign = 1
|
|
217
|
+
self._len = max(1 + (self.stop - self.start - step_sign) // self.step, 0)
|
|
218
|
+
self.parent_cls = cls
|
|
219
|
+
|
|
220
|
+
def __contains__(self, value):
|
|
221
|
+
if isinstance(value, int):
|
|
222
|
+
return self._index(value) != -1
|
|
223
|
+
return any(n == value for n in self)
|
|
224
|
+
|
|
225
|
+
def __eq__(self, other):
|
|
226
|
+
if not isinstance(other, type(self)):
|
|
227
|
+
return False
|
|
228
|
+
if self._len != len(other):
|
|
229
|
+
return False
|
|
230
|
+
if self._len == 0:
|
|
231
|
+
return True
|
|
232
|
+
if self.start != other.start:
|
|
233
|
+
return False
|
|
234
|
+
if self[-1] == other[-1]:
|
|
235
|
+
return True
|
|
236
|
+
return False
|
|
237
|
+
|
|
238
|
+
def __getitem__(self, index):
|
|
239
|
+
def adjust_indices(length, start, stop, step):
|
|
240
|
+
if step is None:
|
|
241
|
+
step = 1
|
|
242
|
+
else:
|
|
243
|
+
step = int(step)
|
|
244
|
+
|
|
245
|
+
if start is None:
|
|
246
|
+
start = length - 1 if step < 0 else 0
|
|
247
|
+
else:
|
|
248
|
+
start = int(start)
|
|
249
|
+
if start < 0:
|
|
250
|
+
start += length
|
|
251
|
+
if start < 0:
|
|
252
|
+
start = -1 if step < 0 else 0
|
|
253
|
+
elif start >= length:
|
|
254
|
+
start = length - 1 if step < 0 else length
|
|
255
|
+
|
|
256
|
+
if stop is None:
|
|
257
|
+
stop = -1 if step < 0 else length
|
|
258
|
+
else:
|
|
259
|
+
stop = int(stop)
|
|
260
|
+
if stop < 0:
|
|
261
|
+
stop += length
|
|
262
|
+
if stop < 0:
|
|
263
|
+
stop = -1 if step < 0 else 0
|
|
264
|
+
elif stop >= length:
|
|
265
|
+
stop = length - 1 if step < 0 else length
|
|
266
|
+
|
|
267
|
+
return start, stop, step
|
|
268
|
+
|
|
269
|
+
if isinstance(index, slice):
|
|
270
|
+
start, stop, step = adjust_indices(self._len, index.start, index.stop, index.step)
|
|
271
|
+
return self.parent_cls.range(self.start + self.step * start, self.start + self.step * stop, self.step * step)
|
|
272
|
+
index = int(index)
|
|
273
|
+
if index < 0:
|
|
274
|
+
index += self._len
|
|
275
|
+
if not 0 <= index < self._len:
|
|
276
|
+
raise IndexError("range object index out of range")
|
|
277
|
+
return self.parent_cls(self.start + self.step * index)
|
|
278
|
+
|
|
279
|
+
def __hash__(self):
|
|
280
|
+
if self._len == 0:
|
|
281
|
+
return id(self.parent_cls.range)
|
|
282
|
+
return hash((self._len, self.start, int(self[-1])))
|
|
283
|
+
|
|
284
|
+
def __iter__(self):
|
|
285
|
+
value = self.start
|
|
286
|
+
if self.step > 0:
|
|
287
|
+
while value < self.stop:
|
|
288
|
+
yield self.parent_cls(value)
|
|
289
|
+
value += self.step
|
|
290
|
+
else:
|
|
291
|
+
while value > self.stop:
|
|
292
|
+
yield self.parent_cls(value)
|
|
293
|
+
value += self.step
|
|
294
|
+
|
|
295
|
+
def __len__(self):
|
|
296
|
+
return self._len
|
|
297
|
+
|
|
298
|
+
def __repr__(self):
|
|
299
|
+
if self.step == 1:
|
|
300
|
+
return f"{self.parent_cls.__name__}.range({self.start}, {self.stop})"
|
|
301
|
+
return f"{self.parent_cls.__name__}.range({self.start}, {self.stop}, {self.step})"
|
|
302
|
+
|
|
303
|
+
def __reversed__(self):
|
|
304
|
+
return iter(self[::-1])
|
|
305
|
+
|
|
306
|
+
def _index(self, value):
|
|
307
|
+
index_mul_step = value - self.start
|
|
308
|
+
if index_mul_step % self.step:
|
|
309
|
+
return -1
|
|
310
|
+
index = index_mul_step // self.step
|
|
311
|
+
if 0 <= index < self._len:
|
|
312
|
+
return index
|
|
313
|
+
return -1
|
|
314
|
+
|
|
315
|
+
def count(self, value):
|
|
316
|
+
"""
|
|
317
|
+
Rangeobject.count(value) -> integer
|
|
318
|
+
Return number of occurrences of value.
|
|
319
|
+
"""
|
|
320
|
+
return sum(1 for n in self if int(n) == int(value))
|
|
321
|
+
|
|
322
|
+
def index(self, value, start=0, stop=None):
|
|
323
|
+
if start < 0:
|
|
324
|
+
start = max(self._len + start, 0)
|
|
325
|
+
if stop is None:
|
|
326
|
+
stop = self._len
|
|
327
|
+
if stop < 0:
|
|
328
|
+
stop += self._len
|
|
329
|
+
|
|
330
|
+
if isinstance(value, int):
|
|
331
|
+
index = self._index(value)
|
|
332
|
+
if start <= index < stop:
|
|
333
|
+
return index
|
|
334
|
+
raise ValueError(f"{value} is not in range")
|
|
335
|
+
|
|
336
|
+
i = start
|
|
337
|
+
n = self.start + self.step * i
|
|
338
|
+
while i < stop:
|
|
339
|
+
if n == int(value):
|
|
340
|
+
return i
|
|
341
|
+
i += 1
|
|
342
|
+
n += self.step
|
|
343
|
+
raise ValueError(f"{value} is not in range")
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def main():
|
|
347
|
+
print(repr(istr("").join(istr.range(5))))
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
if __name__ == "__main__":
|
|
351
|
+
main()
|
|
352
|
+
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: istr-python
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: istr description
|
|
5
|
+
Author-email: Ruud van der Ham <rt.van.der.ham@gmail.com>
|
|
6
|
+
Requires-Python: >=3.7
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
|
|
9
|
+
# Introduction
|
|
10
|
+
|
|
11
|
+
The istrlib module has exactly one class: istr.
|
|
12
|
+
|
|
13
|
+
With this it is possible to interpret string as if they were integers. This can be very handy for solving
|
|
14
|
+
puzzles, but also for other purposes.
|
|
15
|
+
|
|
16
|
+
# Installation
|
|
17
|
+
Installing istrlib with pip is easy.
|
|
18
|
+
```
|
|
19
|
+
$ pip install istrlib
|
|
20
|
+
```
|
|
21
|
+
or when you want to upgrade,
|
|
22
|
+
```
|
|
23
|
+
$ pip install istrlib --upgrade
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Alternatively, istrlib.py can be just copied into you current work directory from GitHub (https://github.com/salabim/istrlib).
|
|
27
|
+
|
|
28
|
+
No dependencies!
|
|
29
|
+
|
|
30
|
+
# Usage
|
|
31
|
+
Just start with
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
from istrlib import istr
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Now we can define some istrs:
|
|
38
|
+
```
|
|
39
|
+
four = istr("4")
|
|
40
|
+
five = istr("5")
|
|
41
|
+
```
|
|
42
|
+
Them we can do
|
|
43
|
+
```
|
|
44
|
+
x= four * five
|
|
45
|
+
```
|
|
46
|
+
, after which x is `istr("40")`
|
|
47
|
+
|
|
48
|
+
And now we can do
|
|
49
|
+
```
|
|
50
|
+
print(x == 40)
|
|
51
|
+
print(x == "40")
|
|
52
|
+
```
|
|
53
|
+
resulting in two times `True`. That's because istr instances are treated as int, although they are strings.
|
|
54
|
+
|
|
55
|
+
That means that we can also say
|
|
56
|
+
```
|
|
57
|
+
print(x < 50)
|
|
58
|
+
print(x >= "30")
|
|
59
|
+
```
|
|
60
|
+
again resulting in two times `True`.
|
|
61
|
+
|
|
62
|
+
In contrast to an ordinary string
|
|
63
|
+
```
|
|
64
|
+
print(four + five)
|
|
65
|
+
```
|
|
66
|
+
prints `9`, as istr are treated as ints.
|
|
67
|
+
|
|
68
|
+
So, how can we concatenate istrs? Just use the or operator (|):
|
|
69
|
+
```
|
|
70
|
+
print(four | five)
|
|
71
|
+
```
|
|
72
|
+
will output `45`.
|
|
73
|
+
|
|
74
|
+
And the result is again an istr.
|
|
75
|
+
|
|
76
|
+
That means that
|
|
77
|
+
```
|
|
78
|
+
(four | five) / 3
|
|
79
|
+
```
|
|
80
|
+
is `istr("9")`.
|
|
81
|
+
|
|
82
|
+
In order to multiply a string in the usual sense, you cannot use `3 * four`, as that will be `12`.
|
|
83
|
+
|
|
84
|
+
We use the matrix multiplication operator (@) for this. So `3 @ four` is `444`.
|
|
85
|
+
|
|
86
|
+
Also allowed are
|
|
87
|
+
```
|
|
88
|
+
abs(four)
|
|
89
|
+
-four
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
For the in operator a istr is treated as an ordinary string, although it is possible to use ints as well:
|
|
93
|
+
```
|
|
94
|
+
"34" in istr(1234)
|
|
95
|
+
34 in istr(1234)
|
|
96
|
+
```
|
|
97
|
+
On the left hand side an istr is always treated as a string:
|
|
98
|
+
```
|
|
99
|
+
istr(1234) in "01234566890ABCDEF"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Note that all calculations are strictly integer calculations. That means that if a float variale is ever produced it will be converted to an int.
|
|
103
|
+
Also divisions are always floor divisions!
|
|
104
|
+
|
|
105
|
+
There's a special case for `istr("")`. This is a proper empty string, but also represents the value of 0.
|
|
106
|
+
That is to allow for istr("").join(i for i in "01234"
|
|
107
|
+
|
|
108
|
+
Sorting a list of istrs is based on the integer value, not the string. So
|
|
109
|
+
|
|
110
|
+
`' '.join(sorted('1 3 2 4 5 6 11 7 9 8 10 12 0'.split()))`
|
|
111
|
+
|
|
112
|
+
is
|
|
113
|
+
|
|
114
|
+
`'0 1 10 11 2 3 4 5 6 7 8 9'`
|
|
115
|
+
|
|
116
|
+
,whereas
|
|
117
|
+
|
|
118
|
+
`' '.join(sorted(istr('1 3 2 4 5 6 11 7 9 8 10 12 0'.split())))`
|
|
119
|
+
|
|
120
|
+
is
|
|
121
|
+
|
|
122
|
+
`'0 1 2 3 4 5 6 7 8 9 10 11'`
|
|
123
|
+
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
istr/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
istr/install istr.py,sha256=MnVdIOGm7z1Gf6xX95sfEFGDQ2Q3Mh37M27EjeogEPo,7734
|
|
3
|
+
istr/istr.py,sha256=7W9yT5t_mI7OTwhsOGYH3yP1psPG-wFawyW4j3WqRtg,10714
|
|
4
|
+
istr_python-0.0.0.dist-info/METADATA,sha256=733e1TJoFqx_x6w3o2I_5kTWNa0AasTu0fih19JOcyc,2732
|
|
5
|
+
istr_python-0.0.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
6
|
+
istr_python-0.0.0.dist-info/top_level.txt,sha256=WpBHA-t036vALUt_W4rIOw-f-gydb8hs-Ahwn2IqIE8,5
|
|
7
|
+
istr_python-0.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
istr
|