istr-python 0.1.3__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 +2 -0
- istr/install istr.py +219 -0
- istr/istr.py +584 -0
- istr_python-0.1.3.0.dist-info/METADATA +421 -0
- istr_python-0.1.3.0.dist-info/RECORD +7 -0
- istr_python-0.1.3.0.dist-info/WHEEL +5 -0
- istr_python-0.1.3.0.dist-info/top_level.txt +1 -0
istr/__init__.py
ADDED
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,584 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import math
|
|
3
|
+
|
|
4
|
+
# _ _
|
|
5
|
+
# (_) ___ | |_ _ __
|
|
6
|
+
# | |/ __|| __|| '__|
|
|
7
|
+
# | |\__ \| |_ | |
|
|
8
|
+
# |_||___/ \__||_|
|
|
9
|
+
# strings you can count on
|
|
10
|
+
|
|
11
|
+
__version__ = "0.1.3.0"
|
|
12
|
+
import functools
|
|
13
|
+
import math
|
|
14
|
+
|
|
15
|
+
"""
|
|
16
|
+
changelog
|
|
17
|
+
|
|
18
|
+
version 0.1.2 2024-04-26
|
|
19
|
+
-------------------------
|
|
20
|
+
Added all relevant string methods to return istrs or data structures with istrs.
|
|
21
|
+
Added corresponding tests.
|
|
22
|
+
|
|
23
|
+
version 0.1.0 2024-04-22
|
|
24
|
+
-------------------------
|
|
25
|
+
Changed the way istr.range is implemennted.
|
|
26
|
+
|
|
27
|
+
Changed the context manager istr.format() to be used directly without the with statement.
|
|
28
|
+
Also, noww istr.format() works without any argument and then returns the current format.
|
|
29
|
+
|
|
30
|
+
istr class now uses __slots__
|
|
31
|
+
|
|
32
|
+
All internal values and methods now start with an underscore.
|
|
33
|
+
|
|
34
|
+
Introduced istr.repr_mode()
|
|
35
|
+
|
|
36
|
+
Introduced istr.base()
|
|
37
|
+
|
|
38
|
+
Extended tests for new functionality
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
version 0.0.8 2024-04-18
|
|
42
|
+
-------------------------
|
|
43
|
+
initial version with changelog
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class _range:
|
|
48
|
+
"""
|
|
49
|
+
based on https://codereview.stackexchange.com/questions/229073/pure-python-range-implementation
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def __init__(self, cls, start, stop=None, step=1):
|
|
53
|
+
if stop is None:
|
|
54
|
+
start, stop = 0, start
|
|
55
|
+
self.start, self.stop, self.step = (int(obj) for obj in (start, stop, step))
|
|
56
|
+
if step == 0:
|
|
57
|
+
raise ValueError("range() arg 3 must not be zero")
|
|
58
|
+
if self.step < 0:
|
|
59
|
+
step_sign = -1
|
|
60
|
+
else:
|
|
61
|
+
step_sign = 1
|
|
62
|
+
self._len = max(1 + (self.stop - self.start - step_sign) // self.step, 0)
|
|
63
|
+
self.parent_cls = cls
|
|
64
|
+
|
|
65
|
+
def __contains__(self, value):
|
|
66
|
+
if isinstance(value, int):
|
|
67
|
+
return self._index(value) != -1
|
|
68
|
+
return any(n == value for n in self)
|
|
69
|
+
|
|
70
|
+
def __eq__(self, other):
|
|
71
|
+
if not isinstance(other, type(self)):
|
|
72
|
+
return False
|
|
73
|
+
if self._len != len(other):
|
|
74
|
+
return False
|
|
75
|
+
if self._len == 0:
|
|
76
|
+
return True
|
|
77
|
+
if self.start != other.start:
|
|
78
|
+
return False
|
|
79
|
+
if self[-1] == other[-1]:
|
|
80
|
+
return True
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
def __getitem__(self, index):
|
|
84
|
+
def adjust_indices(length, start, stop, step):
|
|
85
|
+
if step is None:
|
|
86
|
+
step = 1
|
|
87
|
+
else:
|
|
88
|
+
step = int(step)
|
|
89
|
+
|
|
90
|
+
if start is None:
|
|
91
|
+
start = length - 1 if step < 0 else 0
|
|
92
|
+
else:
|
|
93
|
+
start = int(start)
|
|
94
|
+
if start < 0:
|
|
95
|
+
start += length
|
|
96
|
+
if start < 0:
|
|
97
|
+
start = -1 if step < 0 else 0
|
|
98
|
+
elif start >= length:
|
|
99
|
+
start = length - 1 if step < 0 else length
|
|
100
|
+
|
|
101
|
+
if stop is None:
|
|
102
|
+
stop = -1 if step < 0 else length
|
|
103
|
+
else:
|
|
104
|
+
stop = int(stop)
|
|
105
|
+
if stop < 0:
|
|
106
|
+
stop += length
|
|
107
|
+
if stop < 0:
|
|
108
|
+
stop = -1 if step < 0 else 0
|
|
109
|
+
elif stop >= length:
|
|
110
|
+
stop = length - 1 if step < 0 else length
|
|
111
|
+
|
|
112
|
+
return start, stop, step
|
|
113
|
+
|
|
114
|
+
if isinstance(index, slice):
|
|
115
|
+
start, stop, step = adjust_indices(self._len, index.start, index.stop, index.step)
|
|
116
|
+
return self.parent_cls.range(self.start + self.step * start, self.start + self.step * stop, self.step * step)
|
|
117
|
+
index = int(index)
|
|
118
|
+
if index < 0:
|
|
119
|
+
index += self._len
|
|
120
|
+
if not 0 <= index < self._len:
|
|
121
|
+
raise IndexError("range object index out of range")
|
|
122
|
+
return self.parent_cls(self.start + self.step * index)
|
|
123
|
+
|
|
124
|
+
def __hash__(self):
|
|
125
|
+
if self._len == 0:
|
|
126
|
+
return id(self.parent_cls.range)
|
|
127
|
+
return hash((self._len, self.start, int(self[-1])))
|
|
128
|
+
|
|
129
|
+
def __iter__(self):
|
|
130
|
+
value = self.start
|
|
131
|
+
if self.step > 0:
|
|
132
|
+
while value < self.stop:
|
|
133
|
+
yield self.parent_cls(value)
|
|
134
|
+
value += self.step
|
|
135
|
+
else:
|
|
136
|
+
while value > self.stop:
|
|
137
|
+
yield self.parent_cls(value)
|
|
138
|
+
value += self.step
|
|
139
|
+
|
|
140
|
+
def __len__(self):
|
|
141
|
+
return self._len
|
|
142
|
+
|
|
143
|
+
def __repr__(self):
|
|
144
|
+
if self.step == 1:
|
|
145
|
+
return f"{self.parent_cls.__name__}.range({self.start}, {self.stop})"
|
|
146
|
+
return f"{self.parent_cls.__name__}.range({self.start}, {self.stop}, {self.step})"
|
|
147
|
+
|
|
148
|
+
def __reversed__(self):
|
|
149
|
+
return iter(self[::-1])
|
|
150
|
+
|
|
151
|
+
def _index(self, value):
|
|
152
|
+
index_mul_step = value - self.start
|
|
153
|
+
if index_mul_step % self.step:
|
|
154
|
+
return -1
|
|
155
|
+
index = index_mul_step // self.step
|
|
156
|
+
if 0 <= index < self._len:
|
|
157
|
+
return index
|
|
158
|
+
return -1
|
|
159
|
+
|
|
160
|
+
def count(self, value):
|
|
161
|
+
"""
|
|
162
|
+
Rangeobject.count(value) -> integer
|
|
163
|
+
Return number of occurrences of value.
|
|
164
|
+
"""
|
|
165
|
+
return sum(1 for n in self if int(n) == int(value))
|
|
166
|
+
|
|
167
|
+
def index(self, value, start=0, stop=None):
|
|
168
|
+
if start < 0:
|
|
169
|
+
start = max(self._len + start, 0)
|
|
170
|
+
if stop is None:
|
|
171
|
+
stop = self._len
|
|
172
|
+
if stop < 0:
|
|
173
|
+
stop += self._len
|
|
174
|
+
|
|
175
|
+
if isinstance(value, int):
|
|
176
|
+
index = self._index(value)
|
|
177
|
+
if start <= index < stop:
|
|
178
|
+
return index
|
|
179
|
+
raise ValueError(f"{value} is not in range")
|
|
180
|
+
|
|
181
|
+
i = start
|
|
182
|
+
n = self.start + self.step * i
|
|
183
|
+
while i < stop:
|
|
184
|
+
if n == int(value):
|
|
185
|
+
return i
|
|
186
|
+
i += 1
|
|
187
|
+
n += self.step
|
|
188
|
+
raise ValueError(f"{value} is not in range")
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class istr(str):
|
|
192
|
+
"""
|
|
193
|
+
istr object
|
|
194
|
+
|
|
195
|
+
Parameters
|
|
196
|
+
----------
|
|
197
|
+
value : any
|
|
198
|
+
if str the value will to be interpreted as an int
|
|
199
|
+
istr("8") ==> istr("8")
|
|
200
|
+
if numeric, the value will be interpreted as an int
|
|
201
|
+
istr(8) ==> istr("8")
|
|
202
|
+
if a dict (or subtype of dict), the same type dict will be returned with all values istr"ed
|
|
203
|
+
istr({0: 0, 1: 1, 2: 4}) ==> {0: istr("0"), 1: istr("1"), 2: istr("4")}
|
|
204
|
+
if an iterator, the iterator will be mapped with istr
|
|
205
|
+
istr(i * i for i in range(3)) ==> <map object>
|
|
206
|
+
list(istr(i * i for i in range(3))) ==> [istr("0"), istr("1"), istr("4")]
|
|
207
|
+
if an iterable, the same type will be returned with all elements istr"ed
|
|
208
|
+
istr([0, 1, 4]) ==> [istr("0"), istr("1"), istr("4")]
|
|
209
|
+
istr((0, 1, 4)) ==> (istr("0"), istr("1"), istr("4"))
|
|
210
|
+
istr({0, 1, 4}) ==> {istr("4"), istr("0"), istr("1")} # or similar
|
|
211
|
+
if a range, an istr.range instance will be returned
|
|
212
|
+
istr(range(3)) ==> istr.range(3)
|
|
213
|
+
list(istr(range(3))) ==> [istr("0"), istr("1"), istr("2")]
|
|
214
|
+
len(istr(range(3))) ==> 3
|
|
215
|
+
|
|
216
|
+
it is possible to give more than one parameter, in which case a tuple
|
|
217
|
+
of the istrs of the parameters will be returned, which can be handy
|
|
218
|
+
to multiple assign, e.g.
|
|
219
|
+
a, b, c = istr(5, 6, 7) ==> a=istr("5") , b=istr("6"), c=istr("7")"""
|
|
220
|
+
|
|
221
|
+
__slots__ = ("_as_int", "_as_repr")
|
|
222
|
+
|
|
223
|
+
_format = ""
|
|
224
|
+
_mode = "istr"
|
|
225
|
+
_base = 10
|
|
226
|
+
|
|
227
|
+
@staticmethod
|
|
228
|
+
def _to_base(number, base):
|
|
229
|
+
if number < 0:
|
|
230
|
+
raise ValueError(f"negative numbers are not allowed for base {base}")
|
|
231
|
+
result = ""
|
|
232
|
+
while number:
|
|
233
|
+
result += "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[number % base]
|
|
234
|
+
number //= base
|
|
235
|
+
return result[::-1] or "0"
|
|
236
|
+
|
|
237
|
+
@classmethod
|
|
238
|
+
def _to_int(cls, value):
|
|
239
|
+
try:
|
|
240
|
+
if cls._base != 10 and isinstance(value, str):
|
|
241
|
+
return int(value, cls._base)
|
|
242
|
+
else:
|
|
243
|
+
return int(value)
|
|
244
|
+
except (ValueError, TypeError):
|
|
245
|
+
raise ValueError(f"unable to convert {repr(value)} to int (base {cls._base})")
|
|
246
|
+
|
|
247
|
+
def __new__(cls, *value):
|
|
248
|
+
if len(value) == 0:
|
|
249
|
+
raise TypeError("no parameter given")
|
|
250
|
+
if len(value) == 1:
|
|
251
|
+
value = value[0] # normal case of 1 parameter
|
|
252
|
+
if isinstance(value, range):
|
|
253
|
+
return cls.range(value.start, value.stop, value.step)
|
|
254
|
+
if isinstance(value, _range):
|
|
255
|
+
return value
|
|
256
|
+
if isinstance(value, cls):
|
|
257
|
+
return value
|
|
258
|
+
if isinstance(value, dict):
|
|
259
|
+
return type(value)((k, cls(v)) for k, v in value.items())
|
|
260
|
+
if not isinstance(value, (str, type)) and hasattr(value, "__iter__"):
|
|
261
|
+
if hasattr(value, "__next__"):
|
|
262
|
+
return map(functools.partial(cls), value)
|
|
263
|
+
return type(value)(map(functools.partial(cls), value))
|
|
264
|
+
if value == "":
|
|
265
|
+
as_str = ""
|
|
266
|
+
as_int = 0
|
|
267
|
+
else:
|
|
268
|
+
as_int = cls._to_int(value)
|
|
269
|
+
if (cls._format == "" or cls._base != 10) and not isinstance(value, istr):
|
|
270
|
+
if isinstance(value, str):
|
|
271
|
+
as_str = value
|
|
272
|
+
else:
|
|
273
|
+
if cls._base == 10:
|
|
274
|
+
as_str = str(as_int)
|
|
275
|
+
else:
|
|
276
|
+
as_str = istr._to_base(as_int, cls._base)
|
|
277
|
+
else:
|
|
278
|
+
as_str = f"{as_int:{cls._format}}"
|
|
279
|
+
|
|
280
|
+
self = super().__new__(cls, as_str)
|
|
281
|
+
self._as_int = as_int
|
|
282
|
+
if self._mode == "istr":
|
|
283
|
+
self._as_repr = f"{cls.__name__}({repr(as_str)})"
|
|
284
|
+
elif self._mode == "int":
|
|
285
|
+
self._as_repr = repr(as_int)
|
|
286
|
+
else:
|
|
287
|
+
self._as_repr = repr(as_str)
|
|
288
|
+
return self
|
|
289
|
+
|
|
290
|
+
def __hash__(self):
|
|
291
|
+
return hash((self.__class__, str(self)))
|
|
292
|
+
|
|
293
|
+
def __eq__(self, other):
|
|
294
|
+
if isinstance(other, self.__class__):
|
|
295
|
+
return self._as_int == other._as_int
|
|
296
|
+
if isinstance(other, str):
|
|
297
|
+
return super().__eq__(other)
|
|
298
|
+
try:
|
|
299
|
+
return self._as_int == self._to_int(other)
|
|
300
|
+
except Exception:
|
|
301
|
+
return False
|
|
302
|
+
|
|
303
|
+
def __ne__(self, other):
|
|
304
|
+
return not self == other
|
|
305
|
+
|
|
306
|
+
def __contains__(self, other):
|
|
307
|
+
return super().__contains__(str(other))
|
|
308
|
+
|
|
309
|
+
def __repr__(self):
|
|
310
|
+
return self._as_repr
|
|
311
|
+
|
|
312
|
+
def __le__(self, other):
|
|
313
|
+
return self._as_int <= self._to_int(other)
|
|
314
|
+
|
|
315
|
+
def __lt__(self, other):
|
|
316
|
+
return self._as_int < self._to_int(other)
|
|
317
|
+
|
|
318
|
+
def __ge__(self, other):
|
|
319
|
+
return self._as_int >= self._to_int(other)
|
|
320
|
+
|
|
321
|
+
def __gt__(self, other):
|
|
322
|
+
return self._as_int > self._to_int(other)
|
|
323
|
+
|
|
324
|
+
def __bool__(self):
|
|
325
|
+
return bool(self._as_int)
|
|
326
|
+
|
|
327
|
+
def __add__(self, other):
|
|
328
|
+
return self.__class__(self._as_int + self._to_int(other))
|
|
329
|
+
|
|
330
|
+
def __sub__(self, other):
|
|
331
|
+
return self.__class__(self._as_int - self._to_int(other))
|
|
332
|
+
|
|
333
|
+
def __mul__(self, other):
|
|
334
|
+
return self.__class__(self._as_int * self._to_int(other))
|
|
335
|
+
|
|
336
|
+
def __floordiv__(self, other):
|
|
337
|
+
return self.__class__(self._as_int // self._to_int(other))
|
|
338
|
+
|
|
339
|
+
def __rfloordiv__(self, other):
|
|
340
|
+
return self.__class__(self._to_int(other) // self._as_int)
|
|
341
|
+
|
|
342
|
+
def __truediv__(self, other):
|
|
343
|
+
return self.__class__(self._as_int // self._to_int(other))
|
|
344
|
+
|
|
345
|
+
def __rtruediv__(self, other):
|
|
346
|
+
return self.__class__(self._to_int(other) // self._as_int)
|
|
347
|
+
|
|
348
|
+
def __pow__(self, other):
|
|
349
|
+
return self.__class__(self._as_int ** self._to_int(other))
|
|
350
|
+
|
|
351
|
+
def __rpow__(self, other):
|
|
352
|
+
return self.__class__(self._to_int(other) ** self._as_int)
|
|
353
|
+
|
|
354
|
+
def __radd__(self, other):
|
|
355
|
+
return self.__class__(self._to_int(other) + self._as_int)
|
|
356
|
+
|
|
357
|
+
def __rsub__(self, other):
|
|
358
|
+
return self.__class__(self._to_int(other) - self._as_int)
|
|
359
|
+
|
|
360
|
+
def __rmul__(self, other):
|
|
361
|
+
return self.__class__(self._to_int(other) * self._as_int)
|
|
362
|
+
|
|
363
|
+
def __mod__(self, other):
|
|
364
|
+
return self.__class__(self._as_int % self._to_int(other))
|
|
365
|
+
|
|
366
|
+
def __rmod__(self, other):
|
|
367
|
+
return self.__class__(self._to_int(other) % self._as_int)
|
|
368
|
+
|
|
369
|
+
def __or__(self, other):
|
|
370
|
+
return self.__class__("".join((self, self.__class__(other))))
|
|
371
|
+
|
|
372
|
+
def __ror__(self, other):
|
|
373
|
+
return self.__class__("".join((self.__class__(other), self)))
|
|
374
|
+
|
|
375
|
+
def __int__(self):
|
|
376
|
+
return int(self._as_int)
|
|
377
|
+
|
|
378
|
+
def __round__(self):
|
|
379
|
+
return self.__class__(round(self._as_int))
|
|
380
|
+
|
|
381
|
+
def __trunc__(self):
|
|
382
|
+
return self.__class__(math.trunc(self._as_int))
|
|
383
|
+
|
|
384
|
+
def __floor__(self):
|
|
385
|
+
return self.__class__(math.floor(self._as_int))
|
|
386
|
+
|
|
387
|
+
def __ceil__(self):
|
|
388
|
+
return self.__class__(math.ceil(self._as_int))
|
|
389
|
+
|
|
390
|
+
def __matmul__(self, other):
|
|
391
|
+
return self.__class__(super().__mul__(other))
|
|
392
|
+
|
|
393
|
+
def __rmatmul__(self, other):
|
|
394
|
+
return self.__class__(super().__rmul__(other))
|
|
395
|
+
|
|
396
|
+
def __divmod__(self, other):
|
|
397
|
+
return self.__class__(divmod(self._as_int, self._to_int(other)))
|
|
398
|
+
|
|
399
|
+
def __rdivmod__(self, other):
|
|
400
|
+
return self.__class__(divmod(self._to_int(other), self._as_int))
|
|
401
|
+
|
|
402
|
+
def __neg__(self):
|
|
403
|
+
return self.__class__(-self._as_int)
|
|
404
|
+
|
|
405
|
+
def __pos__(self):
|
|
406
|
+
return self
|
|
407
|
+
|
|
408
|
+
def __abs__(self):
|
|
409
|
+
return self.__class__(abs(self._as_int))
|
|
410
|
+
|
|
411
|
+
def is_even(self):
|
|
412
|
+
return self._as_int % 2 == 0
|
|
413
|
+
|
|
414
|
+
def is_odd(self):
|
|
415
|
+
return self._as_int % 2 == 1
|
|
416
|
+
|
|
417
|
+
def reversed(self):
|
|
418
|
+
return self[::-1]
|
|
419
|
+
|
|
420
|
+
def __getitem__(self, key):
|
|
421
|
+
return self.__class__(super().__getitem__(key))
|
|
422
|
+
|
|
423
|
+
@classmethod
|
|
424
|
+
def concat(cls, iterable):
|
|
425
|
+
return map(lambda x: istr("").join(x), istr(iterable))
|
|
426
|
+
|
|
427
|
+
@classmethod
|
|
428
|
+
def enumerate(cls, iterable, start=0):
|
|
429
|
+
for i, value in enumerate(iterable, start):
|
|
430
|
+
yield cls(i), value
|
|
431
|
+
|
|
432
|
+
@classmethod
|
|
433
|
+
class format:
|
|
434
|
+
def __new__(cls, cls_format, format=None):
|
|
435
|
+
if format is None:
|
|
436
|
+
return cls_format._format
|
|
437
|
+
return super().__new__(cls)
|
|
438
|
+
|
|
439
|
+
def __init__(self, cls, format):
|
|
440
|
+
self.saved_format = cls._format
|
|
441
|
+
self.saved_cls = cls
|
|
442
|
+
if not (isinstance(format, str) and all(x in "0123456789" for x in format)):
|
|
443
|
+
raise ValueError(f"{repr(format)} is incorrect format")
|
|
444
|
+
|
|
445
|
+
cls._format = format
|
|
446
|
+
|
|
447
|
+
def __enter__(self):
|
|
448
|
+
...
|
|
449
|
+
|
|
450
|
+
def __exit__(self, exc_type, exc_value, exc_tb):
|
|
451
|
+
self.saved_cls._format = self.saved_format
|
|
452
|
+
|
|
453
|
+
@classmethod
|
|
454
|
+
class repr_mode:
|
|
455
|
+
def __new__(cls, cls_mode, mode=None):
|
|
456
|
+
if mode is None:
|
|
457
|
+
return cls_mode._mode
|
|
458
|
+
if mode in ("istr", "str", "int"):
|
|
459
|
+
return super().__new__(cls)
|
|
460
|
+
raise TypeError(f"mode not 'istr', 'str' or 'int', but {repr(mode)}")
|
|
461
|
+
|
|
462
|
+
def __init__(self, cls, mode):
|
|
463
|
+
self.saved_mode = cls._mode
|
|
464
|
+
self.saved_cls = cls
|
|
465
|
+
cls._mode = mode
|
|
466
|
+
|
|
467
|
+
def __enter__(self):
|
|
468
|
+
...
|
|
469
|
+
|
|
470
|
+
def __exit__(self, exc_type, exc_value, exc_tb):
|
|
471
|
+
self.saved_cls._mode = self.saved_mode
|
|
472
|
+
|
|
473
|
+
@classmethod
|
|
474
|
+
class base:
|
|
475
|
+
def __new__(cls, cls_base, base=None):
|
|
476
|
+
if base is None:
|
|
477
|
+
return cls_base._base
|
|
478
|
+
if 2 <= base <= 36:
|
|
479
|
+
return super().__new__(cls)
|
|
480
|
+
raise ValueError(f"base not between 2 and 36, but {base}")
|
|
481
|
+
|
|
482
|
+
def __init__(self, cls, base):
|
|
483
|
+
self.saved_base = cls._base
|
|
484
|
+
self.saved_cls = cls
|
|
485
|
+
cls._base = base
|
|
486
|
+
|
|
487
|
+
def __enter__(self):
|
|
488
|
+
...
|
|
489
|
+
|
|
490
|
+
def __exit__(self, exc_type, exc_value, exc_tb):
|
|
491
|
+
self.saved_cls._base = self.saved_base
|
|
492
|
+
|
|
493
|
+
@classmethod
|
|
494
|
+
def range(cls, start, stop=None, step=1):
|
|
495
|
+
return _range(cls, start, stop, step)
|
|
496
|
+
|
|
497
|
+
def capitalize1(self, *args, **kwargs):
|
|
498
|
+
return self.__class__(super().capitalize(*args, **kwargs))
|
|
499
|
+
|
|
500
|
+
def casefold(self, *args, **kwargs):
|
|
501
|
+
return self.__class__(super().casefold(*args, **kwargs))
|
|
502
|
+
|
|
503
|
+
def center(self, *args, **kwargs):
|
|
504
|
+
return self.__class__(super().center(*args, **kwargs))
|
|
505
|
+
|
|
506
|
+
def expandtabs(self, *args, **kwargs):
|
|
507
|
+
return self.__class__(super().expandtabs(*args, **kwargs))
|
|
508
|
+
|
|
509
|
+
def join(self, *args, **kwargs):
|
|
510
|
+
return self.__class__(super().join(*args, **kwargs))
|
|
511
|
+
|
|
512
|
+
def ljust(self, *args, **kwargs):
|
|
513
|
+
return self.__class__(super().ljust(*args, **kwargs))
|
|
514
|
+
|
|
515
|
+
def lower(self, *args, **kwargs):
|
|
516
|
+
return self.__class__(super().lower(*args, **kwargs))
|
|
517
|
+
|
|
518
|
+
def lstrip(self, *args, **kwargs):
|
|
519
|
+
return self.__class__(super().lstrip(*args, **kwargs))
|
|
520
|
+
|
|
521
|
+
def partition(self, *args, **kwargs):
|
|
522
|
+
return self.__class__(super().partition(*args, **kwargs))
|
|
523
|
+
|
|
524
|
+
def removeprefix(self, *args, **kwargs):
|
|
525
|
+
return self.__class__(super().removeprefix(*args, **kwargs))
|
|
526
|
+
|
|
527
|
+
def removesuffix(self, *args, **kwargs):
|
|
528
|
+
return self.__class__(super().removesuffix(*args, **kwargs))
|
|
529
|
+
|
|
530
|
+
def replace(self, *args, **kwargs):
|
|
531
|
+
return self.__class__(super().replace(*args, **kwargs))
|
|
532
|
+
|
|
533
|
+
def rjust(self, *args, **kwargs):
|
|
534
|
+
return self.__class__(super().rjust(*args, **kwargs))
|
|
535
|
+
|
|
536
|
+
def rpartition(self, *args, **kwargs):
|
|
537
|
+
return self.__class__(super().rpartition(*args, **kwargs))
|
|
538
|
+
|
|
539
|
+
def rsplit(self, *args, **kwargs):
|
|
540
|
+
return self.__class__(super().rsplit(*args, **kwargs))
|
|
541
|
+
|
|
542
|
+
def rstrip(self, *args, **kwargs):
|
|
543
|
+
return self.__class__(super().rstrip(*args, **kwargs))
|
|
544
|
+
|
|
545
|
+
def split(self, *args, **kwargs):
|
|
546
|
+
return self.__class__(super().split(*args, **kwargs))
|
|
547
|
+
|
|
548
|
+
def strip(self, *args, **kwargs):
|
|
549
|
+
return self.__class__(super().strip(*args, **kwargs))
|
|
550
|
+
|
|
551
|
+
def swapcase(self, *args, **kwargs):
|
|
552
|
+
return self.__class__(super().swapcase(*args, **kwargs))
|
|
553
|
+
|
|
554
|
+
def title(self, *args, **kwargs):
|
|
555
|
+
return self.__class__(super().title(*args, **kwargs))
|
|
556
|
+
|
|
557
|
+
def translate(self, *args, **kwargs):
|
|
558
|
+
return self.__class__(super().translate(*args, **kwargs))
|
|
559
|
+
|
|
560
|
+
def upper(self, *args, **kwargs):
|
|
561
|
+
return self.__class__(super().upper(*args, **kwargs))
|
|
562
|
+
|
|
563
|
+
def zfill(self, *args, **kwargs):
|
|
564
|
+
return self.__class__(super().zfill(*args, **kwargs))
|
|
565
|
+
|
|
566
|
+
f = "capitalize"
|
|
567
|
+
exec(
|
|
568
|
+
f"""
|
|
569
|
+
def {f}(self, *args, **kwargs):
|
|
570
|
+
return self.__class__(super(istr,self).{f}(*args, **kwargs))
|
|
571
|
+
""",
|
|
572
|
+
globals(),
|
|
573
|
+
locals(),
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
def main():
|
|
578
|
+
a = istr("123123")
|
|
579
|
+
print(repr(a.capitalize()))
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
if __name__ == "__main__":
|
|
583
|
+
main()
|
|
584
|
+
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: istr-python
|
|
3
|
+
Version: 0.1.3.0
|
|
4
|
+
Summary: istr - strings you can count on
|
|
5
|
+
Author-email: Ruud van der Ham <rt.van.der.ham@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/salabim/istr
|
|
7
|
+
Project-URL: Repository, https://github.com/salabim/istr
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
11
|
+
Requires-Python: >=3.7
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
<img src="https://www.salabim.org/istr_logo.png" width=500>
|
|
15
|
+
|
|
16
|
+
# Introduction
|
|
17
|
+
|
|
18
|
+
The istr module has exactly one class: istr.
|
|
19
|
+
|
|
20
|
+
With this it is possible to interpret strings as if they were integers.
|
|
21
|
+
|
|
22
|
+
This can be very handy for solving puzzles, but also for other purposes. For instance the
|
|
23
|
+
famous send more money puzzle
|
|
24
|
+
```
|
|
25
|
+
S E N D
|
|
26
|
+
M O R E
|
|
27
|
+
--------- +
|
|
28
|
+
M O N E Y
|
|
29
|
+
```
|
|
30
|
+
can be nicely, albeit not very efficient, coded as:
|
|
31
|
+
```
|
|
32
|
+
import itertools
|
|
33
|
+
from istr import istr
|
|
34
|
+
|
|
35
|
+
for s, e, n, d, m, o, r, y in istr(itertools.permutations(range(10), 8)):
|
|
36
|
+
if m and ((s|e|n|d) + (m|o|r|e) == (m|o|n|e|y)):
|
|
37
|
+
print(f" {s|e|n|d}")
|
|
38
|
+
print(f" {m|o|r|e}")
|
|
39
|
+
print("-----")
|
|
40
|
+
print(f"{m|o|n|e|y}")
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
And it is a nice demonstration of extending a class (str) with extra and changed functionality.
|
|
44
|
+
|
|
45
|
+
# Installation
|
|
46
|
+
Installing istr with pip is easy.
|
|
47
|
+
```
|
|
48
|
+
$ pip install istr-python
|
|
49
|
+
```
|
|
50
|
+
or when you want to upgrade,
|
|
51
|
+
```
|
|
52
|
+
$ pip install istr-python --upgrade
|
|
53
|
+
```
|
|
54
|
+
Alternatively, istr.py can be just copied into you current work directory from GitHub (https://github.com/salabim/istr).
|
|
55
|
+
|
|
56
|
+
No dependencies!
|
|
57
|
+
|
|
58
|
+
# Usage
|
|
59
|
+
Just start with
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
from istr import istr
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Now we can define some istrs:
|
|
66
|
+
```
|
|
67
|
+
four = istr("4")
|
|
68
|
+
five = istr("5")
|
|
69
|
+
```
|
|
70
|
+
Then we can do
|
|
71
|
+
```
|
|
72
|
+
x= four * five
|
|
73
|
+
```
|
|
74
|
+
, after which x is `istr("20")`
|
|
75
|
+
|
|
76
|
+
And now we can do
|
|
77
|
+
```
|
|
78
|
+
print(x == 20)
|
|
79
|
+
print(x == "20")
|
|
80
|
+
```
|
|
81
|
+
resulting in two times `True`. That's because istrs instances are treated as int, although they are strings.
|
|
82
|
+
|
|
83
|
+
That means that we can also say
|
|
84
|
+
```
|
|
85
|
+
print(x < 30)
|
|
86
|
+
print(x >= "10")
|
|
87
|
+
```
|
|
88
|
+
again resulting in two times `True`.
|
|
89
|
+
|
|
90
|
+
In contrast to an ordinary string
|
|
91
|
+
```
|
|
92
|
+
print(four + five)
|
|
93
|
+
```
|
|
94
|
+
prints `9`, as istr are treated as ints.
|
|
95
|
+
|
|
96
|
+
Please note that `four` and `five` could have also be initialized with
|
|
97
|
+
```
|
|
98
|
+
four = istr(4)
|
|
99
|
+
five = istr(5)
|
|
100
|
+
```
|
|
101
|
+
or even
|
|
102
|
+
```
|
|
103
|
+
four, five = istr(4, 5)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
But how can we concatenate istrs? Just use the or operator (|):
|
|
107
|
+
```
|
|
108
|
+
print(four | five)
|
|
109
|
+
```
|
|
110
|
+
will output `45`.
|
|
111
|
+
|
|
112
|
+
And the result is again an istr.
|
|
113
|
+
|
|
114
|
+
That means that
|
|
115
|
+
```
|
|
116
|
+
(four | five) / 3
|
|
117
|
+
```
|
|
118
|
+
is `istr("9")`.
|
|
119
|
+
|
|
120
|
+
In order to repeat a string in the usual sense, you cannot use `3 * four`, as that woud be `12`.
|
|
121
|
+
|
|
122
|
+
We use the matrix multiplication operator (@) for this. So `3 @ four` is `444`. As is `four @ 3`.
|
|
123
|
+
|
|
124
|
+
Also allowed are
|
|
125
|
+
```
|
|
126
|
+
abs(four)
|
|
127
|
+
-four
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The bool operator works on the integer value of an istr. So
|
|
131
|
+
`bool("0")` ==> `False`
|
|
132
|
+
`bool("1")` ==> `True`
|
|
133
|
+
The code
|
|
134
|
+
```
|
|
135
|
+
if istr("0"):
|
|
136
|
+
print("True")
|
|
137
|
+
else:
|
|
138
|
+
print("False")
|
|
139
|
+
```
|
|
140
|
+
will print `False`
|
|
141
|
+
|
|
142
|
+
For the in operator, an istr is treated as an ordinary string, although it is possible to use ints as well:
|
|
143
|
+
```
|
|
144
|
+
"34" in istr(1234)
|
|
145
|
+
34 in istr(1234)
|
|
146
|
+
```
|
|
147
|
+
On the left hand side an istr is always treated as a string:
|
|
148
|
+
```
|
|
149
|
+
istr(1234) in "01234566890ABCDEF"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
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.
|
|
153
|
+
Also divisions are always floor divisions!
|
|
154
|
+
|
|
155
|
+
There's a special case for `istr("")`. This is a proper empty string, but also represents the value of 0.
|
|
156
|
+
That is to allow for `istr("").join(i for i in "01234)"`, resulting in `istr("01234")`.
|
|
157
|
+
|
|
158
|
+
Sorting a list of istrs is based on the integer value, not the string. So
|
|
159
|
+
|
|
160
|
+
`" ".join(sorted("1 3 2 4 5 6 11 7 9 8 10 12 0".split()))`
|
|
161
|
+
|
|
162
|
+
is
|
|
163
|
+
|
|
164
|
+
`"0 1 10 11 2 3 4 5 6 7 8 9"`
|
|
165
|
+
|
|
166
|
+
,whereas
|
|
167
|
+
|
|
168
|
+
`" ".join(sorted(istr("1 3 2 4 5 6 11 7 9 8 10 12 0".split())))`
|
|
169
|
+
|
|
170
|
+
is
|
|
171
|
+
|
|
172
|
+
`"0 1 2 3 4 5 6 7 8 9 10 11"`
|
|
173
|
+
|
|
174
|
+
# Using other values for istr than numeric value or str
|
|
175
|
+
Apart from with simple numeric (to be interpreted as an int) or str, istr can be initialized with
|
|
176
|
+
several other types:
|
|
177
|
+
|
|
178
|
+
- if a dict (or subtype of dict), the same type dict will be returned with all values istr'ed
|
|
179
|
+
|
|
180
|
+
`istr({0: 0, 1: 1, 2: 4})` ==> `{0: istr("0"), 1: istr("1"), 2: istr("4")}`
|
|
181
|
+
|
|
182
|
+
- if an iterator, the iterator will be mapped with istr
|
|
183
|
+
|
|
184
|
+
`istr(i * i for i in range(3))` ==> `<map object>`
|
|
185
|
+
|
|
186
|
+
`list(istr(i * i for i in range(3)))` ==> `[istr("0"), istr("1"), istr("4")]`
|
|
187
|
+
|
|
188
|
+
- if an iterable, the same type will be returned with all elements istr'ed
|
|
189
|
+
|
|
190
|
+
`istr([0, 1, 4])` ==> `[istr("0"), istr("1"), istr("4")]`
|
|
191
|
+
|
|
192
|
+
`istr((0, 1, 4))` ==> `(istr("0"), istr("1"), istr("4"))`
|
|
193
|
+
|
|
194
|
+
`istr({0, 1, 4})` ==> `{istr("4"), istr("0"), istr("1")} # or similar`
|
|
195
|
+
|
|
196
|
+
- if a range, an istr.range instance will be returned
|
|
197
|
+
|
|
198
|
+
`istr(range(3))` ==> `istr.range(3)`
|
|
199
|
+
|
|
200
|
+
`list(istr(range(3)))` ==> `[istr("0"), istr("1"), istr("2")]`
|
|
201
|
+
|
|
202
|
+
`len(istr(range(3)))` ==> `3`
|
|
203
|
+
|
|
204
|
+
- if an istr.range instance, the same istr.range will be returned
|
|
205
|
+
|
|
206
|
+
# More than one parameter for istr
|
|
207
|
+
It is possible to give more than one parameter, in which case a tuple
|
|
208
|
+
of the istrs of the parameters will be returned, which can be handy
|
|
209
|
+
to unpack multiple values, e.g.
|
|
210
|
+
|
|
211
|
+
`a, b, c = istr(5, 6, 7)` ==> `a=istr("5") , b=istr("6"), c=istr("7")`
|
|
212
|
+
|
|
213
|
+
# test for even/odd
|
|
214
|
+
It is possible to test for even/odd with the
|
|
215
|
+
|
|
216
|
+
`is_even` and `is_odd` method, e.g.
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
print(istr(4).is_even())
|
|
220
|
+
print(istr(5).is_odd())
|
|
221
|
+
```
|
|
222
|
+
This will print `True` twice.
|
|
223
|
+
|
|
224
|
+
# reverse an istr
|
|
225
|
+
The method `istr.reversed()` will return the an istr with the reversed content:
|
|
226
|
+
```
|
|
227
|
+
print(repr(istr(456).reversed()))
|
|
228
|
+
print(repr(istr("0456").reversed()))
|
|
229
|
+
```
|
|
230
|
+
result:
|
|
231
|
+
```
|
|
232
|
+
istr("654")
|
|
233
|
+
istr("6540")
|
|
234
|
+
```
|
|
235
|
+
The same can -of course- be achieved with
|
|
236
|
+
```
|
|
237
|
+
print(repr(istr(456)[::-1]))
|
|
238
|
+
print(repr(istr("0456")[::-1]))
|
|
239
|
+
```
|
|
240
|
+
Note that is impossible to reverse a negative istr.
|
|
241
|
+
|
|
242
|
+
# enumerate with istrs
|
|
243
|
+
|
|
244
|
+
The `istr.enumerate` method can be used just as the builtin enumerate function.
|
|
245
|
+
The iteration counter however is an istr rather than an int. E.g.
|
|
246
|
+
```
|
|
247
|
+
for i,c in istr.enumerate("abc"):
|
|
248
|
+
print(f"{repr(i)} {c}")
|
|
249
|
+
```
|
|
250
|
+
prints
|
|
251
|
+
```
|
|
252
|
+
istr('0') a
|
|
253
|
+
istr('1') b
|
|
254
|
+
istr('2') c
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
# concatenate an iterable
|
|
258
|
+
|
|
259
|
+
The `istr.concat1 method can be useful to map all items of an iterable
|
|
260
|
+
to `istr` and then concatenate these.
|
|
261
|
+
|
|
262
|
+
`list(istr.concat(((1,2),(3,4)))` ==> `istr([12,34])`
|
|
263
|
+
|
|
264
|
+
`list(istr.concat(itertools.permutations(range(3),2)))` ==> `[istr('01'), istr('02'), istr('10'), istr('12'), istr('20'), istr('21')]`
|
|
265
|
+
|
|
266
|
+
# Subclassing istr
|
|
267
|
+
When a class is derived from istr, all methods will return that newly derived class.
|
|
268
|
+
|
|
269
|
+
E.g.
|
|
270
|
+
```
|
|
271
|
+
class jstr(istr):
|
|
272
|
+
...
|
|
273
|
+
|
|
274
|
+
print(repr(jstr(4) * jstr(5)))
|
|
275
|
+
```
|
|
276
|
+
will print `jstr('20')`
|
|
277
|
+
|
|
278
|
+
# Changing the way repr works
|
|
279
|
+
|
|
280
|
+
It is possible to control the way an `istr` instance will be repr'ed.
|
|
281
|
+
|
|
282
|
+
By default, the `istr('5')` is represented as `istr('5')`.
|
|
283
|
+
|
|
284
|
+
With the istr.repr_mode() context manager, that can be changed:
|
|
285
|
+
```
|
|
286
|
+
with istr.repr_mode("str"):
|
|
287
|
+
five = istr('5')
|
|
288
|
+
print(repr(five))
|
|
289
|
+
with istr.repr_mode("int"):
|
|
290
|
+
five = istr('5')
|
|
291
|
+
print(repr(five))
|
|
292
|
+
with istr.repr_mode("istr"):
|
|
293
|
+
five = istr('5')
|
|
294
|
+
print(repr(five))
|
|
295
|
+
```
|
|
296
|
+
This will print
|
|
297
|
+
```
|
|
298
|
+
'5'
|
|
299
|
+
5
|
|
300
|
+
istr('5')
|
|
301
|
+
```
|
|
302
|
+
Note that the way an `istr` is represented is determined at initialization.
|
|
303
|
+
|
|
304
|
+
It is also possible to set the repr mode without a context manager:
|
|
305
|
+
|
|
306
|
+
```
|
|
307
|
+
istr.repr_mode("str")
|
|
308
|
+
five = istr('5')
|
|
309
|
+
print(repr(five))
|
|
310
|
+
```
|
|
311
|
+
This will print
|
|
312
|
+
```
|
|
313
|
+
'5'
|
|
314
|
+
```
|
|
315
|
+
Finally, the current repr mode can be queried with `istr.repr_mode()`. So upon start:
|
|
316
|
+
```
|
|
317
|
+
print(repr(istr.repr_mode()))
|
|
318
|
+
```
|
|
319
|
+
will output `istr`.
|
|
320
|
+
|
|
321
|
+
# Changing the base system
|
|
322
|
+
|
|
323
|
+
By default, `istr` works in base 10. However it is possible to change the base system with the `istr.base()` context manager / method.
|
|
324
|
+
|
|
325
|
+
Any base between 2 and 36 may be used.
|
|
326
|
+
|
|
327
|
+
Note that the integer is always stored in base 10 mode, but the string
|
|
328
|
+
representation will reflect the chosen base (at time of initialization).
|
|
329
|
+
|
|
330
|
+
Some examples:
|
|
331
|
+
```
|
|
332
|
+
with istr.base(16):
|
|
333
|
+
a = istr("7fff")
|
|
334
|
+
print(int(a))
|
|
335
|
+
|
|
336
|
+
b = istr(15)
|
|
337
|
+
print(repr(b))
|
|
338
|
+
```
|
|
339
|
+
This will result in
|
|
340
|
+
```
|
|
341
|
+
32767
|
|
342
|
+
istr('F')
|
|
343
|
+
```
|
|
344
|
+
All calculations are done in the decimal 10 system.
|
|
345
|
+
|
|
346
|
+
Note that the way an `istr` is interpreted is determined at initialization.
|
|
347
|
+
|
|
348
|
+
It is also possible to set the repr mode without a context manager:
|
|
349
|
+
```
|
|
350
|
+
istr.base(16)
|
|
351
|
+
print(int(istr("7fff")))
|
|
352
|
+
```
|
|
353
|
+
This will print
|
|
354
|
+
```
|
|
355
|
+
32767
|
|
356
|
+
```
|
|
357
|
+
Finally, the current base can be queried with `istr.base()`, so upon start:
|
|
358
|
+
```
|
|
359
|
+
print(istr.base())
|
|
360
|
+
```
|
|
361
|
+
will result in `10`.
|
|
362
|
+
|
|
363
|
+
# Changing the format of the string
|
|
364
|
+
|
|
365
|
+
By default, `istr` does not change the way an istr is stored when a str is to initialize:
|
|
366
|
+
|
|
367
|
+
`repr('4'))` ==> `istr('4')`
|
|
368
|
+
|
|
369
|
+
`repr(' 4'))` ==> `istr(' 4')`
|
|
370
|
+
|
|
371
|
+
`repr('4 '))` ==> `istr('4 ')`
|
|
372
|
+
|
|
373
|
+
For initializing with an int (or other numeric) value, the string is simply the str representation
|
|
374
|
+
|
|
375
|
+
`repr(4))` ==> `istr('4')`
|
|
376
|
+
|
|
377
|
+
With the `istr.format()` context manager this behavior can be changed.
|
|
378
|
+
If the format specifier is a number, most likely a single digit, that
|
|
379
|
+
will be the minimum number of characters in the string:
|
|
380
|
+
```
|
|
381
|
+
with istr.format("3"):
|
|
382
|
+
print(repr(istr(1)))
|
|
383
|
+
print(repr(istr(12)))
|
|
384
|
+
print(repr(istr(123)))
|
|
385
|
+
print(repr(istr(1234)))
|
|
386
|
+
```
|
|
387
|
+
will print
|
|
388
|
+
```
|
|
389
|
+
istr(' 1')
|
|
390
|
+
istr(' 12')
|
|
391
|
+
istr('123')
|
|
392
|
+
istr('1234')
|
|
393
|
+
```
|
|
394
|
+
If the string starts with a `0`, the string will be zero filled:
|
|
395
|
+
```
|
|
396
|
+
with istr.format("03"):
|
|
397
|
+
print(repr(istr(1)))
|
|
398
|
+
print(repr(istr(12)))
|
|
399
|
+
print(repr(istr(123)))
|
|
400
|
+
print(repr(istr(1234)))
|
|
401
|
+
```
|
|
402
|
+
will print
|
|
403
|
+
```
|
|
404
|
+
istr('001')
|
|
405
|
+
istr('012')
|
|
406
|
+
istr('123')
|
|
407
|
+
istr('1234')
|
|
408
|
+
```
|
|
409
|
+
Note that if a format other than the default `''` is used, the string will reformatted even if the `istr` is specified with a string:
|
|
410
|
+
```
|
|
411
|
+
with istr.format("03"):
|
|
412
|
+
print(repr(istr(" 12 ")))
|
|
413
|
+
```
|
|
414
|
+
will result in `istr('0012')`
|
|
415
|
+
|
|
416
|
+
Remark: For bases other than 10, the string will never be reformatted!
|
|
417
|
+
|
|
418
|
+
# Test script
|
|
419
|
+
There's an extensive pytest script in the `\tests` directory.
|
|
420
|
+
|
|
421
|
+
This script also shows clearly the ways istr can be used.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
istr/__init__.py,sha256=xeAK58Bnq1NPGZDQY_l1JXHK243tJHPicwXjjbt6E7k,50
|
|
2
|
+
istr/install istr.py,sha256=MnVdIOGm7z1Gf6xX95sfEFGDQ2Q3Mh37M27EjeogEPo,7734
|
|
3
|
+
istr/istr.py,sha256=aQY_NL7vl3K49j6Oa0-2ErWx0liOSYXYWtAn8l9JqPM,18174
|
|
4
|
+
istr_python-0.1.3.0.dist-info/METADATA,sha256=_nrTUw1gom9jItbyV5DM_VrspAPV-WcUkWpN0By5IO8,10340
|
|
5
|
+
istr_python-0.1.3.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
6
|
+
istr_python-0.1.3.0.dist-info/top_level.txt,sha256=WpBHA-t036vALUt_W4rIOw-f-gydb8hs-Ahwn2IqIE8,5
|
|
7
|
+
istr_python-0.1.3.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
istr
|