summary refs log tree commit diff stats
path: root/lib/pure/strutils.nim
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pure/strutils.nim')
-rw-r--r--lib/pure/strutils.nim129
1 files changed, 85 insertions, 44 deletions
diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim
index 55a204b4c..1b248126b 100644
--- a/lib/pure/strutils.nim
+++ b/lib/pure/strutils.nim
@@ -386,7 +386,7 @@ proc split*(s: string, sep: string): seq[string] {.noSideEffect,
   ## `split iterator <#split.i,string,string>`_.
   accumulateResult(split(s, sep))
 
-proc toHex*(x: BiggestInt, len: int): string {.noSideEffect,
+proc toHex*(x: BiggestInt, len: Positive): string {.noSideEffect,
   rtl, extern: "nsuToHex".} =
   ## Converts `x` to its hexadecimal representation.
   ##
@@ -395,13 +395,15 @@ proc toHex*(x: BiggestInt, len: int): string {.noSideEffect,
   const
     HexChars = "0123456789ABCDEF"
   var
-    shift: BiggestInt
+    n = x
   result = newString(len)
   for j in countdown(len-1, 0):
-    result[j] = HexChars[toU32(x shr shift) and 0xF'i32]
-    shift = shift + 4
+    result[j] = HexChars[n and 0xF]
+    n = n shr 4
+    # handle negative overflow
+    if n == 0 and x < 0: n = -1
 
-proc intToStr*(x: int, minchars: int = 1): string {.noSideEffect,
+proc intToStr*(x: int, minchars: Positive = 1): string {.noSideEffect,
   rtl, extern: "nsuIntToStr".} =
   ## Converts `x` to its decimal representation.
   ##
@@ -497,30 +499,51 @@ proc parseEnum*[T: enum](s: string, default: T): T =
       return e
   result = default
 
-proc repeatChar*(count: int, c: char = ' '): string {.noSideEffect,
+proc repeat*(c: char, count: Natural): string {.noSideEffect,
   rtl, extern: "nsuRepeatChar".} =
   ## Returns a string of length `count` consisting only of
   ## the character `c`. You can use this proc to left align strings. Example:
   ##
   ## .. code-block:: nim
+  ##   proc tabexpand(indent: int, text: string, tabsize: int = 4) =
+  ##     echo '\t'.repeat(indent div tabsize), ' '.repeat(indent mod tabsize), text
+  ##
+  ##   tabexpand(4, "At four")
+  ##   tabexpand(5, "At five")
+  ##   tabexpand(6, "At six")
+  result = newString(count)
+  for i in 0..count-1: result[i] = c
+
+proc repeat*(s: string, n: Natural): string {.noSideEffect,
+  rtl, extern: "nsuRepeatStr".} =
+  ## Returns String `s` concatenated `n` times.  Example:
+  ##
+  ## .. code-block:: nim
+  ##   echo "+++ STOP ".repeat(4), "+++"
+  result = newStringOfCap(n * s.len)
+  for i in 1..n: result.add(s)
+
+template spaces*(n: Natural): string =  repeat(' ',n)
+  ## Returns a String with `n` space characters. You can use this proc
+  ## to left align strings. Example:
+  ##
+  ## .. code-block:: nim
   ##   let
   ##     width = 15
   ##     text1 = "Hello user!"
   ##     text2 = "This is a very long string"
-  ##   echo text1 & repeatChar(max(0, width - text1.len)) & "|"
-  ##   echo text2 & repeatChar(max(0, width - text2.len)) & "|"
-  result = newString(count)
-  for i in 0..count-1: result[i] = c
+  ##   echo text1 & spaces(max(0, width - text1.len)) & "|"
+  ##   echo text2 & spaces(max(0, width - text2.len)) & "|"
 
-proc repeatStr*(count: int, s: string): string {.noSideEffect,
-  rtl, extern: "nsuRepeatStr".} =
-  ## Returns `s` concatenated `count` times.
-  result = newStringOfCap(count*s.len)
-  for i in 0..count-1: result.add(s)
+proc repeatChar*(count: Natural, c: char = ' '): string {.deprecated.} = repeat(c, count)
+  ## deprecated: use repeat() or spaces()
+
+proc repeatStr*(count: Natural, s: string): string {.deprecated.} = repeat(s, count)
+  ## deprecated: use repeat(string, count) or string.repeat(count)
 
-proc align*(s: string, count: int, padding = ' '): string {.
+proc align*(s: string, count: Natural, padding = ' '): string {.
   noSideEffect, rtl, extern: "nsuAlignString".} =
-  ## Aligns a string `s` with `padding`, so that is of length `count`.
+  ## Aligns a string `s` with `padding`, so that it is of length `count`.
   ##
   ## `padding` characters (by default spaces) are added before `s` resulting in
   ## right alignment. If ``s.len >= count``, no spaces are added and `s` is
@@ -659,7 +682,7 @@ proc endsWith*(s, suffix: string): bool {.noSideEffect,
     inc(i)
   if suffix[i] == '\0': return true
 
-proc continuesWith*(s, substr: string, start: int): bool {.noSideEffect,
+proc continuesWith*(s, substr: string, start: Natural): bool {.noSideEffect,
   rtl, extern: "nsuContinuesWith".} =
   ## Returns true iff ``s`` continues with ``substr`` at position ``start``.
   ##
@@ -670,8 +693,8 @@ proc continuesWith*(s, substr: string, start: int): bool {.noSideEffect,
     if s[i+start] != substr[i]: return false
     inc(i)
 
-proc addSep*(dest: var string, sep = ", ", startLen = 0) {.noSideEffect,
-                                                           inline.} =
+proc addSep*(dest: var string, sep = ", ", startLen: Natural = 0)
+  {.noSideEffect, inline.} =
   ## Adds a separator to `dest` only if its length is bigger than `startLen`.
   ##
   ## A shorthand for:
@@ -761,7 +784,7 @@ proc findAux(s, sub: string, start: int, a: SkipTable): int =
     inc(j, a[s[j+m]])
   return -1
 
-proc find*(s, sub: string, start: int = 0): int {.noSideEffect,
+proc find*(s, sub: string, start: Natural = 0): int {.noSideEffect,
   rtl, extern: "nsuFindStr".} =
   ## Searches for `sub` in `s` starting at position `start`.
   ##
@@ -770,7 +793,7 @@ proc find*(s, sub: string, start: int = 0): int {.noSideEffect,
   preprocessSub(sub, a)
   result = findAux(s, sub, start, a)
 
-proc find*(s: string, sub: char, start: int = 0): int {.noSideEffect,
+proc find*(s: string, sub: char, start: Natural = 0): int {.noSideEffect,
   rtl, extern: "nsuFindChar".} =
   ## Searches for `sub` in `s` starting at position `start`.
   ##
@@ -779,7 +802,7 @@ proc find*(s: string, sub: char, start: int = 0): int {.noSideEffect,
     if sub == s[i]: return i
   return -1
 
-proc find*(s: string, chars: set[char], start: int = 0): int {.noSideEffect,
+proc find*(s: string, chars: set[char], start: Natural = 0): int {.noSideEffect,
   rtl, extern: "nsuFindCharSet".} =
   ## Searches for `chars` in `s` starting at position `start`.
   ##
@@ -803,10 +826,20 @@ proc rfind*(s, sub: string, start: int = -1): int {.noSideEffect.} =
     if result != -1: return
   return -1
 
+proc rfind*(s: string, sub: char, start: int = -1): int {.noSideEffect,
+  rtl.} =
+  ## Searches for `sub` in `s` in reverse starting at position `start`.
+  ##
+  ## Searching is case-sensitive. If `sub` is not in `s`, -1 is returned.
+  let realStart = if start == -1: s.len-1 else: start
+  for i in countdown(realStart, 0):
+    if sub == s[i]: return i
+  return -1
+
 proc count*(s: string, sub: string, overlapping: bool = false): int {.noSideEffect,
   rtl, extern: "nsuCountString".} =
-  ## Count the occurences of a substring `sub` in the string `s`.
-  ## Overlapping occurences of `sub` only count when `overlapping`
+  ## Count the occurrences of a substring `sub` in the string `s`.
+  ## Overlapping occurrences of `sub` only count when `overlapping`
   ## is set to true.
   var i = 0
   while true:
@@ -821,14 +854,14 @@ proc count*(s: string, sub: string, overlapping: bool = false): int {.noSideEffe
 
 proc count*(s: string, sub: char): int {.noSideEffect,
   rtl, extern: "nsuCountChar".} =
-  ## Count the occurences of the character `sub` in the string `s`.
+  ## Count the occurrences of the character `sub` in the string `s`.
   for c in s:
     if c == sub:
       inc result
 
 proc count*(s: string, subs: set[char]): int {.noSideEffect,
   rtl, extern: "nsuCountCharSet".} =
-  ## Count the occurences of the group of character `subs` in the string `s`.
+  ## Count the occurrences of the group of character `subs` in the string `s`.
   for c in s:
     if c in subs:
       inc result
@@ -888,7 +921,7 @@ proc replaceWord*(s, sub: string, by = ""): string {.noSideEffect,
   rtl, extern: "nsuReplaceWord".} =
   ## Replaces `sub` in `s` by the string `by`.
   ##
-  ## Each occurance of `sub` has to be surrounded by word boundaries
+  ## Each occurrence of `sub` has to be surrounded by word boundaries
   ## (comparable to ``\\w`` in regular expressions), otherwise it is not
   ## replaced.
   const wordChars = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\128'..'\255'}
@@ -900,7 +933,7 @@ proc replaceWord*(s, sub: string, by = ""): string {.noSideEffect,
     var j = findAux(s, sub, i, a)
     if j < 0: break
     # word boundary?
-    if (j == 0 or s[j-1] notin wordChars) and 
+    if (j == 0 or s[j-1] notin wordChars) and
         (j+sub.len >= s.len or s[j+sub.len] notin wordChars):
       add result, substr(s, i, j - 1)
       add result, by
@@ -943,7 +976,7 @@ proc parseOctInt*(s: string): int {.noSideEffect,
     of '\0': break
     else: raise newException(ValueError, "invalid integer: " & s)
 
-proc toOct*(x: BiggestInt, len: int): string {.noSideEffect,
+proc toOct*(x: BiggestInt, len: Positive): string {.noSideEffect,
   rtl, extern: "nsuToOct".} =
   ## Converts `x` into its octal representation.
   ##
@@ -959,7 +992,7 @@ proc toOct*(x: BiggestInt, len: int): string {.noSideEffect,
     shift = shift + 3
     mask = mask shl 3
 
-proc toBin*(x: BiggestInt, len: int): string {.noSideEffect,
+proc toBin*(x: BiggestInt, len: Positive): string {.noSideEffect,
   rtl, extern: "nsuToBin".} =
   ## Converts `x` into its binary representation.
   ##
@@ -1032,18 +1065,20 @@ proc unescape*(s: string, prefix = "\"", suffix = "\""): string {.noSideEffect,
   ## ValueError exception will be raised.
   result = newStringOfCap(s.len)
   var i = 0
-  if s[0 .. prefix.len-1] != prefix:
+  if not s.startsWith(prefix):
     raise newException(ValueError,
                        "String does not start with a prefix of: " & prefix)
-  i.inc()
+  inc(i)
   while true:
     if i == s.len-suffix.len: break
     case s[i]
     of '\\':
       case s[i+1]:
       of 'x':
-        let j = parseHexInt(s[i+2 .. i+3])
-        result.add(chr(j))
+        inc i
+        var c: int
+        i += parseutils.parseHex(s, c, i)
+        result.add(chr(c))
         inc(i, 2)
       of '\\':
         result.add('\\')
@@ -1056,8 +1091,8 @@ proc unescape*(s: string, prefix = "\"", suffix = "\""): string {.noSideEffect,
     of '\0': break
     else:
       result.add(s[i])
-    i.inc()
-  if s[i .. -1] != suffix:
+    inc(i)
+  if not s.endsWith(suffix):
     raise newException(ValueError,
                        "String does not end with a suffix of: " & suffix)
 
@@ -1186,7 +1221,7 @@ proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault,
   ## of significant digits to be printed.
   ## `precision`'s default value is the maximum number of meaningful digits
   ## after the decimal point for Nim's ``biggestFloat`` type.
-  ## 
+  ##
   ## If ``precision == 0``, it tries to format it nicely.
   const floatFormatToChar: array[FloatFormatMode, char] = ['g', 'f', 'e']
   var
@@ -1251,7 +1286,7 @@ proc findNormalized(x: string, inArray: openArray[string]): int =
   return -1
 
 proc invalidFormatString() {.noinline.} =
-  raise newException(ValueError, "invalid format string")  
+  raise newException(ValueError, "invalid format string")
 
 proc addf*(s: var string, formatstr: string, a: varargs[string, `$`]) {.
   noSideEffect, rtl, extern: "nsuAddf".} =
@@ -1367,21 +1402,27 @@ when isMainModule:
   doAssert align("a", 0) == "a"
   doAssert align("1232", 6) == "  1232"
   doAssert align("1232", 6, '#') == "##1232"
-  echo wordWrap(""" this is a long text --  muchlongerthan10chars and here
-                   it goes""", 10, false)
+
+  let
+    inp = """ this is a long text --  muchlongerthan10chars and here
+               it goes"""
+    outp = " this is a\nlong text\n--\nmuchlongerthan10chars\nand here\nit goes"
+  doAssert wordWrap(inp, 10, false) == outp
+
   doAssert formatBiggestFloat(0.00000000001, ffDecimal, 11) == "0.00000000001"
   doAssert formatBiggestFloat(0.00000000001, ffScientific, 1) == "1.0e-11"
 
   doAssert "$# $3 $# $#" % ["a", "b", "c"] == "a c b c"
-  echo formatSize(1'i64 shl 31 + 300'i64) # == "4,GB"
-  echo formatSize(1'i64 shl 31)
+  when not defined(testing):
+    echo formatSize(1'i64 shl 31 + 300'i64) # == "4,GB"
+    echo formatSize(1'i64 shl 31)
 
   doAssert "$animal eats $food." % ["animal", "The cat", "food", "fish"] ==
            "The cat eats fish."
 
   doAssert "-ld a-ldz -ld".replaceWord("-ld") == " a-ldz "
   doAssert "-lda-ldz -ld abc".replaceWord("-ld") == "-lda-ldz  abc"
-  
+
   type MyEnum = enum enA, enB, enC, enuD, enE
   doAssert parseEnum[MyEnum]("enu_D") == enuD