funcnodes-basic 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,740 @@
1
+ """
2
+ basic functionalities for strings
3
+ """
4
+ import funcnodes as fn
5
+ from typing import Any, List, Tuple, Literal, Optional
6
+ import re
7
+
8
+
9
+ POSSIBLE_DECODINGS = [
10
+ "ascii",
11
+ "big5",
12
+ "big5hkscs",
13
+ "cp037",
14
+ "cp273",
15
+ "cp424",
16
+ "cp437",
17
+ "cp500",
18
+ "cp720",
19
+ "cp737",
20
+ "cp775",
21
+ "cp850",
22
+ "cp852",
23
+ "cp855",
24
+ "cp856",
25
+ "cp857",
26
+ "cp858",
27
+ "cp860",
28
+ "cp861",
29
+ "cp862",
30
+ "cp863",
31
+ "cp864",
32
+ "cp865",
33
+ "cp866",
34
+ "cp869",
35
+ "cp874",
36
+ "cp875",
37
+ "cp932",
38
+ "cp949",
39
+ "cp950",
40
+ "cp1006",
41
+ "cp1026",
42
+ "cp1125",
43
+ "cp1140",
44
+ "cp1250",
45
+ "cp1251",
46
+ "cp1252",
47
+ "cp1253",
48
+ "cp1254",
49
+ "cp1255",
50
+ "cp1256",
51
+ "cp1257",
52
+ "cp1258",
53
+ "euc_jp",
54
+ "euc_jis_2004",
55
+ "euc_jisx0213",
56
+ "euc_kr",
57
+ "gb2312",
58
+ "gbk",
59
+ "gb18030",
60
+ "hz",
61
+ "iso2022_jp",
62
+ "iso2022_jp_1",
63
+ "iso2022_jp_2",
64
+ "iso2022_jp_2004",
65
+ "iso2022_jp_3",
66
+ "iso2022_jp_ext",
67
+ "iso2022_kr",
68
+ "latin_1",
69
+ "iso8859_2",
70
+ "iso8859_3",
71
+ "iso8859_4",
72
+ "iso8859_5",
73
+ "iso8859_6",
74
+ "iso8859_7",
75
+ "iso8859_8",
76
+ "iso8859_9",
77
+ "iso8859_10",
78
+ "iso8859_11",
79
+ "iso8859_13",
80
+ "iso8859_14",
81
+ "iso8859_15",
82
+ "iso8859_16",
83
+ "johab",
84
+ "koi8_r",
85
+ "koi8_t",
86
+ "koi8_u",
87
+ "kz1048",
88
+ "mac_cyrillic",
89
+ "mac_greek",
90
+ "mac_iceland",
91
+ "mac_latin2",
92
+ "mac_roman",
93
+ "mac_turkish",
94
+ "ptcp154",
95
+ "shift_jis",
96
+ "shift_jis_2004",
97
+ "shift_jisx0213",
98
+ "utf_32",
99
+ "utf_32_be",
100
+ "utf_32_le",
101
+ "utf_16",
102
+ "utf_16_be",
103
+ "utf_16_le",
104
+ "utf_7",
105
+ "utf_8",
106
+ "utf_8_sig",
107
+ ]
108
+
109
+ POSSIBLE_DECODINGS_TYPE = Literal[*POSSIBLE_DECODINGS]
110
+
111
+
112
+ @fn.NodeDecorator(
113
+ node_id="string.length",
114
+ node_name="Length",
115
+ description="Concatenate two strings",
116
+ outputs=[
117
+ {"name": "length"},
118
+ ],
119
+ )
120
+ def string_length(s: str) -> int:
121
+ return len(s)
122
+
123
+
124
+ @fn.NodeDecorator(
125
+ node_id="string.concat",
126
+ node_name="Concatenate",
127
+ description="Concatenate two strings",
128
+ outputs=[
129
+ {"name": "concatenated"},
130
+ ],
131
+ )
132
+ def string_concat(s1: str, s2: str) -> str:
133
+ return s1 + s2
134
+
135
+
136
+ @fn.NodeDecorator(
137
+ node_id="string.split",
138
+ node_name="Split",
139
+ description="Split a string by a delimiter",
140
+ outputs=[
141
+ {"name": "split"},
142
+ ],
143
+ )
144
+ def string_split(s: str, delimiter: Optional[str] = None) -> List[str]:
145
+ return s.split(delimiter)
146
+
147
+
148
+ @fn.NodeDecorator(
149
+ node_id="string.join",
150
+ node_name="Join",
151
+ description="Join a list of strings by a delimiter",
152
+ outputs=[
153
+ {"name": "joined"},
154
+ ],
155
+ )
156
+ def string_join(strings: List[str], delimiter: str = " ") -> str:
157
+ return delimiter.join(strings)
158
+
159
+
160
+ @fn.NodeDecorator(
161
+ node_id="string.upper",
162
+ node_name="Upper",
163
+ description="Convert a string to uppercase",
164
+ outputs=[
165
+ {"name": "upper"},
166
+ ],
167
+ )
168
+ def string_upper(s: str) -> str:
169
+ return s.upper()
170
+
171
+
172
+ @fn.NodeDecorator(
173
+ node_id="string.lower",
174
+ node_name="Lower",
175
+ description="Convert a string to lowercase",
176
+ outputs=[
177
+ {"name": "lower"},
178
+ ],
179
+ )
180
+ def string_lower(s: str) -> str:
181
+ return s.lower()
182
+
183
+
184
+ @fn.NodeDecorator(
185
+ node_id="string.replace",
186
+ node_name="Replace",
187
+ description="Replace a substring with another",
188
+ outputs=[
189
+ {"name": "replaced"},
190
+ ],
191
+ )
192
+ def string_replace(s: str, old: str, new: str) -> str:
193
+ return s.replace(old, new)
194
+
195
+
196
+ @fn.NodeDecorator(
197
+ node_id="string.strip",
198
+ node_name="Strip",
199
+ description="Remove leading and trailing whitespace or specified characters",
200
+ outputs=[
201
+ {"name": "stripped"},
202
+ ],
203
+ )
204
+ def string_strip(s: str, chars: str = None) -> str:
205
+ return s.strip(chars)
206
+
207
+
208
+ @fn.NodeDecorator(
209
+ node_id="string.startswith",
210
+ node_name="Starts With",
211
+ description="Check if a string starts with a substring",
212
+ outputs=[
213
+ {"name": "starts_with"},
214
+ ],
215
+ )
216
+ def string_startswith(s: str, prefix: str) -> bool:
217
+ return s.startswith(prefix)
218
+
219
+
220
+ @fn.NodeDecorator(
221
+ node_id="string.endswith",
222
+ node_name="Ends With",
223
+ description="Check if a string ends with a substring",
224
+ outputs=[
225
+ {"name": "ends_with"},
226
+ ],
227
+ )
228
+ def string_endswith(s: str, suffix: str) -> bool:
229
+ return s.endswith(suffix)
230
+
231
+
232
+ @fn.NodeDecorator(
233
+ node_id="string.contains",
234
+ node_name="Contains",
235
+ description="Check if a string contains a substring",
236
+ outputs=[
237
+ {"name": "contains"},
238
+ ],
239
+ )
240
+ def string_contains(s: str, sub: str) -> bool:
241
+ return sub in s
242
+
243
+
244
+ @fn.NodeDecorator(
245
+ node_id="string.format_map",
246
+ node_name="Format Map",
247
+ description="Format a string with a mapping",
248
+ outputs=[
249
+ {"name": "formatted"},
250
+ ],
251
+ )
252
+ def string_format_map(s: str, mapping: dict) -> str:
253
+ return s.format_map(mapping)
254
+
255
+
256
+ @fn.NodeDecorator(
257
+ node_id="string.capitalize",
258
+ node_name="Capitalize",
259
+ description="Capitalize the first character of a string",
260
+ outputs=[
261
+ {"name": "capitalized"},
262
+ ],
263
+ )
264
+ def string_capitalize(s: str) -> str:
265
+ return s.capitalize()
266
+
267
+
268
+ @fn.NodeDecorator(
269
+ node_id="string.title",
270
+ node_name="Title",
271
+ description="Capitalize the first character of each word in a string",
272
+ outputs=[
273
+ {"name": "titled"},
274
+ ],
275
+ )
276
+ def string_title(s: str) -> str:
277
+ return s.title()
278
+
279
+
280
+ @fn.NodeDecorator(
281
+ node_id="string.swapcase",
282
+ node_name="Swapcase",
283
+ description="Swap the case of each character in a string",
284
+ outputs=[
285
+ {"name": "swapped"},
286
+ ],
287
+ )
288
+ def string_swapcase(s: str) -> str:
289
+ return s.swapcase()
290
+
291
+
292
+ @fn.NodeDecorator(
293
+ node_id="string.zfill",
294
+ node_name="Zfill",
295
+ description="Fill a string with zeros to a specified width",
296
+ outputs=[
297
+ {"name": "zfilled"},
298
+ ],
299
+ )
300
+ def string_zfill(s: str, width: int) -> str:
301
+ return s.zfill(width)
302
+
303
+
304
+ @fn.NodeDecorator(
305
+ node_id="string.center",
306
+ node_name="Center",
307
+ description="Center a string in a field of a specified width",
308
+ outputs=[
309
+ {"name": "centered"},
310
+ ],
311
+ )
312
+ def string_center(s: str, width: int, fillchar: str = " ") -> str:
313
+ return s.center(width, fillchar)
314
+
315
+
316
+ @fn.NodeDecorator(
317
+ node_id="string.ljust",
318
+ node_name="Left Justify",
319
+ description="Left justify a string in a field of a specified width",
320
+ outputs=[
321
+ {"name": "left_justified"},
322
+ ],
323
+ )
324
+ def string_ljust(s: str, width: int, fillchar: str = " ") -> str:
325
+ return s.ljust(width, fillchar)
326
+
327
+
328
+ @fn.NodeDecorator(
329
+ node_id="string.rjust",
330
+ node_name="Right Justify",
331
+ description="Right justify a string in a field of a specified width",
332
+ outputs=[
333
+ {"name": "right_justified"},
334
+ ],
335
+ )
336
+ def string_rjust(s: str, width: int, fillchar: str = " ") -> str:
337
+ return s.rjust(width, fillchar)
338
+
339
+
340
+ @fn.NodeDecorator(
341
+ node_id="string.count",
342
+ node_name="Count",
343
+ description="Count the occurrences of a substring in a string",
344
+ outputs=[
345
+ {"name": "count"},
346
+ ],
347
+ )
348
+ def string_count(s: str, sub: str) -> int:
349
+ return s.count(sub)
350
+
351
+
352
+ @fn.NodeDecorator(
353
+ node_id="string.find",
354
+ node_name="Find",
355
+ description="Find the index of the first occurrence of a substring in a string",
356
+ outputs=[
357
+ {"name": "index"},
358
+ ],
359
+ )
360
+ def string_find(s: str, sub: str) -> int:
361
+ return s.find(sub)
362
+
363
+
364
+ @fn.NodeDecorator(
365
+ node_id="string.rfind",
366
+ node_name="Rfind",
367
+ description="Find the index of the last occurrence of a substring in a string",
368
+ outputs=[
369
+ {"name": "index"},
370
+ ],
371
+ )
372
+ def string_rfind(s: str, sub: str) -> int:
373
+ return s.rfind(sub)
374
+
375
+
376
+ @fn.NodeDecorator(
377
+ node_id="string.index",
378
+ node_name="Index",
379
+ description="Find the index of the first occurrence of a substring in a string",
380
+ outputs=[
381
+ {"name": "index"},
382
+ ],
383
+ )
384
+ def string_index(s: str, sub: str) -> int:
385
+ return s.index(sub)
386
+
387
+
388
+ @fn.NodeDecorator(
389
+ node_id="string.rindex",
390
+ node_name="Rindex",
391
+ description="Find the index of the last occurrence of a substring in a string",
392
+ outputs=[
393
+ {"name": "index"},
394
+ ],
395
+ )
396
+ def string_rindex(s: str, sub: str) -> int:
397
+ return s.rindex(sub)
398
+
399
+
400
+ @fn.NodeDecorator(
401
+ node_id="string.isalnum",
402
+ node_name="Is Alphanumeric",
403
+ description="Check if a string is alphanumeric",
404
+ outputs=[
405
+ {"name": "is_alphanumeric"},
406
+ ],
407
+ )
408
+ def string_isalnum(s: str) -> bool:
409
+ return s.isalnum()
410
+
411
+
412
+ @fn.NodeDecorator(
413
+ node_id="string.isalpha",
414
+ node_name="Is Alphabetical",
415
+ description="Check if a string is alphabetical",
416
+ outputs=[
417
+ {"name": "is_alphabetical"},
418
+ ],
419
+ )
420
+ def string_isalpha(s: str) -> bool:
421
+ return s.isalpha()
422
+
423
+
424
+ @fn.NodeDecorator(
425
+ node_id="string.isdigit",
426
+ node_name="Is Digit",
427
+ description="Check if a string is a digit",
428
+ outputs=[
429
+ {"name": "is_digit"},
430
+ ],
431
+ )
432
+ def string_isdigit(s: str) -> bool:
433
+ return s.isdigit()
434
+
435
+
436
+ @fn.NodeDecorator(
437
+ node_id="string.islower",
438
+ node_name="Is Lowercase",
439
+ description="Check if a string is lowercase",
440
+ outputs=[
441
+ {"name": "is_lowercase"},
442
+ ],
443
+ )
444
+ def string_islower(s: str) -> bool:
445
+ return s.islower()
446
+
447
+
448
+ @fn.NodeDecorator(
449
+ node_id="string.isupper",
450
+ node_name="Is Uppercase",
451
+ description="Check if a string is uppercase",
452
+ outputs=[
453
+ {"name": "is_uppercase"},
454
+ ],
455
+ )
456
+ def string_isupper(s: str) -> bool:
457
+ return s.isupper()
458
+
459
+
460
+ @fn.NodeDecorator(
461
+ node_id="string.isspace",
462
+ node_name="Is Space",
463
+ description="Check if a string is whitespace",
464
+ outputs=[
465
+ {"name": "is_space"},
466
+ ],
467
+ )
468
+ def string_isspace(s: str) -> bool:
469
+ return s.isspace()
470
+
471
+
472
+ @fn.NodeDecorator(
473
+ node_id="string.istitle",
474
+ node_name="Is Title",
475
+ description="Check if a string is titlecase",
476
+ outputs=[
477
+ {"name": "is_title"},
478
+ ],
479
+ )
480
+ def string_istitle(s: str) -> bool:
481
+ return s.istitle()
482
+
483
+
484
+ @fn.NodeDecorator(
485
+ node_id="string.isprintable",
486
+ node_name="Is Printable",
487
+ description="Check if a string is printable",
488
+ outputs=[
489
+ {"name": "is_printable"},
490
+ ],
491
+ )
492
+ def string_isprintable(s: str) -> bool:
493
+ return s.isprintable()
494
+
495
+
496
+ @fn.NodeDecorator(
497
+ node_id="string.isidentifier",
498
+ node_name="Is Identifier",
499
+ description="Check if a string is a valid identifier",
500
+ outputs=[
501
+ {"name": "is_identifier"},
502
+ ],
503
+ )
504
+ def string_isidentifier(s: str) -> bool:
505
+ return s.isidentifier()
506
+
507
+
508
+ @fn.NodeDecorator(
509
+ node_id="string.isdecimal",
510
+ node_name="Is Decimal",
511
+ description="Check if a string is a decimal",
512
+ outputs=[
513
+ {"name": "is_decimal"},
514
+ ],
515
+ )
516
+ def string_isdecimal(s: str) -> bool:
517
+ return s.isdecimal()
518
+
519
+
520
+ @fn.NodeDecorator(
521
+ node_id="string.isnumeric",
522
+ node_name="Is Numeric",
523
+ description="Check if a string is numeric",
524
+ outputs=[
525
+ {"name": "is_numeric"},
526
+ ],
527
+ )
528
+ def string_isnumeric(s: str) -> bool:
529
+ return s.isnumeric()
530
+
531
+
532
+ @fn.NodeDecorator(
533
+ node_id="string.isascii",
534
+ node_name="Is ASCII",
535
+ description="Check if a string is ASCII",
536
+ outputs=[
537
+ {"name": "is_ascii"},
538
+ ],
539
+ )
540
+ def string_isascii(s: str) -> bool:
541
+ return s.isascii()
542
+
543
+
544
+ @fn.NodeDecorator(
545
+ node_id="string.encode",
546
+ node_name="Encode",
547
+ description="Encode a string",
548
+ outputs=[
549
+ {"name": "encoded"},
550
+ ],
551
+ )
552
+ def string_encode(s: str, encoding: POSSIBLE_DECODINGS_TYPE = "utf-8") -> bytes:
553
+ return s.encode(encoding)
554
+
555
+
556
+ @fn.NodeDecorator(
557
+ node_id="string.decode",
558
+ node_name="Decode",
559
+ description="Decode a string",
560
+ outputs=[
561
+ {"name": "decoded"},
562
+ ],
563
+ )
564
+ def string_decode(b: bytes, encoding: POSSIBLE_DECODINGS_TYPE = "utf-8") -> str:
565
+ return b.decode(encoding)
566
+
567
+
568
+ @fn.NodeDecorator(
569
+ node_id="re.match",
570
+ node_name="Match",
571
+ description="Match a string against a regular expression",
572
+ outputs=[
573
+ {"name": "match"},
574
+ {"name": "groups"},
575
+ ],
576
+ )
577
+ def re_match(pattern: str, string: str) -> Tuple[bool, List[str]]:
578
+ matches = re.match(pattern, string)
579
+ if not matches:
580
+ return False, []
581
+
582
+ groups = [matches[0]] + list(matches.groups())
583
+ return True, groups
584
+
585
+
586
+ @fn.NodeDecorator(
587
+ node_id="re.fullmatch",
588
+ node_name="Full Match",
589
+ description="Match a string against a regular expression",
590
+ outputs=[
591
+ {"name": "match"},
592
+ {"name": "groups"},
593
+ ],
594
+ )
595
+ def re_fullmatch(pattern: str, string: str) -> Tuple[bool, List[str]]:
596
+ matches = re.fullmatch(pattern, string)
597
+ if not matches:
598
+ return False, []
599
+
600
+ groups = [matches[0]] + list(matches.groups())
601
+ return True, groups
602
+
603
+
604
+ @fn.NodeDecorator(
605
+ node_id="re.search",
606
+ node_name="Search",
607
+ description="Search a string for a regular expression",
608
+ outputs=[
609
+ {"name": "match"},
610
+ {"name": "groups"},
611
+ ],
612
+ )
613
+ def re_search(pattern: str, string: str) -> Tuple[bool, List[str]]:
614
+ matches = re.search(pattern, string)
615
+ if not matches:
616
+ return False, []
617
+
618
+ groups = [matches[0]] + list(matches.groups())
619
+ return True, groups
620
+
621
+
622
+ @fn.NodeDecorator(
623
+ node_id="re.findall",
624
+ node_name="Find All",
625
+ description="Find all occurrences of a regular expression in a string",
626
+ outputs=[
627
+ {"name": "matches"},
628
+ ],
629
+ )
630
+ def re_findall(pattern: str, string: str) -> List[List[str]]:
631
+ return re.findall(pattern, string)
632
+
633
+
634
+ @fn.NodeDecorator(
635
+ node_id="re.sub",
636
+ node_name="Substitute",
637
+ description="Substitute occurrences of a regular expression in a string",
638
+ outputs=[
639
+ {"name": "substituted"},
640
+ ],
641
+ )
642
+ def re_sub(pattern: str, repl: str, string: str) -> str:
643
+ return re.sub(pattern, repl, string)
644
+
645
+
646
+ @fn.NodeDecorator(
647
+ node_id="re.subn",
648
+ node_name="Substitute N",
649
+ description="Substitute occurrences of a regular expression in a string",
650
+ outputs=[
651
+ {"name": "substituted"},
652
+ {"name": "count"},
653
+ ],
654
+ )
655
+ def re_subn(pattern: str, repl: str, string: str) -> Tuple[str, int]:
656
+ return re.subn(pattern, repl, string)
657
+
658
+
659
+ @fn.NodeDecorator(
660
+ node_id="re.escape",
661
+ node_name="Escape",
662
+ description="Escape special characters in a regular expression",
663
+ outputs=[
664
+ {"name": "escaped"},
665
+ ],
666
+ )
667
+ def re_escape(pattern: str) -> str:
668
+ return re.escape(pattern)
669
+
670
+
671
+ @fn.NodeDecorator(
672
+ node_id="re.split",
673
+ node_name="Split",
674
+ description="Split a string by a regular expression",
675
+ outputs=[
676
+ {"name": "splitted"},
677
+ ],
678
+ )
679
+ def re_split(pattern: str, string: str) -> List[str]:
680
+ return re.split(pattern, string)
681
+
682
+
683
+ regex_shelf = fn.Shelf(
684
+ nodes=[
685
+ re_match,
686
+ re_fullmatch,
687
+ re_search,
688
+ re_findall,
689
+ re_sub,
690
+ re_subn,
691
+ re_escape,
692
+ re_split,
693
+ ],
694
+ name="Regular Expressions",
695
+ description="Basic regular expression operations.",
696
+ )
697
+
698
+ NODE_SHELF = fn.Shelf(
699
+ nodes=[
700
+ string_length,
701
+ string_concat,
702
+ string_split,
703
+ string_join,
704
+ string_upper,
705
+ string_lower,
706
+ string_replace,
707
+ string_strip,
708
+ string_startswith,
709
+ string_endswith,
710
+ string_contains,
711
+ string_format_map,
712
+ string_capitalize,
713
+ string_title,
714
+ string_swapcase,
715
+ string_zfill,
716
+ string_center,
717
+ string_ljust,
718
+ string_rjust,
719
+ string_count,
720
+ string_find,
721
+ string_rfind,
722
+ string_index,
723
+ string_rindex,
724
+ string_isalnum,
725
+ string_isalpha,
726
+ string_isdigit,
727
+ string_islower,
728
+ string_isupper,
729
+ string_isspace,
730
+ string_istitle,
731
+ string_isprintable,
732
+ string_isidentifier,
733
+ string_isdecimal,
734
+ string_isnumeric,
735
+ string_isascii,
736
+ ],
737
+ subshelves=[regex_shelf],
738
+ name="Strings",
739
+ description="Basic string manipulation and regular expressions.",
740
+ )