unicode: added strip, align, alignLeft, repeat procs; refs #6301

This commit is contained in:
Andreas Rumpf
2018-10-14 11:00:22 +02:00
parent 77a8c6ffd8
commit 8960efa340
2 changed files with 151 additions and 2 deletions

View File

@@ -22,8 +22,8 @@
- There is a new stdlib module `editdistance` as a replacement for the
deprecated `strutils.editDistance`.
- Added `split`, `splitWhitespace`, `size` procs and iterators
to `unicode.nim`.
- Added `split`, `splitWhitespace`, `size`, `alignLeft`, `align`,
`strip`, `repeat` procs and iterators to `unicode.nim`.
### Library changes

View File

@@ -1892,6 +1892,121 @@ proc split*(s: string, sep: Rune, maxsplit: int = -1): seq[string] {.noSideEffec
## that returns a sequence of substrings.
accResult(split(s, sep, maxsplit))
proc strip*(s: string, leading = true, trailing = true,
runes: openarray[Rune] = unicodeSpaces): string {.noSideEffect,
rtl, extern: "nucStrip".} =
## Strips leading or trailing `runes` from `s` and returns
## the resulting string.
##
## If `leading` is true, leading `runes` are stripped.
## If `trailing` is true, trailing `runes` are stripped.
## If both are false, the string is returned unchanged.
var
s_i = 0 ## starting index into string ``s``
e_i = len(s) - 1 ## ending index into ``s``, where the last ``Rune`` starts
if leading:
var
i = 0
l_i: int ## value of ``s_i`` at the beginning of the iteration
rune: Rune
while i < len(s):
l_i = i
fastRuneAt(s, i, rune)
s_i = i # Assume to start from next rune
if not runes.contains(rune):
s_i = l_i # Go back to where the current rune starts
break
if trailing:
var
i = e_i
l_i: int
rune: Rune
while i >= 0:
l_i = i
fastRuneAt(s, l_i, rune)
var p_i = i - 1
while p_i >= 0:
var
p_i_end = p_i
p_rune: Rune
fastRuneAt(s, p_i_end, p_rune)
if p_i_end < l_i: break
i = p_i
rune = p_rune
dec(p_i)
if not runes.contains(rune):
e_i = l_i - 1
break
dec(i)
let newLen = e_i - s_i
result = newStringOfCap(newLen)
if e_i > s_i:
result.add s[s_i .. e_i]
proc repeat*(c: Rune, count: Natural): string {.noSideEffect,
rtl, extern: "nucRepeatRune".} =
## Returns a string of `count` Runes `c`.
##
## The returned string will have a rune-length of `count`.
let s = $c
result = newStringOfCap(count * s.len)
for i in 0 ..< count:
result.add s
proc align*(s: string, count: Natural, padding = ' '.Rune): string {.
noSideEffect, rtl, extern: "nucAlignString".} =
## Aligns a unicode string `s` with `padding`, so that it has a rune-length
## of `count`.
##
## `padding` characters (by default spaces) are added before `s` resulting in
## right alignment. If ``s.runelen >= count``, no spaces are added and `s` is
## returned unchanged. If you need to left align a string use the `alignLeft
## proc <#alignLeft>`_.
runnableExamples:
assert align("abc", 4) == " abc"
assert align("a", 0) == "a"
assert align("1232", 6) == " 1232"
assert align("1232", 6, '#'.Rune) == "##1232"
assert align("Åge", 5) == " Åge"
assert align("×", 4, '_'.Rune) == "___×"
let sLen = s.runeLen
if sLen < count:
let padStr = $padding
result = newStringOfCap(padStr.len * count)
let spaces = count - sLen
for i in 0 ..< spaces: result.add padStr
result.add s
else:
result = s
proc alignLeft*(s: string, count: Natural, padding = ' '.Rune): string {.
noSideEffect.} =
## Left-Aligns a unicode string `s` with `padding`, so that it has a
## rune-length of `count`.
##
## `padding` characters (by default spaces) are added after `s` resulting in
## left alignment. If ``s.runelen >= count``, no spaces are added and `s` is
## returned unchanged. If you need to right align a string use the `align
## proc <#align>`_.
runnableExamples:
assert alignLeft("abc", 4) == "abc "
assert alignLeft("a", 0) == "a"
assert alignLeft("1232", 6) == "1232 "
assert alignLeft("1232", 6, '#'.Rune) == "1232##"
assert alignLeft("Åge", 5) == "Åge "
assert alignLeft("×", 4, '_'.Rune) == "×___"
let sLen = s.runeLen
if sLen < count:
let padStr = $padding
result = newStringOfCap(s.len + (count - sLen) * padStr.len)
result.add s
for i in sLen ..< count:
result.add padStr
else:
result = s
when isMainModule:
proc asRune(s: static[string]): Rune =
@@ -2041,3 +2156,37 @@ when isMainModule:
doAssert s3.split(seps = [':'.Rune, "×".asRune]) == @["", "this", "is", "an", "example", "", ""]
doAssert s.split(maxsplit = 4) == @["", "this", "is", "an", "example "]
doAssert s.split(' '.Rune, maxsplit = 1) == @["", "this is an example "]
block stripTests:
doAssert(strip(" foofoofoo ") == "foofoofoo")
doAssert(strip("sfoofoofoos", runes = ['s'.Rune]) == "foofoofoo")
block:
let stripTestRunes = ['b'.Rune, 'a'.Rune, 'r'.Rune]
doAssert(strip("barfoofoofoobar", runes = stripTestRunes) == "foofoofoo")
doAssert(strip("sfoofoofoos", leading = false, runes = ['s'.Rune]) == "sfoofoofoo")
doAssert(strip("sfoofoofoos", trailing = false, runes = ['s'.Rune]) == "foofoofoos")
block:
let stripTestRunes = ["«".asRune, "»".asRune]
doAssert(strip("«TEXT»", runes = stripTestRunes) == "TEXT")
doAssert(strip("copyright©", leading = false, runes = ["©".asRune]) == "copyright")
doAssert(strip("¿Question?", trailing = false, runes = ["¿".asRune]) == "Question?")
doAssert(strip("×text×", leading = false, runes = ["×".asRune]) == "×text")
doAssert(strip("×text×", trailing = false, runes = ["×".asRune]) == "text×")
block repeatTests:
doAssert repeat('c'.Rune, 5) == "ccccc"
doAssert repeat("×".asRune, 5) == "×××××"
block alignTests:
doAssert align("abc", 4) == " abc"
doAssert align("a", 0) == "a"
doAssert align("1232", 6) == " 1232"
doAssert align("1232", 6, '#'.Rune) == "##1232"
doAssert align("1232", 6, "×".asRune) == "××1232"
doAssert alignLeft("abc", 4) == "abc "
doAssert alignLeft("a", 0) == "a"
doAssert alignLeft("1232", 6) == "1232 "
doAssert alignLeft("1232", 6, '#'.Rune) == "1232##"
doAssert alignLeft("1232", 6, "×".asRune) == "1232××"