Skip to content

Commit 4952f92

Browse files
committed
Add new functions and factor part of utils out into "iterative" module.
1 parent 69c7045 commit 4952f92

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+545
-197
lines changed

doc-source/api/iterative.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
=================================
2+
:mod:`domdf_python_tools.iterative`
3+
=================================
4+
5+
.. automodule:: domdf_python_tools.iterative

domdf_python_tools/dates.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
3030
# MA 02110-1301, USA.
3131
#
32+
# calc_easter from https://code.activestate.com/recipes/576517-calculate-easter-western-given-a-year/
33+
# Copyright © 2008 Martin Diers
34+
# Licensed under the MIT License
35+
#
3236

3337
# stdlib
3438
import datetime
@@ -45,6 +49,7 @@
4549
"parse_month",
4650
"get_month_number",
4751
"check_date",
52+
"calc_easter",
4853
]
4954

5055
try:
@@ -268,3 +273,24 @@ def check_date(month: Union[str, int], day: int, leap_year: bool = True) -> bool
268273
return True
269274
except ValueError:
270275
return False
276+
277+
278+
def calc_easter(year: int) -> datetime.date:
279+
"""
280+
Returns the date of Easter in the given year.
281+
282+
:param year:
283+
284+
.. versionadded:: 1.4.0
285+
"""
286+
287+
a = year % 19
288+
b = year // 100
289+
c = year % 100
290+
d = (19 * a + b - b // 4 - ((b - (b + 8) // 25 + 1) // 3) + 15) % 30
291+
e = (32 + 2 * (b % 4) + 2 * (c // 4) - d - (c % 4)) % 7
292+
f = d + e - 7 * ((a + 11 * d + 22 * e) // 451) + 114
293+
month = f // 31
294+
day = f % 31 + 1
295+
296+
return datetime.date(year, month, day)

domdf_python_tools/iterative.py

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
#!/usr/bin/env python
2+
#
3+
# iterative.py
4+
"""
5+
Functions for iteration, looping etc.
6+
7+
.. versionadded:: 1.4.0
8+
"""
9+
#
10+
# Copyright © 2018-2020 Dominic Davis-Foster <[email protected]>
11+
#
12+
# This program is free software; you can redistribute it and/or modify
13+
# it under the terms of the GNU Lesser General Public License as published by
14+
# the Free Software Foundation; either version 3 of the License, or
15+
# (at your option) any later version.
16+
#
17+
# This program is distributed in the hope that it will be useful,
18+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
19+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20+
# GNU Lesser General Public License for more details.
21+
#
22+
# You should have received a copy of the GNU Lesser General Public License
23+
# along with this program; if not, write to the Free Software
24+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25+
# MA 02110-1301, USA.
26+
#
27+
# chunks from https://stackoverflow.com/a/312464/3092681
28+
# Copyright © 2008 Ned Batchelder
29+
# Licensed under CC-BY-SA
30+
#
31+
32+
# stdlib
33+
import itertools
34+
import textwrap
35+
from typing import Any, Generator, Iterable, Iterator, List, Sequence, Tuple, Type, Union
36+
37+
__all__ = [
38+
"chunks",
39+
"permutations",
40+
"split_len",
41+
"Len",
42+
"double_chain",
43+
"flatten",
44+
"make_tree",
45+
]
46+
47+
48+
def chunks(l: Sequence[Any], n: int) -> Generator[Any, None, None]:
49+
"""
50+
Yield successive n-sized chunks from l.
51+
52+
:param l: The objects to yield chunks from
53+
:param n: The size of the chunks
54+
55+
.. versionchanged:: 1.4.0 Moved from :mod:`domdf_python_tools.utils`
56+
"""
57+
58+
for i in range(0, len(l), n):
59+
yield l[i:i + n]
60+
61+
62+
def permutations(data: Iterable[Any], n: int = 2) -> List[Tuple[Any, ...]]:
63+
"""
64+
Return permutations containing ``n`` items from ``data`` without any reverse duplicates.
65+
66+
If ``n`` is equal to or greater than the length of the data an empty list of returned.
67+
68+
:param data:
69+
:param n:
70+
71+
.. versionchanged:: 1.4.0 Moved from :mod:`domdf_python_tools.utils`
72+
"""
73+
74+
if n == 0:
75+
raise ValueError("'n' cannot be 0")
76+
77+
perms = []
78+
for i in itertools.permutations(data, n):
79+
if i[::-1] not in perms:
80+
perms.append(i)
81+
82+
return perms
83+
84+
85+
def split_len(string: str, n: int) -> List[str]:
86+
"""
87+
Split a string every ``n`` characters.
88+
89+
:param string: The string to split
90+
:param n: The number of characters to split after
91+
92+
:return: The split string
93+
94+
.. versionchanged:: 1.4.0 Moved from :mod:`domdf_python_tools.utils`
95+
"""
96+
97+
return [string[i:i + n] for i in range(0, len(string), n)]
98+
99+
100+
def Len(obj: Any, start: int = 0, step: int = 1) -> range:
101+
"""
102+
Shorthand for ``range(len(obj))``.
103+
104+
Returns an object that produces a sequence of integers from start (inclusive)
105+
to len(obj) (exclusive) by step.
106+
107+
:param obj: The object to iterate over the length of.
108+
:param start: The start value of the range.
109+
:param step: The step of the range.
110+
111+
.. versionadded:: 0.4.7
112+
113+
.. versionchanged:: 1.4.0 Moved from :mod:`domdf_python_tools.utils`
114+
"""
115+
116+
return range(start, len(obj), step)
117+
118+
119+
def double_chain(iterable: Iterable[Iterable]):
120+
"""
121+
Flatten a list of lists of lists into a single list.
122+
123+
Literally just:
124+
125+
.. code-block:: python
126+
127+
chain.from_iterable(chain.from_iterable(iterable))
128+
129+
Converts
130+
131+
.. code-block:: python
132+
133+
[[(1, 2), (3, 4)], [(5, 6), (7, 8)]]
134+
135+
to
136+
137+
.. code-block:: python
138+
139+
[1, 2, 3, 4, 5, 6, 7, 8]
140+
141+
142+
:param iterable: The iterable to chain.
143+
144+
:rtype:
145+
146+
.. versionadded:: 0.4.7
147+
148+
.. versionchanged:: 1.4.0 Moved from :mod:`domdf_python_tools.utils`
149+
"""
150+
151+
yield from itertools.chain.from_iterable(itertools.chain.from_iterable(iterable))
152+
153+
154+
def flatten(iterable: Iterable, primitives: Tuple[Type] = (str, int, float)):
155+
"""
156+
Flattens a mixed list of primitive types and iterables of those types into a single list,
157+
regardless of nesting.
158+
159+
:param iterable:
160+
:param primitives: The primitive types to allow.
161+
162+
.. versionadded:: 1.4.0
163+
""" # noqa: D400
164+
165+
for item in iterable:
166+
if isinstance(item, primitives):
167+
yield item
168+
elif isinstance(item, Iterable):
169+
yield from flatten(item)
170+
else:
171+
raise NotImplementedError
172+
173+
174+
Branch = Union[List[str], List["Branch"]]
175+
176+
177+
def make_tree(tree: Branch) -> Iterator[str]:
178+
"""
179+
Returns the string representation of a mixed list of strings and lists of strings,
180+
similar to :manpage:`tree(1)`.
181+
182+
:param tree:
183+
184+
.. versionadded:: 1.4.0
185+
""" # noqa: D400
186+
187+
last_string = 0
188+
for idx, entry in enumerate(tree):
189+
if isinstance(entry, str):
190+
last_string = idx
191+
192+
for idx, entry in enumerate(tree[:-1]):
193+
if isinstance(entry, str):
194+
if idx > last_string:
195+
yield f"│ {entry}"
196+
elif idx == last_string:
197+
yield f"└── {entry}"
198+
else:
199+
yield f"├── {entry}"
200+
201+
elif isinstance(entry, Iterable):
202+
for line in make_tree(entry):
203+
if idx - 1 == last_string:
204+
yield textwrap.indent(line, "└── ")
205+
else:
206+
yield textwrap.indent(line, "│ ")
207+
208+
if tree:
209+
if isinstance(tree[-1], str):
210+
yield f"└── {tree[-1]}"
211+
elif isinstance(tree[-1], Iterable):
212+
for line in make_tree(tree[-1]):
213+
yield textwrap.indent(line, " ")

domdf_python_tools/paths.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,24 @@ def __exit__(self, t, v, tb):
696696
# removed in the future.
697697
pass
698698

699+
def is_relative_to(self, *other: Union[str, os.PathLike]) -> bool:
700+
r"""
701+
Returns whether the path is relative to another path.
702+
703+
:param \*other:
704+
705+
:rtype:
706+
707+
.. versionadded:: 0.3.8 for Python 3.9 and above
708+
.. versionadded:: 1.4.0 for Python 3.6 and Python 3.7
709+
"""
710+
711+
try:
712+
self.relative_to(*other)
713+
return True
714+
except ValueError:
715+
return False
716+
699717
def abspath(self) -> "PathPlus":
700718
"""
701719
Return the absolute version of the path.

0 commit comments

Comments
 (0)