yarnover 0.1__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.
yarnover-0.1/PKG-INFO ADDED
@@ -0,0 +1,24 @@
1
+ Metadata-Version: 2.4
2
+ Name: yarnover
3
+ Version: 0.1
4
+ Summary: yarnover
5
+ Description-Content-Type: text/markdown
6
+
7
+
8
+ Pattern - represents a knitting pattern
9
+
10
+ `Pattern.cast_on(n)`
11
+
12
+ alias `co`
13
+
14
+ Cast on `n` stitches
15
+
16
+
17
+ `Work.work_stitch(
18
+ - name: The name or code of the stitch
19
+ - parent_count = 1: The number of stitches from the left needle that will be worked
20
+ - slip_parent_stitches = True: Indicates whether to slip the parent stitches
21
+ - add_stitch_to_right_needle = True: Indicates whether to add the new stitch to the right needle
22
+ - tags = []:
23
+
24
+ `Work.slip_stitch()
@@ -0,0 +1,12 @@
1
+ [build-system]
2
+ requires = [
3
+ "setuptools>=42",
4
+ "wheel"
5
+ ]
6
+ build-backend = "setuptools.build_meta"
7
+
8
+ [project]
9
+ name = "yarnover"
10
+ version = "0.1"
11
+ description = "yarnover"
12
+ readme = "readme.md"
yarnover-0.1/readme.md ADDED
@@ -0,0 +1,18 @@
1
+
2
+ Pattern - represents a knitting pattern
3
+
4
+ `Pattern.cast_on(n)`
5
+
6
+ alias `co`
7
+
8
+ Cast on `n` stitches
9
+
10
+
11
+ `Work.work_stitch(
12
+ - name: The name or code of the stitch
13
+ - parent_count = 1: The number of stitches from the left needle that will be worked
14
+ - slip_parent_stitches = True: Indicates whether to slip the parent stitches
15
+ - add_stitch_to_right_needle = True: Indicates whether to add the new stitch to the right needle
16
+ - tags = []:
17
+
18
+ `Work.slip_stitch()
yarnover-0.1/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,12 @@
1
+ from yarnover.pattern import Pattern
2
+ from yarnover.swatch import MetricSwatch
3
+
4
+ def co(count : int = 0) -> Pattern:
5
+ """Star new :class:`Pattern`, optionally casting on ``count`` stitches
6
+
7
+ Equivant to ``Pattern().co(count)``
8
+
9
+ :param count: The number of stitches to cast on.
10
+ """
11
+ return Pattern().co(count)
12
+
@@ -0,0 +1,7 @@
1
+
2
+ class KnittingException(Exception):
3
+ ...
4
+
5
+
6
+ class PatternException(Exception):
7
+ ...
@@ -0,0 +1,142 @@
1
+ from yarnover.exceptions import KnittingException
2
+ from typing import List, Optional, Set
3
+
4
+ from logging import getLogger
5
+
6
+ logger = getLogger(__name__)
7
+
8
+ Stitch = int
9
+
10
+ Marker = str
11
+
12
+ class Needle:
13
+ def __init__(self, initial_stitches : List[Stitch|Marker] = list()) -> None:
14
+ self.is_circular = False
15
+ self.items : List[Stitch|Marker] = list()
16
+ if initial_stitches:
17
+ self.items.extend(initial_stitches)
18
+
19
+ def __repr__(self):
20
+ return f"<Needle ({len(self.items)})>"
21
+
22
+ def push(self, stitch):
23
+ self.items.append(stitch)
24
+
25
+ def extend(self, items):
26
+ for item in items:
27
+ self.push(item)
28
+
29
+ def _pop_next_item(self):
30
+ if self.is_empty:
31
+ raise KnittingException("No items left to pop")
32
+ return self.items.pop()
33
+
34
+ def pop(self, count : int = 1) -> List[Stitch|Marker]:
35
+ items = list()
36
+ for _ in range(count):
37
+ item = self._pop_next_item()
38
+ items.append(item)
39
+ return items
40
+
41
+ def get_next_stitch(self) -> Stitch:
42
+ for i in range(len(self.items)):
43
+ item = self.peek(i)
44
+ if isinstance(item, Stitch):
45
+ return item
46
+ else:
47
+ raise KnittingException("No stitches left on needle")
48
+
49
+
50
+ def peek(self, position : int = 0):
51
+ return self.items[-(position+1)]
52
+
53
+ @property
54
+ def is_empty(self):
55
+ return len(self.items) == 0
56
+
57
+ def __iter__(self):
58
+ return reversed(self.items)
59
+
60
+ def distance_to_marker(self) -> int:
61
+ """
62
+ Returns the number of stitches left before the next marker.
63
+
64
+ :param stitches_left: The number of stitches that must be left before the marker.
65
+ """
66
+ for n, item in enumerate(self):
67
+ if isinstance(item, Marker):
68
+ return n
69
+ else:
70
+ raise KnittingException("No markers left found")
71
+
72
+ def distance_to_end(self):
73
+ for _, item in enumerate(self):
74
+ if isinstance(item, Marker):
75
+ raise KnittingException("Marker found before end")
76
+ return len(self.items)
77
+
78
+ def distance_to_marker_or_end(self):
79
+ distance = 0
80
+ for item in iter(self):
81
+ if isinstance(item, Marker):
82
+ return distance
83
+ else:
84
+ distance += 1
85
+ return distance
86
+
87
+
88
+ @property
89
+ def stitch_count(self):
90
+ count = 0
91
+ for item in self.items:
92
+ if isinstance(item, Stitch):
93
+ count += 1
94
+ return count
95
+
96
+ def measure(self, from_marker, to_marker):
97
+ state = 0
98
+ count = 0
99
+ if from_marker is None:
100
+ state = 1
101
+ for item in self:
102
+ if state == 0 and isinstance(item, Marker):
103
+ assert item != to_marker
104
+ if item == from_marker:
105
+ state = 1
106
+ elif state == 1:
107
+ if isinstance(item, Stitch):
108
+ count += 1
109
+ elif isinstance(item, Marker):
110
+ assert item == to_marker
111
+ state = 2
112
+ if to_marker is not None:
113
+ assert state == 2
114
+ return count
115
+
116
+ def summarize(self):
117
+ summary = [0]
118
+ for item in self:
119
+ if isinstance(item, Stitch):
120
+ summary[-1] += 1
121
+ elif isinstance(item, Marker):
122
+ summary.append(0)
123
+ return summary
124
+
125
+
126
+ class BackNeedle(Needle):
127
+ def __init__(self, needle : Needle) -> None:
128
+ self.is_circular = True
129
+ needle.is_circular = True
130
+ self.items : List[Stitch|Marker] = needle.items
131
+
132
+ def push(self, stitch):
133
+ self.items.insert(0, stitch)
134
+
135
+ def _pop_next_item(self):
136
+ return self.items.pop(0)
137
+
138
+ def peek(self, position : int = 0):
139
+ return self.items[position]
140
+
141
+ def __iter__(self):
142
+ return iter(self.items)
@@ -0,0 +1,701 @@
1
+ from contextlib import contextmanager
2
+ import math
3
+ from networkx import DiGraph, reverse_view
4
+ from typing import Callable, List, Optional, Self, Sequence, Set
5
+
6
+ from yarnover.exceptions import KnittingException, PatternException
7
+ from yarnover.needle import Needle, BackNeedle, Stitch, Marker
8
+
9
+ import logging
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ def format_marker(action, name):
14
+ if name:
15
+ return f"{action}<{name}>"
16
+ else:
17
+ return action
18
+
19
+ def format_stitches(code, count):
20
+ if count == 1:
21
+ return code
22
+ else:
23
+ return f"{code}{count}"
24
+
25
+ def format_do_this_to_that(code, stitches_left, count, log_count):
26
+ if stitches_left == 0:
27
+ if log_count:
28
+ return f"{code}({count})"
29
+ else:
30
+ return code
31
+ else:
32
+ if log_count:
33
+ return f"{code}-{stitches_left}({count})"
34
+ else:
35
+ return f"{code}-{stitches_left}"
36
+
37
+ def format_row_block_reference(from_row, to_row):
38
+ if from_row == to_row-1:
39
+ return f"row {from_row}"
40
+ else:
41
+ return f"rows {from_row}-{to_row-1}"
42
+
43
+ def format_increase_count(inc):
44
+ if inc < -1:
45
+ return f"{-inc} sts decreased"
46
+ if inc == -1:
47
+ return f"{-inc} st decreased"
48
+ if inc == 0:
49
+ return f"no sts increased"
50
+ if inc == 1:
51
+ return f"{inc} st increased"
52
+ if inc > 1:
53
+ return f"{inc} sts increased"
54
+
55
+ class Repeater:
56
+ """Repeats a callable with summarized logging.
57
+ """
58
+ def __init__(self, p : "Pattern", f : Callable[["Pattern"], "Pattern"]):
59
+ # TODO: Kan afhængighed til Pattern fjernes?
60
+ """
61
+ :param p: The pattern whose logging is replaced.
62
+ :param f: The callable to be called. Executing the callable should affect the state of ``p``.
63
+ """
64
+ self.p = p
65
+ self.f = f
66
+
67
+ def once(self):
68
+ return self.f(self.p)
69
+
70
+ def repeat(self, count : int):
71
+ log_length = len(self.p.row_log)
72
+ for _ in range(count):
73
+ self.f(self.p)
74
+ self.p.row_log = self.p.row_log[:log_length]
75
+ self.p.row_log.append(f"repeat {count} times")
76
+ return self.p
77
+
78
+ def to_end(self, stitches_left : int = 0, log_count : bool = True):
79
+ """Repeat the callable to end, summarising output.
80
+
81
+ :param stitches_left: The number of stitches before the end to stop.
82
+ :param log_count: If True, the number for repetitions will be logged.
83
+ """
84
+ log_length = len(self.p.row_log)
85
+ count = 0
86
+ while not self.p.at_end(stitches_left):
87
+ self.f(self.p)
88
+ count += 1
89
+ self.p.row_log = self.p.row_log[:log_length]
90
+ if count == 0:
91
+ return self.p
92
+ if stitches_left:
93
+ if log_count:
94
+ if count == 1:
95
+ self.p.row_log.append(f"repeat once to {stitches_left} before end")
96
+ if count > 1:
97
+ self.p.row_log.append(f"repeat to {stitches_left} before end ({count} times)")
98
+ else:
99
+ self.p.row_log.append(f"repeat to {stitches_left} before end")
100
+ else:
101
+ self.p.row_log.append(f"repeat to end")
102
+ return self.p
103
+
104
+ def to_marker(self, marker_name : str = "", stitches_left : int = 0, log_count : bool = True):
105
+ log_length = len(self.p.row_log)
106
+ count = 0
107
+ while not self.p.at_marker(marker_name, stitches_left):
108
+ self.f(self.p)
109
+ count += 1
110
+ self.p.row_log = self.p.row_log[:log_length]
111
+ if stitches_left:
112
+ if log_count:
113
+ self.p.row_log.append(f"repeat to {stitches_left} before marker ({count} times)")
114
+ else:
115
+ self.p.row_log.append(f"repeat to {stitches_left} before marker")
116
+ else:
117
+ if log_count:
118
+ self.p.row_log.append(f"repeat to marker ({count} times)")
119
+ else:
120
+ self.p.row_log.append(f"repeat to marker")
121
+ return self.p
122
+
123
+ class Pattern:
124
+ def __init__(self):
125
+ self.left = Needle()
126
+ self.right = Needle()
127
+ self.g : DiGraph = DiGraph()
128
+ self.g_rev = self.g.reverse(copy=False)
129
+
130
+ self.next_stitch_number : int = 0
131
+ self.last_stitch_worked : int | None = None
132
+
133
+ self.row_number = 0
134
+ self.stitch_number = 0
135
+ self.initial_stitch_count = 0
136
+
137
+ self.row_log = list()
138
+
139
+ self.quiet = False
140
+
141
+ def log_paragraph(self, text : str):
142
+ if self.row_log:
143
+ raise PatternException("Row in progress, can't log paragraph") # TODO: Skal vi det?
144
+ print(text)
145
+
146
+ def work_stitch(self,
147
+ code : str,
148
+ parent_count : int = 1,
149
+ slip_from_left_needle : bool = True,
150
+ add_to_right_needle : bool = True):
151
+ """
152
+ Adds a stitches to the work.
153
+
154
+ :param code: The code of the new stitch.
155
+ :param parent_count: The number of stitches from the left needle to work.
156
+ :param slip_from_left_needle: Whether to slip the parent stitches from the left needle
157
+ :param add_to_right_needle: Whether to add the new stitch to the right needle
158
+ :meta private:
159
+ """
160
+ stitch = self.next_stitch_number
161
+
162
+ column_number = self.get_column_number(stitch, parent_count)
163
+
164
+ if column_number is None:
165
+ print("!")
166
+
167
+ self.g.add_node(
168
+ stitch,
169
+ name=code,
170
+ row_number=self.row_number,
171
+ column_number=column_number,
172
+ stitch_number=self.stitch_number,
173
+ )
174
+
175
+ if self.last_stitch_worked is not None:
176
+ self.g.add_edge(self.last_stitch_worked, stitch, relation="successor")
177
+
178
+ self.last_stitch_worked = stitch
179
+ self.next_stitch_number += 1
180
+ self.stitch_number += 1 # on row
181
+
182
+ if parent_count > 0:
183
+ for i in range(parent_count):
184
+ parent_stitch = self.left.peek(i)
185
+ if not isinstance(parent_stitch, Stitch):
186
+ raise KnittingException(f"Trying to work non-stitch item: {parent_stitch}")
187
+ self.g.add_edge(parent_stitch, stitch, relation="child")
188
+
189
+ if slip_from_left_needle:
190
+ self.left.pop(parent_count)
191
+ if add_to_right_needle:
192
+ self.right.push(stitch)
193
+
194
+ def get_column_number(self, stitch, parent_count):
195
+ # If this is the first row, number sequentially
196
+ if self.row_number == 0:
197
+ return stitch
198
+
199
+ # If this is an unparented increase, bump and assign
200
+ elif parent_count == 0:
201
+ if self.left.is_empty:
202
+ if self.last_stitch_worked:
203
+ return self.g.nodes[self.last_stitch_worked]["column_number"]+1
204
+ else:
205
+ raise KnittingException("No next stitch, no previous stitch")
206
+ else:
207
+ return self.g.nodes[self.left.get_next_stitch()]["column_number"]
208
+ self.bump_stitches(column_number)
209
+
210
+ # If the first parent stitch has other children, bump and insert
211
+ elif parent_count > 0:
212
+ parent_stitch = self.left.peek()
213
+ if not isinstance(parent_stitch, Stitch):
214
+ raise KnittingException(f"Trying to knit non-stitch item: {parent_stitch}")
215
+ has_sibling = False
216
+ for related_stitch in self.g.successors(parent_stitch):
217
+ if self.g[parent_stitch][related_stitch]["relation"] == "child":
218
+ has_sibling = True
219
+ return self.g.nodes[parent_stitch]["column_number"]
220
+ self.bump_stitches(column_number)
221
+ break
222
+ if not has_sibling:
223
+ return self.g.nodes[parent_stitch]["column_number"]
224
+
225
+
226
+ def bump_stitches(self, from_column : int):
227
+ nodes_to_bump = list()
228
+ for node, column_number in self.g.nodes(data="column_number"):
229
+ if column_number >= from_column: # type: ignore
230
+ nodes_to_bump.append(node)
231
+ for node in nodes_to_bump:
232
+ self.g.nodes[node]["column_number"] += 1
233
+
234
+ def repeat_stitch(self,
235
+ code : str,
236
+ parent_count : int = 1,
237
+ slip_from_left_needle : bool = True,
238
+ add_to_right_needle : bool = True,
239
+ count : int = 1):
240
+ """
241
+ Convenience function to work the same stitch several times.
242
+
243
+ :param code: The code of the new stitch.
244
+ :param count: The number of stitches to work.
245
+ :meta private:
246
+ """
247
+ logger.debug(f"{self.row_number}.{self.stitch_number}: {code}*{count}")
248
+ for _ in range(count):
249
+ self.work_stitch(
250
+ code,
251
+ parent_count,
252
+ slip_from_left_needle,
253
+ add_to_right_needle
254
+ )
255
+
256
+ def stitch_to_end(self,
257
+ code : str,
258
+ parent_count : int = 1,
259
+ slip_from_left_needle : bool = True,
260
+ add_to_right_needle : bool = True,
261
+ stitches_left : int = 0,
262
+ marker_action : str = "fail") -> int:
263
+ """
264
+ Repeat a stitch until all the stitches on the needle have been worked and all markers slipped or removed.
265
+
266
+ :param code: The code of the stitch.
267
+ :param stitches_left: The number of stitches that should be left unworked.
268
+ :param marker_action: What to do when a marker is encountered - "fail", "slip" or "remove".
269
+ :meta private:
270
+ """
271
+ total_count = 0
272
+ log_length = len(self.row_log)
273
+ blocks = self.summarize()
274
+ while len(blocks) > 1:
275
+ block = blocks.pop(0)
276
+ total_count += block
277
+ self.repeat_stitch(
278
+ code,
279
+ parent_count,
280
+ slip_from_left_needle,
281
+ add_to_right_needle,
282
+ block)
283
+ if marker_action == "slip":
284
+ self.sm()
285
+ elif marker_action == "remove":
286
+ self.rm()
287
+ else:
288
+ raise KnittingException("Marker before end")
289
+ block = blocks.pop(0)
290
+ if block < stitches_left:
291
+ raise KnittingException(f"Less than {stitches_left} left")
292
+ self.repeat_stitch(
293
+ code,
294
+ parent_count,
295
+ slip_from_left_needle,
296
+ add_to_right_needle,
297
+ block)
298
+ total_count += block-stitches_left
299
+ self.row_log = self.row_log[:log_length]
300
+ return total_count
301
+
302
+ def stitch_to_marker(self,
303
+ code : str,
304
+ parent_count : int = 1,
305
+ slip_from_left_needle : bool = True,
306
+ add_to_right_needle : bool = True,
307
+ stitches_left : int = 0):
308
+ """
309
+ Repeat a stitch until all the stitches until the next marker is reached
310
+
311
+ :param code: The code of the stitch.
312
+ :param stitches_left: The number of stitches that should be left unworked.
313
+ :meta private:
314
+ """
315
+ log_length = len(self.row_log)
316
+ distance = self.left.distance_to_marker()
317
+ if distance < stitches_left:
318
+ raise KnittingException(f"Less than {stitches_left} stitches left to marker")
319
+ count = distance-stitches_left
320
+ self.repeat_stitch(
321
+ code,
322
+ parent_count,
323
+ slip_from_left_needle,
324
+ add_to_right_needle,
325
+ count)
326
+ self.row_log = self.row_log[:log_length]
327
+ return count
328
+
329
+ # Markers
330
+ def at_marker(self, marker_name : str = "", stitches_left : int = 0) -> bool:
331
+ """
332
+ Return True iff the next item on the left needle is a marker.
333
+
334
+ :param stitches_left: The number of stitches ahead the marker must be.
335
+ :meta private:
336
+ """
337
+ if self.left.is_empty:
338
+ return False
339
+ next_item = self.left.peek(stitches_left)
340
+ if isinstance(next_item, Marker):
341
+ if marker_name:
342
+ return next_item == marker_name
343
+ else:
344
+ return True
345
+
346
+ # Distances
347
+ def at_end(self, stitches_left : int = 0):
348
+ if self.left.is_circular:
349
+ raise KnittingException(f"Circular needle has no end")
350
+ distance = self.left.distance_to_end()
351
+ if distance < stitches_left:
352
+ raise KnittingException(f"Less than {stitches_left} stitches left")
353
+ return distance == stitches_left
354
+
355
+ def distance_to_marker(self, stitches_left : int = 0):
356
+ """
357
+ Returns the number of stitches left before the next marker.
358
+ There must be at least one marker left on the needle.
359
+ If `stitches_left` is provided, this will return the number of stitches before
360
+ that number of stitches are left.
361
+
362
+ :meta private:
363
+ """
364
+ distance = self.left.distance_to_marker()
365
+ if distance < stitches_left:
366
+ raise KnittingException(f"Less than {stitches_left} stitches left before marker")
367
+ return distance - stitches_left
368
+
369
+ # Stitches
370
+ def co(self, count : int = 1):
371
+ """
372
+ Cast on one or more stitches.
373
+ """
374
+ self.repeat_stitch(code="co", parent_count=0, count=count)
375
+ self.row_log.append(format_stitches("co", count))
376
+ return self
377
+
378
+ def s(self, count : int = 1):
379
+ """
380
+ Slip one or more stitches from the left needle to the right.
381
+
382
+ :raise KnittingException: If there are no stitches to slip.
383
+ """
384
+ for _ in range(count):
385
+ if self.left.is_empty:
386
+ raise KnittingException(f"No more stitches to work")
387
+ if not isinstance(self.left.peek(), Stitch):
388
+ raise KnittingException(f"Next item is not a stitch: {self.left.peek()}")
389
+ self.right.extend(self.left.pop())
390
+ self.row_log.append(format_stitches("s", count))
391
+ return self
392
+
393
+ def k(self, count : int = 1):
394
+ """Knit one or more s0titches.
395
+ """
396
+ self.repeat_stitch(code="k", count=count)
397
+ self.row_log.append(format_stitches("k", count))
398
+ return self
399
+
400
+ def ktbl(self, count : int = 1):
401
+ """Knit one or more stitches through the back loop.
402
+ """
403
+ self.repeat_stitch(code="ktbl", count=count)
404
+ self.row_log.append(format_stitches("ktbl", count))
405
+ return self
406
+
407
+ def p(self, count : int = 1):
408
+ """Purl one or more stitches.
409
+ """
410
+ self.repeat_stitch("p", count=count)
411
+ self.row_log.append(format_stitches("p", count))
412
+ return self
413
+
414
+ # Decreases
415
+ def k2tog(self):
416
+ """Knit two together.
417
+ """
418
+ self.work_stitch("k2tog", parent_count=2)
419
+ self.row_log.append(f"k2tog")
420
+ return self
421
+
422
+ def ssk(self):
423
+ """Slip, slip, knit decrease"""
424
+ self.work_stitch("ssk", parent_count=2)
425
+ self.row_log.append(f"ssk")
426
+ return self
427
+
428
+ # Increases
429
+ def yo(self, count : int = 1):
430
+ """Yarnover
431
+ """
432
+ self.repeat_stitch(code="yo", parent_count=0, count=count)
433
+ self.row_log.append(format_stitches("yo", count))
434
+ return self
435
+
436
+ def m(self, count : int = 1):
437
+ """
438
+ An unspecified and unparented increase.
439
+ """
440
+ self.work_stitch("m", parent_count=0)
441
+ self.row_log.append(format_stitches("m", count))
442
+ return self
443
+
444
+ def m1l(self):
445
+ """Make one left.
446
+ """
447
+ self.work_stitch("m1l", parent_count=0)
448
+ self.row_log.append(f"m1l")
449
+ return self
450
+
451
+ def m1r(self):
452
+ """Make one right."""
453
+ self.work_stitch("m1r", parent_count=0)
454
+ self.row_log.append(f"m1r")
455
+ return self
456
+
457
+ def kfb(self):
458
+ """Knit front and back.
459
+ """
460
+ self.work_stitch("kfb1", slip_from_left_needle=False)
461
+ self.work_stitch("kfb2")
462
+ self.row_log.append(f"kfb")
463
+ return self
464
+
465
+ # Bindoff
466
+ def bo(self, count : int = 1):
467
+ """Bind off.
468
+ """
469
+ self.repeat_stitch(code="bo", add_to_right_needle=False, count=count)
470
+ self.row_log.append(format_stitches("bo", count))
471
+ return self
472
+
473
+
474
+ def pte(self,
475
+ stitches_left : int = 0,
476
+ slip_markers : bool = False,
477
+ log_count : bool = True):
478
+ """Purl to end
479
+ If knitting in the round, this purls all stitches currently on the needle.
480
+
481
+ :params stitches_left: The number of stitches to leave on the
482
+ right needle before the end.
483
+ :param slip_markers: If true, slip any markers encountered. If false,
484
+ markers will trigger an error.
485
+ :param log_count: Whether to log the total count knitted.
486
+ """
487
+ count = self.stitch_to_end(code="p",
488
+ stitches_left=stitches_left,
489
+ marker_action="slip" if slip_markers else "none",
490
+ )
491
+ self.row_log.append(format_do_this_to_that("pte", stitches_left, count, log_count))
492
+ return self
493
+
494
+ def kte(self,
495
+ stitches_left : int = 0,
496
+ slip_markers : bool = False,
497
+ log_count : bool = True,
498
+ ):
499
+ """Knit to end.
500
+
501
+ If knitting in the round, this knits all stitches currently on the needle
502
+
503
+ :params stitches_left: The number of stitches to leave on the
504
+ right needle before the end.
505
+ :param slip_markers: If true, slip any markers encountered. If false,
506
+ markers will trigger an error.
507
+ :param log_count: Whether to log the total count knitted.
508
+ """
509
+ count = self.stitch_to_end(code="k",
510
+ stitches_left=stitches_left,
511
+ marker_action="slip" if slip_markers else "none",
512
+ )
513
+ self.row_log.append(format_do_this_to_that("kte", stitches_left, count, log_count))
514
+ return self
515
+
516
+ def ktm(self, stitches_left : int = 0, log_count : bool = True):
517
+ """
518
+ Knit to the next marker.
519
+
520
+ :params stitches_left: The number of stitches to leave on the
521
+ right needle before the marker.
522
+ :param log_count: Whether to log the total count knitted.
523
+ """
524
+ count = self.stitch_to_marker("k", stitches_left=stitches_left)
525
+ self.row_log.append(format_do_this_to_that("ktm", stitches_left, count, log_count))
526
+ return self
527
+
528
+ def ptm(self, stitches_left : int = 0, log_count : bool = False):
529
+ """
530
+ Purl to the next marker.
531
+
532
+ :params stitches_left: The number of stitches to leave on the
533
+ right needle before the marker.
534
+ """
535
+ count = self.stitch_to_marker("p", stitches_left=stitches_left)
536
+ self.row_log.append(format_do_this_to_that("ptm", stitches_left, count, log_count))
537
+ return self
538
+
539
+ def bind_off_to_end(self, stitches_left : int = 0):
540
+ self.stitch_to_end(code="bo",
541
+ stitches_left=stitches_left,
542
+ marker_action="remove"
543
+ )
544
+ self.row_log.append("bind off to end")
545
+ return self
546
+
547
+ # Markers
548
+ def pm(self, name : Optional[str] = None):
549
+ """Place a marker.
550
+
551
+ :param name: If specified, this will be the name of the marker.
552
+ """
553
+ logger.debug(f"Placing marker {name}")
554
+ marker = Marker(name)
555
+ self.right.push(marker)
556
+ self.row_log.append(format_marker("pm", name))
557
+ return self
558
+
559
+ def sm(self, name : Optional[str] = None):
560
+ "Slip a marker from the left to the right needle"
561
+ (marker,) = self.left.pop()
562
+ if not isinstance(marker, Marker):
563
+ raise KnittingException(f"Next item is not a marker")
564
+ if name and marker != name:
565
+ raise KnittingException(f"Expecting marker named {name}, got {marker}")
566
+ logger.debug(f"Slipping marker {marker}")
567
+ self.right.push(marker)
568
+ self.row_log.append(format_marker("sm", name))
569
+ return self
570
+
571
+ def rm(self, name : Optional[str] = None):
572
+ """Remove a marker. If ``name`` is specified, the marker must have that name.
573
+
574
+ :param name: The name of the marker. If this is left out, the maker will be removed, regardless of name.
575
+ :raise KnittingException: If the next item on the left needle is not a marker
576
+ or the name of the marker doesn't match.
577
+ """
578
+ logger.debug(f"Removing marker {name}")
579
+ (marker,) = self.left.pop()
580
+ if not isinstance(marker, Marker):
581
+ raise KnittingException(f"Next item is not a marker")
582
+ if name and marker != name:
583
+ raise KnittingException(f"Expecting marker named {name}, got {marker}")
584
+ self.row_log.append(format_marker("rm", name))
585
+ return self
586
+
587
+ def turn(self, short_row=False):
588
+ """
589
+ Swap the left and right needles.
590
+
591
+ Unless `short_row` is set to True, turning with stitches left on the left needle
592
+ will raise an exception.
593
+ """
594
+ if short_row or self.left.is_empty:
595
+ self.right, self.left = self.left, self.right
596
+ if not short_row:
597
+ self.end_of_row()
598
+ logger.debug(f"Turn")
599
+ else:
600
+ raise KnittingException(f"Short row")
601
+ return self
602
+
603
+ def end_of_row(self, description : str = ""):
604
+ """End the current row and output logging.
605
+
606
+ :param description: A descriptive text to replace the generated log of the row.
607
+ """
608
+ increases = self.left.stitch_count - self.initial_stitch_count
609
+ description = description or ' '.join(self.row_log)
610
+ if not self.quiet:
611
+ if increases == 0 or self.left.stitch_count == 0:
612
+ print(f"- Row {self.row_number}: {description}")
613
+ else:
614
+ print(f"- Row {self.row_number}: {description} ({format_increase_count(increases)})")
615
+ self.row_log.clear()
616
+ logger.debug(f"End of row")
617
+ self.stitch_number = 0
618
+ self.row_number += 1
619
+ self.initial_stitch_count = self.left.stitch_count
620
+ return self
621
+
622
+ def join(self):
623
+ """
624
+ Join the work.
625
+
626
+ This replaces the current left needle with a "reverse view" of the right needle (to simulate a circular needle),
627
+ and requires the current left needle to be empty.
628
+ """
629
+ if self.left.items:
630
+ raise KnittingException(f"Unable to join, {len(self.left.items)} left on left needle")
631
+ self.left = BackNeedle(self.right)
632
+ return self
633
+
634
+ def move_stitches(self, count : int):
635
+ "Move stitches to another needle."
636
+ logger.debug(f"Moving {count} stitches from left needle")
637
+ self.row_log.append(f"remove {count} stitches")
638
+ return self.left.pop(count)
639
+
640
+ def do(self, f : Callable[[Self], Self], description : Optional[str] = None):
641
+ """
642
+ Execute a callable once and return a :class:`Repeater` that allows you to repeat it.
643
+
644
+ :param f: The callable to repeat.
645
+ :return: A :class:`Repeater` object that
646
+ """
647
+ log_checkpoint = len(self.row_log)
648
+ f(self)
649
+ self.row_log, block_log = self.row_log[:log_checkpoint], self.row_log[log_checkpoint:]
650
+ self.row_log.append(f"[{description or ' '.join(block_log)}]")
651
+
652
+ return Repeater(self, f)
653
+
654
+ def repeat(self, count : int):
655
+ """Yields numbers from 0 to ``count``.
656
+
657
+ After the first iteration, output is suppressed. On completion,
658
+ a summary of the additional repetitions is added to the log.
659
+
660
+ :param count: The number of repetitions, including the first one.
661
+ """
662
+ start_row = self.row_number
663
+ yield 0
664
+ end_row = self.row_number
665
+ with self.quietly:
666
+ for i in range(1, count):
667
+ yield i
668
+
669
+ print(f"- Rows {format_row_block_reference(end_row, self.row_number)}: Repeat {format_row_block_reference(start_row, end_row)} {count-1} more times")
670
+
671
+ def summarize(self):
672
+ """Summarizes the stitches on the left needle.
673
+
674
+ This method returns a list of numbers that represent the sections of stitches between
675
+ the current location, the end, and any markers in between.
676
+ The numbers sum to the number of stitches on the left needle and the length of the list is
677
+ one more than the number of markers (i.e. one number if there are zero markers, two if
678
+ there is one marker).
679
+
680
+ .. attention::
681
+
682
+ If knitting in the round with a single BOR marker that has just been slipped, a list of *two*
683
+ items is still returned: The total number of stitches on the needle and zero, which is the number of stitches
684
+ from the BOR marker to the current location.
685
+
686
+ :return: A list of integers one longer than the number of markers (i.e. one number if there are zero markers)
687
+ :meta private:
688
+ """
689
+ return self.left.summarize()
690
+
691
+
692
+ @property
693
+ @contextmanager
694
+ def quietly(self):
695
+ log_checkpoint = len(self.row_log)
696
+ yield None
697
+ self.row_log = self.row_log[:log_checkpoint]
698
+
699
+ def resume(self, stitches):
700
+ if self.left.is_empty:
701
+ self.left.items.extend(stitches)
@@ -0,0 +1,86 @@
1
+
2
+ from typing import Optional
3
+ from yarnover.exceptions import KnittingException
4
+
5
+
6
+ Distance = float
7
+
8
+ class Swatch:
9
+ """Represents a swatch, used for converting measurements to rows and stitches.
10
+ """
11
+ def __init__(self,
12
+ swatch_unit_count : int,
13
+ unit : str,
14
+ swatch_stitches,
15
+ swatch_rows):
16
+ self.swatch_unit_count = swatch_unit_count
17
+ self.unit = unit
18
+ self.stitches_per_unit = swatch_stitches / swatch_unit_count
19
+ self.rows_per_unit = swatch_rows / swatch_unit_count
20
+
21
+ def units_to_stitches(self,
22
+ distance : float,
23
+ divisible_by : int = 1) -> int:
24
+ """Convert units of measurement to stitches.
25
+
26
+ :param distance: A distance, measured in the unit of the swatch
27
+ """
28
+ if divisible_by != 1:
29
+ return divisible_by * self.units_to_stitches(distance / divisible_by)
30
+ return round(distance * self.stitches_per_unit)
31
+
32
+ def stitches_to_units(self, stitches : int) -> int:
33
+ """Convert stitches to units of measurement.
34
+
35
+ :param stitches: The number of stitches
36
+ """
37
+ return round(stitches / self.stitches_per_unit)
38
+
39
+ def units_to_rows(self,
40
+ distance : float,
41
+ divisible_by : int = 1) -> int:
42
+ """Convert units of measurement to rows"""
43
+ if divisible_by != 1:
44
+ return divisible_by * self.units_to_rows(distance / divisible_by)
45
+ return round(distance * self.rows_per_unit)
46
+
47
+ def rows_to_units(self, rows : int) -> int:
48
+ """Convert rows to units of measurement.
49
+
50
+ :param rows: The number of rows
51
+ """
52
+ return round(rows / self.rows_per_unit)
53
+
54
+ class MetricSwatch(Swatch):
55
+ def __init__(self, stitches_per_10_cm : int, rows_per_10_cm : Optional[int] = None):
56
+ """Initiate a metric swatch from row and stitch counts for a 10 cm swatch.
57
+ """
58
+ super().__init__(
59
+ 10,
60
+ "cm",
61
+ stitches_per_10_cm,
62
+ rows_per_10_cm
63
+ )
64
+
65
+ def cm_to_stitches(self, cm : float, divisible_by : int = 1) -> int:
66
+ """Convert measurements in cm to stitches
67
+
68
+ :param cm: The number of cm to convert
69
+ :param divisible_by: A number that the return value must be divisible by.
70
+ :return: A number of stitches
71
+ """
72
+ return self.units_to_stitches(cm, divisible_by)
73
+
74
+ def cm_to_rows(self, cm : float, divisible_by : int = 1) -> int:
75
+ """Convert measurements in cm to rows
76
+
77
+ :param cm: The number of cm to convert
78
+ :param divisible_by: A number that the return value must be divisible by.
79
+ :return: A number of rows
80
+ """
81
+ return self.units_to_rows(cm, divisible_by)
82
+
83
+
84
+ if __name__=="__main__":
85
+ print(MetricSwatch(29, 31).cm_to_stitches(10, 4))
86
+ print(MetricSwatch(29, 31).cm_to_rows(10, 4))
@@ -0,0 +1,96 @@
1
+ from typing import Callable
2
+ from yarnover.exceptions import PatternException
3
+
4
+ def not_negative(i : int):
5
+ if i < 0:
6
+ raise PatternException(f"Expecting a non-negative number, got {i}")
7
+ return i
8
+
9
+ def positive(i : int):
10
+ if i <= 0:
11
+ raise PatternException(f"Expecting a positive number, got {i}")
12
+ return i
13
+
14
+ def half(i):
15
+ result, remainder = divmod(i, 2)
16
+ if remainder:
17
+ raise PatternException(f"Expecting an even number, got {i}")
18
+ else:
19
+ return result
20
+
21
+ def decrease_pattern(from_stitches : int,
22
+ to_stitches : int,
23
+ over_rows,
24
+ odd_row_placement : str = "even"):
25
+ decreases = from_stitches - to_stitches
26
+ if decreases < 0:
27
+ raise PatternException()
28
+ decrease_pairs = half(decreases)
29
+ return split_number(over_rows, decrease_pairs, odd_row_placement)
30
+
31
+ def decrease(from_stitches : int,
32
+ to_stitches : int,
33
+ row_count : int,
34
+ f : Callable[[float], float]):
35
+ decreases = from_stitches - to_stitches
36
+ for i in range(row_count):
37
+ stitches = int(f(float(i) / decreases))
38
+ print(stitches)
39
+
40
+
41
+
42
+
43
+ def centered_split(total : int,
44
+ group_count : int,
45
+ group_size : int = 1,
46
+ ):
47
+ total_to_divide = total - group_count*group_size
48
+ space_count = group_count + 1
49
+ space_size, remainder = divmod(total_to_divide, space_count)
50
+ a = remainder // 2
51
+ b = remainder - a
52
+ return a, space_count-1, space_size, space_size+b
53
+
54
+ """
55
+ split n into a groups of (m+1) then b groups of m
56
+ a+b = g
57
+ """
58
+
59
+ def uncentered_split(total : int,
60
+ group_count : int,
61
+ group_size : int = 1,
62
+ ):
63
+ total_to_divide = total - group_count*group_size
64
+ space_count = group_count
65
+ space_size, remainder = divmod(total_to_divide, space_count)
66
+ large_spaces = remainder
67
+ small_spaces = space_count - remainder
68
+ return large_spaces, space_size+1, small_spaces, space_size
69
+
70
+
71
+ def split_number(number : int,
72
+ group_count : int,
73
+ remainder_placement : str = "even"):
74
+ if number < 0:
75
+ raise PatternException()
76
+ group_size, remainder = divmod(number, group_count)
77
+ groups = [group_size] * group_count
78
+ if remainder_placement == "top":
79
+ groups[0] += remainder
80
+ elif remainder_placement == "bottom":
81
+ groups[-1] += remainder
82
+ elif remainder_placement == "even":
83
+ for block_number in range(remainder):
84
+ groups[block_number] += 1
85
+ return groups
86
+
87
+ if __name__=="__main__":
88
+ w1 = 90
89
+ w2 = 46
90
+ rows = 116
91
+ decrease_pairs = half(w1-w2)
92
+ head, repeats, width, tail = centered_split(rows, decrease_pairs)
93
+ # print(f"{head} rows, then {repeats} times [{width} rows + decrease] + {tail}")
94
+ # print(decrease_pattern(w1, w2, rows))
95
+ # print(uncentered_split(rows, decrease_pairs))
96
+ decrease(100, 50, lambda x: 1.0-x)
@@ -0,0 +1,24 @@
1
+ Metadata-Version: 2.4
2
+ Name: yarnover
3
+ Version: 0.1
4
+ Summary: yarnover
5
+ Description-Content-Type: text/markdown
6
+
7
+
8
+ Pattern - represents a knitting pattern
9
+
10
+ `Pattern.cast_on(n)`
11
+
12
+ alias `co`
13
+
14
+ Cast on `n` stitches
15
+
16
+
17
+ `Work.work_stitch(
18
+ - name: The name or code of the stitch
19
+ - parent_count = 1: The number of stitches from the left needle that will be worked
20
+ - slip_parent_stitches = True: Indicates whether to slip the parent stitches
21
+ - add_stitch_to_right_needle = True: Indicates whether to add the new stitch to the right needle
22
+ - tags = []:
23
+
24
+ `Work.slip_stitch()
@@ -0,0 +1,12 @@
1
+ pyproject.toml
2
+ readme.md
3
+ src/yarnover/__init__.py
4
+ src/yarnover/exceptions.py
5
+ src/yarnover/needle.py
6
+ src/yarnover/pattern.py
7
+ src/yarnover/swatch.py
8
+ src/yarnover/util.py
9
+ src/yarnover.egg-info/PKG-INFO
10
+ src/yarnover.egg-info/SOURCES.txt
11
+ src/yarnover.egg-info/dependency_links.txt
12
+ src/yarnover.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ yarnover