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

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