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 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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.43.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ istr