OILS / spec / ysh-methods.test.sh View on Github | oils.pub

690 lines, 423 significant
1# spec/ysh-methods
2
3## our_shell: ysh
4
5#### => operator for pure computation is allowed (may be mandatory later)
6
7# later we may make it mandatory
8
9if ("abc" => startsWith("a")) {
10 echo yes
11}
12
13var mylist = [1, 2, 3]
14
15# This one should be ->
16call mylist->pop()
17echo 'ok'
18
19## STDOUT:
20yes
21ok
22## END
23
24#### => can be used to chain free functions
25
26func dictfunc() {
27 return ({k1: 'spam', k2: 'eggs'})
28}
29
30echo $[list(dictfunc()) => join('/') => upper()]
31
32# This is nicer and more consistent
33echo $[dictfunc() => list() => join('/') => upper()]
34
35## STDOUT:
36K1/K2
37K1/K2
38## END
39
40#### Str => startsWith(Str) and endsWith(Str), simple
41func test(s, p) { echo $[s => startsWith(p)] $[s => endsWith(p)] }
42
43call test('', '')
44call test('abc', '')
45call test('abc', 'a')
46call test('abc', 'b')
47call test('abc', 'c')
48call test('abc', 'z')
49call test('', 'abc')
50## status: 0
51## STDOUT:
52true true
53true true
54true false
55false false
56false true
57false false
58false false
59## END
60
61#### Str => startsWith(Str) and endsWith(Str), matches bytes not runes
62func test(s, p) { echo $[s => startsWith(p)] $[s => endsWith(p)] }
63
64call test(b'\yce\ya3', u'\u{03a3}')
65call test(b'\yce\ya3', b'\yce')
66call test(b'\yce\ya3', b'\ya3')
67call test(b'\yce', b'\yce')
68## status: 0
69## STDOUT:
70true true
71true false
72false true
73true true
74## END
75
76#### Str => startsWith(Str) and endsWith(Str), eggex
77func test(s, p) { echo $[s => startsWith(p)] $[s => endsWith(p)] }
78
79call test('abc', / d+ /)
80call test('abc', / [ a b c ] /)
81call test('abc', / 'abc' /)
82call test('cba', / d+ /)
83call test('cba', / [ a b c ] /)
84call test('cba', / 'abc' /)
85## status: 0
86## STDOUT:
87false false
88true true
89true true
90false false
91true true
92false false
93## END
94
95#### Str => startsWith(Str) and endsWith(Str), eggex with anchors
96func test(s, p) { echo $[s => startsWith(p)] $[s => endsWith(p)] }
97
98call test('ab', / %start 'a' /)
99call test('ab', / 'a' %end /)
100call test('ab', / %start 'a' %end /)
101call test('ab', / %start 'b' /)
102call test('ab', / 'b' %end /)
103call test('ab', / %start 'b' %end /)
104## status: 0
105## STDOUT:
106true false
107false false
108false false
109false false
110false true
111false false
112## END
113
114#### Str => startsWith(Str) and endsWith(Str), eggex matches runes
115func test(s, p) { echo $[s.startsWith(p)] $[s.endsWith(p)] }
116
117call test(u'\u{03a3}', / dot /)
118call test(u'\u{03a3}', / ![z] /)
119call test(b'\yce', / dot /)
120call test(b'\yce', / ![z] /)
121## status: 0
122## STDOUT:
123true true
124true true
125false false
126false false
127## END
128
129#### Str => startsWith(), no args
130= 'abc' => startsWith()
131## status: 3
132
133#### Str => startsWith(), too many args
134= 'abc' => startsWith('extra', 'arg')
135## status: 3
136
137#### Str => endsWith(), no args
138= 'abc' => endsWith()
139## status: 3
140
141#### Str => endsWith(), too many args
142= 'abc' => endsWith('extra', 'arg')
143## status: 3
144
145#### Str => trim*() with no args trims whitespace
146func test(s) { write --sep ', ' --j8 $[s => trimStart()] $[s => trimEnd()] $[s => trim()] }
147
148call test("")
149call test(" ")
150call test("mystr")
151call test(" mystr")
152call test("mystr ")
153call test(" mystr ")
154call test(" my str ")
155## status: 0
156## STDOUT:
157"", "", ""
158"", "", ""
159"mystr", "mystr", "mystr"
160"mystr", " mystr", "mystr"
161"mystr ", "mystr", "mystr"
162"mystr ", " mystr", "mystr"
163"my str ", " my str", "my str"
164## END
165
166#### Str => trim*() with a simple string pattern trims pattern
167func test(s, p) { write --sep ', ' --j8 $[s => trimStart(p)] $[s => trimEnd(p)] $[s => trim(p)] }
168
169call test('' , 'xyz')
170call test(' ' , 'xyz')
171call test('xy' , 'xyz')
172call test('yz' , 'xyz')
173call test('xyz' , 'xyz')
174call test('xyzxyz' , 'xyz')
175call test('xyzxyzxyz', 'xyz')
176## status: 0
177## STDOUT:
178"", "", ""
179" ", " ", " "
180"xy", "xy", "xy"
181"yz", "yz", "yz"
182"", "", ""
183"xyz", "xyz", ""
184"xyzxyz", "xyzxyz", "xyz"
185## END
186
187#### Str => trim*() with a string pattern trims bytes not runes
188func test(s, p) { write --sep ', ' --j8 $[s => trimStart(p)] $[s => trimEnd(p)] $[s => trim(p)] }
189
190call test(b'\yce\ya3', u'\u{03a3}')
191call test(b'\yce\ya3', b'\yce')
192call test(b'\yce\ya3', b'\ya3')
193## status: 0
194## STDOUT:
195"", "", ""
196b'\ya3', "Σ", b'\ya3'
197"Σ", b'\yce', b'\yce'
198## END
199
200#### Str => trim*() with an eggex pattern trims pattern
201func test(s, p) { write --sep ', ' --j8 $[s => trimStart(p)] $[s => trimEnd(p)] $[s => trim(p)] }
202
203call test('' , / 'xyz' /)
204call test(' ' , / 'xyz' /)
205call test('xy' , / 'xyz' /)
206call test('yz' , / 'xyz' /)
207call test('xyz' , / 'xyz' /)
208call test('xyzxyz' , / 'xyz' /)
209call test('xyzxyzxyz', / 'xyz' /)
210call test('xyzabcxyz', / 'xyz' /)
211call test('xyzabcxyz', / %start 'xyz' /)
212call test('xyzabcxyz', / 'xyz' %end /)
213call test('123abc123', / d+ /)
214## status: 0
215## STDOUT:
216"", "", ""
217" ", " ", " "
218"xy", "xy", "xy"
219"yz", "yz", "yz"
220"", "", ""
221"xyz", "xyz", ""
222"xyzxyz", "xyzxyz", "xyz"
223"abcxyz", "xyzabc", "abc"
224"abcxyz", "xyzabcxyz", "abcxyz"
225"xyzabcxyz", "xyzabc", "xyzabc"
226"abc123", "123abc", "abc"
227## END
228
229#### Str => trim*() with an eggex pattern trims bytes not runes
230func test(s, p) { write --sep ', ' --j8 $[s.trimStart(p)] $[s.trimEnd(p)] $[s.trim(p)] }
231
232call test(u'\u{03a3}', / dot /)
233call test(u'\u{03a3}', / ![z] /)
234call test(b'\yce', / dot /)
235call test(b'\yce', / ![z] /)
236## status: 0
237## STDOUT:
238"", "", ""
239"", "", ""
240b'\yce', b'\yce', b'\yce'
241b'\yce', b'\yce', b'\yce'
242## END
243
244#### Str => trim(), too many args
245= 'mystr' => trim('extra', 'args')
246## status: 3
247
248#### Str => trimStart(), too many args
249= 'mystr' => trimStart('extra', 'args')
250## status: 3
251
252#### Str => trimEnd(), too many args
253= 'mystr' => trimEnd('extra', 'args')
254## status: 3
255
256#### Str => trim(), unicode whitespace aware
257
258# Supported set of whitespace characters. The full set of Unicode whitespace
259# characters is not supported. See comments in the implementation.
260var spaces = [
261 b'\u{0009}', # Horizontal tab (\t)
262 b'\u{000A}', # Newline (\n)
263 b'\u{000B}', # Vertical tab (\v)
264 b'\u{000C}', # Form feed (\f)
265 b'\u{000D}', # Carriage return (\r)
266 b'\u{0020}', # Normal space
267 b'\u{00A0}', # No-break space <NBSP>
268 b'\u{FEFF}', # Zero-width no-break space <ZWNBSP>
269] => join('')
270
271echo $["$spaces YSH $spaces" => trim()]
272## status: 0
273## STDOUT:
274YSH
275## END
276
277#### Str => trim*(), unicode decoding errors
278var badUtf = b'\yF9'
279
280echo trim
281
282# We only decode UTF until the first non-space char. So the bad UTF-8 is
283# missed.
284try { call " a$[badUtf]b " => trim() }
285echo status=$_status
286
287# These require trim to decode the badUtf, so an error is raised
288try { call "$[badUtf]b " => trim() }
289echo status=$_status
290try { call " a$[badUtf]" => trim() }
291echo status=$_status
292
293# Similarly, trim{Left,Right} will assume correct encoding until shown
294# otherwise.
295echo trimStart
296try { call " a$[badUtf]" => trimStart() }
297echo status=$_status
298try { call "$[badUtf]b " => trimStart() }
299echo status=$_status
300
301echo trimEnd
302try { call "$[badUtf]b " => trimEnd() }
303echo status=$_status
304try { call " a$[badUtf]" => trimEnd() }
305echo status=$_status
306
307## STDOUT:
308trim
309status=0
310status=3
311status=3
312trimStart
313status=0
314status=3
315trimEnd
316status=0
317status=3
318## END
319
320#### Str => trimStart(), unicode decoding error types
321var badStrs = [
322 b'\yF4\yA2\yA4\yB0', # Too large of a codepoint
323 b'\yED\yBF\y80', # Surrogate
324 b'\yC1\y81', # Overlong
325 b'\y80', b'\yFF', # Does not match UTF8 bit pattern
326]
327
328for badStr in (badStrs) {
329 try { call badStr => trimStart() }
330 echo status=$_status
331}
332
333## STDOUT:
334status=3
335status=3
336status=3
337status=3
338status=3
339## END
340
341#### Str => trimEnd(), unicode decoding error types
342# Tests the backwards UTF-8 decoder
343var badStrs = [
344 b'\yF4\yA2\yA4\yB0', # Too large of a codepoint
345 b'\yED\yBF\y80', # Surrogate
346 b'\yC1\y81', # Overlong
347 b'\y80', b'\yFF', # Does not match UTF8 bit pattern
348]
349
350for badStr in (badStrs) {
351 try { call badStr => trimEnd() }
352 echo status=$_status
353}
354
355## STDOUT:
356status=3
357status=3
358status=3
359status=3
360status=3
361## END
362
363#### Str => trim*(), zero-codepoints are not NUL-terminators
364json write (b' \y00 ' => trim())
365json write (b' \y00 ' => trimStart())
366json write (b' \y00 ' => trimEnd())
367## STDOUT:
368"\u0000"
369"\u0000 "
370" \u0000"
371## END
372
373#### Str => split(sep), non-empty str sep
374pp test_ ('a,b,c'.split(','))
375pp test_ ('aa'.split('a'))
376pp test_ ('a<>b<>c<d'.split('<>'))
377pp test_ ('a;b;;c'.split(';'))
378pp test_ (''.split('foo'))
379## STDOUT:
380(List) ["a","b","c"]
381(List) ["","",""]
382(List) ["a","b","c<d"]
383(List) ["a","b","","c"]
384(List) []
385## END
386
387#### Str => split(sep), eggex sep
388pp test_ ('a,b;c'.split(/ ',' | ';' /))
389pp test_ ('aa'.split(/ dot /))
390pp test_ ('a<>b@@c<d'.split(/ '<>' | '@@' /))
391pp test_ ('a b cd'.split(/ space+ /))
392pp test_ (''.split(/ dot /))
393## STDOUT:
394(List) ["a","b","c"]
395(List) ["","",""]
396(List) ["a","b","c<d"]
397(List) ["a","b","cd"]
398(List) []
399## END
400
401#### Str => split(sep, count), non-empty str sep
402pp test_ ('a,b,c'.split(',', count=-1))
403pp test_ ('a,b,c'.split(',', count=-2)) # Any negative count means "ignore count"
404pp test_ ('aa'.split('a', count=1))
405pp test_ ('a<>b<>c<d'.split('<>', count=10))
406pp test_ ('a;b;;c'.split(';', count=2))
407pp test_ (''.split('foo', count=3))
408pp test_ ('a,b,c'.split(',', count=0))
409pp test_ (''.split(',', count=0))
410## STDOUT:
411(List) ["a","b","c"]
412(List) ["a","b","c"]
413(List) ["","a"]
414(List) ["a","b","c<d"]
415(List) ["a","b",";c"]
416(List) []
417(List) ["a,b,c"]
418(List) []
419## END
420
421#### Str => split(sep, count), eggex sep
422pp test_ ('a,b;c'.split(/ ',' | ';' /, count=-1))
423pp test_ ('aa'.split(/ dot /, count=1))
424pp test_ ('a<>b@@c<d'.split(/ '<>' | '@@' /, count=50))
425pp test_ ('a b c'.split(/ space+ /, count=0))
426pp test_ (''.split(/ dot /, count=1))
427## STDOUT:
428(List) ["a","b","c"]
429(List) ["","a"]
430(List) ["a","b","c<d"]
431(List) ["a b c"]
432(List) []
433## END
434
435#### Str => split(), usage errors
436try { pp test_ ('abc'.split('')) } # Sep cannot be ""
437echo status=$[_error.code]
438try { pp test_ ('abc'.split()) } # Sep must be present
439echo status=$[_error.code]
440try { pp test_ (b'\y00a\y01'.split(/ 'a' /)) } # Cannot split by eggex when str has NUL-byte
441echo status=$[_error.code]
442try { pp test_ (b'abc'.split(/ space* /)) } # Eggex cannot accept empty string
443echo status=$[_error.code]
444try { pp test_ (b'abc'.split(/ dot* /)) } # But in some cases the input doesn't cause an
445 # infinite loop, so we actually allow it!
446echo status=$[_error.code]
447## STDOUT:
448status=3
449status=3
450status=3
451status=3
452(List) ["",""]
453status=0
454## END
455
456#### Str => split(), non-ascii
457pp test_ ('🌞🌝🌞🌝🌞'.split('🌝'))
458pp test_ ('🌞🌝🌞🌝🌞'.split(/ '🌝' /))
459## STDOUT:
460(List) ["🌞","🌞","🌞"]
461(List) ["🌞","🌞","🌞"]
462## END
463
464#### Str.lines()
465
466var empty = u''
467pp test_ (empty.lines())
468
469var one = u'\n'
470pp test_ (one.lines())
471
472var two = u'\n\n'
473pp test_ (two.lines())
474
475var three = u'one\ntwo\nthree' # no trailing newline
476pp test_ (three.lines())
477
478var foo = u'foo' # no trailing newline
479pp test_ (foo.lines())
480
481echo
482
483var s = u'foo\nbar\n'
484pp test_ (s.lines())
485
486var z = b'foo\y00bar\y00'
487pp test_ (z.lines(eol=\y00))
488
489var r = u'foo\r\nbar\r\n'
490pp test_ (r.lines(eol=u'\r\n'))
491
492echo
493
494pp test_ (empty.lines(eol=\y00))
495pp test_ (z.lines())
496pp test_ (r.lines())
497
498## STDOUT:
499(List) []
500(List) [""]
501(List) ["",""]
502(List) ["one","two","three"]
503(List) ["foo"]
504
505(List) ["foo","bar"]
506(List) ["foo","bar"]
507(List) ["foo","bar"]
508
509(List) []
510(List) ["foo\u0000bar\u0000"]
511(List) ["foo\r","bar\r"]
512## END
513
514#### read-lines can be built on top of read --all and Str.lines()
515
516proc read-lines (; out) {
517 read --all
518 call out->setValue(_reply.lines())
519}
520
521seq 4 7 > tmp.txt
522
523read-lines (&x) < tmp.txt
524
525pp test_ (x)
526
527## STDOUT:
528(List) ["4","5","6","7"]
529## END
530
531#### Dict => values()
532var en2fr = {}
533setvar en2fr["hello"] = "bonjour"
534setvar en2fr["friend"] = "ami"
535setvar en2fr["cat"] = "chat"
536pp test_ (en2fr => values())
537## status: 0
538## STDOUT:
539(List) ["bonjour","ami","chat"]
540## END
541
542#### Dict -> erase()
543var book = {title: "The Histories", author: "Herodotus"}
544call book->erase("author")
545pp test_ (book)
546# confirm method is idempotent
547call book->erase("author")
548pp test_ (book)
549## status: 0
550## STDOUT:
551(Dict) {"title":"The Histories"}
552(Dict) {"title":"The Histories"}
553## END
554
555#### Dict -> get()
556var book = {title: "Hitchhiker's Guide", published: 1979}
557pp test_ (book => get("title", ""))
558pp test_ (book => get("published", 0))
559pp test_ (book => get("author", ""))
560## status: 0
561## STDOUT:
562(Str) "Hitchhiker's Guide"
563(Int) 1979
564(Str) ""
565## END
566
567#### Separation of -> attr and () calling
568const check = "abc" => startsWith
569pp test_ (check("a"))
570## status: 0
571## STDOUT:
572(Bool) true
573## END
574
575#### Bound methods, receiver value/reference semantics
576var is_a_ref = { "foo": "bar" }
577const f = is_a_ref => keys
578pp test_ (f())
579setvar is_a_ref["baz"] = 42
580pp test_ (f())
581
582var is_a_val = "abc"
583const g = is_a_val => startsWith
584pp test_ (g("a"))
585setvar is_a_val = "xyz"
586pp test_ (g("a"))
587## status: 0
588## STDOUT:
589(List) ["foo"]
590(List) ["foo","baz"]
591(Bool) true
592(Bool) true
593## END
594
595#### List->clear()
596var empty = []
597var items = [1, 2, 3]
598
599call empty->clear()
600call items->clear()
601
602pp test_ (empty)
603pp test_ (items)
604
605## STDOUT:
606(List) []
607(List) []
608## END
609
610#### List => indexOf()
611var items = [1, '2', 3, { 'a': 5 }]
612
613json write (items => indexOf('a'))
614json write (items => indexOf(1))
615json write (items => indexOf('2'))
616json write (items => indexOf({'a': 5}))
617## STDOUT:
618-1
6190
6201
6213
622## END
623
624#### List => lastIndexOf()
625var items = ['a', 'b', 'c', 'b', 'a']
626
627json write (items => lastIndexOf('a'))
628json write (items => lastIndexOf('b'))
629json write (items => lastIndexOf('c'))
630json write (items => lastIndexOf('d'))
631## STDOUT:
6324
6333
6342
635-1
636## END
637
638#### List => join()
639var items = [1, 2, 3]
640
641json write (items => join()) # default separator is ''
642json write (items => join(" ")) # explicit separator (can be any number or chars)
643json write (items => join(", ")) # separator can be any number of chars
644
645try {
646 json write (items => join(1)) # separator must be a string
647}
648echo "failed with status $_status"
649## STDOUT:
650"123"
651"1 2 3"
652"1, 2, 3"
653failed with status 3
654## END
655
656#### List->reverse()
657
658var empty = []
659
660var a = [0]
661var b = [2, 1, 3]
662var c = :| hello world |
663
664call empty->reverse()
665call a->reverse()
666call b->reverse()
667call c->reverse()
668
669pp test_ (empty)
670pp test_ (a)
671pp test_ (b)
672pp test_ (c)
673
674## STDOUT:
675(List) []
676(List) [0]
677(List) [3,1,2]
678(List) ["world","hello"]
679## END
680
681#### List->reverse() from iterator
682var x = list(0 ..< 3)
683call x->reverse()
684write @x
685## STDOUT:
6862
6871
6880
689## END
690