istr-python 0.0.0__tar.gz

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.
@@ -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,115 @@
1
+ # Introduction
2
+
3
+ The istrlib module has exactly one class: istr.
4
+
5
+ With this it is possible to interpret string as if they were integers. This can be very handy for solving
6
+ puzzles, but also for other purposes.
7
+
8
+ # Installation
9
+ Installing istrlib with pip is easy.
10
+ ```
11
+ $ pip install istrlib
12
+ ```
13
+ or when you want to upgrade,
14
+ ```
15
+ $ pip install istrlib --upgrade
16
+ ```
17
+
18
+ Alternatively, istrlib.py can be just copied into you current work directory from GitHub (https://github.com/salabim/istrlib).
19
+
20
+ No dependencies!
21
+
22
+ # Usage
23
+ Just start with
24
+
25
+ ```
26
+ from istrlib import istr
27
+ ```
28
+
29
+ Now we can define some istrs:
30
+ ```
31
+ four = istr("4")
32
+ five = istr("5")
33
+ ```
34
+ Them we can do
35
+ ```
36
+ x= four * five
37
+ ```
38
+ , after which x is `istr("40")`
39
+
40
+ And now we can do
41
+ ```
42
+ print(x == 40)
43
+ print(x == "40")
44
+ ```
45
+ resulting in two times `True`. That's because istr instances are treated as int, although they are strings.
46
+
47
+ That means that we can also say
48
+ ```
49
+ print(x < 50)
50
+ print(x >= "30")
51
+ ```
52
+ again resulting in two times `True`.
53
+
54
+ In contrast to an ordinary string
55
+ ```
56
+ print(four + five)
57
+ ```
58
+ prints `9`, as istr are treated as ints.
59
+
60
+ So, how can we concatenate istrs? Just use the or operator (|):
61
+ ```
62
+ print(four | five)
63
+ ```
64
+ will output `45`.
65
+
66
+ And the result is again an istr.
67
+
68
+ That means that
69
+ ```
70
+ (four | five) / 3
71
+ ```
72
+ is `istr("9")`.
73
+
74
+ In order to multiply a string in the usual sense, you cannot use `3 * four`, as that will be `12`.
75
+
76
+ We use the matrix multiplication operator (@) for this. So `3 @ four` is `444`.
77
+
78
+ Also allowed are
79
+ ```
80
+ abs(four)
81
+ -four
82
+ ```
83
+
84
+ For the in operator a istr is treated as an ordinary string, although it is possible to use ints as well:
85
+ ```
86
+ "34" in istr(1234)
87
+ 34 in istr(1234)
88
+ ```
89
+ On the left hand side an istr is always treated as a string:
90
+ ```
91
+ istr(1234) in "01234566890ABCDEF"
92
+ ```
93
+
94
+ 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.
95
+ Also divisions are always floor divisions!
96
+
97
+ There's a special case for `istr("")`. This is a proper empty string, but also represents the value of 0.
98
+ That is to allow for istr("").join(i for i in "01234"
99
+
100
+ Sorting a list of istrs is based on the integer value, not the string. So
101
+
102
+ `' '.join(sorted('1 3 2 4 5 6 11 7 9 8 10 12 0'.split()))`
103
+
104
+ is
105
+
106
+ `'0 1 10 11 2 3 4 5 6 7 8 9'`
107
+
108
+ ,whereas
109
+
110
+ `' '.join(sorted(istr('1 3 2 4 5 6 11 7 9 8 10 12 0'.split())))`
111
+
112
+ is
113
+
114
+ `'0 1 2 3 4 5 6 7 8 9 10 11'`
115
+
File without changes
@@ -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
+
@@ -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,10 @@
1
+ README.md
2
+ pyproject.toml
3
+ istr/__init__.py
4
+ istr/install istr.py
5
+ istr/istr.py
6
+ istr_python.egg-info/PKG-INFO
7
+ istr_python.egg-info/SOURCES.txt
8
+ istr_python.egg-info/dependency_links.txt
9
+ istr_python.egg-info/top_level.txt
10
+ test/test_istrlib.py
@@ -0,0 +1,15 @@
1
+ [build-system]
2
+ requires = ["setuptools", "setuptools-scm"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "istr-python"
7
+ authors = [
8
+ {name = "Ruud van der Ham", email = "rt.van.der.ham@gmail.com"}
9
+ ]
10
+ description = "istr description"
11
+ version = "0.0.0"
12
+ readme = "README.md"
13
+ requires-python = ">=3.7"
14
+ dependencies = [
15
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,362 @@
1
+ from __future__ import print_function
2
+ from __future__ import division
3
+ from istrlib import istr
4
+ import math
5
+
6
+ import pytest
7
+
8
+ for i, name in enumerate("minus_one zero one two three four five six seven eight nine ten eleven twelve thirteen".split(), -1):
9
+ globals()[name] = istr(i)
10
+ one_to_twelve = istr.range(1, thirteen)
11
+
12
+
13
+ def test_arithmetic():
14
+ assert two + three == "5"
15
+ assert two + three == five
16
+ assert 2 + three == five
17
+ assert two + 3 == five
18
+
19
+ assert two - three == "-1"
20
+ assert two - three == minus_one
21
+ assert 2 - three == minus_one
22
+ assert two - 3 == minus_one
23
+
24
+ assert two * three == "6"
25
+ assert two * three == six
26
+ assert 2 * three == six
27
+ assert two * 3 == six
28
+
29
+ assert twelve // three == "4"
30
+ assert twelve // three == four
31
+ assert 12 // three == four
32
+ assert twelve // 3 == four
33
+
34
+ assert twelve / three == "4"
35
+ assert twelve / three == four
36
+ assert 12 / three == four
37
+ assert twelve / 3 == four
38
+
39
+ assert thirteen / three == "4"
40
+ assert thirteen / three == four
41
+ assert 13 / three == four
42
+ assert thirteen / 3 == four
43
+
44
+ assert twelve % five == 2
45
+ assert twelve % 5 == 2
46
+ assert 12 % five == 2
47
+
48
+ assert twelve % five == "2"
49
+ assert twelve % 5 == "2"
50
+ assert 12 % five == "2"
51
+
52
+ assert two**three == "8"
53
+ assert two**3 == "8"
54
+ assert 2**three == "8"
55
+
56
+
57
+ def test_lt():
58
+ assert two < three
59
+ assert 2 < three
60
+ assert two < 3
61
+
62
+ assert not three < two
63
+ assert not 3 < two
64
+ assert not three < 2
65
+
66
+ assert not two < two
67
+ assert not 2 < two
68
+ assert not two < 2
69
+
70
+
71
+ def test_le():
72
+ assert two <= three
73
+ assert 2 <= three
74
+ assert two <= 3
75
+
76
+ assert two <= two
77
+ assert 2 <= two
78
+ assert two <= two
79
+
80
+
81
+ def test_gt():
82
+ assert three > two
83
+ assert 3 > two
84
+ assert three > 2
85
+
86
+ assert not two > three
87
+ assert not 2 > three
88
+ assert not two > 3
89
+
90
+ assert not three > three
91
+ assert not 3 > three
92
+ assert not three > 3
93
+
94
+
95
+ def test_ge():
96
+ assert three >= two
97
+ assert 3 >= two
98
+ assert three >= 2
99
+
100
+ assert three >= three
101
+ assert 3 >= three
102
+ assert three >= three
103
+
104
+
105
+ def test_eq():
106
+ assert two == istr("2")
107
+ assert two == 2
108
+ assert two == "2"
109
+ assert 2 == two
110
+ assert "2" == two
111
+
112
+ assert not two == "ab"
113
+ assert not two == istr
114
+
115
+
116
+ def test_ne():
117
+ assert not two != istr("2")
118
+ assert not two != 2
119
+ assert not two != "2"
120
+ assert not 2 != two
121
+ assert not "2" != two
122
+
123
+ assert two != "ab"
124
+ assert two != istr
125
+
126
+
127
+ def test_order():
128
+ assert " ".join(sorted(istr.range(1, 13))) == "1 2 3 4 5 6 7 8 9 10 11 12"
129
+ assert " ".join(sorted(map(istr, range(1, 13)))) == "1 2 3 4 5 6 7 8 9 10 11 12"
130
+
131
+
132
+ def test_range():
133
+ assert one_to_twelve == istr.range("1", "13")
134
+ assert one_to_twelve == istr.range(one, thirteen, one)
135
+ assert one_to_twelve == istr(range(1, 13))
136
+
137
+ assert len(one_to_twelve) == 12
138
+ assert 2 in one_to_twelve
139
+ assert "2" in one_to_twelve
140
+ assert two in one_to_twelve
141
+
142
+ assert 13 not in one_to_twelve
143
+ assert "13" not in one_to_twelve
144
+ assert thirteen not in one_to_twelve
145
+
146
+ assert one_to_twelve[2] == 3
147
+ assert one_to_twelve[2] == "3"
148
+ assert one_to_twelve[2] == three
149
+
150
+ assert one_to_twelve[2:4] == istr.range(3, 5)
151
+
152
+ with pytest.raises(IndexError):
153
+ a = one_to_twelve[12]
154
+
155
+
156
+ def test_misc():
157
+ assert istr("") == ""
158
+ assert istr("") == 0
159
+ with pytest.raises(ValueError):
160
+ istr(" ")
161
+ assert istr(istr(6)) == "6"
162
+ assert istr(" 12 ") == " 12 "
163
+ with istr.format("03"):
164
+ assert istr(" 12 ") == "012"
165
+ assert istr("")==""
166
+
167
+
168
+ def test_divmod():
169
+ assert divmod(eleven, three) == (istr(3), istr(2))
170
+ assert divmod(11, three) == (istr(3), istr(2))
171
+ assert divmod(eleven, 3) == (istr(3), istr(2))
172
+ assert divmod(11, 3) == (3, 2)
173
+
174
+
175
+ def test_iter():
176
+ assert [x for x in istr.range(3)] == [istr(0), istr(1), istr(2)]
177
+
178
+
179
+ def test_reversed():
180
+ assert [x for x in reversed(istr.range(3))] == [istr(2), istr(1), istr(0)]
181
+
182
+
183
+ def test_lazy():
184
+ for x in istr.range(10000000000000000000000000):
185
+ if x == four:
186
+ break
187
+ assert x == 4
188
+
189
+ for x in istr(range(10000000000000000000)):
190
+ if x == four:
191
+ break
192
+ assert x == 4
193
+
194
+
195
+ def test_str_repr():
196
+ assert repr(one) == "istr('1')"
197
+ assert str(one) == "1"
198
+ assert f"{one}" == "1"
199
+ assert repr(one_to_twelve) == "istr.range(1, 13)"
200
+ assert repr(istr.range(10)) == "istr.range(0, 10)"
201
+ assert repr(istr.range(one, two, three)) == "istr.range(1, 2, 3)"
202
+
203
+ assert str(one_to_twelve) == "istr.range(1, 13)"
204
+ assert str(istr.range(10)) == "istr.range(0, 10)"
205
+ assert str(istr.range(one, two, three)) == "istr.range(1, 2, 3)"
206
+
207
+
208
+ def test_index():
209
+ assert (one_to_twelve.index(2)) == 1
210
+ assert (one_to_twelve.index(two)) == 1
211
+ assert (one_to_twelve.index("2")) == 1
212
+ with pytest.raises(ValueError):
213
+ one_to_twelve.index(13)
214
+ with pytest.raises(ValueError):
215
+ one_to_twelve.index(thirteen)
216
+ with pytest.raises(ValueError):
217
+ one_to_twelve.index("13")
218
+
219
+
220
+ def test_count():
221
+ assert one_to_twelve.count(2) == 1
222
+ assert one_to_twelve.count(two) == 1
223
+ assert one_to_twelve.count("2") == 1
224
+ assert one_to_twelve.count(13) == 0
225
+ assert one_to_twelve.count(thirteen) == 0
226
+ assert one_to_twelve.count("13") == 0
227
+
228
+
229
+ def test_hash():
230
+ assert hash(istr.range(1, 13)) == hash(one_to_twelve)
231
+ assert hash(istr.range(1, 12)) != hash(one_to_twelve)
232
+
233
+
234
+ def test_format():
235
+ assert istr(" 1 ") == " 1 "
236
+ with istr.format("0"):
237
+ assert istr(" 1 ") == "1"
238
+ with istr.format("03"):
239
+ assert istr(1) == "001"
240
+ assert istr(1234) == "1234"
241
+ with istr.format("3"):
242
+ assert istr(1) == " 1"
243
+ assert istr(1234) == "1234"
244
+ with istr.format(""):
245
+ assert istr(1234) == "1234"
246
+ with istr.format("003"):
247
+ assert istr(1) == "001"
248
+ with pytest.raises(ValueError):
249
+ with istr.format(" 1"):
250
+ ...
251
+ with pytest.raises(ValueError):
252
+ with istr.format("1 "):
253
+ ...
254
+ with pytest.raises(ValueError):
255
+ with istr.format("a"):
256
+ ...
257
+ with pytest.raises(ValueError):
258
+ with istr.format(1):
259
+ ...
260
+ with istr.format("0"):
261
+ assert istr(" 3 ") == "3"
262
+ assert istr.default_format() == ""
263
+ istr.default_format("03")
264
+ assert istr.default_format() == "03"
265
+ assert istr(" 8 ") == "008"
266
+ istr.default_format("")
267
+ assert istr(" 8 ") == " 8 "
268
+
269
+
270
+ def test_range_format():
271
+ r = istr.range(11)
272
+ assert repr(r) == "istr.range(0, 11)"
273
+ assert " ".join(r) == "0 1 2 3 4 5 6 7 8 9 10"
274
+ r = istr.range(11)
275
+ with istr.format("02"):
276
+ assert " ".join(r) == "00 01 02 03 04 05 06 07 08 09 10"
277
+
278
+
279
+ def test_even_odd():
280
+ assert istr(1).is_odd()
281
+ assert not istr(1).is_even()
282
+
283
+ assert istr(12345678).is_even()
284
+ assert not istr(12345678).is_odd()
285
+
286
+
287
+ def test_join():
288
+ s = "".join(istr(("4", "5", "6")))
289
+ assert s == "456"
290
+ assert type(s) == str
291
+
292
+ s = istr("").join(("4", "5", "6"))
293
+ assert s == "456"
294
+ assert s == 456
295
+ assert type(s) == istr
296
+
297
+ s = istr("").join(istr(("4", "5", "6")))
298
+ assert s == "456"
299
+ assert s == 456
300
+ assert type(s) == istr
301
+
302
+ s = istr("").join(istr(("", "", "6")))
303
+ assert s == "6"
304
+ assert s == 6
305
+ assert type(s) == istr
306
+
307
+
308
+ def test_matmul():
309
+ assert five @ 3 == "555"
310
+ assert five @ 3 == 555
311
+
312
+ assert 3 @ five == "555"
313
+ assert 3 @ five == 555
314
+
315
+ with pytest.raises(TypeError):
316
+ three @ five
317
+ with pytest.raises(TypeError):
318
+ three @ "5"
319
+ with pytest.raises(TypeError):
320
+ "3" @ five
321
+
322
+
323
+ def test_str():
324
+ assert repr(str(five)) == "'5'"
325
+
326
+
327
+ def test_trunc_and_friends():
328
+ assert repr(math.trunc(one)) == "istr('1')"
329
+ assert repr(math.ceil(one)) == "istr('1')"
330
+ assert repr(math.floor(one)) == "istr('1')"
331
+ assert repr(round(one)) == "istr('1')"
332
+
333
+
334
+ def test_data_structures():
335
+ assert istr(list(range(1, 4))) == [1, 2, 3]
336
+ assert istr(list(range(1, 4))) == ["1", "2", "3"]
337
+
338
+ assert istr(tuple(range(1, 4))) == (1, 2, 3)
339
+ assert istr(tuple(range(1, 4))) == ("1", "2", "3")
340
+ assert istr(set(range(1, 4))) == {istr(1), istr(2), istr(3)}
341
+
342
+ assert list(istr(range(1, 4))) == [1, 2, 3]
343
+ assert list(istr(range(1, 4))) == ["1", "2", "3"]
344
+
345
+ assert list(istr(range(1, 4))) == [1, 2, 3]
346
+ assert list(istr(range(1, 4))) == ["1", "2", "3"]
347
+
348
+ assert list(istr.enumerate("abc")) == [(0, "a"), (1, "b"), (2, "c")]
349
+ assert list(istr.enumerate("abc")) == [("0", "a"), ("1", "b"), ("2", "c")]
350
+
351
+
352
+ def test_edge_cases():
353
+ with pytest.raises(ValueError):
354
+ istr("ab")
355
+ with pytest.raises(TypeError):
356
+ istr(istr)
357
+ assert repr(istr(istr(one))) == "istr('1')"
358
+
359
+
360
+ if __name__ == "__main__":
361
+ pytest.main(["-vv", "-s", "-x", __file__])
362
+