Skip to content

Commit 08d77ef

Browse files
committed
feat: add difflib module to help kcl unit test cases
Signed-off-by: peefy <[email protected]>
1 parent 4124a1f commit 08d77ef

File tree

5 files changed

+220
-0
lines changed

5 files changed

+220
-0
lines changed

difflib/README.md

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
## Introduction
2+
3+
Module `difflib` -- helpers for computing deltas between objects.
4+
5+
## How to Use
6+
7+
+ Add the dependency
8+
9+
```shell
10+
kcl mod add difflib
11+
```
12+
13+
+ Write the code
14+
15+
```python
16+
import difflib
17+
import yaml
18+
19+
data1 = {
20+
"firstName": "John",
21+
"lastName": "Doe",
22+
"age": 30,
23+
"address": {
24+
"streetAddress": "1234 Main St",
25+
"city": "New York",
26+
"state": "NY",
27+
"postalCode": "10001"
28+
},
29+
"phoneNumbers": [
30+
{
31+
"type": "home",
32+
"number": "212-555-1234"
33+
},
34+
{
35+
"type": "work",
36+
"number": "646-555-5678"
37+
}
38+
]
39+
}
40+
data2 = {
41+
"firstName": "John",
42+
"lastName": "Doe",
43+
"age": 30,
44+
"address": {
45+
"streetAddress": "1234 Main St",
46+
"city": "New York",
47+
"state": "NY",
48+
"postalCode": None
49+
},
50+
"phoneNumbers": [
51+
{
52+
"type": "work",
53+
"number": "646-555-5678"
54+
}
55+
]
56+
}
57+
diff = difflib.diff(yaml.encode(data1), yaml.encode(data2))
58+
```
59+
60+
## Resource
61+
62+
The code source and documents are [here](https://github.com/kcl-lang/artifacthub/tree/main/difflib)

difflib/kcl.mod

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[package]
2+
name = "difflib"
3+
edition = "v0.9.0"
4+
version = "0.1.0"
5+

difflib/kcl.mod.lock

Whitespace-only changes.

difflib/main.k

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
"""Module difflib -- helpers for computing deltas between objects.
2+
"""
3+
4+
_looper_n = lambda elements: [any], n: int, func: (any, any) -> any, initial: any -> any {
5+
assert n >= 0
6+
result = initial
7+
if n < len(elements):
8+
result = _looper_n(elements, n + 1, func, func(result, elements[n]))
9+
10+
result
11+
}
12+
13+
looper = lambda initial: any, elements: [any], func: (any, any) -> any -> any {
14+
_looper_n(elements, 0, func, initial)
15+
}
16+
17+
looper_enumerate = lambda initial: any, elements: [any] | {str:}, func: (any, str | int, any) -> any -> any {
18+
looper(initial, [{"k" = k, "v" = v} for k, v in elements], lambda initial, value {
19+
func(initial, value.k, value.v)
20+
})
21+
}
22+
23+
for_each = lambda elements: [any], func: (any) {
24+
[func(i) for i in elements]
25+
Undefined
26+
}
27+
28+
while_loop = lambda condition: ([any]) -> bool, body: ([any]) -> [any], vals: [any] -> [any] {
29+
"""Do a while loop using the condition function, body function and variables with side effects that need to be modified in place, such as iteration variables, etc."""
30+
vals if not condition(vals) else while_loop(condition, body, body(vals))
31+
}
32+
33+
list_set_index = lambda l: [], i: int, v {
34+
"""Set the list `l` at index `i` with the value `v`"""
35+
l = l[:i:] + [v] + l[i + 1::]
36+
l
37+
}
38+
39+
longest_common_subsequence = lambda a: [], b: [] -> [] {
40+
"""Longest Common Subsequence (LCS) is a typical algorithm for calculating the length of the longest common subsequence between two sequences."""
41+
# Build the lengths matrix for dp
42+
lengths = [[0] * (len(b) + 1) for _ in range(len(a) + 1)]
43+
lengths = looper_enumerate(lengths, a, lambda m, i, x {
44+
looper_enumerate(m, b, lambda v, j, y {
45+
list_set_index(v, i + 1, list_set_index(v[i + 1], j + 1, v[i][j] + 1 if x == y else max(v[i + 1][j], v[i][j + 1])))
46+
})
47+
})
48+
vals = [len(a), len(b), []]
49+
# Read the substrings out from the matrix
50+
while_loop(lambda vals: [any] {
51+
vals[0] != 0 and vals[1] != 0
52+
}, lambda vals: [any] {
53+
x = vals[0]
54+
y = vals[1]
55+
result = vals[2]
56+
if lengths[x][y] == lengths[x - 1][y]:
57+
x -= 1
58+
elif lengths[x][y] == lengths[x][y - 1]:
59+
y -= 1
60+
else:
61+
assert a[x - 1] == b[y - 1], "{} != {}".format(a[x - 1], b[y - 1])
62+
result = [a[x - 1]] + result
63+
x -= 1
64+
y -= 1
65+
[x, y, result]
66+
}, vals)[-1]
67+
}
68+
69+
ndiff = lambda a: [str], b: [str] -> str {
70+
"""Compare a and b (lists of strings); return a Differ-style delta string."""
71+
lcs = longest_common_subsequence(a, b)
72+
# while loop variabels: [i, j, lcs, diff_str]
73+
vals = [0, 0, lcs, ""]
74+
len_a = len(a)
75+
len_b = len(b)
76+
while_loop(lambda vals {
77+
vals[0] < len_a or vals[1] < len_b
78+
}, lambda vals {
79+
i = vals[0]
80+
j = vals[1]
81+
lcs = vals[2]
82+
diff_str = vals[3]
83+
if i < len(a) and j < len(b) and a[i] == b[j]:
84+
diff_str += " " + a[i] + "\n"
85+
i += 1
86+
j += 1
87+
elif j < len(b) and (not lcs or i >= len(a) or a[i] != lcs[0]):
88+
diff_str += "+ " + b[j] + "\n"
89+
j += 1
90+
elif i < len(a) and (not lcs or j >= len(b) or b[j] != lcs[0]):
91+
diff_str += "- " + a[i] + "\n"
92+
i += 1
93+
else:
94+
if lcs:
95+
lcs = lcs[1:]
96+
if i < len(a):
97+
i += 1
98+
if j < len(b):
99+
j += 1
100+
101+
[i, j, lcs, diff_str]
102+
}, vals)[-1]
103+
}
104+
105+
diff = lambda a: str, b: str {
106+
"""Compare a and b (string type); return a Differ-style delta string."""
107+
ndiff(a.splitlines(), b.splitlines())
108+
}

difflib/main_test.k

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import yaml
2+
3+
schema Suite:
4+
a: str
5+
b: str
6+
result: str
7+
8+
test_longest_common_subsequence = lambda {
9+
cases: [Suite] = [
10+
Suite {
11+
a: "\n".join(["this", "is", "a", "example", "xxx"])
12+
b: "\n".join(["this", "is", "an", "example", "xxx"])
13+
result: """ this
14+
is
15+
+ an
16+
+ example
17+
+ xxx
18+
- a
19+
- example
20+
- xxx
21+
"""
22+
}
23+
Suite {
24+
a: yaml.encode({
25+
"a": 1
26+
"b": 2
27+
})
28+
b: yaml.encode({
29+
"a": 1
30+
"c": 1
31+
"d": 3
32+
"b": 2
33+
})
34+
result: """ a: 1
35+
+ c: 1
36+
+ d: 3
37+
b: 2
38+
"""
39+
}
40+
]
41+
for_each(cases, lambda case: Suite {
42+
result = diff(case.a, case.b)
43+
assert result == case.result, "expect ${case.result}, got ${result}"
44+
})
45+
}

0 commit comments

Comments
 (0)