1before:
2  base_module         = "string"
3  this_module         = "std.string"
4  global_table        = "_G"
5
6  extend_base         = { "__concat", "__index",
7                          "caps", "chomp", "escape_pattern", "escape_shell",
8                          "finds", "format", "ltrim", "monkey_patch",
9                          "numbertosi", "ordinal_suffix", "pad", "pickle",
10                          "prettytostring", "render", "rtrim", "split",
11                          "tfind", "trim", "wrap" }
12  deprecations        = { "assert", "require_version", "tostring" }
13
14  M = require (this_module)
15  getmetatable ("").__concat = M.__concat
16  getmetatable ("").__index = M.__index
17
18specify std.string:
19- before:
20    subject = "a string \n\n"
21
22- context when required:
23  - context by name:
24    - it does not touch the global table:
25        expect (show_apis {added_to=global_table, by=this_module}).
26          to_equal {}
27    - it does not touch the core string table:
28        expect (show_apis {added_to=base_module, by=this_module}).
29          to_equal {}
30    - it contains apis from the core string table:
31        apis = require "std.base".copy (extend_base)
32        for _, v in ipairs (deprecations) do
33          apis[#apis + 1] = v
34        end
35        expect (show_apis {from=base_module, not_in=this_module}).
36          to_contain.a_permutation_of (apis)
37
38  - context via the std module:
39    - it does not touch the global table:
40        expect (show_apis {added_to=global_table, by="std"}).
41          to_equal {}
42    - it does not touch the core string table:
43        expect (show_apis {added_to=base_module, by="std"}).
44          to_equal {}
45
46- describe ..:
47  - it concatenates string arguments:
48      target = "a string \n\n another string"
49      expect (subject .. " another string").to_be (target)
50  - it stringifies non-string arguments:
51      argument = { "a table" }
52      expect (subject .. argument).
53        to_be (string.format ("%s%s", subject, require "std".tostring (argument)))
54  - it stringifies nil arguments:
55      argument = nil
56      expect (subject .. argument).
57        to_be (string.format ("%s%s", subject, require "std".tostring (argument)))
58  - it does not perturb the original subject:
59      original = subject
60      newstring = subject .. " concatenate something"
61      expect (subject).to_be (original)
62
63
64- describe assert:
65  - before:
66      f = M.assert
67
68  - it writes a deprecation warning:
69      setdebug { deprecate = "nil" }
70      expect (capture (f, {"std.string"})).to_contain_error "was deprecated"
71      setdebug { deprecate = false }
72      expect (capture (f, {"std.string"})).not_to_contain_error "was deprecated"
73
74  - context when it does not trigger:
75    - it has a truthy initial argument:
76        expect (f (1)).not_to_raise "any error"
77        expect (f (true)).not_to_raise "any error"
78        expect (f "yes").not_to_raise "any error"
79        expect (f (false == false)).not_to_raise "any error"
80    - it returns the initial argument:
81        expect (f (1)).to_be (1)
82        expect (f (true)).to_be (true)
83        expect (f "yes").to_be "yes"
84        expect (f (false == false)).to_be (true)
85  - context when it triggers:
86    - it has a falsey initial argument:
87        expect (f ()).to_raise ()
88        expect (f (false)).to_raise ()
89        expect (f (1 == 0)).to_raise ()
90    - it throws an optional error string:
91        expect (f (false, "ah boo")).to_raise "ah boo"
92    - it plugs specifiers with string.format: |
93        expect (f (nil, "%s %d: %q", "here", 42, "a string")).
94          to_raise (string.format ("%s %d: %q", "here", 42, "a string"))
95
96
97- describe caps:
98  - before:
99      f = M.caps
100
101  - context with bad arguments:
102      badargs.diagnose (f, "std.string.caps (string)")
103
104  - it capitalises words of a string:
105      target = "A String \n\n"
106      expect (f (subject)).to_be (target)
107  - it changes only the first letter of each word:
108      expect (f "a stRiNg").to_be "A StRiNg"
109  - it is available as a string metamethod:
110      expect (("a stRiNg"):caps ()).to_be "A StRiNg"
111  - it does not perturb the original subject:
112      original = subject
113      newstring = f (subject)
114      expect (subject).to_be (original)
115
116
117- describe chomp:
118  - before:
119      target = "a string \n"
120      f = M.chomp
121
122  - context with bad arguments:
123      badargs.diagnose (f, "std.string.chomp (string)")
124
125  - it removes a single trailing newline from a string:
126      expect (f (subject)).to_be (target)
127  - it does not change a string with no trailing newline:
128      subject = "a string "
129      expect (f (subject)).to_be (subject)
130  - it is available as a string metamethod:
131      expect (subject:chomp ()).to_be (target)
132  - it does not perturb the original subject:
133      original = subject
134      newstring = f (subject)
135      expect (subject).to_be (original)
136
137
138- describe escape_pattern:
139  - before:
140      magic = {}
141      meta = "^$()%.[]*+-?"
142      for i = 1, string.len (meta) do
143        magic[meta:sub (i, i)] = true
144      end
145      f = M.escape_pattern
146
147  - context with bad arguments:
148      badargs.diagnose (f, "std.string.escape_pattern (string)")
149
150  - context with each printable ASCII char:
151    - before:
152        subject, target = "", ""
153        for c = 32, 126 do
154          s = string.char (c)
155          subject = subject .. s
156          if magic[s] then target = target .. "%" end
157          target = target .. s
158        end
159    - "it inserts a % before any non-alphanumeric in a string":
160        expect (f (subject)).to_be (target)
161    - it is available as a string metamethod:
162        expect (subject:escape_pattern ()).to_be (target)
163  - it does not perturb the original subject:
164      original = subject
165      newstring = f (subject)
166      expect (subject).to_be (original)
167
168
169- describe escape_shell:
170  - before:
171      f = M.escape_shell
172
173  - context with bad arguments:
174      badargs.diagnose (f, "std.string.escape_shell (string)")
175
176  - context with each printable ASCII char:
177    - before:
178        subject, target = "", ""
179        for c = 32, 126 do
180          s = string.char (c)
181          subject = subject .. s
182          if s:match ("[][ ()\\\"']") then target = target .. "\\" end
183          target = target .. s
184        end
185    - "it inserts a \\ before any shell metacharacters":
186        expect (f (subject)).to_be (target)
187    - it is available as a string metamethod:
188        expect (subject:escape_shell ()).to_be (target)
189  - it does not perturb the original subject:
190      original = subject
191      newstring = f (subject)
192      expect (subject).to_be (original)
193  - "it diagnoses non-string arguments":
194      expect (f ()).to_raise ("string expected")
195      expect (f {"a table"}).to_raise ("string expected")
196
197
198- describe finds:
199  - before:
200      subject = "abcd"
201      f = M.finds
202
203  - context with bad arguments:
204      badargs.diagnose (f, "std.string.finds (string, string, ?int, ?boolean|:plain)")
205
206  - context given a complex nested list:
207    - before:
208        target = { { 1, 2; capt = { "a", "b" } }, { 3, 4; capt = { "c", "d" } } }
209    - it creates a list of pattern captures:
210        expect ({f (subject, "(.)(.)")}).to_equal ({ target })
211    - it is available as a string metamethod:
212        expect ({subject:finds ("(.)(.)")}).to_equal ({ target })
213  - it creates an empty list where no captures are matched:
214      target = {}
215      expect ({f (subject, "(x)")}).to_equal ({ target })
216  - it creates an empty list for a pattern without captures:
217      target = { { 1, 1; capt = {} } }
218      expect ({f (subject, "a")}).to_equal ({ target })
219  - it starts the search at a specified index into the subject:
220      target = { { 8, 9; capt = { "a", "b" } }, { 10, 11; capt = { "c", "d" } } }
221      expect ({f ("garbage" .. subject, "(.)(.)", 8)}).to_equal ({ target })
222  - it does not perturb the original subject:
223      original = subject
224      newstring = f (subject, "...")
225      expect (subject).to_be (original)
226
227
228- describe format:
229  - before:
230      subject = "string=%s, number=%d"
231
232      f = M.format
233
234  - context with bad arguments:
235      badargs.diagnose (f, "std.string.format (string, ?any*)")
236
237  - it returns a single argument without attempting formatting:
238      expect (f (subject)).to_be (subject)
239  - it is available as a string metamethod:
240      expect (subject:format ()).to_be (subject)
241  - it does not perturb the original subject:
242      original = subject
243      newstring = f (subject)
244      expect (subject).to_be (original)
245
246
247- describe ltrim:
248  - before:
249      subject = " \t\r\n  a  short  string  \t\r\n   "
250
251      f = M.ltrim
252
253  - context with bad arguments:
254      badargs.diagnose (f, "std.string.ltrim (string, ?string)")
255
256  - it removes whitespace from the start of a string:
257      target = "a  short  string  \t\r\n   "
258      expect (f (subject)).to_equal (target)
259  - it supports custom removal patterns:
260      target = "\r\n  a  short  string  \t\r\n   "
261      expect (f (subject, "[ \t\n]+")).to_equal (target)
262  - it is available as a string metamethod:
263      target = "\r\n  a  short  string  \t\r\n   "
264      expect (subject:ltrim ("[ \t\n]+")).to_equal (target)
265  - it does not perturb the original subject:
266      original = subject
267      newstring = f (subject, "%W")
268      expect (subject).to_be (original)
269
270
271- describe monkey_patch:
272  - before:
273      f = M.monkey_patch
274
275  - context with bad arguments:
276      badargs.diagnose (f, "std.string.monkey_patch (?table)")
277
278  # Ideally, `.to_be (M)`, except that M is cloned from a nested context
279  # by Specl to prevent us from affecting any other examples, thus the
280  # address is different by now.
281  - it returns std.string module table:
282      expect (f {}).to_equal (M)
283  - it injects std.string apis into given namespace:
284      namespace = {}
285      f (namespace)
286      for _, api in ipairs (extend_base) do
287        expect (namespace.string[api]).to_be (M[api])
288      end
289  - it installs string metamethods:
290      # FIXME: string metatable monkey-patches leak out!
291      mt = getmetatable ""
292      expect (mt.__concat).to_be (M.__concat)
293      expect (mt.__index).to_be (M.__index)
294
295
296- describe numbertosi:
297  - before:
298      f = M.numbertosi
299
300  - context with bad arguments:
301      badargs.diagnose (f, "std.string.numbertosi (number|string)")
302
303  - it returns a number using SI suffixes:
304      target = {"1e-9", "1y", "1z", "1a", "1f", "1p", "1n", "1mu", "1m", "1",
305                "1k", "1M", "1G", "1T", "1P", "1E", "1Z", "1Y", "1e9"}
306      subject = {}
307      for n = -28, 28, 3 do
308        m = 10 * (10 ^ n)
309        table.insert (subject, f (m))
310      end
311      expect (subject).to_equal (target)
312  - it coerces string arguments to a number:
313      expect (f "1000").to_be "1k"
314
315
316- describe ordinal_suffix:
317  - before:
318      f = M.ordinal_suffix
319
320  - context with bad arguments:
321      badargs.diagnose (f, "std.string.ordinal_suffix (int|string)")
322
323  - it returns the English suffix for a number:
324      subject, target = {}, {}
325      for n = -120, 120 do
326        suffix = "th"
327        m = math.abs (n) % 10
328        if m == 1 and math.abs (n) % 100 ~= 11 then suffix = "st"
329        elseif m == 2 and math.abs (n) % 100 ~= 12 then suffix = "nd"
330        elseif m == 3 and math.abs (n) % 100 ~= 13 then suffix = "rd"
331        end
332        table.insert (target, n .. suffix)
333        table.insert (subject, n .. f (n))
334      end
335      expect (subject).to_equal (target)
336  - it coerces string arguments to a number:
337      expect (f "-91").to_be "st"
338
339
340- describe pad:
341  - before:
342      width = 20
343
344      f = M.pad
345
346  - context with bad arguments:
347      badargs.diagnose (f, "std.string.pad (string, int, ?string)")
348
349  - context when string is shorter than given width:
350    - before:
351        subject = "short string"
352    - it right pads a string to the given width with spaces:
353        target = "short string        "
354        expect (f (subject, width)).to_be (target)
355    - it left pads a string to the given negative width with spaces:
356        width = -width
357        target = "        short string"
358        expect (f (subject, width)).to_be (target)
359    - it is available as a string metamethod:
360        target = "short string        "
361        expect (subject:pad (width)).to_be (target)
362
363  - context when string is longer than given width:
364    - before:
365        subject = "a string that's longer than twenty characters"
366    - it truncates a string to the given width:
367        target = "a string that's long"
368        expect (f (subject, width)).to_be (target)
369    - it left pads a string to given width with spaces:
370        width = -width
371        target = "an twenty characters"
372        expect (f (subject, width)).to_be (target)
373    - it is available as a string metamethod:
374        target = "a string that's long"
375        expect (subject:pad (width)).to_be (target)
376
377  - it does not perturb the original subject:
378      original = subject
379      newstring = f (subject, width)
380      expect (subject).to_be (original)
381
382
383- describe pickle:
384  - before:
385      loadstring = loadstring or load
386      function unpickle (s) return loadstring ("return " .. s) () end
387      t = {1, {{2, 3}, 4, {5}}}
388      f = M.pickle
389  - it converts a primitive to a representative string:
390      expect (f (nil)).to_be "nil"
391      expect (f (false)).to_be "false"
392      expect (f (42)).to_be "42"
393      expect (f "string").to_be '"string"'
394  - it returns a loadable string that results in the original value:
395      expect (unpickle (f (nil))).to_be (nil)
396      expect (unpickle (f (false))).to_be (false)
397      expect (unpickle (f (42))).to_be (42)
398      expect (unpickle (f "string")).to_be "string"
399  - it converts a table to a representative string:
400      expect (f {"table", 42}).to_be '{[1]="table",[2]=42}'
401  - it returns a loadable string that results in the original table:
402      expect (unpickle (f {"table", 42})).to_equal {"table", 42}
403  - it converts a nested table to a representative string:
404      expect (f (t)).
405        to_be "{[1]=1,[2]={[1]={[1]=2,[2]=3},[2]=4,[3]={[1]=5}}}"
406  - it returns a loadable string that results in the original nested table:
407      expect (unpickle (f (t))).to_equal (t)
408
409
410- describe prettytostring:
411  - before:
412      f = M.prettytostring
413
414  - context with bad arguments:
415      badargs.diagnose (f, "std.string.prettytostring (?any, ?string, ?string)")
416
417  - it renders nil exactly like system tostring:
418      expect (f (nil)).to_be (tostring (nil))
419  - it renders booleans exactly like system tostring:
420      expect (f (true)).to_be (tostring (true))
421      expect (f (false)).to_be (tostring (false))
422  - it renders numbers exactly like system tostring:
423      n = 8723643
424      expect (f (n)).to_be (tostring (n))
425  - it renders functions exactly like system tostring:
426      expect (f (f)).to_be (tostring (f))
427  - it renders strings with format "%q" styling:
428      s = "a string"
429      expect (f (s)).to_be (string.format ("%q", s))
430  - it renders empty tables as a pair of braces:
431      expect (f {}).to_be ("{\n}")
432  - it renders an array prettily:
433      a = {"one", "two", "three"}
434      expect (f (a, "")).
435        to_be '{\n[1] = "one",\n[2] = "two",\n[3] = "three",\n}'
436  - it renders a table prettily:
437      t = { one = true, two = 2, three = {3}}
438      expect (f (t, "")).
439        to_be '{\none = true,\nthree =\n{\n[1] = 3,\n},\ntwo = 2,\n}'
440  - it renders table keys in table.sort order:
441      t = { one = 3, two = 5, three = 4, four = 2, five = 1 }
442      expect (f (t, "")).
443        to_be '{\nfive = 1,\nfour = 2,\none = 3,\nthree = 4,\ntwo = 5,\n}'
444  - it renders keys with invalid symbol names in long hand:
445      t = { _ = 0, word = 0, ["?"] = 1, ["a-key"] = 1, ["[]"] = 1 }
446      expect (f (t, "")).
447        to_be '{\n["?"] = 1,\n["[]"] = 1,\n_ = 0,\n["a-key"] = 1,\nword = 0,\n}'
448
449
450- describe render:
451  - before:
452      term = function (s) return function () return s end end
453      pair = function (_, _, _, i, v) return i .. "=" .. v end
454      sep  = function (_, i, _, j) return (i and j) and "," or "" end
455      r = function (x)
456            return M.render (x, term "{", term "}", tostring, pair, sep)
457          end
458      t = {1, {{2, 3}, 4, {5}}}
459
460      f = M.render
461
462  - context with bad arguments:
463      badargs.diagnose (f, "std.string.render (?any, func, func, func, func, func, ?table)")
464
465  - it converts a primitive to a representative string:
466      expect (r (nil)).to_be "nil"
467      expect (r (false)).to_be "false"
468      expect (r (42)).to_be "42"
469      expect (r ("string")).to_be "string"
470  - it converts a table to a representative string:
471      expect (r ({"table", 42})).to_be '{1=table,2=42}'
472  - it converts a nested table to a representative string:
473      expect (r (t)).
474        to_be "{1=1,2={1={1=2,2=3},2=4,3={1=5}}}"
475  - it converts a recursive table to a representative string:
476      t[1] = t
477      expect (r (t)).
478        to_be ("{1="..tostring (t)..",2={1={1=2,2=3},2=4,3={1=5}}}")
479
480
481- describe require_version:
482  - before:
483      f = M.require_version
484
485  - it writes a deprecation warning:
486      setdebug { deprecate = "nil" }
487      expect (capture (f, {"std.string"})).to_contain_error "was deprecated"
488      setdebug { deprecate = false }
489      expect (capture (f, {"std.string"})).not_to_contain_error "was deprecated"
490
491  - it diagnoses non-existent module:
492      expect (f ("module-not-exists", "", "")).to_raise "module-not-exists"
493  - it diagnoses module too old:
494      expect (f ("std", "9999", "9999")).to_raise ()
495  - it diagnoses module too new:
496      expect (f ("std", "0", "0")).to_raise ()
497  - context when the module version is compatible:
498    - it returns the module table:
499        expect (f ("std", "0", "9999")).to_be (require "std")
500    - it places no upper bound by default:
501        expect (f ("std", "41")).to_be (require "std")
502    - it places no lower bound by default:
503        expect (f "std").to_be (require "std")
504    - it uses _VERSION when version field is nil:
505        std = require "std"
506        std._VERSION, std.version = std.version, std._VERSION
507        expect (f ("std", "41", "9999")).to_be (require "std")
508        std._VERSION, std.version = std.version, std._VERSION
509  - context with semantic versioning:
510    - before:
511        std = require "std"
512        ver = std.version
513        std.version = "1.2.3"
514    - after:
515        std.version = ver
516    - it diagnoses module too old:
517        expect (f ("std", "1.2.4")).to_raise ()
518        expect (f ("std", "1.3")).to_raise ()
519        expect (f ("std", "2.1.2")).to_raise ()
520        expect (f ("std", "2")).to_raise ()
521        expect (f ("std", "1.2.10")).to_raise ()
522    - it diagnoses module too new:
523        expect (f ("std", nil, "1.2.2")).to_raise ()
524        expect (f ("std", nil, "1.1")).to_raise ()
525        expect (f ("std", nil, "1.1.2")).to_raise ()
526        expect (f ("std", nil, "1")).to_raise ()
527    - it returns modules with version in range:
528        expect (f ("std")).to_be (std)
529        expect (f ("std", "1")).to_be (std)
530        expect (f ("std", "1.2.3")).to_be (std)
531        expect (f ("std", nil, "2")).to_be (std)
532        expect (f ("std", nil, "1.3")).to_be (std)
533        expect (f ("std", nil, "1.2.10")).to_be (std)
534        expect (f ("std", "1.2.3", "1.2.4")).to_be (std)
535
536
537- describe rtrim:
538  - before:
539      subject = " \t\r\n  a  short  string  \t\r\n   "
540
541      f = M.rtrim
542
543  - context with bad arguments:
544      badargs.diagnose (f, "std.string.rtrim (string, ?string)")
545
546  - it removes whitespace from the end of a string:
547      target = " \t\r\n  a  short  string"
548      expect (f (subject)).to_equal (target)
549  - it supports custom removal patterns:
550      target = " \t\r\n  a  short  string  \t\r"
551      expect (f (subject, "[ \t\n]+")).to_equal (target)
552  - it is available as a string metamethod:
553      target = " \t\r\n  a  short  string  \t\r"
554      expect (subject:rtrim ("[ \t\n]+")).to_equal (target)
555  - it does not perturb the original subject:
556      original = subject
557      newstring = f (subject, "%W")
558      expect (subject).to_be (original)
559
560
561- describe split:
562  - before:
563      target = { "first", "the second one", "final entry" }
564      subject = table.concat (target, ", ")
565
566      f = M.split
567
568  - context with bad arguments:
569      badargs.diagnose (f, "std.string.split (string, ?string)")
570
571  - it falls back to "%s+" when no pattern is given:
572      expect (f (subject)).
573        to_equal {"first,", "the", "second", "one,", "final", "entry"}
574  - it returns a one-element list for an empty string:
575      expect (f ("", ", ")).to_equal {""}
576  - it makes a table of substrings delimited by a separator:
577      expect (f (subject,  ", ")).to_equal (target)
578  - it returns n+1 elements for n separators:
579      expect (f (subject, "zero")).to_have_size (1)
580      expect (f (subject, "c")).to_have_size (2)
581      expect (f (subject, "s")).to_have_size (3)
582      expect (f (subject, "t")).to_have_size (4)
583      expect (f (subject, "e")).to_have_size (5)
584  - it returns an empty string element for consecutive separators:
585      expect (f ("xyzyzxy", "yz")).to_equal {"x", "", "xy"}
586  - it returns an empty string element when starting with separator:
587      expect (f ("xyzyzxy", "xyz")).to_equal {"", "yzxy"}
588  - it returns an empty string element when ending with separator:
589      expect (f ("xyzyzxy", "zxy")).to_equal {"xyzy", ""}
590  - it returns a table of 1-character strings for "" separator:
591      expect (f ("abcdef", "")).to_equal {"", "a", "b", "c", "d", "e", "f", ""}
592  - it is available as a string metamethod:
593      expect (subject:split ", ").to_equal (target)
594      expect (("/foo/bar/baz.quux"):split "/").
595        to_equal {"", "foo", "bar", "baz.quux"}
596  - it does not perturb the original subject:
597      original = subject
598      newstring = f (subject, "e")
599      expect (subject).to_be (original)
600  - it takes a Lua pattern as a separator:
601      expect (f (subject, "%s+")).
602        to_equal {"first,", "the", "second", "one,", "final", "entry"}
603
604
605- describe tfind:
606  - before:
607      subject = "abc"
608
609      f = M.tfind
610
611  - context with bad arguments:
612      badargs.diagnose (f, "std.string.tfind (string, string, ?int, ?boolean|:plain)")
613
614  - it creates a list of pattern captures:
615      target = { 1, 3, { "a", "b", "c" } }
616      expect ({f (subject, "(.)(.)(.)")}).to_equal (target)
617  - it creates an empty list where no captures are matched:
618      target = { nil, nil, {} }
619      expect ({f (subject, "(x)(y)(z)")}).to_equal (target)
620  - it creates an empty list for a pattern without captures:
621      target = { 1, 1, {} }
622      expect ({f (subject, "a")}).to_equal (target)
623  - it starts the search at a specified index into the subject:
624      target = { 8, 10, { "a", "b", "c" } }
625      expect ({f ("garbage" .. subject, "(.)(.)(.)", 8)}).to_equal (target)
626  - it is available as a string metamethod:
627      target = { 8, 10, { "a", "b", "c" } }
628      expect ({("garbage" .. subject):tfind ("(.)(.)(.)", 8)}).to_equal (target)
629  - it does not perturb the original subject:
630      original = subject
631      newstring = f (subject, "...")
632      expect (subject).to_be (original)
633
634
635- describe tostring:
636  - before:
637      f = M.tostring
638
639  - it writes a deprecation warning:
640      setdebug { deprecate = "nil" }
641      expect (capture (f, {"std.string"})).to_contain_error "was deprecated"
642      setdebug { deprecate = false }
643      expect (capture (f, {"std.string"})).not_to_contain_error "was deprecated"
644
645  - it renders primitives exactly like system tostring:
646      expect (f (nil)).to_be (tostring (nil))
647      expect (f (false)).to_be (tostring (false))
648      expect (f (42)).to_be (tostring (42))
649      expect (f (f)).to_be (tostring (f))
650      expect (f "a string").to_be "a string"
651  - it renders empty tables as a pair of braces:
652      expect (f {}).to_be ("{}")
653  - it renders table array part compactly:
654      expect (f {"one", "two", "five"}).
655        to_be '{1=one,2=two,3=five}'
656  - it renders a table dictionary part compactly:
657      expect (f { one = true, two = 2, three = {3}}).
658        to_be '{one=true,three={1=3},two=2}'
659  - it renders table keys in table.sort order:
660      expect (f { one = 3, two = 5, three = 4, four = 2, five = 1 }).
661        to_be '{five=1,four=2,one=3,three=4,two=5}'
662  - it renders keys with invalid symbol names compactly:
663      expect (f { _ = 0, word = 0, ["?"] = 1, ["a-key"] = 1, ["[]"] = 1 }).
664        to_be '{?=1,[]=1,_=0,a-key=1,word=0}'
665
666
667- describe trim:
668  - before:
669      subject = " \t\r\n  a  short  string  \t\r\n   "
670
671      f = M.trim
672
673  - context with bad arguments:
674      badargs.diagnose (f, "std.string.trim (string, ?string)")
675
676  - it removes whitespace from each end of a string:
677      target = "a  short  string"
678      expect (f (subject)).to_equal (target)
679  - it supports custom removal patterns:
680      target = "\r\n  a  short  string  \t\r"
681      expect (f (subject, "[ \t\n]+")).to_equal (target)
682  - it is available as a string metamethod:
683      target = "\r\n  a  short  string  \t\r"
684      expect (subject:trim ("[ \t\n]+")).to_equal (target)
685  - it does not perturb the original subject:
686      original = subject
687      newstring = f (subject, "%W")
688      expect (subject).to_be (original)
689
690
691- describe wrap:
692  - before:
693      subject = "This is a collection of Lua libraries for Lua 5.1 " ..
694        "and 5.2. The libraries are copyright by their authors 2000" ..
695        "-2015 (see the AUTHORS file for details), and released und" ..
696        "er the MIT license (the same license as Lua itself). There" ..
697        " is no warranty."
698
699      f = M.wrap
700
701  - context with bad arguments:
702      badargs.diagnose (f, "std.string.wrap (string, ?int, ?int, ?int)")
703
704  - it inserts newlines to wrap a string:
705      target = "This is a collection of Lua libraries for Lua 5.1 a" ..
706        "nd 5.2. The libraries are\ncopyright by their authors 2000" ..
707        "-2015 (see the AUTHORS file for details), and\nreleased un" ..
708        "der the MIT license (the same license as Lua itself). Ther" ..
709        "e is no\nwarranty."
710      expect (f (subject)).to_be (target)
711  - it honours a column width parameter:
712      target = "This is a collection of Lua libraries for Lua 5.1 a" ..
713        "nd 5.2. The libraries\nare copyright by their authors 2000" ..
714        "-2015 (see the AUTHORS file for\ndetails), and released un" ..
715        "der the MIT license (the same license as Lua\nitself). The" ..
716        "re is no warranty."
717      expect (f (subject, 72)).to_be (target)
718  - it supports indenting by a fixed number of columns:
719      target = "        This is a collection of Lua libraries for L" ..
720        "ua 5.1 and 5.2. The\n        libraries are copyright by th" ..
721        "eir authors 2000-2015 (see the\n        AUTHORS file for d" ..
722        "etails), and released under the MIT license\n        (the " ..
723        "same license as Lua itself). There is no warranty."
724      expect (f (subject, 72, 8)).to_be (target)
725  - context given a long unwrapped string:
726    - before:
727        target = "    This is a collection of Lua libraries for Lua 5" ..
728          ".1 and 5.2.\n  The libraries are copyright by their author" ..
729          "s 2000-2015 (see\n  the AUTHORS file for details), and rel" ..
730          "eased under the MIT\n  license (the same license as Lua it" ..
731          "self). There is no\n  warranty."
732    - it can indent the first line differently:
733        expect (f (subject, 64, 2, 4)).to_be (target)
734    - it is available as a string metamethod:
735        expect (subject:wrap (64, 2, 4)).to_be (target)
736  - it does not perturb the original subject:
737      original = subject
738      newstring = f (subject, 55, 5)
739      expect (subject).to_be (original)
740  - it diagnoses indent greater than line width:
741      expect (f (subject, 10, 12)).to_raise ("less than the line width")
742      expect (f (subject, 99, 99)).to_raise ("less than the line width")
743  - it diagnoses non-string arguments:
744      expect (f ()).to_raise ("string expected")
745      expect (f {"a table"}).to_raise ("string expected")
746