gandora-std 0.1.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.
@@ -0,0 +1,222 @@
1
+ defmodule Enum do
2
+ @moduledoc "Eager, data-first collection functions (GEP-0010). Every subject comes first, so everything pipes."
3
+ @moduledoc_trans zh_CN: "急切求值、数据优先的集合函数(GEP-0010)。主语在首位,全部可入管道。"
4
+
5
+ @doc "Applies `f` to every element."
6
+ @doc_trans zh_CN: "对每个元素应用 `f`。"
7
+ @example """
8
+ gan> Enum.map([1, 2, 3], fn x -> x * 10 end)
9
+ [10, 20, 30]
10
+ """
11
+ def map(xs, f), do: :builtins.list(:builtins.map(f, xs))
12
+
13
+ @doc "Keeps elements for which `f` is truthy."
14
+ @doc_trans zh_CN: "保留 `f` 为真值的元素。"
15
+ @example """
16
+ gan> Enum.filter([1, 2, 3, 4], fn x -> rem(x, 2) == 0 end)
17
+ [2, 4]
18
+ """
19
+ def filter(xs, f), do: ~python([x for x in <%= xs %> if <%= f %>(x)])
20
+
21
+ @doc "Drops elements for which `f` is truthy."
22
+ @doc_trans zh_CN: "丢弃 `f` 为真值的元素。"
23
+ def reject(xs, f), do: ~python([x for x in <%= xs %> if not <%= f %>(x)])
24
+
25
+ @doc "Folds left with Elixir argument order: `f` receives (element, acc)."
26
+ @doc_trans zh_CN: "左折叠,参数顺序与 Elixir 一致:`f` 接收 (元素, 累积值)。"
27
+ @example """
28
+ gan> Enum.reduce([1, 2, 3, 4], 0, fn x, acc -> acc + x end)
29
+ 10
30
+ """
31
+ def reduce(xs, acc, f) do
32
+ :functools.reduce(fn a, x -> f.(x, a) end, xs, acc)
33
+ end
34
+
35
+ @doc "Sum of the elements."
36
+ @doc_trans zh_CN: "元素之和。"
37
+ def sum(xs), do: :builtins.sum(xs)
38
+
39
+ @doc "Number of elements."
40
+ @doc_trans zh_CN: "元素个数。"
41
+ def count(xs), do: :builtins.len(xs)
42
+
43
+ @doc "Ascending sort."
44
+ @doc_trans zh_CN: "升序排序。"
45
+ def sort(xs), do: :builtins.sorted(xs)
46
+
47
+ @doc "Sorts by the key computed by `f` (Python `key=`, not a comparator)."
48
+ @doc_trans zh_CN: "按 `f` 计算的键排序(Python 的 `key=`,非比较器)。"
49
+ @example """
50
+ gan> Enum.sort_by(["ccc", "a", "bb"], fn s -> String.length(s) end)
51
+ ['a', 'bb', 'ccc']
52
+ """
53
+ def sort_by(xs, f), do: :builtins.sorted(xs, key: f)
54
+
55
+ @doc "Elements in reverse order."
56
+ @doc_trans zh_CN: "逆序排列的元素。"
57
+ def reverse(xs), do: ~python(list(reversed(<%= xs %>)))
58
+
59
+ @doc "Joins elements (converted by `to_string`) with `sep`."
60
+ @doc_trans zh_CN: "以 `sep` 连接各元素(先经 `to_string` 转换)。"
61
+ @example """
62
+ gan> Enum.join([1, 2, 3], "-")
63
+ '1-2-3'
64
+ """
65
+ def join(xs, sep), do: sep.join(~python([str(x) for x in <%= xs %>]))
66
+
67
+ @doc "The element at `index` (negative counts from the end), or nil."
68
+ @doc_trans zh_CN: "位于 `index` 的元素(负数从尾部数起),越界返回 nil。"
69
+ def at(xs, index) do
70
+ if index < :builtins.len(xs) and index >= -:builtins.len(xs) do
71
+ ~python(<%= xs %>[<%= index %>])
72
+ else
73
+ nil
74
+ end
75
+ end
76
+
77
+ @doc "The first `n` elements."
78
+ @doc_trans zh_CN: "前 `n` 个元素。"
79
+ def take(xs, n), do: ~python(<%= xs %>[:<%= n %>])
80
+
81
+ @doc "The elements after the first `n`."
82
+ @doc_trans zh_CN: "跳过前 `n` 个之后的元素。"
83
+ def drop(xs, n), do: ~python(<%= xs %>[<%= n %>:])
84
+
85
+ @doc "Pairs up two lists into `{a, b}` tuples, stopping at the shorter."
86
+ @doc_trans zh_CN: "将两个列表配成 `{a, b}` 元组,止于较短者。"
87
+ def zip(xs, ys), do: :builtins.list(:builtins.zip(xs, ys))
88
+
89
+ @doc "Each element paired with its index: `{element, index}`."
90
+ @doc_trans zh_CN: "每个元素与其下标配对:`{元素, 下标}`。"
91
+ @example """
92
+ gan> Enum.with_index([:a, :b])
93
+ [('a', 0), ('b', 1)]
94
+ """
95
+ def with_index(xs), do: ~python([(x, i) for i, x in enumerate(<%= xs %>)])
96
+
97
+ @doc "Whether `x` is an element."
98
+ @doc_trans zh_CN: "`x` 是否为其中元素。"
99
+ def member?(xs, x), do: ~python(<%= x %> in <%= xs %>)
100
+
101
+ @doc "Whether `f` is truthy for every element."
102
+ @doc_trans zh_CN: "`f` 是否对所有元素为真值。"
103
+ def all?(xs, f), do: ~python(all(<%= f %>(x) for x in <%= xs %>))
104
+
105
+ @doc "Whether `f` is truthy for any element."
106
+ @doc_trans zh_CN: "`f` 是否对任一元素为真值。"
107
+ def any?(xs, f), do: ~python(any(<%= f %>(x) for x in <%= xs %>))
108
+
109
+ @doc "Whether the collection has no elements."
110
+ @doc_trans zh_CN: "集合是否为空。"
111
+ def empty?(xs), do: :builtins.len(xs) == 0
112
+
113
+ @doc "Removes duplicates, keeping first occurrences in order."
114
+ @doc_trans zh_CN: "去重,按序保留首次出现者。"
115
+ def uniq(xs), do: ~python(list(dict.fromkeys(<%= xs %>)))
116
+
117
+ @doc "Maps `f` (which returns a list) and concatenates the results."
118
+ @doc_trans zh_CN: "对每个元素应用返回列表的 `f`,并拼接全部结果。"
119
+ @example """
120
+ gan> Enum.flat_map([1, 2], fn x -> [x, x * 10] end)
121
+ [1, 10, 2, 20]
122
+ """
123
+ def flat_map(xs, f), do: ~python([y for x in <%= xs %> for y in <%= f %>(x)])
124
+
125
+ @doc "Runs `f` on each element for its side effects; returns :ok."
126
+ @doc_trans zh_CN: "对每个元素执行 `f`(取其副作用);返回 :ok。"
127
+ def each(xs, f) do
128
+ ~python([<%= f %>(x) for x in <%= xs %>])
129
+ :ok
130
+ end
131
+
132
+ @doc "The smallest element."
133
+ @doc_trans zh_CN: "最小元素。"
134
+ def min(xs), do: :builtins.min(xs)
135
+
136
+ @doc "The largest element."
137
+ @doc_trans zh_CN: "最大元素。"
138
+ def max(xs), do: :builtins.max(xs)
139
+ @doc "The first element for which `f` is truthy, or nil."
140
+ @doc_trans zh_CN: "首个使 `f` 为真值的元素,无则 nil。"
141
+ @example """
142
+ gan> Enum.find([1, 2, 3, 4], fn x -> x > 2 end)
143
+ 3
144
+ """
145
+ def find(xs, f), do: ~python(next((x for x in <%= xs %> if <%= f %>(x)), None))
146
+
147
+ @doc "The index of the first element for which `f` is truthy, or nil."
148
+ @doc_trans zh_CN: "首个使 `f` 为真值的元素下标,无则 nil。"
149
+ def find_index(xs, f),
150
+ do: ~python(next((i for i, x in enumerate(<%= xs %>) if <%= f %>(x)), None))
151
+
152
+ @doc "A map from each distinct element to its occurrence count."
153
+ @doc_trans zh_CN: "从每个不同元素到其出现次数的映射。"
154
+ @example """
155
+ gan> Enum.frequencies(["a", "b", "a"])
156
+ {'a': 2, 'b': 1}
157
+ """
158
+ def frequencies(xs), do: :builtins.dict(:collections.Counter(xs))
159
+
160
+ @doc "Groups elements by the key computed by `f`, preserving order."
161
+ @doc_trans zh_CN: "按 `f` 计算的键分组,保持顺序。"
162
+ @example """
163
+ gan> Enum.group_by([1, 2, 3, 4, 5], fn x -> rem(x, 2) end)
164
+ {1: [1, 3, 5], 0: [2, 4]}
165
+ """
166
+ def group_by(xs, f),
167
+ do: ~python({k: [x for x in <%= xs %> if <%= f %>(x) == k] for k in dict.fromkeys(<%= f %>(x) for x in <%= xs %>)})
168
+
169
+ @doc "The element maximizing the key computed by `f`; raises on empty."
170
+ @doc_trans zh_CN: "使 `f` 计算的键最大的元素;空集合抛错。"
171
+ def max_by(xs, f), do: :builtins.max(xs, key: f)
172
+
173
+ @doc "The element minimizing the key computed by `f`; raises on empty."
174
+ @doc_trans zh_CN: "使 `f` 计算的键最小的元素;空集合抛错。"
175
+ def min_by(xs, f), do: :builtins.min(xs, key: f)
176
+
177
+ @doc "The product of the elements (1 for an empty collection)."
178
+ @doc_trans zh_CN: "元素之积(空集合为 1)。"
179
+ def product(xs), do: :math.prod(xs)
180
+
181
+ @doc "Leading elements while `f` stays truthy."
182
+ @doc_trans zh_CN: "自头部起 `f` 持续为真值的元素。"
183
+ def take_while(xs, f), do: :builtins.list(:itertools.takewhile(f, xs))
184
+
185
+ @doc "Drops leading elements while `f` stays truthy, keeps the rest."
186
+ @doc_trans zh_CN: "丢弃头部 `f` 持续为真值的元素,保留其余。"
187
+ def drop_while(xs, f), do: :builtins.list(:itertools.dropwhile(f, xs))
188
+
189
+ @doc "Splits into chunks of `n` elements; the last chunk may be shorter."
190
+ @doc_trans zh_CN: "按每组 `n` 个元素分块;末组可较短。"
191
+ @example """
192
+ gan> Enum.chunk_every([1, 2, 3, 4, 5], 2)
193
+ [[1, 2], [3, 4], [5]]
194
+ """
195
+ def chunk_every(xs, n),
196
+ do: ~python([<%= xs %>[i:i + <%= n %>] for i in range(0, len(<%= xs %>), <%= n %>)])
197
+
198
+ @doc "Concatenates a list of lists (one level)."
199
+ @doc_trans zh_CN: "拼接列表的列表(单层)。"
200
+ def concat(xss), do: ~python([y for x in <%= xss %> for y in x])
201
+
202
+ @doc "Puts `sep` between every two elements."
203
+ @doc_trans zh_CN: "在每两个元素之间放入 `sep`。"
204
+ @example """
205
+ gan> Enum.intersperse([1, 2, 3], :x)
206
+ [1, 'x', 2, 'x', 3]
207
+ """
208
+ def intersperse(xs, sep), do: ~python([v for x in <%= xs %> for v in (x, <%= sep %>)][:-1])
209
+
210
+ @doc "`count` elements starting at `start` (negative start counts from the end)."
211
+ @doc_trans zh_CN: "自 `start` 起的 `count` 个元素(负的 start 从尾部数起)。"
212
+ def slice(xs, start, count), do: ~python(<%= xs %>[<%= start %>:] [:<%= count %>])
213
+
214
+ @doc "Collapses consecutive duplicate elements."
215
+ @doc_trans zh_CN: "折叠连续的重复元素。"
216
+ @example """
217
+ gan> Enum.dedup([1, 1, 2, 2, 2, 1])
218
+ [1, 2, 1]
219
+ """
220
+ def dedup(xs),
221
+ do: ~python([x for i, x in enumerate(<%= xs %>) if i == 0 or x != <%= xs %>[i - 1]])
222
+ end
@@ -0,0 +1,43 @@
1
+ defmodule Keyword do
2
+ @moduledoc "Functions over keyword lists — lists of {atom, value} tuples (GEP-0010)."
3
+ @moduledoc_trans zh_CN: "关键字列表({原子, 值} 元组的列表)上的函数(GEP-0010)。"
4
+
5
+ @doc "The first value for `key`, or nil."
6
+ @doc_trans zh_CN: "键 `key` 的首个值,不存在时为 nil。"
7
+ @example """
8
+ gan> Keyword.get([a: 1, b: 2], :a)
9
+ 1
10
+ """
11
+ def get(kw, key), do: ~python(next((v for k, v in <%= kw %> if k == <%= key %>), None))
12
+
13
+ @doc "Replaces `key` with `value`, prepending it (Elixir Keyword.put)."
14
+ @doc_trans zh_CN: "以 `value` 替换 `key` 并将其前置(Elixir 的 Keyword.put)。"
15
+ def put(kw, key, value) do
16
+ [{key, value}] ++ Enum.reject(kw, fn {k, _} -> k == key end)
17
+ end
18
+
19
+ @doc "The keys, in order, duplicates included."
20
+ @doc_trans zh_CN: "按序的全部键(含重复)。"
21
+ def keys(kw), do: Enum.map(kw, fn {k, _} -> k end)
22
+
23
+ @doc "The values, in order."
24
+ @doc_trans zh_CN: "按序的全部值。"
25
+ def values(kw), do: Enum.map(kw, fn {_, v} -> v end)
26
+
27
+ @doc "Whether `key` is present."
28
+ @doc_trans zh_CN: "是否存在键 `key`。"
29
+ def has_key?(kw, key), do: Enum.any?(kw, fn {k, _} -> k == key end)
30
+ @doc "Removes every entry for `key`."
31
+ @doc_trans zh_CN: "移除键为 `key` 的全部条目。"
32
+ def delete(kw, key), do: Enum.reject(kw, fn {k, _} -> k == key end)
33
+
34
+ @doc "Merges `kw2` into `kw1`; `kw2` wins, its entries appended."
35
+ @doc_trans zh_CN: "将 `kw2` 并入 `kw1`;`kw2` 胜出,其条目附加在后。"
36
+ @example """
37
+ gan> Keyword.merge([a: 1, b: 2], [b: 9, c: 3])
38
+ [('a', 1), ('b', 9), ('c', 3)]
39
+ """
40
+ def merge(kw1, kw2) do
41
+ Enum.reject(kw1, fn {k, _} -> has_key?(kw2, k) end) ++ kw2
42
+ end
43
+ end
@@ -0,0 +1,72 @@
1
+ defmodule List do
2
+ @moduledoc "List-shape helpers; element-wise work lives in Enum (GEP-0010)."
3
+ @moduledoc_trans zh_CN: "列表形状辅助函数;逐元素操作在 Enum 中(GEP-0010)。"
4
+
5
+ @doc "The first element, or nil for an empty list."
6
+ @doc_trans zh_CN: "首元素,空列表为 nil。"
7
+ def first(xs), do: Enum.at(xs, 0)
8
+
9
+ @doc "The last element, or nil for an empty list."
10
+ @doc_trans zh_CN: "末元素,空列表为 nil。"
11
+ def last(xs), do: Enum.at(xs, -1)
12
+
13
+ @doc "Flattens nested lists to any depth."
14
+ @doc_trans zh_CN: "任意深度展平嵌套列表。"
15
+ @example """
16
+ gan> List.flatten([1, [2, [3, 4]], 5])
17
+ [1, 2, 3, 4, 5]
18
+ """
19
+ def flatten(xs) do
20
+ Enum.flat_map(xs, fn x ->
21
+ if is_list(x) do
22
+ flatten(x)
23
+ else
24
+ [x]
25
+ end
26
+ end)
27
+ end
28
+
29
+ @doc "Wraps a value in a list: nil becomes [], lists pass through, anything else becomes [value]."
30
+ @doc_trans zh_CN: "包装为列表:nil 变 [],列表原样返回,其余变 [值]。"
31
+ def wrap(value) do
32
+ cond do
33
+ is_nil(value) -> []
34
+ is_list(value) -> value
35
+ true -> [value]
36
+ end
37
+ end
38
+
39
+ @doc "A list of `n` copies of `value`."
40
+ @doc_trans zh_CN: "由 `n` 个 `value` 构成的列表。"
41
+ def duplicate(value, n), do: ~python([<%= value %>] * <%= n %>)
42
+
43
+ @doc "Inserts `value` at `index` (negative counts from the end, as in Elixir)."
44
+ @doc_trans zh_CN: "在 `index` 处插入 `value`(负数从尾部数起,同 Elixir)。"
45
+ def insert_at(xs, index, value),
46
+ do: ~python(<%= xs %>[:<%= index %>] + [<%= value %>] + <%= xs %>[<%= index %>:])
47
+
48
+ @doc "Removes the element at `index`; out-of-range leaves the list unchanged."
49
+ @doc_trans zh_CN: "删除 `index` 处的元素;越界时列表原样返回。"
50
+ def delete_at(xs, index),
51
+ do: ~python(<%= xs %>[:<%= index %>] + <%= xs %>[<%= index %>:][1:])
52
+ @doc "The list as a tuple."
53
+ @doc_trans zh_CN: "转换为元组的列表。"
54
+ def to_tuple(xs), do: :builtins.tuple(xs)
55
+
56
+ @doc "Whether the list starts with `prefix`."
57
+ @doc_trans zh_CN: "是否以 `prefix` 开头。"
58
+ def starts_with?(xs, prefix), do: ~python(<%= xs %>[:len(<%= prefix %>)] == <%= prefix %>)
59
+
60
+ @doc "Replaces the element at `index` (negative counts from the end)."
61
+ @doc_trans zh_CN: "替换 `index` 处的元素(负数从尾部数起)。"
62
+ def replace_at(xs, index, value),
63
+ do: ~python(<%= xs %>[:<%= index %>] + [<%= value %>] + <%= xs %>[<%= index %>:][1:])
64
+
65
+ @doc "Updates the element at `index` by `f`."
66
+ @doc_trans zh_CN: "以 `f` 更新 `index` 处的元素。"
67
+ @example """
68
+ gan> List.update_at([1, 2, 3], 1, fn v -> v * 10 end)
69
+ [1, 20, 3]
70
+ """
71
+ def update_at(xs, index, f), do: replace_at(xs, index, f.(Enum.at(xs, index)))
72
+ end
@@ -0,0 +1,91 @@
1
+ defmodule Map do
2
+ @moduledoc "Data-first dict functions; all updates return new maps (GEP-0010)."
3
+ @moduledoc_trans zh_CN: "数据优先的 dict 函数;所有更新都返回新映射(GEP-0010)。"
4
+
5
+ @doc "The value for `key`, or `default` (nil unless given) when absent."
6
+ @doc_trans zh_CN: "键 `key` 的值;不存在时为 `default`(未给出则为 nil)。"
7
+ @example """
8
+ gan> Map.get(%{a: 1}, :b, 0)
9
+ 0
10
+ """
11
+ def get(m, key, default \\ nil), do: m.get(key, default)
12
+
13
+ @doc "A new map with `key` set to `value`."
14
+ @doc_trans zh_CN: "设置 `key` 为 `value` 后的新映射。"
15
+ @example """
16
+ gan> Map.put(%{a: 1}, :b, 2)
17
+ {'a': 1, 'b': 2}
18
+ """
19
+ def put(m, key, value), do: ~python({**<%= m %>, <%= key %>: <%= value %>})
20
+
21
+ @doc "A new map without `key`."
22
+ @doc_trans zh_CN: "移除 `key` 后的新映射。"
23
+ def delete(m, key), do: ~python({k: v for k, v in <%= m %>.items() if k != <%= key %>})
24
+
25
+ @doc "The keys as a list."
26
+ @doc_trans zh_CN: "键的列表。"
27
+ def keys(m), do: :builtins.list(m.keys())
28
+
29
+ @doc "The values as a list."
30
+ @doc_trans zh_CN: "值的列表。"
31
+ def values(m), do: :builtins.list(m.values())
32
+
33
+ @doc "Merges `m2` into `m1`; `m2` wins on conflicts."
34
+ @doc_trans zh_CN: "将 `m2` 并入 `m1`;冲突时 `m2` 胜出。"
35
+ def merge(m1, m2), do: ~python({**<%= m1 %>, **<%= m2 %>})
36
+
37
+ @doc "Whether `key` is present."
38
+ @doc_trans zh_CN: "是否存在键 `key`。"
39
+ def has_key?(m, key), do: ~python(<%= key %> in <%= m %>)
40
+
41
+ @doc "The entries as a list of `{key, value}` tuples."
42
+ @doc_trans zh_CN: "以 `{键, 值}` 元组列表表示的条目。"
43
+ def to_list(m), do: :builtins.list(m.items())
44
+
45
+ @doc "An empty map."
46
+ @doc_trans zh_CN: "空映射。"
47
+ def new(), do: %{}
48
+
49
+ @doc "Updates `key` by `f`; uses `default` when the key is absent (Elixir Map.update/4)."
50
+ @doc_trans zh_CN: "以 `f` 更新 `key`;键不存在时使用 `default`(Elixir 的 Map.update/4)。"
51
+ @example """
52
+ gan> Map.update(%{a: 1}, :a, 0, fn v -> v + 10 end)
53
+ {'a': 11}
54
+ """
55
+ def update(m, key, default, f) do
56
+ if has_key?(m, key) do
57
+ put(m, key, f.(get(m, key)))
58
+ else
59
+ put(m, key, default)
60
+ end
61
+ end
62
+ @doc "The value for `key`; raises Python KeyError when absent."
63
+ @doc_trans zh_CN: "键 `key` 的值;不存在时抛 Python KeyError。"
64
+ def fetch!(m, key), do: ~python(<%= m %>[<%= key %>])
65
+
66
+ @doc "Sets `key` only when absent."
67
+ @doc_trans zh_CN: "仅当 `key` 不存在时设置。"
68
+ def put_new(m, key, value) do
69
+ if has_key?(m, key) do
70
+ m
71
+ else
72
+ put(m, key, value)
73
+ end
74
+ end
75
+
76
+ @doc "The submap with only `keys` (missing keys ignored)."
77
+ @doc_trans zh_CN: "仅含 `keys` 的子映射(缺失的键忽略)。"
78
+ def take(m, keys), do: ~python({k: v for k, v in <%= m %>.items() if k in <%= keys %>})
79
+
80
+ @doc "The map without `keys`."
81
+ @doc_trans zh_CN: "移除 `keys` 后的映射。"
82
+ def drop(m, keys), do: ~python({k: v for k, v in <%= m %>.items() if k not in <%= keys %>})
83
+
84
+ @doc "Keeps entries for which `f` (receiving a `{key, value}` tuple) is truthy."
85
+ @doc_trans zh_CN: "保留使 `f`(接收 `{键, 值}` 元组)为真值的条目。"
86
+ @example """
87
+ gan> Map.filter(%{"a" => 1, "b" => 5}, fn pair -> elem(pair, 1) > 2 end)
88
+ {'b': 5}
89
+ """
90
+ def filter(m, f), do: ~python({k: v for k, v in <%= m %>.items() if <%= f %>((k, v))})
91
+ end
@@ -0,0 +1,117 @@
1
+ defmodule String do
2
+ @moduledoc "Data-first string functions over Python str (GEP-0010)."
3
+ @moduledoc_trans zh_CN: "基于 Python str 的数据优先字符串函数(GEP-0010)。"
4
+
5
+ @doc "Uppercases the string."
6
+ @doc_trans zh_CN: "转为大写。"
7
+ def upcase(s), do: s.upper()
8
+
9
+ @doc "Lowercases the string."
10
+ @doc_trans zh_CN: "转为小写。"
11
+ def downcase(s), do: s.lower()
12
+
13
+ @doc "Uppercases the first character, lowercases the rest."
14
+ @doc_trans zh_CN: "首字符大写,其余小写。"
15
+ def capitalize(s), do: s.capitalize()
16
+
17
+ @doc "Splits on whitespace runs, dropping empty parts (Elixir semantics)."
18
+ @doc_trans zh_CN: "按连续空白切分并丢弃空段(Elixir 语义)。"
19
+ @example """
20
+ gan> String.split(" a b c ")
21
+ ['a', 'b', 'c']
22
+ """
23
+ def split(s), do: s.split()
24
+
25
+ @doc "Splits on the separator `sep`."
26
+ @doc_trans zh_CN: "按分隔符 `sep` 切分。"
27
+ def split_on(s, sep), do: s.split(sep)
28
+
29
+ @doc "Removes leading and trailing whitespace."
30
+ @doc_trans zh_CN: "去除首尾空白。"
31
+ def trim(s), do: s.strip()
32
+
33
+ @doc "Replaces every occurrence of `pattern` with `replacement`."
34
+ @doc_trans zh_CN: "将每处 `pattern` 替换为 `replacement`。"
35
+ def replace(s, pattern, replacement), do: s.replace(pattern, replacement)
36
+
37
+ @doc "Whether the string contains `part`."
38
+ @doc_trans zh_CN: "是否包含子串 `part`。"
39
+ def contains?(s, part), do: ~python(<%= part %> in <%= s %>)
40
+
41
+ @doc "Whether the string starts with `prefix`."
42
+ @doc_trans zh_CN: "是否以 `prefix` 开头。"
43
+ def starts_with?(s, prefix), do: s.startswith(prefix)
44
+
45
+ @doc "Whether the string ends with `suffix`."
46
+ @doc_trans zh_CN: "是否以 `suffix` 结尾。"
47
+ def ends_with?(s, suffix), do: s.endswith(suffix)
48
+
49
+ @doc "The number of characters (Unicode code points, Python `len`)."
50
+ @doc_trans zh_CN: "字符数(Unicode 码点,即 Python 的 `len`)。"
51
+ def length(s), do: :builtins.len(s)
52
+
53
+ @doc "The substring of `len` characters starting at `start` (negative start counts from the end)."
54
+ @doc_trans zh_CN: "自 `start` 起、长 `len` 的子串(负的 start 从尾部数起)。"
55
+ @example """
56
+ gan> String.slice("gandora", 3, 4)
57
+ 'dora'
58
+ """
59
+ def slice(s, start, len), do: ~python(<%= s %>[<%= start %>:] [:<%= len %>])
60
+
61
+ @doc "Pads on the left with spaces to `width`."
62
+ @doc_trans zh_CN: "左侧以空格填充至 `width`。"
63
+ def pad_leading(s, width), do: s.rjust(width)
64
+
65
+ @doc "Pads on the right with spaces to `width`."
66
+ @doc_trans zh_CN: "右侧以空格填充至 `width`。"
67
+ def pad_trailing(s, width), do: s.ljust(width)
68
+
69
+ @doc "Parses an integer; raises Python ValueError on bad input."
70
+ @doc_trans zh_CN: "解析整数;非法输入抛 Python ValueError。"
71
+ def to_integer(s), do: :builtins.int(s)
72
+
73
+ @doc "Parses a float; raises Python ValueError on bad input."
74
+ @doc_trans zh_CN: "解析浮点数;非法输入抛 Python ValueError。"
75
+ def to_float(s), do: :builtins.float(s)
76
+ @doc "The character at `index` (negative counts from the end), or nil."
77
+ @doc_trans zh_CN: "位于 `index` 的字符(负数从尾部数起),越界为 nil。"
78
+ def at(s, index) do
79
+ if index < :builtins.len(s) and index >= -:builtins.len(s) do
80
+ ~python(<%= s %>[<%= index %>])
81
+ else
82
+ nil
83
+ end
84
+ end
85
+
86
+ @doc "The string reversed."
87
+ @doc_trans zh_CN: "反转后的字符串。"
88
+ def reverse(s), do: ~python(<%= s %>[::-1])
89
+
90
+ @doc "The string repeated `n` times."
91
+ @doc_trans zh_CN: "重复 `n` 次的字符串。"
92
+ def duplicate(s, n), do: ~python(<%= s %> * <%= n %>)
93
+
94
+ @doc "Removes leading whitespace."
95
+ @doc_trans zh_CN: "去除首部空白。"
96
+ def trim_leading(s), do: s.lstrip()
97
+
98
+ @doc "Removes trailing whitespace."
99
+ @doc_trans zh_CN: "去除尾部空白。"
100
+ def trim_trailing(s), do: s.rstrip()
101
+
102
+ @doc "The characters as a list of one-character strings."
103
+ @doc_trans zh_CN: "以单字符字符串列表表示的全部字符。"
104
+ @example """
105
+ gan> String.codepoints("héllo") |> Enum.take(2)
106
+ ['h', 'é']
107
+ """
108
+ def codepoints(s), do: :builtins.list(s)
109
+
110
+ @doc "Whether the compiled regex (`~r/.../`) matches anywhere in the string."
111
+ @doc_trans zh_CN: "编译后的正则(`~r/.../`)是否在字符串中命中。"
112
+ @example """
113
+ gan> String.match?("gandora-2026", ~r/\\d+/)
114
+ True
115
+ """
116
+ def match?(s, regex), do: not is_nil(regex.search(s))
117
+ end
gandora_std/enum.py ADDED
@@ -0,0 +1,278 @@
1
+ """Eager, data-first collection functions (GEP-0010). Every subject comes first, so everything pipes."""
2
+
3
+ import builtins
4
+ import collections
5
+ import functools
6
+ import itertools
7
+ import math
8
+ import gandora_std.string
9
+
10
+
11
+ def _gan_div(a, b):
12
+ q = a // b
13
+ if q < 0 and q * b != a:
14
+ q += 1
15
+ return q
16
+
17
+
18
+ def _gan_rem(a, b):
19
+ return a - _gan_div(a, b) * b
20
+
21
+
22
+ def map(xs, f):
23
+ """Applies `f` to every element.
24
+
25
+
26
+ >>> map([1, 2, 3], lambda x: x * 10)
27
+ [10, 20, 30]
28
+ """
29
+ return builtins.list(builtins.map(f, xs))
30
+
31
+
32
+ def filter(xs, f):
33
+ """Keeps elements for which `f` is truthy.
34
+
35
+
36
+ >>> filter([1, 2, 3, 4], lambda x: _gan_rem(x, 2) == 0)
37
+ [2, 4]
38
+ """
39
+ return ([x for x in (xs) if (f)(x)])
40
+
41
+
42
+ def reject(xs, f):
43
+ """Drops elements for which `f` is truthy."""
44
+ return ([x for x in (xs) if not (f)(x)])
45
+
46
+
47
+ def reduce(xs, acc, f):
48
+ """Folds left with Elixir argument order: `f` receives (element, acc).
49
+
50
+
51
+ >>> reduce([1, 2, 3, 4], 0, lambda x, acc: acc + x)
52
+ 10
53
+ """
54
+ return functools.reduce(lambda a, x: f(x, a), xs, acc)
55
+
56
+
57
+ def sum(xs):
58
+ """Sum of the elements."""
59
+ return builtins.sum(xs)
60
+
61
+
62
+ def count(xs):
63
+ """Number of elements."""
64
+ return builtins.len(xs)
65
+
66
+
67
+ def sort(xs):
68
+ """Ascending sort."""
69
+ return builtins.sorted(xs)
70
+
71
+
72
+ def sort_by(xs, f):
73
+ """Sorts by the key computed by `f` (Python `key=`, not a comparator).
74
+
75
+
76
+ >>> sort_by(["ccc", "a", "bb"], lambda s: gandora_std.string.length(s))
77
+ ['a', 'bb', 'ccc']
78
+ """
79
+ return builtins.sorted(xs, key=f)
80
+
81
+
82
+ def reverse(xs):
83
+ """Elements in reverse order."""
84
+ return (list(reversed((xs))))
85
+
86
+
87
+ def join(xs, sep):
88
+ """Joins elements (converted by `to_string`) with `sep`.
89
+
90
+
91
+ >>> join([1, 2, 3], "-")
92
+ '1-2-3'
93
+ """
94
+ return sep.join(([str(x) for x in (xs)]))
95
+
96
+
97
+ def at(xs, index):
98
+ """The element at `index` (negative counts from the end), or nil."""
99
+ if (index < builtins.len(xs)) and (index >= -(builtins.len(xs))):
100
+ return ((xs)[(index)])
101
+ else:
102
+ return None
103
+
104
+
105
+ def take(xs, n):
106
+ """The first `n` elements."""
107
+ return ((xs)[:(n)])
108
+
109
+
110
+ def drop(xs, n):
111
+ """The elements after the first `n`."""
112
+ return ((xs)[(n):])
113
+
114
+
115
+ def zip(xs, ys):
116
+ """Pairs up two lists into `{a, b}` tuples, stopping at the shorter."""
117
+ return builtins.list(builtins.zip(xs, ys))
118
+
119
+
120
+ def with_index(xs):
121
+ """Each element paired with its index: `{element, index}`.
122
+
123
+
124
+ >>> with_index(["a", "b"])
125
+ [('a', 0), ('b', 1)]
126
+ """
127
+ return ([(x, i) for i, x in enumerate((xs))])
128
+
129
+
130
+ def member_p(xs, x):
131
+ """Whether `x` is an element."""
132
+ return ((x) in (xs))
133
+
134
+
135
+ def all_p(xs, f):
136
+ """Whether `f` is truthy for every element."""
137
+ return (all((f)(x) for x in (xs)))
138
+
139
+
140
+ def any_p(xs, f):
141
+ """Whether `f` is truthy for any element."""
142
+ return (any((f)(x) for x in (xs)))
143
+
144
+
145
+ def empty_p(xs):
146
+ """Whether the collection has no elements."""
147
+ return builtins.len(xs) == 0
148
+
149
+
150
+ def uniq(xs):
151
+ """Removes duplicates, keeping first occurrences in order."""
152
+ return (list(dict.fromkeys((xs))))
153
+
154
+
155
+ def flat_map(xs, f):
156
+ """Maps `f` (which returns a list) and concatenates the results.
157
+
158
+
159
+ >>> flat_map([1, 2], lambda x: [x, x * 10])
160
+ [1, 10, 2, 20]
161
+ """
162
+ return ([y for x in (xs) for y in (f)(x)])
163
+
164
+
165
+ def each(xs, f):
166
+ """Runs `f` on each element for its side effects; returns :ok."""
167
+ ([(f)(x) for x in (xs)])
168
+ return "ok"
169
+
170
+
171
+ def min(xs):
172
+ """The smallest element."""
173
+ return builtins.min(xs)
174
+
175
+
176
+ def max(xs):
177
+ """The largest element."""
178
+ return builtins.max(xs)
179
+
180
+
181
+ def find(xs, f):
182
+ """The first element for which `f` is truthy, or nil.
183
+
184
+
185
+ >>> find([1, 2, 3, 4], lambda x: x > 2)
186
+ 3
187
+ """
188
+ return (next((x for x in (xs) if (f)(x)), None))
189
+
190
+
191
+ def find_index(xs, f):
192
+ """The index of the first element for which `f` is truthy, or nil."""
193
+ return (next((i for i, x in enumerate((xs)) if (f)(x)), None))
194
+
195
+
196
+ def frequencies(xs):
197
+ """A map from each distinct element to its occurrence count.
198
+
199
+
200
+ >>> frequencies(["a", "b", "a"])
201
+ {'a': 2, 'b': 1}
202
+ """
203
+ return builtins.dict(collections.Counter(xs))
204
+
205
+
206
+ def group_by(xs, f):
207
+ """Groups elements by the key computed by `f`, preserving order.
208
+
209
+
210
+ >>> group_by([1, 2, 3, 4, 5], lambda x: _gan_rem(x, 2))
211
+ {1: [1, 3, 5], 0: [2, 4]}
212
+ """
213
+ return ({k: [x for x in (xs) if (f)(x) == k] for k in dict.fromkeys((f)(x) for x in (xs))})
214
+
215
+
216
+ def max_by(xs, f):
217
+ """The element maximizing the key computed by `f`; raises on empty."""
218
+ return builtins.max(xs, key=f)
219
+
220
+
221
+ def min_by(xs, f):
222
+ """The element minimizing the key computed by `f`; raises on empty."""
223
+ return builtins.min(xs, key=f)
224
+
225
+
226
+ def product(xs):
227
+ """The product of the elements (1 for an empty collection)."""
228
+ return math.prod(xs)
229
+
230
+
231
+ def take_while(xs, f):
232
+ """Leading elements while `f` stays truthy."""
233
+ return builtins.list(itertools.takewhile(f, xs))
234
+
235
+
236
+ def drop_while(xs, f):
237
+ """Drops leading elements while `f` stays truthy, keeps the rest."""
238
+ return builtins.list(itertools.dropwhile(f, xs))
239
+
240
+
241
+ def chunk_every(xs, n):
242
+ """Splits into chunks of `n` elements; the last chunk may be shorter.
243
+
244
+
245
+ >>> chunk_every([1, 2, 3, 4, 5], 2)
246
+ [[1, 2], [3, 4], [5]]
247
+ """
248
+ return ([(xs)[i:i + (n)] for i in range(0, len((xs)), (n))])
249
+
250
+
251
+ def concat(xss):
252
+ """Concatenates a list of lists (one level)."""
253
+ return ([y for x in (xss) for y in x])
254
+
255
+
256
+ def intersperse(xs, sep):
257
+ """Puts `sep` between every two elements.
258
+
259
+
260
+ >>> intersperse([1, 2, 3], "x")
261
+ [1, 'x', 2, 'x', 3]
262
+ """
263
+ return ([v for x in (xs) for v in (x, (sep))][:-1])
264
+
265
+
266
+ def slice(xs, start, count):
267
+ """`count` elements starting at `start` (negative start counts from the end)."""
268
+ return ((xs)[(start):] [:(count)])
269
+
270
+
271
+ def dedup(xs):
272
+ """Collapses consecutive duplicate elements.
273
+
274
+
275
+ >>> dedup([1, 1, 2, 2, 2, 1])
276
+ [1, 2, 1]
277
+ """
278
+ return ([x for i, x in enumerate((xs)) if i == 0 or x != (xs)[i - 1]])
@@ -0,0 +1,27 @@
1
+ schema = 1
2
+ compiler = "0.1.0"
3
+
4
+ [[modules]]
5
+ name = "Enum"
6
+ python = "gandora_std/enum.py"
7
+ source = "gandora_std/_gan/gandora_std/enum.gan"
8
+
9
+ [[modules]]
10
+ name = "Keyword"
11
+ python = "gandora_std/keyword.py"
12
+ source = "gandora_std/_gan/gandora_std/keyword.gan"
13
+
14
+ [[modules]]
15
+ name = "List"
16
+ python = "gandora_std/list.py"
17
+ source = "gandora_std/_gan/gandora_std/list.gan"
18
+
19
+ [[modules]]
20
+ name = "Map"
21
+ python = "gandora_std/map.py"
22
+ source = "gandora_std/_gan/gandora_std/map.gan"
23
+
24
+ [[modules]]
25
+ name = "String"
26
+ python = "gandora_std/string.py"
27
+ source = "gandora_std/_gan/gandora_std/string.gan"
gandora_std/keyword.py ADDED
@@ -0,0 +1,82 @@
1
+ """Functions over keyword lists — lists of {atom, value} tuples (GEP-0010)."""
2
+
3
+ import gandora_std.enum
4
+
5
+
6
+ class GanMatchError(Exception):
7
+ pass
8
+
9
+
10
+ def get(kw, key):
11
+ """The first value for `key`, or nil.
12
+
13
+
14
+ >>> get([("a", 1), ("b", 2)], "a")
15
+ 1
16
+ """
17
+ return (next((v for k, v in (kw) if k == (key)), None))
18
+
19
+
20
+ def put(kw, key, value):
21
+ """Replaces `key` with `value`, prepending it (Elixir Keyword.put)."""
22
+ def _gan_fn0(*_gan_args):
23
+ match _gan_args:
24
+ case ((k, _) as _gan_t0,) if isinstance(_gan_t0, tuple):
25
+ return k == key
26
+ raise GanMatchError("no clause of _gan_fn0/1 matched " + repr(_gan_args))
27
+ return [(key, value)] + gandora_std.enum.reject(kw, _gan_fn0)
28
+
29
+
30
+ def keys(kw):
31
+ """The keys, in order, duplicates included."""
32
+ def _gan_fn1(*_gan_args):
33
+ match _gan_args:
34
+ case ((k, _) as _gan_t1,) if isinstance(_gan_t1, tuple):
35
+ return k
36
+ raise GanMatchError("no clause of _gan_fn1/1 matched " + repr(_gan_args))
37
+ return gandora_std.enum.map(kw, _gan_fn1)
38
+
39
+
40
+ def values(kw):
41
+ """The values, in order."""
42
+ def _gan_fn2(*_gan_args):
43
+ match _gan_args:
44
+ case ((_, v) as _gan_t2,) if isinstance(_gan_t2, tuple):
45
+ return v
46
+ raise GanMatchError("no clause of _gan_fn2/1 matched " + repr(_gan_args))
47
+ return gandora_std.enum.map(kw, _gan_fn2)
48
+
49
+
50
+ def has_key_p(kw, key):
51
+ """Whether `key` is present."""
52
+ def _gan_fn3(*_gan_args):
53
+ match _gan_args:
54
+ case ((k, _) as _gan_t3,) if isinstance(_gan_t3, tuple):
55
+ return k == key
56
+ raise GanMatchError("no clause of _gan_fn3/1 matched " + repr(_gan_args))
57
+ return gandora_std.enum.any_p(kw, _gan_fn3)
58
+
59
+
60
+ def delete(kw, key):
61
+ """Removes every entry for `key`."""
62
+ def _gan_fn4(*_gan_args):
63
+ match _gan_args:
64
+ case ((k, _) as _gan_t4,) if isinstance(_gan_t4, tuple):
65
+ return k == key
66
+ raise GanMatchError("no clause of _gan_fn4/1 matched " + repr(_gan_args))
67
+ return gandora_std.enum.reject(kw, _gan_fn4)
68
+
69
+
70
+ def merge(kw1, kw2):
71
+ """Merges `kw2` into `kw1`; `kw2` wins, its entries appended.
72
+
73
+
74
+ >>> merge([("a", 1), ("b", 2)], [("b", 9), ("c", 3)])
75
+ [('a', 1), ('b', 9), ('c', 3)]
76
+ """
77
+ def _gan_fn5(*_gan_args):
78
+ match _gan_args:
79
+ case ((k, _) as _gan_t5,) if isinstance(_gan_t5, tuple):
80
+ return has_key_p(kw2, k)
81
+ raise GanMatchError("no clause of _gan_fn5/1 matched " + repr(_gan_args))
82
+ return gandora_std.enum.reject(kw1, _gan_fn5) + kw2
gandora_std/list.py ADDED
@@ -0,0 +1,83 @@
1
+ """List-shape helpers; element-wise work lives in Enum (GEP-0010)."""
2
+
3
+ import builtins
4
+ import gandora_std.enum
5
+
6
+
7
+ def _gan_truthy(value):
8
+ return value is not None and value is not False
9
+
10
+
11
+ def first(xs):
12
+ """The first element, or nil for an empty list."""
13
+ return gandora_std.enum.at(xs, 0)
14
+
15
+
16
+ def last(xs):
17
+ """The last element, or nil for an empty list."""
18
+ return gandora_std.enum.at(xs, -1)
19
+
20
+
21
+ def flatten(xs):
22
+ """Flattens nested lists to any depth.
23
+
24
+
25
+ >>> flatten([1, [2, [3, 4]], 5])
26
+ [1, 2, 3, 4, 5]
27
+ """
28
+ def _gan_fn0(x):
29
+ if _gan_truthy(isinstance(x, list)):
30
+ return flatten(x)
31
+ else:
32
+ return [x]
33
+ return gandora_std.enum.flat_map(xs, _gan_fn0)
34
+
35
+
36
+ def wrap(value):
37
+ """Wraps a value in a list: nil becomes [], lists pass through, anything else becomes [value]."""
38
+ if _gan_truthy((value is None)):
39
+ return []
40
+ elif _gan_truthy(isinstance(value, list)):
41
+ return value
42
+ else:
43
+ return [value]
44
+
45
+
46
+ def duplicate(value, n):
47
+ """A list of `n` copies of `value`."""
48
+ return ([(value)] * (n))
49
+
50
+
51
+ def insert_at(xs, index, value):
52
+ """Inserts `value` at `index` (negative counts from the end, as in Elixir)."""
53
+ return ((xs)[:(index)] + [(value)] + (xs)[(index):])
54
+
55
+
56
+ def delete_at(xs, index):
57
+ """Removes the element at `index`; out-of-range leaves the list unchanged."""
58
+ return ((xs)[:(index)] + (xs)[(index):][1:])
59
+
60
+
61
+ def to_tuple(xs):
62
+ """The list as a tuple."""
63
+ return builtins.tuple(xs)
64
+
65
+
66
+ def starts_with_p(xs, prefix):
67
+ """Whether the list starts with `prefix`."""
68
+ return ((xs)[:len((prefix))] == (prefix))
69
+
70
+
71
+ def replace_at(xs, index, value):
72
+ """Replaces the element at `index` (negative counts from the end)."""
73
+ return ((xs)[:(index)] + [(value)] + (xs)[(index):][1:])
74
+
75
+
76
+ def update_at(xs, index, f):
77
+ """Updates the element at `index` by `f`.
78
+
79
+
80
+ >>> update_at([1, 2, 3], 1, lambda v: v * 10)
81
+ [1, 20, 3]
82
+ """
83
+ return replace_at(xs, index, f(gandora_std.enum.at(xs, index)))
gandora_std/map.py ADDED
@@ -0,0 +1,116 @@
1
+ """Data-first dict functions; all updates return new maps (GEP-0010)."""
2
+
3
+ import builtins
4
+
5
+
6
+ def _gan_truthy(value):
7
+ return value is not None and value is not False
8
+
9
+ class GanMatchError(Exception):
10
+ pass
11
+
12
+
13
+ def get(*_gan_args):
14
+ """The value for `key`, or `default` (nil unless given) when absent.
15
+
16
+
17
+ >>> get({"a": 1}, "b", 0)
18
+ 0
19
+ """
20
+ match _gan_args:
21
+ case (m, key, default,):
22
+ return m.get(key, default)
23
+ case (m, key,):
24
+ return get(m, key, None)
25
+ raise GanMatchError("no clause of get/2,3 matched " + repr(_gan_args))
26
+
27
+
28
+ def put(m, key, value):
29
+ """A new map with `key` set to `value`.
30
+
31
+
32
+ >>> put({"a": 1}, "b", 2)
33
+ {'a': 1, 'b': 2}
34
+ """
35
+ return ({**(m), (key): (value)})
36
+
37
+
38
+ def delete(m, key):
39
+ """A new map without `key`."""
40
+ return ({k: v for k, v in (m).items() if k != (key)})
41
+
42
+
43
+ def keys(m):
44
+ """The keys as a list."""
45
+ return builtins.list(m.keys())
46
+
47
+
48
+ def values(m):
49
+ """The values as a list."""
50
+ return builtins.list(m.values())
51
+
52
+
53
+ def merge(m1, m2):
54
+ """Merges `m2` into `m1`; `m2` wins on conflicts."""
55
+ return ({**(m1), **(m2)})
56
+
57
+
58
+ def has_key_p(m, key):
59
+ """Whether `key` is present."""
60
+ return ((key) in (m))
61
+
62
+
63
+ def to_list(m):
64
+ """The entries as a list of `{key, value}` tuples."""
65
+ return builtins.list(m.items())
66
+
67
+
68
+ def new():
69
+ """An empty map."""
70
+ return {}
71
+
72
+
73
+ def update(m, key, default, f):
74
+ """Updates `key` by `f`; uses `default` when the key is absent (Elixir Map.update/4).
75
+
76
+
77
+ >>> update({"a": 1}, "a", 0, lambda v: v + 10)
78
+ {'a': 11}
79
+ """
80
+ if _gan_truthy(has_key_p(m, key)):
81
+ return put(m, key, f(get(m, key)))
82
+ else:
83
+ return put(m, key, default)
84
+
85
+
86
+ def fetch_bang(m, key):
87
+ """The value for `key`; raises Python KeyError when absent."""
88
+ return ((m)[(key)])
89
+
90
+
91
+ def put_new(m, key, value):
92
+ """Sets `key` only when absent."""
93
+ if _gan_truthy(has_key_p(m, key)):
94
+ return m
95
+ else:
96
+ return put(m, key, value)
97
+
98
+
99
+ def take(m, keys):
100
+ """The submap with only `keys` (missing keys ignored)."""
101
+ return ({k: v for k, v in (m).items() if k in (keys)})
102
+
103
+
104
+ def drop(m, keys):
105
+ """The map without `keys`."""
106
+ return ({k: v for k, v in (m).items() if k not in (keys)})
107
+
108
+
109
+ def filter(m, f):
110
+ """Keeps entries for which `f` (receiving a `{key, value}` tuple) is truthy.
111
+
112
+
113
+ >>> filter({"a": 1, "b": 5}, lambda pair: pair[1] > 2)
114
+ {'b': 5}
115
+ """
116
+ return ({k: v for k, v in (m).items() if (f)((k, v))})
gandora_std/string.py ADDED
@@ -0,0 +1,147 @@
1
+ """Data-first string functions over Python str (GEP-0010)."""
2
+
3
+ import builtins
4
+ import re
5
+ import gandora_std.enum
6
+
7
+
8
+ def _gan_truthy(value):
9
+ return value is not None and value is not False
10
+
11
+
12
+ def upcase(s):
13
+ """Uppercases the string."""
14
+ return s.upper()
15
+
16
+
17
+ def downcase(s):
18
+ """Lowercases the string."""
19
+ return s.lower()
20
+
21
+
22
+ def capitalize(s):
23
+ """Uppercases the first character, lowercases the rest."""
24
+ return s.capitalize()
25
+
26
+
27
+ def split(s):
28
+ """Splits on whitespace runs, dropping empty parts (Elixir semantics).
29
+
30
+
31
+ >>> split(" a b c ")
32
+ ['a', 'b', 'c']
33
+ """
34
+ return s.split()
35
+
36
+
37
+ def split_on(s, sep):
38
+ """Splits on the separator `sep`."""
39
+ return s.split(sep)
40
+
41
+
42
+ def trim(s):
43
+ """Removes leading and trailing whitespace."""
44
+ return s.strip()
45
+
46
+
47
+ def replace(s, pattern, replacement):
48
+ """Replaces every occurrence of `pattern` with `replacement`."""
49
+ return s.replace(pattern, replacement)
50
+
51
+
52
+ def contains_p(s, part):
53
+ """Whether the string contains `part`."""
54
+ return ((part) in (s))
55
+
56
+
57
+ def starts_with_p(s, prefix):
58
+ """Whether the string starts with `prefix`."""
59
+ return s.startswith(prefix)
60
+
61
+
62
+ def ends_with_p(s, suffix):
63
+ """Whether the string ends with `suffix`."""
64
+ return s.endswith(suffix)
65
+
66
+
67
+ def length(s):
68
+ """The number of characters (Unicode code points, Python `len`)."""
69
+ return builtins.len(s)
70
+
71
+
72
+ def slice(s, start, len):
73
+ """The substring of `len` characters starting at `start` (negative start counts from the end).
74
+
75
+
76
+ >>> slice("gandora", 3, 4)
77
+ 'dora'
78
+ """
79
+ return ((s)[(start):] [:(len)])
80
+
81
+
82
+ def pad_leading(s, width):
83
+ """Pads on the left with spaces to `width`."""
84
+ return s.rjust(width)
85
+
86
+
87
+ def pad_trailing(s, width):
88
+ """Pads on the right with spaces to `width`."""
89
+ return s.ljust(width)
90
+
91
+
92
+ def to_integer(s):
93
+ """Parses an integer; raises Python ValueError on bad input."""
94
+ return builtins.int(s)
95
+
96
+
97
+ def to_float(s):
98
+ """Parses a float; raises Python ValueError on bad input."""
99
+ return builtins.float(s)
100
+
101
+
102
+ def at(s, index):
103
+ """The character at `index` (negative counts from the end), or nil."""
104
+ if (index < builtins.len(s)) and (index >= -(builtins.len(s))):
105
+ return ((s)[(index)])
106
+ else:
107
+ return None
108
+
109
+
110
+ def reverse(s):
111
+ """The string reversed."""
112
+ return ((s)[::-1])
113
+
114
+
115
+ def duplicate(s, n):
116
+ """The string repeated `n` times."""
117
+ return ((s) * (n))
118
+
119
+
120
+ def trim_leading(s):
121
+ """Removes leading whitespace."""
122
+ return s.lstrip()
123
+
124
+
125
+ def trim_trailing(s):
126
+ """Removes trailing whitespace."""
127
+ return s.rstrip()
128
+
129
+
130
+ def codepoints(s):
131
+ """The characters as a list of one-character strings.
132
+
133
+
134
+ >>> gandora_std.enum.take(codepoints("héllo"), 2)
135
+ ['h', 'é']
136
+ """
137
+ return builtins.list(s)
138
+
139
+
140
+ def match_p(s, regex):
141
+ """Whether the compiled regex (`~r/.../`) matches anywhere in the string.
142
+
143
+
144
+ >>> match_p("gandora-2026", re.compile("\\d+"))
145
+ True
146
+ """
147
+ return not (_gan_truthy((regex.search(s) is None)))
@@ -0,0 +1,5 @@
1
+ Metadata-Version: 2.4
2
+ Name: gandora-std
3
+ Version: 0.1.0
4
+ Summary: The Gandora standard library: Enum, String, Map, List, Keyword (GEP-0010)
5
+ Requires-Python: >=3.12
@@ -0,0 +1,14 @@
1
+ gandora_std/enum.py,sha256=hOlt5hIUyDM-XeXXMstZ-Mc0OnE6ccbs4zUZVADHQYE,5975
2
+ gandora_std/gandora.toml,sha256=XEu3viPEQ_XfiWQq9MuHe8Kg0MEW35AVFUuinxJKimo,577
3
+ gandora_std/keyword.py,sha256=Bud0dZAAR8AH7MryjlbNbAf-xQsTq0-TKf1_iA61Vvc,2597
4
+ gandora_std/list.py,sha256=4K3WJO7ich5eD5S9H2_TLzJTZrH9Ng-LHoXMof2iF4U,2031
5
+ gandora_std/map.py,sha256=Sm4Gd0bIff6EHXlKW7JP18pxA_SNivRgAga42XtnGMs,2503
6
+ gandora_std/string.py,sha256=jFonH5wjLPJ_cO79Uap4Ht6Tp607jql0Bqi071hQ6DA,3008
7
+ gandora_std/_gan/gandora_std/enum.gan,sha256=I5Fykk85ZwfbpHc8cTgXZIIPlODA9irHgrhbEDYotI0,8812
8
+ gandora_std/_gan/gandora_std/keyword.gan,sha256=mSSHmtQ1lWJSVoBR8If_LUbHf2vyRzqAt4MvPwIY_zU,1745
9
+ gandora_std/_gan/gandora_std/list.gan,sha256=3MAv2wA5V1sHBbxbsfSEUgE-2MoghnoBamZFkllOEVE,2762
10
+ gandora_std/_gan/gandora_std/map.gan,sha256=2mpuRllfYJkiW8lxn6kbmP8tIQvGTqw_WQUv42A6agQ,3408
11
+ gandora_std/_gan/gandora_std/string.gan,sha256=1MWxxOILxV0B80e0jE2rh36Tbn8gzJfi_pYx9YYDrMY,4408
12
+ gandora_std-0.1.0.dist-info/METADATA,sha256=Gk4F9PBr1r0gpAfIHAYrw43d3hsPtVrMM7JRHMou4ZA,162
13
+ gandora_std-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
14
+ gandora_std-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any