Skip to content

Commit a7a2e8a

Browse files
committed
Implement Lens and CompoundLens.
1 parent a92e362 commit a7a2e8a

File tree

1 file changed

+74
-4
lines changed
  • python/selfie-lib/selfie_lib

1 file changed

+74
-4
lines changed

python/selfie-lib/selfie_lib/Lens.py

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,87 @@
1+
import re
12
from abc import ABC, abstractmethod
2-
from typing import Callable, Generic, TypeVar
3+
from typing import Callable, Generic, Iterator, List, Optional, Protocol, TypeVar
34

4-
from .Snapshot import Snapshot
5+
from .Snapshot import Snapshot, SnapshotValue
56

6-
T = TypeVar("T", contravariant=True)
7+
T = TypeVar("T")
8+
9+
10+
class Lens(Protocol[Snapshot]):
11+
def __call__(self, snapshot: Snapshot) -> Snapshot:
12+
raise NotImplementedError
13+
14+
15+
class CompoundLens(Lens):
16+
def __init__(self):
17+
self.lenses: List[Lens] = []
18+
19+
def __call__(self, snapshot: Snapshot) -> Snapshot:
20+
current = snapshot
21+
for lens in self.lenses:
22+
current = lens(current)
23+
return current
24+
25+
def add(self, lens: Lens) -> "CompoundLens":
26+
self.lenses.append(lens)
27+
return self
28+
29+
def mutate_all_facets(
30+
self, perString: Callable[[str], Optional[str]]
31+
) -> "CompoundLens":
32+
def _mutate_each(snapshot: Snapshot) -> Iterator[tuple[str, SnapshotValue]]:
33+
for entry in snapshot.items():
34+
if entry[1].is_binary:
35+
yield entry
36+
else:
37+
mapped = perString(entry[1].value_string())
38+
if mapped is not None:
39+
yield (entry[0], SnapshotValue.of(mapped))
40+
41+
return self.add(lambda snapshot: Snapshot.of_items(_mutate_each(snapshot)))
42+
43+
def replace_all(self, toReplace: str, replacement: str) -> "CompoundLens":
44+
return self.mutate_all_facets(lambda s: s.replace(toReplace, replacement))
45+
46+
def replace_all_regex(
47+
self, pattern: str | re.Pattern[str], replacement: str
48+
) -> "CompoundLens":
49+
return self.mutate_all_facets(lambda s: re.sub(pattern, replacement, s))
50+
51+
def set_facet_from(
52+
self, target: str, source: str, function: Callable[[str], Optional[str]]
53+
) -> "CompoundLens":
54+
def _set_facet_from(snapshot: Snapshot) -> Snapshot:
55+
source_value = snapshot.subject_or_facet_maybe(source)
56+
if source_value is None:
57+
return snapshot
58+
else:
59+
return self.__set_facet_of(
60+
snapshot, target, function(source_value.value_string())
61+
)
62+
63+
return self.add(_set_facet_from)
64+
65+
def __set_facet_of(
66+
self, snapshot: Snapshot, target: str, new_value: Optional[str]
67+
) -> Snapshot:
68+
if new_value is None:
69+
return snapshot
70+
else:
71+
return snapshot.plus_or_replace(target, SnapshotValue.of(new_value))
72+
73+
def mutate_facet(
74+
self, target: str, function: Callable[[str], Optional[str]]
75+
) -> "CompoundLens":
76+
return self.set_facet_from(target, target, function)
777

878

979
class Camera(Generic[T], ABC):
1080
@abstractmethod
1181
def snapshot(self, subject: T) -> Snapshot:
1282
pass
1383

14-
def with_lens(self, lens: Callable[[Snapshot], Snapshot]) -> "Camera[T]":
84+
def with_lens(self, lens: Lens) -> "Camera[T]":
1585
class WithLensCamera(Camera):
1686
def __init__(self, camera: Camera[T], lens: Callable[[Snapshot], Snapshot]):
1787
self.__camera = camera

0 commit comments

Comments
 (0)