├── .gitignore ├── LICENSE ├── README.md ├── nimtomd.nimble ├── src └── nimtomd.nim └── tests ├── complex.md ├── complex.nim ├── config.nims ├── strutils.md ├── strutils.nim └── test.nim /.gitignore: -------------------------------------------------------------------------------- 1 | src/testfile.nim 2 | tests/test1 3 | nimtomd -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Thomas T. Jarløv 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Nim to Markdown 2 | 3 | *Generated with [Nim to Markdown](https://github.com/ThomasTJdev/nimtomd)* 4 | 5 | This Nim package converts Nim code to Markdown. Use `nimtomd` 6 | on your Nim file and transform it into a styled Markdown file. 7 | 8 | You can choose to only include global elements (*) and top 9 | comments, or you can include everything. 10 | 11 | # Usage: 12 | ```nim 13 | nimtomd [options] 14 | ``` 15 | 16 | # Options: 17 | ```nim 18 | Options: 19 | Nim-file to convert into markdown 20 | -h, --help Shows the help menu 21 | -o:, --output:[filename] Outputs the markdown to a file 22 | -ow, --overwrite Allow to overwrite a existing md file 23 | -g, --onlyglobals Only include global elements (*) 24 | -sh Skip headings 25 | -si Skip imports 26 | -st Skip types 27 | -il, --includelines Include linenumbers 28 | -ic, --includeconst Include const 29 | -il, --includelet Include let 30 | -iv, --includevar Include var 31 | ``` 32 | 33 | 34 | ## Requirements 35 | 36 | Your code needs to follow the Nim coding style. Checkout the 37 | source file for examples. 38 | 39 | 40 | ## Examples 41 | 42 | This README.md is made with ``nimtomd`` with the command: 43 | ```nim 44 | nimtomd -o:README.md -ow -g nimtomd.nim 45 | ``` 46 | 47 | # Output to screen 48 | 49 | This prints the Markdown output to the screen: 50 | ```nim 51 | nimtomd filename.nim 52 | ``` 53 | 54 | # Save output to file 55 | 56 | You can force ``nimtomd`` to overwrite an existing file 57 | by using the option ``-ow``. 58 | 59 | ```nim 60 | nimtomd -o:README.md -ow filename.nim 61 | ``` 62 | -------------------------------------------------------------------------------- /nimtomd.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | version = "0.3.2" 3 | author = "ThomasTJdev" 4 | description = "Convert Nim files to markdown" 5 | license = "MIT" 6 | srcDir = "src" 7 | installExt = @["nim"] 8 | bin = @["nimtomd"] 9 | 10 | 11 | # Dependencies 12 | requires "nim >= 1.0.4" 13 | -------------------------------------------------------------------------------- /src/nimtomd.nim: -------------------------------------------------------------------------------- 1 | ## Nim to Markdown 2 | ## --------------- 3 | ## 4 | ## *Generated with [Nim to Markdown](https://github.com/ThomasTJdev/nimtomd)* 5 | ## 6 | ## This Nim package converts Nim code to Markdown. Use `nimtomd` 7 | ## on your Nim file and transform it into a styled Markdown file. 8 | ## 9 | ## You can choose to only include global elements (*) and top 10 | ## comments, or you can include everything. 11 | ## 12 | ## Usage: 13 | ## ===== 14 | ## .. code-block::plain 15 | ## nimtomd [options] 16 | ## 17 | ## Options: 18 | ## ======== 19 | ## .. code-block::plain 20 | ## Options: 21 | ## Nim-file to convert into markdown 22 | ## -h, --help Shows the help menu 23 | ## -o:, --output:[filename] Outputs the markdown to a file 24 | ## -ow, --overwrite Allow to overwrite a existing md file 25 | ## -g, --onlyglobals Only include global elements (*) 26 | ## -sh Skip headings 27 | ## -si Skip imports 28 | ## -st Skip types 29 | ## -il, --includelines Include linenumbers 30 | ## -ic, --includeconst Include const 31 | ## -il, --includelet Include let 32 | ## -iv, --includevar Include var 33 | ## 34 | ## 35 | ## Requirements 36 | ## ------------ 37 | ## 38 | ## Your code needs to follow the Nim coding style. Checkout the 39 | ## source file for examples. 40 | ## 41 | ## 42 | ## Examples 43 | ## -------- 44 | ## 45 | ## This README.md is made with ``nimtomd`` with the command: 46 | ## .. code-block::plain 47 | ## nimtomd -o:README.md -ow -g nimtomd.nim 48 | ## 49 | ## Output to screen 50 | ## ================ 51 | ## 52 | ## This prints the Markdown output to the screen: 53 | ## .. code-block::plain 54 | ## nimtomd filename.nim 55 | ## 56 | ## Save output to file 57 | ## =================== 58 | ## 59 | ## You can force ``nimtomd`` to overwrite an existing file 60 | ## by using the option ``-ow``. 61 | ## 62 | ## .. code-block::plain 63 | ## nimtomd -o:README.md -ow filename.nim 64 | ## 65 | 66 | 67 | 68 | import strutils, re, os, terminal, algorithm 69 | 70 | from times import epochTime 71 | 72 | 73 | type 74 | CodeText = object 75 | data: seq[string] 76 | 77 | CodeImport = object 78 | data: seq[tuple[line: int, global: bool, newline: bool, code: string]] 79 | 80 | CodeFrom = object 81 | data: seq[tuple[line: int, global: bool, newline: bool, code: string]] 82 | 83 | CodeType = object 84 | data: seq[tuple[line: int, global: bool, newline: bool, heading: string, comment: string, code: string]] 85 | 86 | CodeTemplate = object 87 | data: seq[tuple[line: int, global: bool, heading: string, comment: string, code: string, runnable: string]] 88 | 89 | CodeMacro = object 90 | data: seq[tuple[line: int, global: bool, heading: string, comment: string, code: string, runnable: string]] 91 | 92 | CodeProc = object 93 | data: seq[tuple[line: int, global: bool, heading: string, comment: string, code: string, runnable: string]] 94 | 95 | CodeFunc = object 96 | data: seq[tuple[line: int, global: bool, heading: string, comment: string, code: string, runnable: string]] 97 | 98 | CodeIterator = object 99 | data: seq[tuple[line: int, global: bool, heading: string, comment: string, code: string, runnable: string]] 100 | 101 | CodeConst = object 102 | data: seq[tuple[line: int, global: bool, heading: string, comment: string, code: string]] 103 | 104 | CodeLet = object 105 | data: seq[tuple[line: int, global: bool, heading: string, comment: string, code: string]] 106 | 107 | CodeVar = object 108 | data: seq[tuple[line: int, global: bool, heading: string, comment: string, code: string]] 109 | 110 | 111 | var 112 | codeText: CodeText 113 | codeImport: CodeImport 114 | codeFrom: CodeFrom 115 | codeType: CodeType 116 | codeTemplate: CodeTemplate 117 | codeMacro: CodeMacro 118 | codeProc: CodeProc 119 | codeFunc: CodeFunc 120 | codeIterator: CodeIterator 121 | codeConst: CodeConst 122 | codeLet: CodeLet 123 | codeVar: CodeVar 124 | 125 | var 126 | lineForce: int ## Force to a new line, if `forceNewLine=true` 127 | forceNewLine: bool ## Can force the parser to a new line 128 | hasAnyData: bool ## Used to check, if theres any data available 129 | 130 | 131 | 132 | 133 | proc checkElement(line: string): string = 134 | ## Check if line has an element and return the element type 135 | if line.substr(0, 3) == "proc": 136 | return "proc" 137 | elif line.substr(0, 7) == "template": 138 | return "template" 139 | elif line.substr(0, 4) == "macro": 140 | return "macro" 141 | elif line.substr(0, 7) == "iterator": 142 | return "iterator" 143 | elif line.substr(0, 3) == "func": 144 | return "func" 145 | elif line.substr(0, 3) == "type": 146 | return "type" 147 | elif line.substr(0, 5) == "import": 148 | return "import" 149 | elif line.substr(0,3) == "from" and line.contains("import"): 150 | return "from" 151 | elif line.substr(0,4) == "const": 152 | return "const" 153 | elif line.substr(0,2) == "var": 154 | return "var" 155 | elif line.substr(0,2) == "let": 156 | return "let" 157 | else: 158 | return "" 159 | 160 | 161 | proc isGlobal(line: string): bool = 162 | ## Check if line has global identifier 163 | if line.contains(re".*\S.*\*\("): #bla*( 164 | return true 165 | elif line.contains(re".*\S.*\*\:"): #bla*: 166 | return true 167 | elif line.contains(re".*\S.*\*\["): #bla*[ 168 | return true 169 | elif line.contains(re".*\*\{"): #bla*{ 170 | return true 171 | else: 172 | return false 173 | 174 | 175 | proc fillElement(element: string, headLineNr: int, isGlobal: bool, head, comment, code, runnable: string) = 176 | ## Fill an element 177 | 178 | case element 179 | of "template": 180 | codeTemplate.data.add((headLineNr, isGlobal, head, comment, code, runnable)) 181 | of "macro": 182 | codeMacro.data.add((headLineNr, isGlobal, head, comment, code, runnable)) 183 | of "proc": 184 | codeProc.data.add((headLineNr, isGlobal, head, comment, code, runnable)) 185 | of "func": 186 | codeFunc.data.add((headLineNr, isGlobal, head, comment, code, runnable)) 187 | of "iterator": 188 | codeIterator.data.add((headLineNr, isGlobal, head, comment, code, runnable)) 189 | else: 190 | styledWriteLine(stderr, fgRed, "ERROR: ", resetStyle, "Could not fill " & element) 191 | return 192 | 193 | hasAnyData = true 194 | 195 | 196 | proc fillVariable(element: string, headLineNr: int, isGlobal: bool, head, comment, code: string) = 197 | ## Fill an element 198 | 199 | case element 200 | of "const": 201 | codeConst.data.add((headLineNr, isGlobal, head, comment, code)) 202 | of "let": 203 | codeLet.data.add((headLineNr, isGlobal, head, comment, code)) 204 | of "var": 205 | codeVar.data.add((headLineNr, isGlobal, head, comment, code)) 206 | else: 207 | styledWriteLine(stderr, fgRed, "ERROR: ", resetStyle, "Could not fill " & element) 208 | return 209 | 210 | hasAnyData = true 211 | 212 | proc isAlphaNumericStr(s: string): bool = 213 | ## WHY! WHY! WHY don't we have this is in std? 214 | for c in s: 215 | if not isAlphaNumeric(c): 216 | return false 217 | return true 218 | 219 | proc parseVariable(elementValue: string, lineCurrentNr: int, file: seq[string]) = 220 | ## Parse a const, var or let 221 | 222 | var 223 | head: string 224 | comment: string 225 | code: string 226 | headLineNr: int 227 | isGlobal: bool 228 | 229 | for lineNr in countup(lineCurrentNr, file.len()-1): 230 | let line = file[lineNr].strip() 231 | if line == elementValue: 232 | if head != "": 233 | fillVariable(elementValue, headLineNr, isGlobal, head, comment, code) 234 | head = "" 235 | comment = "" 236 | code = "" 237 | headLineNr = 0 238 | isGlobal = false 239 | continue 240 | elif line == "": 241 | fillVariable(elementValue, headLineNr, isGlobal, head, comment, code) 242 | head = "" 243 | comment = "" 244 | code = "" 245 | headLineNr = 0 246 | isGlobal = false 247 | if file[lineNr+1] == "" or file[lineNr+1].substr(0,1) != " ":# and file[lineNr+1].substr(0,1) : 248 | forceNewLine = true 249 | lineForce = lineNr 250 | break 251 | continue 252 | elif head != "" and file[lineNr].substr(0,2) != " " and "#" notin file[lineNr].substr(0,2): 253 | fillVariable(elementValue, headLineNr, isGlobal, head, comment, code) 254 | head = "" 255 | comment = "" 256 | code = "" 257 | headLineNr = 0 258 | isGlobal = false 259 | 260 | if line.contains("##"): 261 | if line.replace(re"##.*", "") == "": 262 | let lineComm = line.replace("##", "").strip() 263 | if comment != "": 264 | comment.add("\n") 265 | comment.add(lineComm) 266 | else: 267 | let lineHead = line.replace(re"##.*", "").replace(elementValue, "").strip() 268 | let lineComm = line.replace(re".*##", "").strip() 269 | if comment != "": 270 | comment.add("\n") 271 | comment.add(lineComm) 272 | head.add(lineHead.replace(re"=.*", "")) 273 | code.add(lineHead) 274 | headLineNr = lineNr 275 | isGlobal = if line.contains("*"): true else: false 276 | 277 | else: 278 | let lineClean = line.replace(elementValue, "").strip() 279 | head.add(lineClean.replace(re"=.*", "")) 280 | code.add(lineClean) 281 | headLineNr = lineNr 282 | isGlobal = if line.contains("*"): true else: false 283 | 284 | 285 | 286 | proc parseElement(elementValue: string, lineCurrentNr: int, file: seq[string]) = 287 | ## Parse `template` 288 | 289 | var 290 | isRunning: bool 291 | isGlobal: bool 292 | isRunnableExample: bool 293 | headLineNr: int 294 | head: string 295 | comment: string 296 | code: string 297 | codeblock: bool 298 | runnable: string 299 | 300 | for lineNr in countup(lineCurrentNr, file.len()-1): 301 | let line = file[lineNr].strip() 302 | let element = checkElement(file[lineNr]) # Do not pass a strip(), then it'll find it as a let element. 303 | 304 | # If a new element was found or line is empty, exit the parser 305 | if (element != elementValue and element != "") or line == "": 306 | forceNewLine = true 307 | lineForce = lineNr 308 | if isRunning: 309 | fillElement(elementValue, headLineNr, isGlobal, head, comment, code, runnable) 310 | break 311 | 312 | # Heading 313 | if element == elementValue: 314 | if isRunning: 315 | fillElement(elementValue, headLineNr, isGlobal, head, comment, code, runnable) 316 | 317 | if line.contains("##"): 318 | comment = line.replace(re".*##\s", "").strip() 319 | 320 | isRunning = true 321 | isGlobal = isGlobal(line) 322 | headLineNr = lineNr + 1 # Adding +1 since fileread starts from 0 323 | head = line.replace(re"\(.*", "").replace("proc", "").strip() # anyNonWord# blabla 324 | code = line.replace(re"#.*", "").strip() 325 | continue 326 | 327 | elif codeblock and line.substr(0,4) == "## " and line.replace("##", "") != "": 328 | comment.add(line.replace("## ", "") & "\n") 329 | 330 | 331 | elif line.substr(0,1) == "##": 332 | if line.contains(".. code-block::"): 333 | codeblock = true 334 | comment.add("\n") 335 | comment.add("```nim") 336 | comment.add("\n") 337 | continue 338 | 339 | if codeblock and file[lineNr-1].contains(".. code-block::"): 340 | continue 341 | 342 | if codeblock: 343 | comment.add("```") 344 | comment.add("\n") 345 | codeblock = false 346 | 347 | var comm = line.replace(re".*##\s", "").replace("##", "").strip() 348 | var isLink = comm.replace(re".*\<", "").replace(re"\>`_.*", "") 349 | 350 | # HTML link 351 | if isLink != "" and isLink.substr(isLink.len()-5, isLink.len()) == ".html": 352 | let link = " [(link)](https://nim-lang.org/docs/" & isLink & ")" 353 | comm = comm.replace("<" & isLink & ">`_", link).replace("`", "") 354 | 355 | # Insert new line 356 | if comment.len() != 0: 357 | comment.add(" ") 358 | 359 | if line == "## See also:": 360 | comment.add("\n\n" & comm) 361 | elif line.substr(0, 3) == "## *": 362 | comment.add("\n" & comm) 363 | elif comm == "": 364 | comment.add("\n\n") 365 | else: 366 | comment.add(comm) 367 | 368 | elif line == "runnableExamples:" or isRunnableExample: 369 | isRunnableExample = true 370 | if line == "runnableExamples:" or file[lineNr].substr(0,3) == " ": 371 | if runnable != "": 372 | runnable.add("\n") 373 | runnable.add(file[lineNr]) 374 | 375 | 376 | elif line.substr(line.len()-1, line.len()-1) == "=": 377 | code.add(" " & line) 378 | 379 | 380 | 381 | proc parseType(lineCurrentNr: int, file: seq[string]) = 382 | ## Parse `type` statements. 383 | 384 | var 385 | typeIsRunning: bool 386 | typeIsGlobal: bool 387 | typeHeadLineNr: int 388 | typeHead: string 389 | typeComment: string 390 | typeCode: string 391 | 392 | for lineNr in countup(lineCurrentNr, file.len()-1): 393 | let line = file[lineNr].strip() 394 | let element = checkElement(line) 395 | 396 | # Empty line continue 397 | if line == "" or line == "type": 398 | continue 399 | 400 | # If a new element was found, exit the parser 401 | if element != "type" and element != "": 402 | forceNewLine = true 403 | lineForce = lineNr 404 | if typeIsRunning: 405 | codeType.data.add((typeHeadLineNr, typeIsGlobal, true, typeHead, typeComment, typeCode)) 406 | hasAnyData = true 407 | break 408 | 409 | # Get heading - indent = 2 410 | if file[lineNr].substr(0,1) == " " and isAlphaNumericStr(file[lineNr].substr(2,2)): 411 | # Code check 412 | if isLowerAscii(file[lineNr][2]): 413 | styledWriteLine(stderr, fgRed, "ERROR: ", resetStyle, "Type is starting with a lower ASCII: " & line) 414 | 415 | # Check if a new type is found 416 | if typeIsRunning: 417 | # ADD type 418 | codeType.data.add((typeHeadLineNr, typeIsGlobal, true, typeHead, typeComment, typeCode)) 419 | hasAnyData = true 420 | typeHead = "" 421 | typeComment = "" 422 | typeCode = "" 423 | 424 | if file[lineNr].contains("##"): 425 | typeComment = line.replace(re".*##\s", "").strip() 426 | 427 | typeIsRunning = true 428 | typeIsGlobal = if file[lineNr].contains("*"): true else: false 429 | typeHeadLineNr = lineNr + 1 # Adding +1 since fileread starts from 0 430 | typeHead = line.replace(re"\W#\s.*", "").strip() # anyNonWord# blabla 431 | typeCode = " " & line.replace(re"#.*", "").strip() # First line (object), strip comment 432 | continue 433 | 434 | # Check if multiline comment is present for head 435 | if line.substr(0, 1) == "##": 436 | typeComment.add(" " & line.replace("##", "").strip) 437 | continue 438 | 439 | # Get items - indent = 4 440 | if file[lineNr].substr(0,3) == " " and isAlphaNumericStr(file[lineNr].substr(4,4)): 441 | let cleanCode = file[lineNr].replace(re"\W#\s.*", "") 442 | 443 | # Check if comment is present 444 | if file[lineNr].contains("##"): 445 | 446 | # Head comment is already present, insert new line 447 | if typeComment.len() != 0: 448 | typeComment.add("\n\n") 449 | 450 | typeComment.add("* __" & cleanCode.strip() & "__: " & line.replace(re".*##\s", "").strip()) 451 | hasAnyData = true 452 | 453 | typeCode.add("\n" & cleanCode) 454 | continue 455 | 456 | 457 | proc parseFrom(lineCurrentNr: int, file: seq[string]) = 458 | ## Parse `from xx import yy` statements. 459 | ## 460 | ## `lineNr` contains "from" and "import". 461 | ## 462 | ## Does not support multiline comments 463 | 464 | for lineNr in countup(lineCurrentNr, file.len()-1): 465 | let line = file[lineNr].strip() 466 | let lineRaw = file[lineNr] 467 | let element = checkElement(line) 468 | 469 | # If a new element was found, exit the parser 470 | if (element != "from" and element != "") or line == "": 471 | forceNewLine = true 472 | lineForce = lineNr 473 | break 474 | 475 | # Add data 476 | # Always new lines, due to to long lines otherwise 477 | if lineRaw.substr(0,1) == "##" or lineRaw.substr(0,1) == "# " or line.substr(0,1) == "##" or line.substr(0,1) == "# ":# or (lineRaw.substr(0,3) != "from" and lineRaw.substr(0,1) != " "): 478 | continue 479 | 480 | # Strip line, so inside code comments is gone. 481 | let lineClean = line.strip() 482 | var data: string 483 | if line.contains("##"): 484 | data = replace(lineClean, "##", ":").strip() # space#space* 485 | elif line.contains("#"): 486 | data = replace(lineClean, re"\s#\s.*", "").strip() # space#space* 487 | else: 488 | data = lineClean 489 | 490 | # If its global, add global bool 491 | # Baah - imports should not be global" 492 | if line.contains("*"): 493 | codeFrom.data.add((lineNr, true, true, data)) 494 | hasAnyData = true 495 | styledWriteLine(stderr, fgYellow, "WARNING: ", resetStyle, "You have an import from set as a global..: " & line) 496 | # Add as normal 497 | else: 498 | codeFrom.data.add((lineNr, false, true, "* " & data)) 499 | hasAnyData = true 500 | 501 | 502 | 503 | proc parseImport(lineCurrentNr: int, file: seq[string]) = 504 | ## Parse `import xx` statements. 505 | ## 506 | ## `lineNr` contains "import". 507 | ## 508 | ## Does not support multiline comments 509 | for lineNr in countup(lineCurrentNr, file.len()-1): 510 | let line = file[lineNr].strip() 511 | let element = checkElement(line) 512 | 513 | # Empty line continue 514 | if line == "import": 515 | continue 516 | 517 | # If a new element was found, exit the parser 518 | if (element != "import" and element != "") or line == "": 519 | forceNewLine = true 520 | lineForce = lineNr 521 | break 522 | 523 | # Check for multiline comment 524 | if line.substr(0,1) == "##" or line.substr(0,0) == "#": 525 | continue 526 | 527 | # Add data 528 | # If line contains comments, specify it as a new line 529 | var newline = if line.contains("##"): true else: false 530 | 531 | # Strip line, so inside code comments is gone. 532 | let lineClean = line.replace(re"import\s", "").replace(",", "").strip() 533 | var data: string 534 | if not newline: 535 | data = "* " & lineClean.split(" ")[0].strip() 536 | else: 537 | data = "* " & replace(lineClean, re"\s.#", ": ").strip() 538 | 539 | # If its global, add global bool 540 | # Baah - imports should not be global" 541 | if line.contains("*"): 542 | codeImport.data.add((lineNr, true, newline, data)) 543 | hasAnyData = true 544 | styledWriteLine(stderr, fgYellow, "WARNING: ", resetStyle, "You have an import from set as a global..: " & line) 545 | # Add as normal 546 | else: 547 | codeImport.data.add((lineNr, false, newline, data)) 548 | hasAnyData = true 549 | 550 | 551 | proc parseCodeText(lineCurrentNr: int, file: seq[string]) = 552 | ## Parse text inside code. E.g. intro text etc. 553 | var skipNextLine: bool 554 | var codeRunning: bool 555 | 556 | for lineNr in countup(lineCurrentNr, file.len()-1): 557 | 558 | # If we already have taken next lines header, then skip the header line. 559 | # Only used on === and ---- 560 | if skipNextLine: 561 | skipNextLine = false 562 | continue 563 | 564 | let line = file[lineNr].strip() 565 | 566 | # Check if next line is a code - if we're currently inserting code 567 | if (line == "" or line.substr(0,1) == "# " or line == "#") and codeRunning: 568 | codeRunning = false 569 | codeText.data.add("```") 570 | 571 | # Empty line continue 572 | if (line == "" or line.substr(0,1) == "# " or line == "#"): 573 | # If the next line does not have a comment, then break 574 | if file[lineNr+1].substr(0,1) != "##" or file[lineNr+1].substr(0,1) != "# ": 575 | forceNewLine = true 576 | lineForce = lineNr 577 | break 578 | 579 | continue 580 | 581 | # If this is a code block 582 | if line.contains(".. code-block::") or codeRunning: 583 | codeRunning = true 584 | let lineClean = line.substr(2, line.len()) 585 | let nextLine = file[lineNr+1].substr(2, file[lineNr+1].len()) 586 | let nextNextLine = file[lineNr+2].substr(2, file[lineNr+2].len()) 587 | 588 | # If we are coming to an end with the code block 589 | if lineClean.strip() == "" and nextLine.substr(0, 1) != " " and nextNextLine.substr(0, 1) != " ": 590 | codeRunning = false 591 | codeText.data.add("```") 592 | 593 | # Code block start 594 | if line.contains(".. code-block::"): 595 | codeText.data.add("```nim") 596 | # Code block code 597 | else: 598 | codeText.data.add(lineClean.strip()) 599 | 600 | # Normal text 601 | else: 602 | # Check the headings 603 | let nextLine = file[lineNr+1].substr(2, file[lineNr+1].len()).strip() 604 | var heading: string 605 | if nextLine.contains("==="): #H1 606 | skipNextLine = true 607 | heading = "# " 608 | elif nextLine.substr(0,0) == "#": #H1 609 | heading = "# " 610 | elif nextLine.contains("---"): #H2 611 | skipNextLine = true 612 | heading = "## " 613 | elif nextLine.substr(0,1) == "##": #H2 614 | heading = "## " 615 | elif nextLine.substr(0,2) == "###": #H3 616 | heading = "### " 617 | elif nextLine.substr(0,3) == "####": #H4 618 | heading = "#### " 619 | elif nextLine.substr(0,4) == "#####": #H5 620 | heading = "##### " 621 | elif nextLine.substr(0,5) == "######": #H6 622 | heading = "###### " 623 | 624 | # Insert text 625 | codeText.data.add(heading & line.replace("#", "").strip()) 626 | 627 | 628 | 629 | proc parseLine(lineNr: int, file: seq[string]) = 630 | ## Parse the current line. If a `proc` or other element is spottet, the 631 | ## parser will continue until it is parsed, and push a new line the reader. 632 | 633 | # Check element 634 | let element = checkElement(file[lineNr]) 635 | 636 | # If this is first run and main text is coming 637 | if element == "" and not hasAnyData and (file[lineNr].substr(0,1) == "##" or file[lineNr].substr(0,1) == "# "): 638 | parseCodeText(lineNr, file) 639 | return 640 | 641 | # If this is inside code 642 | elif element == "": 643 | return 644 | 645 | # If an element was found, parse it 646 | case element 647 | of "import": 648 | parseImport(lineNr, file) 649 | of "from": 650 | parseFrom(lineNr, file) 651 | of "type": 652 | parseType(lineNr, file) 653 | of "template": 654 | parseElement("template", lineNr, file) 655 | of "macro": 656 | parseElement("macro", lineNr, file) 657 | of "proc": 658 | parseElement("proc", lineNr, file) 659 | of "func": 660 | parseElement("func", lineNr, file) 661 | of "iterator": 662 | parseElement("iterator", lineNr, file) 663 | of "const": 664 | parseVariable("const", lineNr, file) 665 | of "let": 666 | parseVariable("let", lineNr, file) 667 | of "var": 668 | parseVariable("var", lineNr, file) 669 | else: 670 | echo element 671 | 672 | return 673 | 674 | 675 | proc generateMarkdown(onlyGlobals, includeHeadings, includeImport, includeTypes, includeLines, includeConst, includeLet, includeVar: bool): seq[string] = 676 | var data: seq[string] 677 | 678 | # Code text 679 | if codeText.data.len() > 0: 680 | for i in codeText.data: 681 | data.add(i) 682 | 683 | 684 | # Import 685 | if includeImport and (codeImport.data.len() > 0 or codeFrom.data.len() > 0): 686 | data.add("# Imports\n") 687 | 688 | for i in codeImport.data.sortedByIt(it.code): 689 | for n in split(i.code, "\n"): 690 | data.add(n) 691 | 692 | data.add("\n") 693 | 694 | for i in codeFrom.data.sortedByIt(it.code): 695 | for n in split(i.code, "\n"): 696 | data.add(n) 697 | 698 | data.add("\n") 699 | 700 | 701 | # Types 702 | if includeTypes and codeType.data.len() > 0: 703 | data.add("# Types\n") 704 | for i in codeType.data: 705 | if onlyGlobals: 706 | if not i.global: continue 707 | 708 | if includeHeadings: 709 | data.add("## " & i.heading & "\n") 710 | for n in split(i.comment, "\n"): 711 | data.add(n) 712 | data.add("```nim") 713 | for n in split(i.code, "\n"): 714 | data.add(n) 715 | data.add("```\n") 716 | data.add("____\n") 717 | data.add("\n") 718 | 719 | 720 | # Const 721 | if includeConst and codeConst.data.len() > 0: 722 | data.add("# Const\n") 723 | for i in codeConst.data: 724 | if onlyGlobals: 725 | if not i.global: continue 726 | 727 | if includeHeadings: 728 | data.add("## " & i.heading & "\n") 729 | data.add("```nim") 730 | data.add(i.code) 731 | data.add("```\n") 732 | for n in split(i.comment, "\n"): 733 | data.add(n) 734 | data.add("____\n") 735 | data.add("\n") 736 | 737 | 738 | # Let 739 | if includeLet and codeLet.data.len() > 0: 740 | data.add("# Let\n") 741 | for i in codeLet.data: 742 | if onlyGlobals: 743 | if not i.global: continue 744 | 745 | if includeHeadings: 746 | data.add("## " & i.heading & "\n") 747 | data.add("```nim") 748 | data.add(i.code) 749 | data.add("```\n") 750 | for n in split(i.comment, "\n"): 751 | data.add(n) 752 | data.add("____\n") 753 | data.add("\n") 754 | 755 | 756 | # Var 757 | if includeVar and codeVar.data.len() > 0: 758 | data.add("# Var\n") 759 | for i in codeVar.data: 760 | if onlyGlobals: 761 | if not i.global: continue 762 | 763 | if includeHeadings: 764 | data.add("## " & i.heading & "\n") 765 | data.add("```nim") 766 | data.add(i.code) 767 | data.add("```\n") 768 | for n in split(i.comment, "\n"): 769 | data.add(n) 770 | data.add("____\n") 771 | data.add("\n") 772 | 773 | 774 | # Procs 775 | if codeProc.data.len() > 0: 776 | data.add("# Procs\n") 777 | for i in codeProc.data: 778 | if onlyGlobals: 779 | if not i.global: continue 780 | 781 | if includeHeadings: 782 | data.add("## " & i.heading & "\n") 783 | data.add("```nim") 784 | data.add(i.code) 785 | data.add("```\n") 786 | if includeLines: 787 | data.add("Line: " & $i.line & "\n") 788 | data.add(i.comment) 789 | data.add("\n") 790 | if i.runnable != "": 791 | #data.add("__Runnable example:__") 792 | data.add("```nim") 793 | for r in i.runnable.split("\n"): 794 | data.add(r.substr(2, r.len())) 795 | data.add("```\n") 796 | data.add("____\n") 797 | 798 | 799 | # Templates 800 | if codeTemplate.data.len() > 0: 801 | data.add("# Templates\n") 802 | for i in codeTemplate.data: 803 | if onlyGlobals: 804 | if not i.global: continue 805 | 806 | if includeHeadings: 807 | data.add("## " & i.heading & "\n") 808 | data.add("```nim") 809 | data.add(i.code) 810 | data.add("```\n") 811 | if includeLines: 812 | data.add("Line: " & $i.line & "\n") 813 | data.add(i.comment) 814 | data.add("\n") 815 | if i.runnable != "": 816 | #data.add("__Runnable example:__") 817 | data.add("```nim") 818 | for r in i.runnable.split("\n"): 819 | data.add(r.substr(2, r.len())) 820 | data.add("```\n") 821 | data.add("____\n") 822 | 823 | 824 | # Macros 825 | if codeMacro.data.len() > 0: 826 | data.add("# Macros\n") 827 | for i in codeMacro.data: 828 | if onlyGlobals: 829 | if not i.global: continue 830 | 831 | if includeHeadings: 832 | data.add("## " & i.heading & "\n") 833 | data.add("```nim") 834 | data.add(i.code) 835 | data.add("```\n") 836 | if includeLines: 837 | data.add("Line: " & $i.line & "\n") 838 | data.add(i.comment) 839 | data.add("\n") 840 | if i.runnable != "": 841 | #data.add("__Runnable example:__") 842 | data.add("```nim") 843 | for r in i.runnable.split("\n"): 844 | data.add(r.substr(2, r.len())) 845 | data.add("```\n") 846 | data.add("____\n") 847 | 848 | 849 | # Func 850 | if codeFunc.data.len() > 0: 851 | data.add("# Funcs\n") 852 | for i in codeFunc.data: 853 | if onlyGlobals: 854 | if not i.global: continue 855 | 856 | if includeHeadings: 857 | data.add("## " & i.heading & "\n") 858 | data.add("```nim") 859 | data.add(i.code) 860 | data.add("```\n") 861 | if includeLines: 862 | data.add("Line: " & $i.line & "\n") 863 | data.add(i.comment) 864 | data.add("\n") 865 | if i.runnable != "": 866 | #data.add("__Runnable example:__") 867 | data.add("```nim") 868 | for r in i.runnable.split("\n"): 869 | data.add(r.substr(2, r.len())) 870 | data.add("```\n") 871 | data.add("____\n") 872 | 873 | 874 | # Iterator 875 | if codeIterator.data.len() > 0: 876 | data.add("# Iterators\n") 877 | for i in codeIterator.data: 878 | if onlyGlobals: 879 | if not i.global: continue 880 | 881 | if includeHeadings: 882 | data.add("## " & i.heading & "\n") 883 | data.add("```nim") 884 | data.add(i.code) 885 | data.add("```\n") 886 | if includeLines: 887 | data.add("Line: " & $i.line & "\n") 888 | data.add(i.comment) 889 | data.add("\n") 890 | if i.runnable != "": 891 | #data.add("__Runnable example:__") 892 | data.add("```nim") 893 | for r in i.runnable.split("\n"): 894 | data.add(r.substr(2, r.len())) 895 | data.add("```\n") 896 | data.add("____\n") 897 | 898 | 899 | return data 900 | 901 | 902 | proc markdownToFile(filename: string, data: seq[string]) = 903 | ## Save markdown to file 904 | var markdown: string 905 | for md in data: 906 | markdown.add(md & "\n") 907 | 908 | try: 909 | writeFile(filename, markdown) 910 | styledWriteLine(stderr, fgGreen, "OK: ", resetStyle, "Markdown written in: " & filename) 911 | except: 912 | styledWriteLine(stderr, fgRed, "ERROR: ", resetStyle, "Could not write markdown to file: " & filename) 913 | 914 | 915 | 916 | proc markdownShow(markdown: seq[string]) = 917 | ## Show markdown 918 | if markdown.len() == 0: 919 | styledWriteLine(stderr, fgYellow, "WARNING: ", resetStyle, "No markdown was generated.") 920 | else: 921 | for line in markdown: 922 | echo line 923 | 924 | 925 | proc parseNim(filename: string, onlyGlobals=false, includeHeadings=true, includeImport=true, includeTypes=true, includeLines=false, includeConst=false, includeLet=false, includeVar=false): seq[string] = 926 | ## Loop through file and generate Markdown. 927 | ## 928 | ## We are reading the number of lines, which will help us to jump back and 929 | ## forward with more control, than if we just did *for lines in file*. 930 | 931 | # Read file 932 | let file = readFile(filename).split("\n") 933 | let lines = file.len() 934 | 935 | styledWriteLine(stderr, fgGreen, "OK: ", resetStyle, "Read file: " & filename) 936 | styledWriteLine(stderr, fgGreen, "OK: ", resetStyle, "Lines to parse: " & $lines) 937 | 938 | # Loop through all lines 939 | for lineNr in countup(0, lines-1): 940 | if forceNewLine: 941 | if lineNr < lineForce: 942 | continue 943 | else: 944 | forceNewLine = false 945 | 946 | parseLine(lineNr, file) 947 | 948 | # Generate markdown 949 | return generateMarkdown(onlyGlobals, includeHeadings, includeImport, includeTypes, includeLines, includeConst, includeLet, includeVar) 950 | 951 | 952 | const help = """ 953 | 954 | nimtomd converts a Nim files into markdown 955 | 956 | Usage: 957 | nimtomd [options] 958 | 959 | Options: 960 | Nim-file to convert into markdown 961 | -h, --help Shows the help menu 962 | -o:, --output:[filename] Outputs the markdown to a file 963 | -ow, --overwrite Allow to overwrite a existing md file 964 | -g, --onlyglobals Only include global elements (*) 965 | -sh Skip headings 966 | -si Skip imports 967 | -st Skip types 968 | -il, --includelines Include linenumbers 969 | -ic, --includeconst Include const 970 | -il, --includelet Include let 971 | -iv, --includevar Include var 972 | """ 973 | 974 | when isMainModule: 975 | let time1 = epochTime() 976 | 977 | let args = multiReplace(commandLineParams().join(","), [("-", ""), (" ", "")]) 978 | case args 979 | of "", " ", "h", "help": 980 | echo help 981 | 982 | else: 983 | var 984 | outputFile = "" 985 | overwrite = false 986 | argOnlyGlobals = false 987 | argIncludeHeadings = true 988 | argIncludeImport = true 989 | argIncludeTypes = true 990 | argIncludeLines = false 991 | argIncludeConst = false 992 | argIncludeLet = false 993 | argIncludeVar = false 994 | 995 | let 996 | argsSplit = args.split(",") 997 | 998 | # Go through args 999 | for arg in argsSplit: 1000 | if arg.contains("o:"): 1001 | outputFile = arg.substr(2, arg.len()) 1002 | elif arg.contains("output:"): 1003 | outputFile = arg.substr(7, arg.len()) 1004 | elif arg == "ow" or arg == "overwrite": 1005 | overwrite = true 1006 | elif arg == "onlyglobals" or arg == "g": 1007 | argOnlyGlobals = true 1008 | elif arg == "skipheading" or arg == "sh": 1009 | argIncludeHeadings = false 1010 | elif arg == "skipimports" or arg == "si": 1011 | argIncludeImport = false 1012 | elif arg == "skiptypes" or arg == "st": 1013 | argIncludeTypes = false 1014 | elif arg == "includelines" or arg == "il": 1015 | argIncludeLines = true 1016 | elif arg == "includeconst" or arg == "ic": 1017 | argIncludeConst = true 1018 | elif arg == "includelet" or arg == "il": 1019 | argIncludeLet = true 1020 | elif arg == "includevar" or arg == "iv": 1021 | argIncludeVar = true 1022 | 1023 | # Check if file with code exists 1024 | if not fileExists(argsSplit[argsSplit.len() - 1]): 1025 | styledWriteLine(stderr, fgRed, "ERROR: ", resetStyle, "File not found: " & argsSplit[argsSplit.len() - 1]) 1026 | quit(0) 1027 | 1028 | # Check if we are going to write the markdown to a file. Then also check if 1029 | # the file already exists- then the -ow arg needs to be specificed. 1030 | if outputFile != "" and fileExists(outputFile) and not overwrite: 1031 | styledWriteLine(stderr, fgYellow, "WARNING: ", resetStyle, "Output file already exists. Specify -ow to overwrite it.") 1032 | quit(0) 1033 | 1034 | # Generate markdown 1035 | let markdown = parseNim(argsSplit[argsSplit.len() - 1], onlyGlobals=argOnlyGlobals, includeHeadings=argIncludeHeadings, includeImport=argIncludeImport, includeTypes=argIncludeTypes, includeLines=argIncludeLines, includeConst=argIncludeConst, includeLet=argIncludeLet, includeVar=argIncludeVar) 1036 | 1037 | if markdown.len() == 0: 1038 | styledWriteLine(stderr, fgYellow, "WARNING: ", resetStyle, "No markdown was generated.") 1039 | quit(0) 1040 | 1041 | # Output markdown 1042 | if outputFile.len() != 0: 1043 | markdownToFile(outputFile, markdown) 1044 | else: 1045 | markdownShow(markdown) 1046 | 1047 | let time2 = epochTime() 1048 | styledWriteLine(stderr, fgGreen, "OK: ", resetStyle, "Time used: " & $(time2 - time1) & " seconds") 1049 | 1050 | -------------------------------------------------------------------------------- /tests/complex.md: -------------------------------------------------------------------------------- 1 | ## My Program 2 | 3 | Important test 4 | and more 5 | 6 | # Usage: 7 | 8 | ```nim 9 | nimtomd [options] 10 | ``` 11 | 12 | # Options: 13 | ```nim 14 | filename.nim File to output in Markdown 15 | help Shows the help menu 16 | ``` 17 | 18 | # Procs 19 | 20 | ## globalProc* 21 | 22 | ```nim 23 | proc globalProc*() = 24 | ``` 25 | 26 | Echo markdown 27 | 28 | 29 | ____ 30 | 31 | # Templates 32 | 33 | ## template nothing 34 | 35 | ```nim 36 | template nothing(): string = 37 | ``` 38 | 39 | 40 | 41 | 42 | ____ 43 | 44 | -------------------------------------------------------------------------------- /tests/complex.nim: -------------------------------------------------------------------------------- 1 | ## My Program 2 | ## ---------- 3 | ## 4 | ## Important test 5 | ## and more 6 | ## 7 | ## Usage: 8 | ## ===== 9 | ## 10 | ## .. code-block::nim 11 | ## nimtomd [options] 12 | ## 13 | ## Options: 14 | ## ======== 15 | ## .. code-block::nim 16 | ## filename.nim File to output in Markdown 17 | ## help Shows the help menu 18 | ## 19 | 20 | var globalVar*: string ## Important global 21 | 22 | 23 | proc globalProc*() = 24 | ## Echo markdown 25 | echo "proc" 26 | 27 | 28 | template nothing(): string = 29 | "nimtomd" 30 | 31 | 32 | macro donothing(): untyped = 33 | discard -------------------------------------------------------------------------------- /tests/config.nims: -------------------------------------------------------------------------------- 1 | switch("path", "$projectDir/../src") -------------------------------------------------------------------------------- /tests/strutils.md: -------------------------------------------------------------------------------- 1 | The system module defines several common functions for working with strings, 2 | such as: 3 | * ``$`` for converting other data-types to strings 4 | * ``&`` for string concatenation 5 | * ``add`` for adding a new character or a string to the existing one 6 | * ``in`` (alias for ``contains``) and ``notin`` for checking if a character 7 | is in a string 8 | 9 | This module builds upon that, providing additional functionality in form of 10 | procedures, iterators and templates for strings. 11 | 12 | ```nim 13 | import strutils 14 | 15 | let 16 | numbers = @[867, 5309] 17 | multiLineString = "first line\nsecond line\nthird line" 18 | 19 | let jenny = numbers.join("-") 20 | assert jenny == "867-5309" 21 | 22 | assert splitLines(multiLineString) == 23 | @["first line", "second line", "third line"] 24 | assert split(multiLineString) == @["first", "line", "second", 25 | "line", "third", "line"] 26 | assert indent(multiLineString, 4) == 27 | " first line\n second line\n third line" 28 | assert 'z'.repeat(5) == "zzzzz" 29 | ``` 30 | 31 | The chaining of functions is possible thanks to the 32 | `method call syntax`_: 33 | 34 | ```nim 35 | import strutils 36 | from sequtils import map 37 | 38 | let jenny = "867-5309" 39 | assert jenny.split('-').map(parseInt) == @[867, 5309] 40 | 41 | assert "Beetlejuice".indent(1).repeat(3).strip == 42 | "Beetlejuice Beetlejuice Beetlejuice" 43 | ``` 44 | 45 | This module is available for the `JavaScript target 46 | `_. 47 | ## 48 | 49 | **See also:** 50 | * `strformat module`_ for string interpolation and formatting 51 | * `unicode module`_ for Unicode UTF-8 handling 52 | * `sequtils module`_ for operations on container 53 | types (including strings) 54 | * `parseutils module`_ for lower-level parsing of tokens, 55 | numbers, identifiers, etc. 56 | * `parseopt module`_ for command-line parsing 57 | * `strtabs module`_ for efficient hash tables 58 | (dictionaries, in some programming languages) mapping from strings to strings 59 | * `pegs module`_ for PEG (Parsing Expression Grammar) support 60 | * `ropes module`_ for rope data type, which can represent very 61 | long strings efficiently 62 | * `re module`_ for regular expression (regex) support 63 | * `strscans`_ for ``scanf`` and ``scanp`` macros, which offer 64 | easier substring extraction than regular expressions 65 | # Imports 66 | 67 | * parseutils 68 | 69 | 70 | * from algorithm import reverse 71 | * from math import pow, floor, log10 72 | 73 | 74 | # Types 75 | 76 | ## SkipTable* = array[char, int] 77 | 78 | 79 | ```nim 80 | SkipTable* = array[char, int] 81 | ``` 82 | 83 | ____ 84 | 85 | 86 | 87 | ## FloatFormatMode* = enum 88 | 89 | the different modes of floating point formatting 90 | 91 | * __ffDefault,__: use the shorter floating point notation 92 | 93 | * __ffDecimal,__: use decimal floating point notation 94 | 95 | * __ffScientific__: use scientific notation (using ``e`` character) 96 | ```nim 97 | FloatFormatMode* = enum 98 | ffDefault, 99 | ffDecimal, 100 | ffScientific 101 | ``` 102 | 103 | ____ 104 | 105 | 106 | 107 | ## BinaryPrefixMode* = enum 108 | 109 | the different names for binary prefixes 110 | ```nim 111 | BinaryPrefixMode* = enum 112 | bpIEC, 113 | bpColloquial 114 | ``` 115 | 116 | ____ 117 | 118 | 119 | 120 | # Procs 121 | 122 | ## isAlphaAscii* 123 | 124 | ```nim 125 | proc isAlphaAscii*(c: char): bool {.noSideEffect, procvar, rtl, extern: "nsuIsAlphaAsciiChar".} = 126 | ``` 127 | 128 | Checks whether or not character `c` is alphabetical. 129 | 130 | This checks a-z, A-Z ASCII characters only. Use Unicode module [(link)](https://nim-lang.org/docs/unicode.html) for UTF-8 support. 131 | 132 | 133 | ```nim 134 | runnableExamples: 135 | doAssert isAlphaAscii('e') == true 136 | doAssert isAlphaAscii('E') == true 137 | doAssert isAlphaAscii('8') == false 138 | ``` 139 | 140 | ____ 141 | 142 | ## isAlphaNumeric* 143 | 144 | ```nim 145 | proc isAlphaNumeric*(c: char): bool {.noSideEffect, procvar, rtl, extern: "nsuIsAlphaNumericChar".} = 146 | ``` 147 | 148 | Checks whether or not `c` is alphanumeric. 149 | 150 | This checks a-z, A-Z, 0-9 ASCII characters only. 151 | 152 | 153 | ```nim 154 | runnableExamples: 155 | doAssert isAlphaNumeric('n') == true 156 | doAssert isAlphaNumeric('8') == true 157 | doAssert isAlphaNumeric(' ') == false 158 | ``` 159 | 160 | ____ 161 | 162 | ## isDigit* 163 | 164 | ```nim 165 | proc isDigit*(c: char): bool {.noSideEffect, procvar, rtl, extern: "nsuIsDigitChar".} = 166 | ``` 167 | 168 | Checks whether or not `c` is a number. 169 | 170 | This checks 0-9 ASCII characters only. 171 | 172 | 173 | ```nim 174 | runnableExamples: 175 | doAssert isDigit('n') == false 176 | doAssert isDigit('8') == true 177 | ``` 178 | 179 | ____ 180 | 181 | ## isSpaceAscii* 182 | 183 | ```nim 184 | proc isSpaceAscii*(c: char): bool {.noSideEffect, procvar, rtl, extern: "nsuIsSpaceAsciiChar".} = 185 | ``` 186 | 187 | Checks whether or not `c` is a whitespace character. 188 | 189 | 190 | ```nim 191 | runnableExamples: 192 | doAssert isSpaceAscii('n') == false 193 | doAssert isSpaceAscii(' ') == true 194 | doAssert isSpaceAscii('\t') == true 195 | ``` 196 | 197 | ____ 198 | 199 | ## isLowerAscii* 200 | 201 | ```nim 202 | proc isLowerAscii*(c: char): bool {.noSideEffect, procvar, rtl, extern: "nsuIsLowerAsciiChar".} = 203 | ``` 204 | 205 | Checks whether or not `c` is a lower case character. 206 | 207 | This checks ASCII characters only. Use Unicode module [(link)](https://nim-lang.org/docs/unicode.html) for UTF-8 support. 208 | 209 | 210 | 211 | See also: 212 | * `toLowerAscii proc<#toLowerAscii,char>`_ 213 | 214 | 215 | ```nim 216 | runnableExamples: 217 | doAssert isLowerAscii('e') == true 218 | doAssert isLowerAscii('E') == false 219 | doAssert isLowerAscii('7') == false 220 | ``` 221 | 222 | ____ 223 | 224 | ## isUpperAscii* 225 | 226 | ```nim 227 | proc isUpperAscii*(c: char): bool {.noSideEffect, procvar, rtl, extern: "nsuIsUpperAsciiChar".} = 228 | ``` 229 | 230 | Checks whether or not `c` is an upper case character. 231 | 232 | This checks ASCII characters only. Use Unicode module [(link)](https://nim-lang.org/docs/unicode.html) for UTF-8 support. 233 | 234 | 235 | 236 | See also: 237 | * `toUpperAscii proc<#toUpperAscii,char>`_ 238 | 239 | 240 | ```nim 241 | runnableExamples: 242 | doAssert isUpperAscii('e') == false 243 | doAssert isUpperAscii('E') == true 244 | doAssert isUpperAscii('7') == false 245 | ``` 246 | 247 | ____ 248 | 249 | ## toLowerAscii* 250 | 251 | ```nim 252 | proc toLowerAscii*(c: char): char {.noSideEffect, procvar, rtl, extern: "nsuToLowerAsciiChar".} = 253 | ``` 254 | 255 | Returns the lower case version of character ``c``. 256 | 257 | This works only for the letters ``A-Z``. See `unicode.toLower `_ for a version that works for any Unicode character. 258 | 259 | 260 | 261 | See also: 262 | * `isLowerAscii proc<#isLowerAscii,char>`_ 263 | * `toLowerAscii proc<#toLowerAscii,string>`_ for converting a string 264 | 265 | 266 | ```nim 267 | runnableExamples: 268 | doAssert toLowerAscii('A') == 'a' 269 | doAssert toLowerAscii('e') == 'e' 270 | result = chr(ord(c) + (ord('a') - ord('A'))) 271 | result = c 272 | ``` 273 | 274 | ____ 275 | 276 | ## toLowerAscii* 277 | 278 | ```nim 279 | proc toLowerAscii*(s: string): string {.noSideEffect, procvar, rtl, extern: "nsuToLowerAsciiStr".} = 280 | ``` 281 | 282 | Converts string `s` into lower case. 283 | 284 | This works only for the letters ``A-Z``. See `unicode.toLower `_ for a version that works for any Unicode character. 285 | 286 | 287 | 288 | See also: 289 | * `normalize proc<#normalize,string>`_ 290 | 291 | 292 | ```nim 293 | runnableExamples: 294 | doAssert toLowerAscii("FooBar!") == "foobar!" 295 | ``` 296 | 297 | ____ 298 | 299 | ## toUpperAscii* 300 | 301 | ```nim 302 | proc toUpperAscii*(c: char): char {.noSideEffect, procvar, rtl, extern: "nsuToUpperAsciiChar".} = 303 | ``` 304 | 305 | Converts character `c` into upper case. 306 | 307 | This works only for the letters ``A-Z``. See `unicode.toUpper `_ for a version that works for any Unicode character. 308 | 309 | 310 | 311 | See also: 312 | * `isLowerAscii proc<#isLowerAscii,char>`_ 313 | * `toUpperAscii proc<#toUpperAscii,string>`_ for converting a string 314 | * `capitalizeAscii proc<#capitalizeAscii,string>`_ 315 | 316 | 317 | ```nim 318 | runnableExamples: 319 | doAssert toUpperAscii('a') == 'A' 320 | doAssert toUpperAscii('E') == 'E' 321 | result = chr(ord(c) - (ord('a') - ord('A'))) 322 | result = c 323 | ``` 324 | 325 | ____ 326 | 327 | ## toUpperAscii* 328 | 329 | ```nim 330 | proc toUpperAscii*(s: string): string {.noSideEffect, procvar, rtl, extern: "nsuToUpperAsciiStr".} = 331 | ``` 332 | 333 | Converts string `s` into upper case. 334 | 335 | This works only for the letters ``A-Z``. See `unicode.toUpper `_ for a version that works for any Unicode character. 336 | 337 | 338 | 339 | See also: 340 | * `capitalizeAscii proc<#capitalizeAscii,string>`_ 341 | 342 | 343 | ```nim 344 | runnableExamples: 345 | doAssert toUpperAscii("FooBar!") == "FOOBAR!" 346 | ``` 347 | 348 | ____ 349 | 350 | ## capitalizeAscii* 351 | 352 | ```nim 353 | proc capitalizeAscii*(s: string): string {.noSideEffect, procvar, rtl, extern: "nsuCapitalizeAscii".} = 354 | ``` 355 | 356 | Converts the first character of string `s` into upper case. 357 | 358 | This works only for the letters ``A-Z``. Use Unicode module [(link)](https://nim-lang.org/docs/unicode.html) for UTF-8 support. 359 | 360 | 361 | 362 | See also: 363 | * `toUpperAscii proc<#toUpperAscii,char>`_ 364 | 365 | 366 | ```nim 367 | runnableExamples: 368 | doAssert capitalizeAscii("foo") == "Foo" 369 | doAssert capitalizeAscii("-bar") == "-bar" 370 | ``` 371 | 372 | ____ 373 | 374 | ## normalize* 375 | 376 | ```nim 377 | proc normalize*(s: string): string {.noSideEffect, procvar, rtl, extern: "nsuNormalize".} = 378 | ``` 379 | 380 | Normalizes the string `s`. 381 | 382 | That means to convert it to lower case and remove any '_'. This should NOT be used to normalize Nim identifier names. 383 | 384 | 385 | 386 | See also: 387 | * `toLowerAscii proc<#toLowerAscii,string>`_ 388 | 389 | 390 | ```nim 391 | runnableExamples: 392 | doAssert normalize("Foo_bar") == "foobar" 393 | doAssert normalize("Foo Bar") == "foo bar" 394 | if s[i] in {'A'..'Z'}: 395 | result[j] = chr(ord(s[i]) + (ord('a') - ord('A'))) 396 | inc j 397 | elif s[i] != '_': 398 | result[j] = s[i] 399 | inc j 400 | ``` 401 | 402 | ____ 403 | 404 | ## cmpIgnoreCase* 405 | 406 | ```nim 407 | proc cmpIgnoreCase*(a, b: string): int {.noSideEffect, rtl, extern: "nsuCmpIgnoreCase", procvar.} = 408 | ``` 409 | 410 | Compares two strings in a case insensitive manner. Returns: 411 | 412 | | 0 if a == b | < 0 if a < b | > 0 if a > b 413 | 414 | 415 | ```nim 416 | runnableExamples: 417 | doAssert cmpIgnoreCase("FooBar", "foobar") == 0 418 | doAssert cmpIgnoreCase("bar", "Foo") < 0 419 | doAssert cmpIgnoreCase("Foo5", "foo4") > 0 420 | result = ord(toLowerAscii(a[i])) - ord(toLowerAscii(b[i])) 421 | if result != 0: return 422 | inc(i) 423 | ``` 424 | 425 | ____ 426 | 427 | ## cmpIgnoreStyle* 428 | 429 | ```nim 430 | proc cmpIgnoreStyle*(a, b: string): int {.noSideEffect, rtl, extern: "nsuCmpIgnoreStyle", procvar.} = 431 | ``` 432 | 433 | Semantically the same as ``cmp(normalize(a), normalize(b))``. It is just optimized to not allocate temporary strings. This should NOT be used to compare Nim identifier names. Use `macros.eqIdent`_ for that. 434 | 435 | Returns: 436 | 437 | | 0 if a == b | < 0 if a < b | > 0 if a > b 438 | 439 | 440 | ```nim 441 | runnableExamples: 442 | doAssert cmpIgnoreStyle("foo_bar", "FooBar") == 0 443 | doAssert cmpIgnoreStyle("foo_bar_5", "FooBar4") > 0 444 | while i < a.len and a[i] == '_': inc i 445 | while j < b.len and b[j] == '_': inc j 446 | var aa = if i < a.len: toLowerAscii(a[i]) else: '\0' 447 | var bb = if j < b.len: toLowerAscii(b[j]) else: '\0' 448 | result = ord(aa) - ord(bb) 449 | if result != 0: return result 450 | # the characters are identical: 451 | if i >= a.len: 452 | # both cursors at the end: 453 | if j >= b.len: return 0 454 | # not yet at the end of 'b': 455 | return -1 456 | elif j >= b.len: 457 | return 1 458 | inc i 459 | inc j 460 | ``` 461 | 462 | ____ 463 | 464 | ## substrEq 465 | 466 | ```nim 467 | proc substrEq(s: string, pos: int, substr: string): bool = 468 | ``` 469 | 470 | 471 | 472 | 473 | ____ 474 | 475 | ## split* 476 | 477 | ```nim 478 | proc split*(s: string, sep: char, maxsplit: int = -1): seq[string] {.noSideEffect, rtl, extern: "nsuSplitChar".} = 479 | ``` 480 | 481 | The same as the `split iterator <#split.i,string,char,int>`_ (see its documentation), but is a proc that returns a sequence of substrings. 482 | 483 | 484 | 485 | See also: 486 | * `split iterator <#split.i,string,char,int>`_ 487 | * `rsplit proc<#rsplit,string,char,int>`_ 488 | * `splitLines proc<#splitLines,string>`_ 489 | * `splitWhitespace proc<#splitWhitespace,string,int>`_ 490 | 491 | 492 | ```nim 493 | runnableExamples: 494 | doAssert "a,b,c".split(',') == @["a", "b", "c"] 495 | doAssert "".split(' ') == @[""] 496 | ``` 497 | 498 | ____ 499 | 500 | ## split* 501 | 502 | ```nim 503 | proc split*(s: string, seps: set[char] = Whitespace, maxsplit: int = -1): seq[string] {. noSideEffect, rtl, extern: "nsuSplitCharSet".} = 504 | ``` 505 | 506 | The same as the `split iterator <#split.i,string,set[char],int>`_ (see its documentation), but is a proc that returns a sequence of substrings. 507 | 508 | 509 | 510 | See also: 511 | * `split iterator <#split.i,string,set[char],int>`_ 512 | * `rsplit proc<#rsplit,string,set[char],int>`_ 513 | * `splitLines proc<#splitLines,string>`_ 514 | * `splitWhitespace proc<#splitWhitespace,string,int>`_ 515 | 516 | 517 | ```nim 518 | runnableExamples: 519 | doAssert "a,b;c".split({',', ';'}) == @["a", "b", "c"] 520 | doAssert "".split({' '}) == @[""] 521 | ``` 522 | 523 | ____ 524 | 525 | ## split* 526 | 527 | ```nim 528 | proc split*(s: string, sep: string, maxsplit: int = -1): seq[string] {.noSideEffect, rtl, extern: "nsuSplitString".} = 529 | ``` 530 | 531 | Splits the string `s` into substrings using a string separator. 532 | 533 | Substrings are separated by the string `sep`. This is a wrapper around the `split iterator <#split.i,string,string,int>`_. 534 | 535 | 536 | 537 | See also: 538 | * `split iterator <#split.i,string,string,int>`_ 539 | * `rsplit proc<#rsplit,string,string,int>`_ 540 | * `splitLines proc<#splitLines,string>`_ 541 | * `splitWhitespace proc<#splitWhitespace,string,int>`_ 542 | 543 | 544 | ```nim 545 | runnableExamples: 546 | doAssert "a,b,c".split(",") == @["a", "b", "c"] 547 | doAssert "a man a plan a canal panama".split("a ") == @["", "man ", "plan ", "canal panama"] 548 | doAssert "".split("Elon Musk") == @[""] 549 | doAssert "a largely spaced sentence".split(" ") == @["a", "", "largely", 550 | "", "", "", "spaced", "sentence"] 551 | doAssert "a largely spaced sentence".split(" ", maxsplit = 1) == @["a", " largely spaced sentence"] 552 | ``` 553 | 554 | ____ 555 | 556 | ## rsplit* 557 | 558 | ```nim 559 | proc rsplit*(s: string, sep: char, maxsplit: int = -1): seq[string] {.noSideEffect, rtl, extern: "nsuRSplitChar".} = 560 | ``` 561 | 562 | The same as the `rsplit iterator <#rsplit.i,string,char,int>`_, but is a proc that returns a sequence of substrings. 563 | 564 | A possible common use case for `rsplit` is path manipulation, particularly on systems that don't use a common delimiter. 565 | 566 | For example, if a system had `#` as a delimiter, you could do the following to get the tail of the path: 567 | 568 | 569 | ```nim 570 | var tailSplit = rsplit("Root#Object#Method#Index", '#', maxsplit=1) 571 | ``` 572 | 573 | 574 | Results in `tailSplit` containing: 575 | 576 | 577 | ```nim 578 | @["Root#Object#Method", "Index"] 579 | ``` 580 | 581 | 582 | 583 | 584 | See also: 585 | * `rsplit iterator <#rsplit.i,string,char,int>`_ 586 | * `split proc<#split,string,char,int>`_ 587 | * `splitLines proc<#splitLines,string>`_ 588 | * `splitWhitespace proc<#splitWhitespace,string,int>`_ 589 | 590 | 591 | ____ 592 | 593 | ## rsplit* 594 | 595 | ```nim 596 | proc rsplit*(s: string, seps: set[char] = Whitespace, {.noSideEffect, rtl, extern: "nsuRSplitCharSet".} = 597 | ``` 598 | 599 | The same as the `rsplit iterator <#rsplit.i,string,set[char],int>`_, but is a proc that returns a sequence of substrings. 600 | 601 | A possible common use case for `rsplit` is path manipulation, particularly on systems that don't use a common delimiter. 602 | 603 | For example, if a system had `#` as a delimiter, you could do the following to get the tail of the path: 604 | 605 | 606 | ```nim 607 | var tailSplit = rsplit("Root#Object#Method#Index", {'#'}, maxsplit=1) 608 | ``` 609 | 610 | 611 | Results in `tailSplit` containing: 612 | 613 | 614 | ```nim 615 | @["Root#Object#Method", "Index"] 616 | ``` 617 | 618 | 619 | 620 | 621 | See also: 622 | * `rsplit iterator <#rsplit.i,string,set[char],int>`_ 623 | * `split proc<#split,string,set[char],int>`_ 624 | * `splitLines proc<#splitLines,string>`_ 625 | * `splitWhitespace proc<#splitWhitespace,string,int>`_ 626 | 627 | 628 | ____ 629 | 630 | ## rsplit* 631 | 632 | ```nim 633 | proc rsplit*(s: string, sep: string, maxsplit: int = -1): seq[string] {.noSideEffect, rtl, extern: "nsuRSplitString".} = 634 | ``` 635 | 636 | The same as the `rsplit iterator <#rsplit.i,string,string,int,bool>`_, but is a proc that returns a sequence of substrings. 637 | 638 | A possible common use case for `rsplit` is path manipulation, particularly on systems that don't use a common delimiter. 639 | 640 | For example, if a system had `#` as a delimiter, you could do the following to get the tail of the path: 641 | 642 | 643 | ```nim 644 | var tailSplit = rsplit("Root#Object#Method#Index", "#", maxsplit=1) 645 | ``` 646 | 647 | 648 | Results in `tailSplit` containing: 649 | 650 | 651 | ```nim 652 | @["Root#Object#Method", "Index"] 653 | ``` 654 | 655 | 656 | 657 | 658 | See also: 659 | * `rsplit iterator <#rsplit.i,string,string,int,bool>`_ 660 | * `split proc<#split,string,string,int>`_ 661 | * `splitLines proc<#splitLines,string>`_ 662 | * `splitWhitespace proc<#splitWhitespace,string,int>`_ 663 | 664 | 665 | ```nim 666 | runnableExamples: 667 | doAssert "a largely spaced sentence".rsplit(" ", maxsplit = 1) == @[ 668 | "a largely spaced", "sentence"] 669 | doAssert "a,b,c".rsplit(",") == @["a", "b", "c"] 670 | doAssert "a man a plan a canal panama".rsplit("a ") == @["", "man ", 671 | "plan ", "canal panama"] 672 | doAssert "".rsplit("Elon Musk") == @[""] 673 | doAssert "a largely spaced sentence".rsplit(" ") == @["a", "", 674 | "largely", "", "", "", "spaced", "sentence"] 675 | ``` 676 | 677 | ____ 678 | 679 | ## splitLines* 680 | 681 | ```nim 682 | proc splitLines*(s: string, keepEol = false): seq[string] {.noSideEffect, rtl, extern: "nsuSplitLines".} = 683 | ``` 684 | 685 | The same as the `splitLines iterator<#splitLines.i,string>`_ (see its documentation), but is a proc that returns a sequence of substrings. 686 | 687 | 688 | 689 | See also: 690 | * `splitLines iterator<#splitLines.i,string>`_ 691 | * `splitWhitespace proc<#splitWhitespace,string,int>`_ 692 | * `countLines proc<#countLines,string>`_ 693 | 694 | 695 | ____ 696 | 697 | ## splitWhitespace* 698 | 699 | ```nim 700 | proc splitWhitespace*(s: string, maxsplit: int = -1): seq[string] {.noSideEffect, rtl, extern: "nsuSplitWhitespace".} = 701 | ``` 702 | 703 | The same as the `splitWhitespace iterator <#splitWhitespace.i,string,int>`_ (see its documentation), but is a proc that returns a sequence of substrings. 704 | 705 | 706 | 707 | See also: 708 | * `splitWhitespace iterator <#splitWhitespace.i,string,int>`_ 709 | * `splitLines proc<#splitLines,string>`_ 710 | 711 | 712 | ____ 713 | 714 | ## toBin* 715 | 716 | ```nim 717 | proc toBin*(x: BiggestInt, len: Positive): string {.noSideEffect, rtl, extern: "nsuToBin".} = 718 | ``` 719 | 720 | Converts `x` into its binary representation. 721 | 722 | The resulting string is always `len` characters long. No leading ``0b`` prefix is generated. 723 | 724 | 725 | ```nim 726 | runnableExamples: 727 | let 728 | a = 29 729 | b = 257 730 | doAssert a.toBin(8) == "00011101" 731 | doAssert b.toBin(8) == "00000001" 732 | doAssert b.toBin(9) == "100000001" 733 | mask = BiggestUInt 1 734 | shift = BiggestUInt 0 735 | result[j] = chr(int((BiggestUInt(x) and mask) shr shift) + ord('0')) 736 | inc shift 737 | mask = mask shl BiggestUInt(1) 738 | ``` 739 | 740 | ____ 741 | 742 | ## toOct* 743 | 744 | ```nim 745 | proc toOct*(x: BiggestInt, len: Positive): string {.noSideEffect, rtl, extern: "nsuToOct".} = 746 | ``` 747 | 748 | Converts `x` into its octal representation. 749 | 750 | The resulting string is always `len` characters long. No leading ``0o`` prefix is generated. 751 | 752 | Do not confuse it with `toOctal proc<#toOctal,char>`_. 753 | 754 | 755 | ```nim 756 | runnableExamples: 757 | let 758 | a = 62 759 | b = 513 760 | doAssert a.toOct(3) == "076" 761 | doAssert b.toOct(3) == "001" 762 | doAssert b.toOct(5) == "01001" 763 | mask = BiggestUInt 7 764 | shift = BiggestUInt 0 765 | result[j] = chr(int((BiggestUInt(x) and mask) shr shift) + ord('0')) 766 | inc shift, 3 767 | mask = mask shl BiggestUInt(3) 768 | ``` 769 | 770 | ____ 771 | 772 | ## toHex* 773 | 774 | ```nim 775 | proc toHex*(x: BiggestInt, len: Positive): string {.noSideEffect, rtl, extern: "nsuToHex".} = 776 | ``` 777 | 778 | Converts `x` to its hexadecimal representation. 779 | 780 | The resulting string will be exactly `len` characters long. No prefix like ``0x`` is generated. `x` is treated as an unsigned value. 781 | 782 | 783 | ```nim 784 | runnableExamples: 785 | let 786 | a = 62 787 | b = 4097 788 | doAssert a.toHex(3) == "03E" 789 | doAssert b.toHex(3) == "001" 790 | doAssert b.toHex(4) == "1001" 791 | HexChars = "0123456789ABCDEF" 792 | n = x 793 | result[j] = HexChars[int(n and 0xF)] 794 | n = n shr 4 795 | # handle negative overflow 796 | if n == 0 and x < 0: n = -1 797 | ``` 798 | 799 | ____ 800 | 801 | ## toHex*[T: SomeInteger] 802 | 803 | ```nim 804 | proc toHex*[T: SomeInteger](x: T): string = 805 | ``` 806 | 807 | Shortcut for ``toHex(x, T.sizeOf * 2)`` 808 | 809 | 810 | ```nim 811 | runnableExamples: 812 | doAssert toHex(1984'i64) == "00000000000007C0" 813 | ``` 814 | 815 | ____ 816 | 817 | ## toHex* 818 | 819 | ```nim 820 | proc toHex*(s: string): string {.noSideEffect, rtl.} = 821 | ``` 822 | 823 | Converts a bytes string to its hexadecimal representation. 824 | 825 | The output is twice the input long. No prefix like ``0x`` is generated. 826 | 827 | 828 | 829 | See also: 830 | * `parseHexStr proc<#parseHexStr,string>`_ for the reverse operation 831 | 832 | 833 | ```nim 834 | runnableExamples: 835 | let 836 | a = "1" 837 | b = "A" 838 | c = "\0\255" 839 | doAssert a.toHex() == "31" 840 | doAssert b.toHex() == "41" 841 | doAssert c.toHex() == "00FF" 842 | ``` 843 | 844 | ____ 845 | 846 | ## toOctal* 847 | 848 | ```nim 849 | proc toOctal*(c: char): string {.noSideEffect, rtl, extern: "nsuToOctal".} = 850 | ``` 851 | 852 | Converts a character `c` to its octal representation. 853 | 854 | The resulting string may not have a leading zero. Its length is always exactly 3. 855 | 856 | Do not confuse it with `toOct proc<#toOct,BiggestInt,Positive>`_. 857 | 858 | 859 | ```nim 860 | runnableExamples: 861 | doAssert toOctal('1') == "061" 862 | doAssert toOctal('A') == "101" 863 | doAssert toOctal('a') == "141" 864 | doAssert toOctal('!') == "041" 865 | ``` 866 | 867 | ____ 868 | 869 | ## fromBin*[T: SomeInteger] 870 | 871 | ```nim 872 | proc fromBin*[T: SomeInteger](s: string): T = 873 | ``` 874 | 875 | Parses a binary integer value from a string `s`. 876 | 877 | If `s` is not a valid binary integer, `ValueError` is raised. `s` can have one of the following optional prefixes: `0b`, `0B`. Underscores within `s` are ignored. 878 | 879 | Does not check for overflow. If the value represented by `s` is too big to fit into a return type, only the value of the rightmost binary digits of `s` is returned without producing an error. 880 | 881 | 882 | ```nim 883 | runnableExamples: 884 | let s = "0b_0100_1000_1000_1000_1110_1110_1001_1001" 885 | doAssert fromBin[int](s) == 1216933529 886 | doAssert fromBin[int8](s) == 0b1001_1001'i8 887 | doAssert fromBin[int8](s) == -103'i8 888 | doAssert fromBin[uint8](s) == 153 889 | doAssert s.fromBin[:int16] == 0b1110_1110_1001_1001'i16 890 | doAssert s.fromBin[:uint64] == 1216933529'u64 891 | ``` 892 | 893 | ____ 894 | 895 | ## fromOct*[T: SomeInteger] 896 | 897 | ```nim 898 | proc fromOct*[T: SomeInteger](s: string): T = 899 | ``` 900 | 901 | Parses an octal integer value from a string `s`. 902 | 903 | If `s` is not a valid octal integer, `ValueError` is raised. `s` can have one of the following optional prefixes: `0o`, `0O`. Underscores within `s` are ignored. 904 | 905 | Does not check for overflow. If the value represented by `s` is too big to fit into a return type, only the value of the rightmost octal digits of `s` is returned without producing an error. 906 | 907 | 908 | ```nim 909 | runnableExamples: 910 | let s = "0o_123_456_777" 911 | doAssert fromOct[int](s) == 21913087 912 | doAssert fromOct[int8](s) == 0o377'i8 913 | doAssert fromOct[int8](s) == -1'i8 914 | doAssert fromOct[uint8](s) == 255'u8 915 | doAssert s.fromOct[:int16] == 24063'i16 916 | doAssert s.fromOct[:uint64] == 21913087'u64 917 | ``` 918 | 919 | ____ 920 | 921 | ## fromHex*[T: SomeInteger] 922 | 923 | ```nim 924 | proc fromHex*[T: SomeInteger](s: string): T = 925 | ``` 926 | 927 | Parses a hex integer value from a string `s`. 928 | 929 | If `s` is not a valid hex integer, `ValueError` is raised. `s` can have one of the following optional prefixes: `0x`, `0X`, `#`. Underscores within `s` are ignored. 930 | 931 | Does not check for overflow. If the value represented by `s` is too big to fit into a return type, only the value of the rightmost hex digits of `s` is returned without producing an error. 932 | 933 | 934 | ```nim 935 | runnableExamples: 936 | let s = "0x_1235_8df6" 937 | doAssert fromHex[int](s) == 305499638 938 | doAssert fromHex[int8](s) == 0xf6'i8 939 | doAssert fromHex[int8](s) == -10'i8 940 | doAssert fromHex[uint8](s) == 246'u8 941 | doAssert s.fromHex[:int16] == -29194'i16 942 | doAssert s.fromHex[:uint64] == 305499638'u64 943 | ``` 944 | 945 | ____ 946 | 947 | ## intToStr* 948 | 949 | ```nim 950 | proc intToStr*(x: int, minchars: Positive = 1): string {.noSideEffect, rtl, extern: "nsuIntToStr".} = 951 | ``` 952 | 953 | Converts `x` to its decimal representation. 954 | 955 | The resulting string will be minimally `minchars` characters long. This is achieved by adding leading zeros. 956 | 957 | 958 | ```nim 959 | runnableExamples: 960 | doAssert intToStr(1984) == "1984" 961 | doAssert intToStr(1984, 6) == "001984" 962 | result = '0' & result 963 | result = '-' & result 964 | ``` 965 | 966 | ____ 967 | 968 | ## parseInt* 969 | 970 | ```nim 971 | proc parseInt*(s: string): int {.noSideEffect, procvar, rtl, extern: "nsuParseInt".} = 972 | ``` 973 | 974 | Parses a decimal integer value contained in `s`. 975 | 976 | If `s` is not a valid integer, `ValueError` is raised. 977 | 978 | 979 | ```nim 980 | runnableExamples: 981 | doAssert parseInt("-0042") == -42 982 | raise newException(ValueError, "invalid integer: " & s) 983 | ``` 984 | 985 | ____ 986 | 987 | ## parseBiggestInt* 988 | 989 | ```nim 990 | proc parseBiggestInt*(s: string): BiggestInt {.noSideEffect, procvar, rtl, extern: "nsuParseBiggestInt".} = 991 | ``` 992 | 993 | Parses a decimal integer value contained in `s`. 994 | 995 | If `s` is not a valid integer, `ValueError` is raised. 996 | 997 | 998 | ____ 999 | 1000 | ## parseUInt* 1001 | 1002 | ```nim 1003 | proc parseUInt*(s: string): uint {.noSideEffect, procvar, rtl, extern: "nsuParseUInt".} = 1004 | ``` 1005 | 1006 | Parses a decimal unsigned integer value contained in `s`. 1007 | 1008 | If `s` is not a valid integer, `ValueError` is raised. 1009 | 1010 | 1011 | ____ 1012 | 1013 | ## parseBiggestUInt* 1014 | 1015 | ```nim 1016 | proc parseBiggestUInt*(s: string): BiggestUInt {.noSideEffect, procvar, rtl, extern: "nsuParseBiggestUInt".} = 1017 | ``` 1018 | 1019 | Parses a decimal unsigned integer value contained in `s`. 1020 | 1021 | If `s` is not a valid integer, `ValueError` is raised. 1022 | 1023 | 1024 | ____ 1025 | 1026 | ## parseFloat* 1027 | 1028 | ```nim 1029 | proc parseFloat*(s: string): float {.noSideEffect, procvar, rtl, extern: "nsuParseFloat".} = 1030 | ``` 1031 | 1032 | Parses a decimal floating point value contained in `s`. 1033 | 1034 | If `s` is not a valid floating point number, `ValueError` is raised. ``NAN``, ``INF``, ``-INF`` are also supported (case insensitive comparison). 1035 | 1036 | 1037 | ```nim 1038 | runnableExamples: 1039 | doAssert parseFloat("3.14") == 3.14 1040 | doAssert parseFloat("inf") == 1.0/0 1041 | raise newException(ValueError, "invalid float: " & s) 1042 | ``` 1043 | 1044 | ____ 1045 | 1046 | ## parseBinInt* 1047 | 1048 | ```nim 1049 | proc parseBinInt*(s: string): int {.noSideEffect, procvar, rtl, extern: "nsuParseBinInt".} = 1050 | ``` 1051 | 1052 | Parses a binary integer value contained in `s`. 1053 | 1054 | If `s` is not a valid binary integer, `ValueError` is raised. `s` can have one of the following optional prefixes: ``0b``, ``0B``. Underscores within `s` are ignored. 1055 | 1056 | 1057 | ```nim 1058 | runnableExamples: 1059 | let 1060 | a = "0b11_0101" 1061 | b = "111" 1062 | doAssert a.parseBinInt() == 53 1063 | doAssert b.parseBinInt() == 7 1064 | ``` 1065 | 1066 | ____ 1067 | 1068 | ## parseOctInt* 1069 | 1070 | ```nim 1071 | proc parseOctInt*(s: string): int {.noSideEffect, rtl, extern: "nsuParseOctInt".} = 1072 | ``` 1073 | 1074 | Parses an octal integer value contained in `s`. 1075 | 1076 | If `s` is not a valid oct integer, `ValueError` is raised. `s` can have one of the following optional prefixes: ``0o``, ``0O``. Underscores within `s` are ignored. 1077 | 1078 | 1079 | ____ 1080 | 1081 | ## parseHexInt* 1082 | 1083 | ```nim 1084 | proc parseHexInt*(s: string): int {.noSideEffect, procvar, rtl, extern: "nsuParseHexInt".} = 1085 | ``` 1086 | 1087 | Parses a hexadecimal integer value contained in `s`. 1088 | 1089 | If `s` is not a valid hex integer, `ValueError` is raised. `s` can have one of the following optional prefixes: ``0x``, ``0X``, ``#``. Underscores within `s` are ignored. 1090 | 1091 | 1092 | ____ 1093 | 1094 | ## generateHexCharToValueMap 1095 | 1096 | ```nim 1097 | proc generateHexCharToValueMap(): string = let o = 1098 | ``` 1099 | 1100 | Generate a string to map a hex digit to uint value 1101 | 1102 | 1103 | ____ 1104 | 1105 | ## parseHexStr* 1106 | 1107 | ```nim 1108 | proc parseHexStr*(s: string): string {.noSideEffect, procvar, rtl, extern: "nsuParseHexStr".} = 1109 | ``` 1110 | 1111 | Convert hex-encoded string to byte string, e.g.: 1112 | 1113 | Raises ``ValueError`` for an invalid hex values. The comparison is case-insensitive. 1114 | 1115 | 1116 | 1117 | See also: 1118 | * `toHex proc<#toHex,string>`_ for the reverse operation 1119 | 1120 | 1121 | ```nim 1122 | runnableExamples: 1123 | let 1124 | a = "41" 1125 | b = "3161" 1126 | c = "00ff" 1127 | doAssert parseHexStr(a) == "A" 1128 | doAssert parseHexStr(b) == "1a" 1129 | doAssert parseHexStr(c) == "\0\255" 1130 | ``` 1131 | 1132 | ____ 1133 | 1134 | ## parseBool* 1135 | 1136 | ```nim 1137 | proc parseBool*(s: string): bool = 1138 | ``` 1139 | 1140 | Parses a value into a `bool`. 1141 | 1142 | If ``s`` is one of the following values: ``y, yes, true, 1, on``, then returns `true`. If ``s`` is one of the following values: ``n, no, false, 0, off``, then returns `false`. If ``s`` is something else a ``ValueError`` exception is raised. 1143 | 1144 | 1145 | ```nim 1146 | runnableExamples: 1147 | let a = "n" 1148 | doAssert parseBool(a) == false 1149 | ``` 1150 | 1151 | ____ 1152 | 1153 | ## parseEnum*[T: enum] 1154 | 1155 | ```nim 1156 | proc parseEnum*[T: enum](s: string): T = 1157 | ``` 1158 | 1159 | Parses an enum ``T``. 1160 | 1161 | Raises ``ValueError`` for an invalid value in `s`. The comparison is done in a style insensitive way. 1162 | 1163 | 1164 | ```nim 1165 | runnableExamples: 1166 | type 1167 | MyEnum = enum 1168 | first = "1st", 1169 | second, 1170 | third = "3rd" 1171 | ``` 1172 | 1173 | ____ 1174 | 1175 | ## parseEnum*[T: enum] 1176 | 1177 | ```nim 1178 | proc parseEnum*[T: enum](s: string, default: T): T = 1179 | ``` 1180 | 1181 | Parses an enum ``T``. 1182 | 1183 | Uses `default` for an invalid value in `s`. The comparison is done in a style insensitive way. 1184 | 1185 | 1186 | ```nim 1187 | runnableExamples: 1188 | type 1189 | MyEnum = enum 1190 | first = "1st", 1191 | second, 1192 | third = "3rd" 1193 | ``` 1194 | 1195 | ____ 1196 | 1197 | ## repeat* 1198 | 1199 | ```nim 1200 | proc repeat*(c: char, count: Natural): string {.noSideEffect, rtl, extern: "nsuRepeatChar".} = 1201 | ``` 1202 | 1203 | Returns a string of length `count` consisting only of the character `c`. 1204 | 1205 | 1206 | ```nim 1207 | runnableExamples: 1208 | let a = 'z' 1209 | doAssert a.repeat(5) == "zzzzz" 1210 | ``` 1211 | 1212 | ____ 1213 | 1214 | ## repeat* 1215 | 1216 | ```nim 1217 | proc repeat*(s: string, n: Natural): string {.noSideEffect, rtl, extern: "nsuRepeatStr".} = 1218 | ``` 1219 | 1220 | Returns string `s` concatenated `n` times. 1221 | 1222 | 1223 | ```nim 1224 | runnableExamples: 1225 | doAssert "+ foo +".repeat(3) == "+ foo ++ foo ++ foo +" 1226 | ``` 1227 | 1228 | ____ 1229 | 1230 | ## spaces* 1231 | 1232 | ```nim 1233 | proc spaces*(n: Natural): string {.inline.} = 1234 | ``` 1235 | 1236 | Returns a string with `n` space characters. You can use this proc to left align strings. 1237 | 1238 | 1239 | 1240 | See also: 1241 | * `align proc<#align,string,Natural,char>`_ 1242 | * `alignLeft proc<#alignLeft,string,Natural,char>`_ 1243 | * `indent proc<#indent,string,Natural,string>`_ 1244 | * `center proc<#center,string,int,char>`_ 1245 | 1246 | 1247 | ```nim 1248 | runnableExamples: 1249 | let 1250 | width = 15 1251 | text1 = "Hello user!" 1252 | text2 = "This is a very long string" 1253 | doAssert text1 & spaces(max(0, width - text1.len)) & "|" == 1254 | "Hello user! |" 1255 | doAssert text2 & spaces(max(0, width - text2.len)) & "|" == 1256 | "This is a very long string|" 1257 | ``` 1258 | 1259 | ____ 1260 | 1261 | ## align* 1262 | 1263 | ```nim 1264 | proc align*(s: string, count: Natural, padding = ' '): string {. noSideEffect, rtl, extern: "nsuAlignString".} = 1265 | ``` 1266 | 1267 | Aligns a string `s` with `padding`, so that it is of length `count`. 1268 | 1269 | `padding` characters (by default spaces) are added before `s` resulting in right alignment. If ``s.len >= count``, no spaces are added and `s` is returned unchanged. If you need to left align a string use the `alignLeft proc <#alignLeft,string,Natural,char>`_. 1270 | 1271 | 1272 | 1273 | See also: 1274 | * `alignLeft proc<#alignLeft,string,Natural,char>`_ 1275 | * `spaces proc<#spaces,Natural>`_ 1276 | * `indent proc<#indent,string,Natural,string>`_ 1277 | * `center proc<#center,string,int,char>`_ 1278 | 1279 | 1280 | ```nim 1281 | runnableExamples: 1282 | assert align("abc", 4) == " abc" 1283 | assert align("a", 0) == "a" 1284 | assert align("1232", 6) == " 1232" 1285 | assert align("1232", 6, '#') == "##1232" 1286 | result = newString(count) 1287 | let spaces = count - s.len 1288 | for i in 0..spaces-1: result[i] = padding 1289 | for i in spaces..count-1: result[i] = s[i-spaces] 1290 | result = s 1291 | ``` 1292 | 1293 | ____ 1294 | 1295 | ## alignLeft* 1296 | 1297 | ```nim 1298 | proc alignLeft*(s: string, count: Natural, padding = ' '): string {. noSideEffect.} = 1299 | ``` 1300 | 1301 | Left-Aligns a string `s` with `padding`, so that it is of length `count`. 1302 | 1303 | `padding` characters (by default spaces) are added after `s` resulting in left alignment. If ``s.len >= count``, no spaces are added and `s` is returned unchanged. If you need to right align a string use the `align proc <#align,string,Natural,char>`_. 1304 | 1305 | 1306 | 1307 | See also: 1308 | * `align proc<#align,string,Natural,char>`_ 1309 | * `spaces proc<#spaces,Natural>`_ 1310 | * `indent proc<#indent,string,Natural,string>`_ 1311 | * `center proc<#center,string,int,char>`_ 1312 | 1313 | 1314 | ```nim 1315 | runnableExamples: 1316 | assert alignLeft("abc", 4) == "abc " 1317 | assert alignLeft("a", 0) == "a" 1318 | assert alignLeft("1232", 6) == "1232 " 1319 | assert alignLeft("1232", 6, '#') == "1232##" 1320 | result = newString(count) 1321 | if s.len > 0: 1322 | result[0 .. (s.len - 1)] = s 1323 | for i in s.len ..< count: 1324 | result[i] = padding 1325 | result = s 1326 | ``` 1327 | 1328 | ____ 1329 | 1330 | ## center* 1331 | 1332 | ```nim 1333 | proc center*(s: string, width: int, fillChar: char = ' '): string {. noSideEffect, rtl, extern: "nsuCenterString".} = 1334 | ``` 1335 | 1336 | Return the contents of `s` centered in a string `width` long using `fillChar` (default: space) as padding. 1337 | 1338 | The original string is returned if `width` is less than or equal to `s.len`. 1339 | 1340 | 1341 | 1342 | See also: 1343 | * `align proc<#align,string,Natural,char>`_ 1344 | * `alignLeft proc<#alignLeft,string,Natural,char>`_ 1345 | * `spaces proc<#spaces,Natural>`_ 1346 | * `indent proc<#indent,string,Natural,string>`_ 1347 | 1348 | 1349 | ```nim 1350 | runnableExamples: 1351 | let a = "foo" 1352 | doAssert a.center(2) == "foo" 1353 | doAssert a.center(5) == " foo " 1354 | doAssert a.center(6) == " foo " 1355 | charsLeft = (width - s.len) 1356 | leftPadding = charsLeft div 2 1357 | if i >= leftPadding and i < leftPadding + s.len: 1358 | # we are where the string should be located 1359 | result[i] = s[i-leftPadding] 1360 | else: 1361 | # we are either before or after where 1362 | # the string s should go 1363 | result[i] = fillChar 1364 | ``` 1365 | 1366 | ____ 1367 | 1368 | ## indent* 1369 | 1370 | ```nim 1371 | proc indent*(s: string, count: Natural, padding: string = " "): string {.noSideEffect, rtl, extern: "nsuIndent".} = 1372 | ``` 1373 | 1374 | Indents each line in ``s`` by ``count`` amount of ``padding``. 1375 | 1376 | 1377 | **Note:** This does not preserve the new line characters used in ``s``. 1378 | 1379 | 1380 | 1381 | See also: 1382 | * `align proc<#align,string,Natural,char>`_ 1383 | * `alignLeft proc<#alignLeft,string,Natural,char>`_ 1384 | * `spaces proc<#spaces,Natural>`_ 1385 | * `unindent proc<#unindent,string,Natural,string>`_ 1386 | 1387 | 1388 | ```nim 1389 | runnableExamples: 1390 | doAssert indent("First line\c\l and second line.", 2) == 1391 | " First line\l and second line." 1392 | if i != 0: 1393 | result.add("\n") 1394 | for j in 1..count: 1395 | result.add(padding) 1396 | result.add(line) 1397 | i.inc 1398 | ``` 1399 | 1400 | ____ 1401 | 1402 | ## unindent* 1403 | 1404 | ```nim 1405 | proc unindent*(s: string, count: Natural, padding: string = " "): string {.noSideEffect, rtl, extern: "nsuUnindent".} = 1406 | ``` 1407 | 1408 | Unindents each line in ``s`` by ``count`` amount of ``padding``. Sometimes called `dedent`:idx: 1409 | 1410 | 1411 | **Note:** This does not preserve the new line characters used in ``s``. 1412 | 1413 | 1414 | 1415 | See also: 1416 | * `align proc<#align,string,Natural,char>`_ 1417 | * `alignLeft proc<#alignLeft,string,Natural,char>`_ 1418 | * `spaces proc<#spaces,Natural>`_ 1419 | * `indent proc<#indent,string,Natural,string>`_ 1420 | 1421 | 1422 | ```nim 1423 | runnableExamples: 1424 | doAssert unindent(" First line\l and second line", 3) == 1425 | "First line\land second line" 1426 | if i != 0: 1427 | result.add("\n") 1428 | var indentCount = 0 1429 | for j in 0..= line.len or line[j .. j + padding.len-1] != padding: 1432 | indentCount = j 1433 | break 1434 | result.add(line[indentCount*padding.len .. ^1]) 1435 | i.inc 1436 | ``` 1437 | 1438 | ____ 1439 | 1440 | ## unindent* 1441 | 1442 | ```nim 1443 | proc unindent*(s: string): string {.noSideEffect, rtl, extern: "nsuUnindentAll".} = 1444 | ``` 1445 | 1446 | Removes all indentation composed of whitespace from each line in ``s``. 1447 | 1448 | 1449 | 1450 | See also: 1451 | * `align proc<#align,string,Natural,char>`_ 1452 | * `alignLeft proc<#alignLeft,string,Natural,char>`_ 1453 | * `spaces proc<#spaces,Natural>`_ 1454 | * `indent proc<#indent,string,Natural,string>`_ 1455 | 1456 | 1457 | ```nim 1458 | runnableExamples: 1459 | let x = """ 1460 | Hello 1461 | There 1462 | """.unindent() 1463 | ``` 1464 | 1465 | ____ 1466 | 1467 | ## delete* 1468 | 1469 | ```nim 1470 | proc delete*(s: var string, first, last: int) {.noSideEffect, rtl, extern: "nsuDelete".} = 1471 | ``` 1472 | 1473 | Deletes in `s` (must be declared as ``var``) the characters at positions ``first ..last`` (both ends included). 1474 | 1475 | This modifies `s` itself, it does not return a copy. 1476 | 1477 | 1478 | ```nim 1479 | runnableExamples: 1480 | var a = "abracadabra" 1481 | ``` 1482 | 1483 | ____ 1484 | 1485 | ## startsWith* 1486 | 1487 | ```nim 1488 | proc startsWith*(s: string, prefix: char): bool {.noSideEffect, inline.} = 1489 | ``` 1490 | 1491 | Returns true if ``s`` starts with character ``prefix``. 1492 | 1493 | 1494 | 1495 | See also: 1496 | * `endsWith proc<#endsWith,string,char>`_ 1497 | * `continuesWith proc<#continuesWith,string,string,Natural>`_ 1498 | * `removePrefix proc<#removePrefix,string,char>`_ 1499 | 1500 | 1501 | ```nim 1502 | runnableExamples: 1503 | let a = "abracadabra" 1504 | doAssert a.startsWith('a') == true 1505 | doAssert a.startsWith('b') == false 1506 | ``` 1507 | 1508 | ____ 1509 | 1510 | ## startsWith* 1511 | 1512 | ```nim 1513 | proc startsWith*(s, prefix: string): bool {.noSideEffect, rtl, extern: "nsuStartsWith".} = 1514 | ``` 1515 | 1516 | Returns true if ``s`` starts with string ``prefix``. 1517 | 1518 | If ``prefix == ""`` true is returned. 1519 | 1520 | 1521 | 1522 | See also: 1523 | * `endsWith proc<#endsWith,string,string>`_ 1524 | * `continuesWith proc<#continuesWith,string,string,Natural>`_ 1525 | * `removePrefix proc<#removePrefix,string,string>`_ 1526 | 1527 | 1528 | ```nim 1529 | runnableExamples: 1530 | let a = "abracadabra" 1531 | doAssert a.startsWith("abra") == true 1532 | doAssert a.startsWith("bra") == false 1533 | if i >= prefix.len: return true 1534 | if i >= s.len or s[i] != prefix[i]: return false 1535 | inc(i) 1536 | ``` 1537 | 1538 | ____ 1539 | 1540 | ## endsWith* 1541 | 1542 | ```nim 1543 | proc endsWith*(s: string, suffix: char): bool {.noSideEffect, inline.} = 1544 | ``` 1545 | 1546 | Returns true if ``s`` ends with ``suffix``. 1547 | 1548 | 1549 | 1550 | See also: 1551 | * `startsWith proc<#startsWith,string,char>`_ 1552 | * `continuesWith proc<#continuesWith,string,string,Natural>`_ 1553 | * `removeSuffix proc<#removeSuffix,string,char>`_ 1554 | 1555 | 1556 | ```nim 1557 | runnableExamples: 1558 | let a = "abracadabra" 1559 | doAssert a.endsWith('a') == true 1560 | doAssert a.endsWith('b') == false 1561 | ``` 1562 | 1563 | ____ 1564 | 1565 | ## endsWith* 1566 | 1567 | ```nim 1568 | proc endsWith*(s, suffix: string): bool {.noSideEffect, rtl, extern: "nsuEndsWith".} = 1569 | ``` 1570 | 1571 | Returns true if ``s`` ends with ``suffix``. 1572 | 1573 | If ``suffix == ""`` true is returned. 1574 | 1575 | 1576 | 1577 | See also: 1578 | * `startsWith proc<#startsWith,string,string>`_ 1579 | * `continuesWith proc<#continuesWith,string,string,Natural>`_ 1580 | * `removeSuffix proc<#removeSuffix,string,string>`_ 1581 | 1582 | 1583 | ```nim 1584 | runnableExamples: 1585 | let a = "abracadabra" 1586 | doAssert a.endsWith("abra") == true 1587 | doAssert a.endsWith("dab") == false 1588 | if s[i+j] != suffix[i]: return false 1589 | inc(i) 1590 | ``` 1591 | 1592 | ____ 1593 | 1594 | ## continuesWith* 1595 | 1596 | ```nim 1597 | proc continuesWith*(s, substr: string, start: Natural): bool {.noSideEffect, rtl, extern: "nsuContinuesWith".} = 1598 | ``` 1599 | 1600 | Returns true if ``s`` continues with ``substr`` at position ``start``. 1601 | 1602 | If ``substr == ""`` true is returned. 1603 | 1604 | 1605 | 1606 | See also: 1607 | * `startsWith proc<#startsWith,string,string>`_ 1608 | * `endsWith proc<#endsWith,string,string>`_ 1609 | 1610 | 1611 | ```nim 1612 | runnableExamples: 1613 | let a = "abracadabra" 1614 | doAssert a.continuesWith("ca", 4) == true 1615 | doAssert a.continuesWith("ca", 5) == false 1616 | doAssert a.continuesWith("dab", 6) == true 1617 | if i >= substr.len: return true 1618 | if i+start >= s.len or s[i+start] != substr[i]: return false 1619 | inc(i) 1620 | ``` 1621 | 1622 | ____ 1623 | 1624 | ## removePrefix* 1625 | 1626 | ```nim 1627 | proc removePrefix*(s: var string, chars: set[char] = Newlines) {. rtl, extern: "nsuRemovePrefixCharSet".} = 1628 | ``` 1629 | 1630 | Removes all characters from `chars` from the start of the string `s` (in-place). 1631 | 1632 | 1633 | 1634 | See also: 1635 | * `removeSuffix proc<#removeSuffix,string,set[char]>`_ 1636 | 1637 | 1638 | ```nim 1639 | runnableExamples: 1640 | var userInput = "\r\n*~Hello World!" 1641 | userInput.removePrefix 1642 | doAssert userInput == "*~Hello World!" 1643 | userInput.removePrefix({'~', '*'}) 1644 | doAssert userInput == "Hello World!" 1645 | ``` 1646 | 1647 | ____ 1648 | 1649 | ## removePrefix* 1650 | 1651 | ```nim 1652 | proc removePrefix*(s: var string, c: char) {. rtl, extern: "nsuRemovePrefixChar".} = 1653 | ``` 1654 | 1655 | Removes all occurrences of a single character (in-place) from the start of a string. 1656 | 1657 | 1658 | 1659 | See also: 1660 | * `removeSuffix proc<#removeSuffix,string,char>`_ 1661 | * `startsWith proc<#startsWith,string,char>`_ 1662 | 1663 | 1664 | ```nim 1665 | runnableExamples: 1666 | var ident = "pControl" 1667 | ident.removePrefix('p') 1668 | doAssert ident == "Control" 1669 | ``` 1670 | 1671 | ____ 1672 | 1673 | ## removePrefix* 1674 | 1675 | ```nim 1676 | proc removePrefix*(s: var string, prefix: string) {. rtl, extern: "nsuRemovePrefixString".} = 1677 | ``` 1678 | 1679 | Remove the first matching prefix (in-place) from a string. 1680 | 1681 | 1682 | 1683 | See also: 1684 | * `removeSuffix proc<#removeSuffix,string,string>`_ 1685 | * `startsWith proc<#startsWith,string,string>`_ 1686 | 1687 | 1688 | ```nim 1689 | runnableExamples: 1690 | var answers = "yesyes" 1691 | answers.removePrefix("yes") 1692 | doAssert answers == "yes" 1693 | s.delete(0, prefix.len - 1) 1694 | ``` 1695 | 1696 | ____ 1697 | 1698 | ## removeSuffix* 1699 | 1700 | ```nim 1701 | proc removeSuffix*(s: var string, chars: set[char] = Newlines) {. rtl, extern: "nsuRemoveSuffixCharSet".} = 1702 | ``` 1703 | 1704 | Removes all characters from `chars` from the end of the string `s` (in-place). 1705 | 1706 | 1707 | 1708 | See also: 1709 | * `removePrefix proc<#removePrefix,string,set[char]>`_ 1710 | 1711 | 1712 | ```nim 1713 | runnableExamples: 1714 | var userInput = "Hello World!*~\r\n" 1715 | userInput.removeSuffix 1716 | doAssert userInput == "Hello World!*~" 1717 | userInput.removeSuffix({'~', '*'}) 1718 | doAssert userInput == "Hello World!" 1719 | ``` 1720 | 1721 | ____ 1722 | 1723 | ## removeSuffix* 1724 | 1725 | ```nim 1726 | proc removeSuffix*(s: var string, c: char) {. rtl, extern: "nsuRemoveSuffixChar".} = 1727 | ``` 1728 | 1729 | Removes all occurrences of a single character (in-place) from the end of a string. 1730 | 1731 | 1732 | 1733 | See also: 1734 | * `removePrefix proc<#removePrefix,string,char>`_ 1735 | * `endsWith proc<#endsWith,string,char>`_ 1736 | 1737 | 1738 | ```nim 1739 | runnableExamples: 1740 | var table = "users" 1741 | table.removeSuffix('s') 1742 | doAssert table == "user" 1743 | ``` 1744 | 1745 | ____ 1746 | 1747 | ## removeSuffix* 1748 | 1749 | ```nim 1750 | proc removeSuffix*(s: var string, suffix: string) {. rtl, extern: "nsuRemoveSuffixString".} = 1751 | ``` 1752 | 1753 | Remove the first matching suffix (in-place) from a string. 1754 | 1755 | 1756 | 1757 | See also: 1758 | * `removePrefix proc<#removePrefix,string,string>`_ 1759 | * `endsWith proc<#endsWith,string,string>`_ 1760 | 1761 | 1762 | ```nim 1763 | runnableExamples: 1764 | var answers = "yeses" 1765 | answers.removeSuffix("es") 1766 | doAssert answers == "yes" 1767 | newLen -= len(suffix) 1768 | s.setLen(newLen) 1769 | ``` 1770 | 1771 | ____ 1772 | 1773 | ## addSep* 1774 | 1775 | ```nim 1776 | proc addSep*(dest: var string, sep = ", ", startLen: Natural = 0) {.noSideEffect, inline.} = 1777 | ``` 1778 | 1779 | Adds a separator to `dest` only if its length is bigger than `startLen`. 1780 | 1781 | A shorthand for: 1782 | 1783 | 1784 | ```nim 1785 | if dest.len > startLen: add(dest, sep) 1786 | ``` 1787 | 1788 | 1789 | This is often useful for generating some code where the items need to be *separated* by `sep`. `sep` is only added if `dest` is longer than `startLen`. The following example creates a string describing an array of integers. 1790 | 1791 | 1792 | ```nim 1793 | runnableExamples: 1794 | var arr = "[" 1795 | for x in items([2, 3, 5, 7, 11]): 1796 | addSep(arr, startLen = len("[")) 1797 | add(arr, $x) 1798 | add(arr, "]") 1799 | doAssert arr == "[2, 3, 5, 7, 11]" 1800 | ``` 1801 | 1802 | ____ 1803 | 1804 | ## allCharsInSet* 1805 | 1806 | ```nim 1807 | proc allCharsInSet*(s: string, theSet: set[char]): bool = 1808 | ``` 1809 | 1810 | Returns true if every character of `s` is in the set `theSet`. 1811 | 1812 | 1813 | ```nim 1814 | runnableExamples: 1815 | doAssert allCharsInSet("aeea", {'a', 'e'}) == true 1816 | doAssert allCharsInSet("", {'a', 'e'}) == true 1817 | ``` 1818 | 1819 | ____ 1820 | 1821 | ## abbrev* 1822 | 1823 | ```nim 1824 | proc abbrev*(s: string, possibilities: openArray[string]): int = 1825 | ``` 1826 | 1827 | Returns the index of the first item in ``possibilities`` which starts with ``s``, if not ambiguous. 1828 | 1829 | Returns -1 if no item has been found and -2 if multiple items match. 1830 | 1831 | 1832 | ```nim 1833 | runnableExamples: 1834 | doAssert abbrev("fac", ["college", "faculty", "industry"]) == 1 1835 | doAssert abbrev("foo", ["college", "faculty", "industry"]) == -1 # Not found 1836 | doAssert abbrev("fac", ["college", "faculty", "faculties"]) == -2 # Ambiguous 1837 | doAssert abbrev("college", ["college", "colleges", "industry"]) == 0 1838 | ``` 1839 | 1840 | ____ 1841 | 1842 | ## join* 1843 | 1844 | ```nim 1845 | proc join*(a: openArray[string], sep: string = ""): string {. noSideEffect, rtl, extern: "nsuJoinSep".} = 1846 | ``` 1847 | 1848 | Concatenates all strings in the container `a`, separating them with `sep`. 1849 | 1850 | 1851 | ```nim 1852 | runnableExamples: 1853 | doAssert join(["A", "B", "Conclusion"], " -> ") == "A -> B -> Conclusion" 1854 | ``` 1855 | 1856 | ____ 1857 | 1858 | ## join*[T: not string] 1859 | 1860 | ```nim 1861 | proc join*[T: not string](a: openArray[T], sep: string = ""): string {. noSideEffect, rtl.} = 1862 | ``` 1863 | 1864 | Converts all elements in the container `a` to strings using `$`, and concatenates them with `sep`. 1865 | 1866 | 1867 | ```nim 1868 | runnableExamples: 1869 | doAssert join([1, 2, 3], " -> ") == "1 -> 2 -> 3" 1870 | ``` 1871 | 1872 | ____ 1873 | 1874 | ## initSkipTable* 1875 | 1876 | ```nim 1877 | proc initSkipTable*(a: var SkipTable, sub: string) {.noSideEffect, rtl, extern: "nsuInitSkipTable".} = 1878 | ``` 1879 | 1880 | Preprocess table `a` for `sub`. 1881 | 1882 | 1883 | ____ 1884 | 1885 | ## find* 1886 | 1887 | ```nim 1888 | proc find*(a: SkipTable, s, sub: string, start: Natural = 0, last = 0): int {.noSideEffect, rtl, extern: "nsuFindStrA".} = 1889 | ``` 1890 | 1891 | Searches for `sub` in `s` inside range `start`..`last` using preprocessed table `a`. If `last` is unspecified, it defaults to `s.high` (the last element). 1892 | 1893 | Searching is case-sensitive. If `sub` is not in `s`, -1 is returned. 1894 | 1895 | 1896 | ____ 1897 | 1898 | ## find* 1899 | 1900 | ```nim 1901 | proc find*(s: string, sub: char, start: Natural = 0, last = 0): int {.noSideEffect, rtl, extern: "nsuFindChar".} = 1902 | ``` 1903 | 1904 | Searches for `sub` in `s` inside range ``start..last`` (both ends included). If `last` is unspecified, it defaults to `s.high` (the last element). 1905 | 1906 | Searching is case-sensitive. If `sub` is not in `s`, -1 is returned. Otherwise the index returned is relative to ``s[0]``, not ``start``. Use `s[start..last].rfind` for a ``start``-origin index. 1907 | 1908 | 1909 | 1910 | See also: 1911 | * `rfind proc<#rfind,string,char,Natural,int>`_ 1912 | * `replace proc<#replace,string,char,char>`_ 1913 | 1914 | 1915 | ____ 1916 | 1917 | ## find* 1918 | 1919 | ```nim 1920 | proc find*(s: string, chars: set[char], start: Natural = 0, last = 0): int {.noSideEffect, rtl, extern: "nsuFindCharSet".} = 1921 | ``` 1922 | 1923 | Searches for `chars` in `s` inside range ``start..last`` (both ends included). If `last` is unspecified, it defaults to `s.high` (the last element). 1924 | 1925 | If `s` contains none of the characters in `chars`, -1 is returned. Otherwise the index returned is relative to ``s[0]``, not ``start``. Use `s[start..last].find` for a ``start``-origin index. 1926 | 1927 | 1928 | 1929 | See also: 1930 | * `rfind proc<#rfind,string,set[char],Natural,int>`_ 1931 | * `multiReplace proc<#multiReplace,string,varargs[]>`_ 1932 | 1933 | 1934 | ____ 1935 | 1936 | ## find* 1937 | 1938 | ```nim 1939 | proc find*(s, sub: string, start: Natural = 0, last = 0): int {.noSideEffect, rtl, extern: "nsuFindStr".} = 1940 | ``` 1941 | 1942 | Searches for `sub` in `s` inside range ``start..last`` (both ends included). If `last` is unspecified, it defaults to `s.high` (the last element). 1943 | 1944 | Searching is case-sensitive. If `sub` is not in `s`, -1 is returned. Otherwise the index returned is relative to ``s[0]``, not ``start``. Use `s[start..last].find` for a ``start``-origin index. 1945 | 1946 | 1947 | 1948 | See also: 1949 | * `rfind proc<#rfind,string,string,Natural,int>`_ 1950 | * `replace proc<#replace,string,string,string>`_ 1951 | 1952 | 1953 | ____ 1954 | 1955 | ## rfind* 1956 | 1957 | ```nim 1958 | proc rfind*(s: string, sub: char, start: Natural = 0, last = -1): int {.noSideEffect, rtl, extern: "nsuRFindChar".} = 1959 | ``` 1960 | 1961 | Searches for `sub` in `s` inside range ``start..last`` (both ends included) in reverse -- starting at high indexes and moving lower to the first character or ``start``. If `last` is unspecified, it defaults to `s.high` (the last element). 1962 | 1963 | Searching is case-sensitive. If `sub` is not in `s`, -1 is returned. Otherwise the index returned is relative to ``s[0]``, not ``start``. Use `s[start..last].find` for a ``start``-origin index. 1964 | 1965 | 1966 | 1967 | See also: 1968 | * `find proc<#find,string,char,Natural,int>`_ 1969 | 1970 | 1971 | ____ 1972 | 1973 | ## rfind* 1974 | 1975 | ```nim 1976 | proc rfind*(s: string, chars: set[char], start: Natural = 0, last = -1): int {.noSideEffect, rtl, extern: "nsuRFindCharSet".} = 1977 | ``` 1978 | 1979 | Searches for `chars` in `s` inside range ``start..last`` (both ends included) in reverse -- starting at high indexes and moving lower to the first character or ``start``. If `last` is unspecified, it defaults to `s.high` (the last element). 1980 | 1981 | If `s` contains none of the characters in `chars`, -1 is returned. Otherwise the index returned is relative to ``s[0]``, not ``start``. Use `s[start..last].rfind` for a ``start``-origin index. 1982 | 1983 | 1984 | 1985 | See also: 1986 | * `find proc<#find,string,set[char],Natural,int>`_ 1987 | 1988 | 1989 | ____ 1990 | 1991 | ## rfind* 1992 | 1993 | ```nim 1994 | proc rfind*(s, sub: string, start: Natural = 0, last = -1): int {.noSideEffect, rtl, extern: "nsuRFindStr".} = 1995 | ``` 1996 | 1997 | Searches for `sub` in `s` inside range ``start..last`` (both ends included) included) in reverse -- starting at high indexes and moving lower to the first character or ``start``. If `last` is unspecified, it defaults to `s.high` (the last element). 1998 | 1999 | Searching is case-sensitive. If `sub` is not in `s`, -1 is returned. Otherwise the index returned is relative to ``s[0]``, not ``start``. Use `s[start..last].rfind` for a ``start``-origin index. 2000 | 2001 | 2002 | 2003 | See also: 2004 | * `find proc<#find,string,string,Natural,int>`_ 2005 | 2006 | 2007 | ____ 2008 | 2009 | ## count* 2010 | 2011 | ```nim 2012 | proc count*(s: string, sub: char): int {.noSideEffect, rtl, extern: "nsuCountChar".} = 2013 | ``` 2014 | 2015 | Count the occurrences of the character `sub` in the string `s`. 2016 | 2017 | 2018 | 2019 | See also: 2020 | * `countLines proc<#countLines,string>`_ 2021 | 2022 | 2023 | ____ 2024 | 2025 | ## count* 2026 | 2027 | ```nim 2028 | proc count*(s: string, subs: set[char]): int {.noSideEffect, rtl, extern: "nsuCountCharSet".} = 2029 | ``` 2030 | 2031 | Count the occurrences of the group of character `subs` in the string `s`. 2032 | 2033 | 2034 | 2035 | See also: 2036 | * `countLines proc<#countLines,string>`_ 2037 | 2038 | 2039 | ____ 2040 | 2041 | ## count* 2042 | 2043 | ```nim 2044 | proc count*(s: string, sub: string, overlapping: bool = false): int {. noSideEffect, rtl, extern: "nsuCountString".} = 2045 | ``` 2046 | 2047 | Count the occurrences of a substring `sub` in the string `s`. Overlapping occurrences of `sub` only count when `overlapping` is set to true (default: false). 2048 | 2049 | 2050 | 2051 | See also: 2052 | * `countLines proc<#countLines,string>`_ 2053 | 2054 | 2055 | ____ 2056 | 2057 | ## countLines* 2058 | 2059 | ```nim 2060 | proc countLines*(s: string): int {.noSideEffect, rtl, extern: "nsuCountLines".} = 2061 | ``` 2062 | 2063 | Returns the number of lines in the string `s`. 2064 | 2065 | This is the same as ``len(splitLines(s))``, but much more efficient because it doesn't modify the string creating temporal objects. Every `character literal `_ newline combination (CR, LF, CR-LF) is supported. 2066 | 2067 | In this context, a line is any string separated by a newline combination. A line can be an empty string. 2068 | 2069 | 2070 | 2071 | See also: 2072 | * `splitLines proc<#splitLines,string>`_ 2073 | 2074 | 2075 | ```nim 2076 | runnableExamples: 2077 | doAssert countLines("First line\l and second line.") == 2 2078 | case s[i] 2079 | of '\c': 2080 | if i+1 < s.len and s[i+1] == '\l': inc i 2081 | inc result 2082 | of '\l': inc result 2083 | else: discard 2084 | inc i 2085 | ``` 2086 | 2087 | ____ 2088 | 2089 | ## contains* 2090 | 2091 | ```nim 2092 | proc contains*(s, sub: string): bool {.noSideEffect.} = 2093 | ``` 2094 | 2095 | Same as ``find(s, sub) >= 0``. 2096 | 2097 | 2098 | 2099 | See also: 2100 | * `find proc<#find,string,string,Natural,int>`_ 2101 | 2102 | 2103 | ____ 2104 | 2105 | ## contains* 2106 | 2107 | ```nim 2108 | proc contains*(s: string, chars: set[char]): bool {.noSideEffect.} = 2109 | ``` 2110 | 2111 | Same as ``find(s, chars) >= 0``. 2112 | 2113 | 2114 | 2115 | See also: 2116 | * `find proc<#find,string,set[char],Natural,int>`_ 2117 | 2118 | 2119 | ____ 2120 | 2121 | ## replace* 2122 | 2123 | ```nim 2124 | proc replace*(s, sub: string, by = ""): string {.noSideEffect, rtl, extern: "nsuReplaceStr".} = 2125 | ``` 2126 | 2127 | Replaces `sub` in `s` by the string `by`. 2128 | 2129 | 2130 | 2131 | See also: 2132 | * `find proc<#find,string,string,Natural,int>`_ 2133 | * `replace proc<#replace,string,char,char>`_ for replacing single characters 2134 | * `replaceWord proc<#replaceWord,string,string,string>`_ 2135 | * `multiReplace proc<#multiReplace,string,varargs[]>`_ 2136 | 2137 | 2138 | ____ 2139 | 2140 | ## replace* 2141 | 2142 | ```nim 2143 | proc replace*(s: string, sub, by: char): string {.noSideEffect, rtl, extern: "nsuReplaceChar".} = 2144 | ``` 2145 | 2146 | Replaces `sub` in `s` by the character `by`. 2147 | 2148 | Optimized version of `replace <#replace,string,string,string>`_ for characters. 2149 | 2150 | 2151 | 2152 | See also: 2153 | * `find proc<#find,string,char,Natural,int>`_ 2154 | * `replaceWord proc<#replaceWord,string,string,string>`_ 2155 | * `multiReplace proc<#multiReplace,string,varargs[]>`_ 2156 | 2157 | 2158 | ____ 2159 | 2160 | ## replaceWord* 2161 | 2162 | ```nim 2163 | proc replaceWord*(s, sub: string, by = ""): string {.noSideEffect, rtl, extern: "nsuReplaceWord".} = 2164 | ``` 2165 | 2166 | Replaces `sub` in `s` by the string `by`. 2167 | 2168 | Each occurrence of `sub` has to be surrounded by word boundaries (comparable to ``\b`` in regular expressions), otherwise it is not replaced. 2169 | 2170 | 2171 | ____ 2172 | 2173 | ## multiReplace* 2174 | 2175 | ```nim 2176 | proc multiReplace*(s: string, replacements: varargs[(string, string)]): string {.noSideEffect.} = 2177 | ``` 2178 | 2179 | Same as replace, but specialized for doing multiple replacements in a single pass through the input string. 2180 | 2181 | `multiReplace` performs all replacements in a single pass, this means it can be used to swap the occurrences of "a" and "b", for instance. 2182 | 2183 | If the resulting string is not longer than the original input string, only a single memory allocation is required. 2184 | 2185 | The order of the replacements does matter. Earlier replacements are preferred over later replacements in the argument list. 2186 | 2187 | 2188 | ____ 2189 | 2190 | ## insertSep* 2191 | 2192 | ```nim 2193 | proc insertSep*(s: string, sep = '_', digits = 3): string {.noSideEffect, rtl, extern: "nsuInsertSep".} = 2194 | ``` 2195 | 2196 | Inserts the separator `sep` after `digits` characters (default: 3) from right to left. 2197 | 2198 | Even though the algorithm works with any string `s`, it is only useful if `s` contains a number. 2199 | 2200 | 2201 | ```nim 2202 | runnableExamples: 2203 | doAssert insertSep("1000000") == "1_000_000" 2204 | ``` 2205 | 2206 | ____ 2207 | 2208 | ## escape* 2209 | 2210 | ```nim 2211 | proc escape*(s: string, prefix = "\"", suffix = "\""): string {.noSideEffect, rtl, extern: "nsuEscape".} = 2212 | ``` 2213 | 2214 | Escapes a string `s`. See `system.addEscapedChar `_ for the escaping scheme. 2215 | 2216 | The resulting string is prefixed with `prefix` and suffixed with `suffix`. Both may be empty strings. 2217 | 2218 | 2219 | 2220 | See also: 2221 | * `unescape proc<#unescape,string,string,string>`_ for the opposite operation 2222 | 2223 | 2224 | ____ 2225 | 2226 | ## unescape* 2227 | 2228 | ```nim 2229 | proc unescape*(s: string, prefix = "\"", suffix = "\""): string {.noSideEffect, rtl, extern: "nsuUnescape".} = 2230 | ``` 2231 | 2232 | Unescapes a string `s`. 2233 | 2234 | This complements `escape proc<#escape,string,string,string>`_ as it performs the opposite operations. 2235 | 2236 | If `s` does not begin with ``prefix`` and end with ``suffix`` a ValueError exception will be raised. 2237 | 2238 | 2239 | ____ 2240 | 2241 | ## validIdentifier* 2242 | 2243 | ```nim 2244 | proc validIdentifier*(s: string): bool {.noSideEffect, rtl, extern: "nsuValidIdentifier".} = 2245 | ``` 2246 | 2247 | Returns true if `s` is a valid identifier. 2248 | 2249 | A valid identifier starts with a character of the set `IdentStartChars` and is followed by any number of characters of the set `IdentChars`. 2250 | 2251 | 2252 | ```nim 2253 | runnableExamples: 2254 | doAssert "abc_def08".validIdentifier 2255 | ``` 2256 | 2257 | ____ 2258 | 2259 | ## formatBiggestFloat* 2260 | 2261 | ```nim 2262 | proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault, noSideEffect, rtl, extern: "nsu$1".} = 2263 | ``` 2264 | 2265 | Converts a floating point value `f` to a string. 2266 | 2267 | If ``format == ffDecimal`` then precision is the number of digits to be printed after the decimal point. If ``format == ffScientific`` then precision is the maximum number 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. 2268 | 2269 | If ``precision == -1``, it tries to format it nicely. 2270 | 2271 | 2272 | ```nim 2273 | runnableExamples: 2274 | let x = 123.456 2275 | doAssert x.formatBiggestFloat() == "123.4560000000000" 2276 | doAssert x.formatBiggestFloat(ffDecimal, 4) == "123.4560" 2277 | doAssert x.formatBiggestFloat(ffScientific, 2) == "1.23e+02" 2278 | var precision = precision 2279 | if precision == -1: 2280 | # use the same default precision as c_sprintf 2281 | precision = 6 2282 | var res: cstring 2283 | case format 2284 | of ffDefault: 2285 | {.emit: "`res` = `f`.toString();".} 2286 | of ffDecimal: 2287 | {.emit: "`res` = `f`.toFixed(`precision`);".} 2288 | of ffScientific: 2289 | {.emit: "`res` = `f`.toExponential(`precision`);".} 2290 | result = $res 2291 | if 1.0 / f == -Inf: 2292 | # JavaScript removes the "-" from negative Zero, add it back here 2293 | result = "-" & $res 2294 | for i in 0 ..< result.len: 2295 | # Depending on the locale either dot or comma is produced, 2296 | # but nothing else is possible: 2297 | if result[i] in {'.', ','}: result[i] = decimalsep 2298 | const floatFormatToChar: array[FloatFormatMode, char] = ['g', 'f', 'e'] 2299 | var 2300 | frmtstr {.noinit.}: array[0..5, char] 2301 | buf {.noinit.}: array[0..2500, char] 2302 | L: cint 2303 | frmtstr[0] = '%' 2304 | if precision >= 0: 2305 | frmtstr[1] = '#' 2306 | frmtstr[2] = '.' 2307 | frmtstr[3] = '*' 2308 | frmtstr[4] = floatFormatToChar[format] 2309 | frmtstr[5] = '\0' 2310 | when defined(nimNoArrayToCstringConversion): 2311 | L = c_sprintf(addr buf, addr frmtstr, precision, f) 2312 | else: 2313 | L = c_sprintf(buf, frmtstr, precision, f) 2314 | else: 2315 | frmtstr[1] = floatFormatToChar[format] 2316 | frmtstr[2] = '\0' 2317 | when defined(nimNoArrayToCstringConversion): 2318 | L = c_sprintf(addr buf, addr frmtstr, f) 2319 | else: 2320 | L = c_sprintf(buf, frmtstr, f) 2321 | result = newString(L) 2322 | for i in 0 ..< L: 2323 | # Depending on the locale either dot or comma is produced, 2324 | # but nothing else is possible: 2325 | if buf[i] in {'.', ','}: result[i] = decimalSep 2326 | else: result[i] = buf[i] 2327 | when defined(windows): 2328 | # VS pre 2015 violates the C standard: "The exponent always contains at 2329 | # least two digits, and only as many more digits as necessary to 2330 | # represent the exponent." [C11 §7.21.6.1] 2331 | # The following post-processing fixes this behavior. 2332 | if result.len > 4 and result[^4] == '+' and result[^3] == '0': 2333 | result[^3] = result[^2] 2334 | result[^2] = result[^1] 2335 | result.setLen(result.len - 1) 2336 | ``` 2337 | 2338 | ____ 2339 | 2340 | ## formatFloat* 2341 | 2342 | ```nim 2343 | proc formatFloat*(f: float, format: FloatFormatMode = ffDefault, noSideEffect, rtl, extern: "nsu$1".} = 2344 | ``` 2345 | 2346 | Converts a floating point value `f` to a string. 2347 | 2348 | If ``format == ffDecimal`` then precision is the number of digits to be printed after the decimal point. If ``format == ffScientific`` then precision is the maximum number of significant digits to be printed. `precision`'s default value is the maximum number of meaningful digits after the decimal point for Nim's ``float`` type. 2349 | 2350 | If ``precision == -1``, it tries to format it nicely. 2351 | 2352 | 2353 | ```nim 2354 | runnableExamples: 2355 | let x = 123.456 2356 | doAssert x.formatFloat() == "123.4560000000000" 2357 | doAssert x.formatFloat(ffDecimal, 4) == "123.4560" 2358 | doAssert x.formatFloat(ffScientific, 2) == "1.23e+02" 2359 | ``` 2360 | 2361 | ____ 2362 | 2363 | ## trimZeros* 2364 | 2365 | ```nim 2366 | proc trimZeros*(x: var string) {.noSideEffect.} = 2367 | ``` 2368 | 2369 | Trim trailing zeros from a formatted floating point value `x` (must be declared as ``var``). 2370 | 2371 | This modifies `x` itself, it does not return a copy. 2372 | 2373 | 2374 | ```nim 2375 | runnableExamples: 2376 | var x = "123.456000000" 2377 | x.trimZeros() 2378 | doAssert x == "123.456" 2379 | if x.contains('e'): 2380 | spl = x.split('e') 2381 | x = spl[0] 2382 | while x[x.high] == '0': 2383 | x.setLen(x.len-1) 2384 | if x[x.high] in [',', '.']: 2385 | x.setLen(x.len-1) 2386 | if spl.len > 0: 2387 | x &= "e" & spl[1] 2388 | ``` 2389 | 2390 | ____ 2391 | 2392 | ## formatSize* 2393 | 2394 | ```nim 2395 | proc formatSize*(bytes: int64, includeSpace = false): string {.noSideEffect.} = 2396 | ``` 2397 | 2398 | Rounds and formats `bytes`. 2399 | 2400 | By default, uses the IEC/ISO standard binary prefixes, so 1024 will be formatted as 1KiB. Set prefix to `bpColloquial` to use the colloquial names from the SI standard (e.g. k for 1000 being reused as 1024). 2401 | 2402 | `includeSpace` can be set to true to include the (SI preferred) space between the number and the unit (e.g. 1 KiB). 2403 | 2404 | 2405 | 2406 | See also: 2407 | * strformat module [(link)](https://nim-lang.org/docs/strformat.html) for string interpolation and formatting 2408 | 2409 | 2410 | ```nim 2411 | runnableExamples: 2412 | doAssert formatSize((1'i64 shl 31) + (300'i64 shl 20)) == "2.293GiB" 2413 | doAssert formatSize((2.234*1024*1024).int) == "2.234MiB" 2414 | doAssert formatSize(4096, includeSpace = true) == "4 KiB" 2415 | doAssert formatSize(4096, prefix = bpColloquial, includeSpace = true) == "4 kB" 2416 | doAssert formatSize(4096) == "4KiB" 2417 | doAssert formatSize(5_378_934, prefix = bpColloquial, decimalSep = ',') == "5,13MB" 2418 | ``` 2419 | 2420 | ____ 2421 | 2422 | ## formatEng* 2423 | 2424 | ```nim 2425 | proc formatEng*(f: BiggestFloat, useUnitSpace = false): string {.noSideEffect.} = proc getPrefix(exp: int): char = 2426 | ``` 2427 | 2428 | Converts a floating point value `f` to a string using engineering notation. 2429 | 2430 | Numbers in of the range -1000.0`_). 2579 | 2580 | 2581 | ____ 2582 | 2583 | ## format* 2584 | 2585 | ```nim 2586 | proc format*(formatstr: string, a: varargs[string, `$`]): string {.noSideEffect, rtl, extern: "nsuFormatVarargs".} = 2587 | ``` 2588 | 2589 | This is the same as ``formatstr % a`` (see `% proc<#%25,string,openArray[string]>`_) except that it supports auto stringification. 2590 | 2591 | 2592 | 2593 | See also: 2594 | * strformat module [(link)](https://nim-lang.org/docs/strformat.html) for string interpolation and formatting 2595 | 2596 | 2597 | ____ 2598 | 2599 | ## strip* 2600 | 2601 | ```nim 2602 | proc strip*(s: string, leading = true, trailing = true, {.noSideEffect, rtl, extern: "nsuStrip".} = 2603 | ``` 2604 | 2605 | Strips leading or trailing `chars` (default: whitespace characters) from `s` and returns the resulting string. 2606 | 2607 | If `leading` is true (default), leading `chars` are stripped. If `trailing` is true (default), trailing `chars` are stripped. If both are false, the string is returned unchanged. 2608 | 2609 | 2610 | 2611 | See also: 2612 | * `stripLineEnd proc<#stripLineEnd,string>`_ 2613 | 2614 | 2615 | ```nim 2616 | runnableExamples: 2617 | let a = " vhellov " 2618 | let b = strip(a) 2619 | doAssert b == "vhellov" 2620 | ``` 2621 | 2622 | ____ 2623 | 2624 | ## stripLineEnd* 2625 | 2626 | ```nim 2627 | proc stripLineEnd*(s: var string) = 2628 | ``` 2629 | 2630 | Returns ``s`` stripped from one of these suffixes: ``\r, \n, \r\n, \f, \v`` (at most once instance). For example, can be useful in conjunction with ``osproc.execCmdEx``. aka: `chomp`:idx: 2631 | 2632 | 2633 | ```nim 2634 | runnableExamples: 2635 | var s = "foo\n\n" 2636 | s.stripLineEnd 2637 | doAssert s == "foo\n" 2638 | s = "foo\r\n" 2639 | s.stripLineEnd 2640 | doAssert s == "foo" 2641 | ``` 2642 | 2643 | ____ 2644 | 2645 | ## editDistance* 2646 | 2647 | ```nim 2648 | proc editDistance*(a, b: string): int {.noSideEffect, deprecated: "use editdistance.editDistanceAscii instead".} = 2649 | ``` 2650 | 2651 | Returns the edit distance between `a` and `b`. 2652 | 2653 | This uses the `Levenshtein`:idx: distance algorithm with only a linear memory overhead. 2654 | 2655 | 2656 | ____ 2657 | 2658 | ## isNilOrEmpty* 2659 | 2660 | ```nim 2661 | proc isNilOrEmpty*(s: string): bool {.noSideEffect, procvar, rtl, deprecated: "use 'x.len == 0' instead".} = 2662 | ``` 2663 | 2664 | Checks if `s` is nil or empty. 2665 | 2666 | 2667 | ____ 2668 | 2669 | ## isNilOrWhitespace* 2670 | 2671 | ```nim 2672 | proc isNilOrWhitespace*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsNilOrWhitespace".} = 2673 | ``` 2674 | 2675 | Checks if `s` is nil or consists entirely of whitespace characters. 2676 | 2677 | 2678 | ____ 2679 | 2680 | ## isAlphaAscii* 2681 | 2682 | ```nim 2683 | proc isAlphaAscii*(s: string): bool {.noSideEffect, procvar, deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = 2684 | ``` 2685 | 2686 | Checks whether or not `s` is alphabetical. 2687 | 2688 | This checks a-z, A-Z ASCII characters only. Returns true if all characters in `s` are alphabetic and there is at least one character in `s`. Use Unicode module [(link)](https://nim-lang.org/docs/unicode.html) for UTF-8 support. 2689 | 2690 | 2691 | ```nim 2692 | runnableExamples: 2693 | doAssert isAlphaAscii("fooBar") == true 2694 | doAssert isAlphaAscii("fooBar1") == false 2695 | doAssert isAlphaAscii("foo Bar") == false 2696 | ``` 2697 | 2698 | ____ 2699 | 2700 | ## isAlphaNumeric* 2701 | 2702 | ```nim 2703 | proc isAlphaNumeric*(s: string): bool {.noSideEffect, procvar, deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = 2704 | ``` 2705 | 2706 | Checks whether or not `s` is alphanumeric. 2707 | 2708 | This checks a-z, A-Z, 0-9 ASCII characters only. Returns true if all characters in `s` are alpanumeric and there is at least one character in `s`. Use Unicode module [(link)](https://nim-lang.org/docs/unicode.html) for UTF-8 support. 2709 | 2710 | 2711 | ```nim 2712 | runnableExamples: 2713 | doAssert isAlphaNumeric("fooBar") == true 2714 | doAssert isAlphaNumeric("fooBar1") == true 2715 | doAssert isAlphaNumeric("foo Bar") == false 2716 | ``` 2717 | 2718 | ____ 2719 | 2720 | ## isDigit* 2721 | 2722 | ```nim 2723 | proc isDigit*(s: string): bool {.noSideEffect, procvar, deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = 2724 | ``` 2725 | 2726 | Checks whether or not `s` is a numeric value. 2727 | 2728 | This checks 0-9 ASCII characters only. Returns true if all characters in `s` are numeric and there is at least one character in `s`. 2729 | 2730 | 2731 | ```nim 2732 | runnableExamples: 2733 | doAssert isDigit("1908") == true 2734 | doAssert isDigit("fooBar1") == false 2735 | ``` 2736 | 2737 | ____ 2738 | 2739 | ## isSpaceAscii* 2740 | 2741 | ```nim 2742 | proc isSpaceAscii*(s: string): bool {.noSideEffect, procvar, deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = 2743 | ``` 2744 | 2745 | Checks whether or not `s` is completely whitespace. 2746 | 2747 | Returns true if all characters in `s` are whitespace characters and there is at least one character in `s`. 2748 | 2749 | 2750 | ```nim 2751 | runnableExamples: 2752 | doAssert isSpaceAscii(" ") == true 2753 | doAssert isSpaceAscii("") == false 2754 | ``` 2755 | 2756 | ____ 2757 | 2758 | ## isLowerAscii* 2759 | 2760 | ```nim 2761 | proc isLowerAscii*(s: string, skipNonAlpha: bool): bool {. deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = 2762 | ``` 2763 | 2764 | Checks whether ``s`` is lower case. 2765 | 2766 | This checks ASCII characters only. 2767 | 2768 | If ``skipNonAlpha`` is true, returns true if all alphabetical characters in ``s`` are lower case. Returns false if none of the characters in ``s`` are alphabetical. 2769 | 2770 | If ``skipNonAlpha`` is false, returns true only if all characters in ``s`` are alphabetical and lower case. 2771 | 2772 | For either value of ``skipNonAlpha``, returns false if ``s`` is an empty string. Use Unicode module [(link)](https://nim-lang.org/docs/unicode.html) for UTF-8 support. 2773 | 2774 | 2775 | ```nim 2776 | runnableExamples: 2777 | doAssert isLowerAscii("1foobar", false) == false 2778 | doAssert isLowerAscii("1foobar", true) == true 2779 | doAssert isLowerAscii("1fooBar", true) == false 2780 | ``` 2781 | 2782 | ____ 2783 | 2784 | ## isUpperAscii* 2785 | 2786 | ```nim 2787 | proc isUpperAscii*(s: string, skipNonAlpha: bool): bool {. deprecated: "Deprecated since version 0.20 since its semantics are unclear".} = 2788 | ``` 2789 | 2790 | Checks whether ``s`` is upper case. 2791 | 2792 | This checks ASCII characters only. 2793 | 2794 | If ``skipNonAlpha`` is true, returns true if all alphabetical characters in ``s`` are upper case. Returns false if none of the characters in ``s`` are alphabetical. 2795 | 2796 | If ``skipNonAlpha`` is false, returns true only if all characters in ``s`` are alphabetical and upper case. 2797 | 2798 | For either value of ``skipNonAlpha``, returns false if ``s`` is an empty string. Use Unicode module [(link)](https://nim-lang.org/docs/unicode.html) for UTF-8 support. 2799 | 2800 | 2801 | ```nim 2802 | runnableExamples: 2803 | doAssert isUpperAscii("1FOO", false) == false 2804 | doAssert isUpperAscii("1FOO", true) == true 2805 | doAssert isUpperAscii("1Foo", true) == false 2806 | ``` 2807 | 2808 | ____ 2809 | 2810 | ## wordWrap* 2811 | 2812 | ```nim 2813 | proc wordWrap*(s: string, maxLineWidth = 80, deprecated: "use wrapWords in std/wordwrap instead".} = 2814 | ``` 2815 | 2816 | Word wraps `s`. 2817 | 2818 | 2819 | ____ 2820 | 2821 | # Templates 2822 | 2823 | ## template toImpl 2824 | 2825 | ```nim 2826 | template toImpl(call) = 2827 | ``` 2828 | 2829 | 2830 | 2831 | 2832 | ____ 2833 | 2834 | ## template stringHasSep 2835 | 2836 | ```nim 2837 | template stringHasSep(s: string, index: int, seps: set[char]): bool = 2838 | ``` 2839 | 2840 | 2841 | 2842 | 2843 | ____ 2844 | 2845 | ## template stringHasSep 2846 | 2847 | ```nim 2848 | template stringHasSep(s: string, index: int, sep: char): bool = 2849 | ``` 2850 | 2851 | 2852 | 2853 | 2854 | ____ 2855 | 2856 | ## template stringHasSep 2857 | 2858 | ```nim 2859 | template stringHasSep(s: string, index: int, sep: string): bool = 2860 | ``` 2861 | 2862 | 2863 | 2864 | 2865 | ____ 2866 | 2867 | ## template splitCommon 2868 | 2869 | ```nim 2870 | template splitCommon(s, sep, maxsplit, sepLen) = 2871 | ``` 2872 | 2873 | Common code for split procs 2874 | 2875 | 2876 | ____ 2877 | 2878 | ## template oldSplit 2879 | 2880 | ```nim 2881 | template oldSplit(s, seps, maxsplit) = 2882 | ``` 2883 | 2884 | 2885 | 2886 | 2887 | ____ 2888 | 2889 | ## template accResult 2890 | 2891 | ```nim 2892 | template accResult(iter: untyped) = 2893 | ``` 2894 | 2895 | 2896 | 2897 | 2898 | ____ 2899 | 2900 | ## template rsplitCommon 2901 | 2902 | ```nim 2903 | template rsplitCommon(s, sep, maxsplit, sepLen) = 2904 | ``` 2905 | 2906 | Common code for rsplit functions 2907 | 2908 | 2909 | ____ 2910 | 2911 | ## template isImpl 2912 | 2913 | ```nim 2914 | template isImpl(call) = 2915 | ``` 2916 | 2917 | 2918 | 2919 | 2920 | ____ 2921 | 2922 | ## template isCaseImpl 2923 | 2924 | ```nim 2925 | template isCaseImpl(s, charProc, skipNonAlpha) = 2926 | ``` 2927 | 2928 | 2929 | 2930 | 2931 | ____ 2932 | 2933 | # Iterators 2934 | 2935 | ## iterator split* 2936 | 2937 | ```nim 2938 | iterator split*(s: string, sep: char, maxsplit: int = -1): string = 2939 | ``` 2940 | 2941 | Splits the string `s` into substrings using a single separator. 2942 | 2943 | Substrings are separated by the character `sep`. The code: 2944 | 2945 | 2946 | ```nim 2947 | for word in split(";;this;is;an;;example;;;", ';'): 2948 | writeLine(stdout, word) 2949 | ``` 2950 | 2951 | 2952 | Results in: 2953 | 2954 | 2955 | ```nim 2956 | "" 2957 | "" 2958 | "this" 2959 | "is" 2960 | "an" 2961 | "" 2962 | "example" 2963 | "" 2964 | "" 2965 | "" 2966 | ``` 2967 | 2968 | 2969 | 2970 | 2971 | See also: 2972 | * `rsplit iterator<#rsplit.i,string,char,int>`_ 2973 | * `splitLines iterator<#splitLines.i,string>`_ 2974 | * `splitWhitespace iterator<#splitWhitespace.i,string,int>`_ 2975 | * `split proc<#split,string,char,int>`_ 2976 | 2977 | 2978 | ____ 2979 | 2980 | ## iterator split* 2981 | 2982 | ```nim 2983 | iterator split*(s: string, seps: set[char] = Whitespace, maxsplit: int = -1): string = 2984 | ``` 2985 | 2986 | Splits the string `s` into substrings using a group of separators. 2987 | 2988 | Substrings are separated by a substring containing only `seps`. 2989 | 2990 | 2991 | ```nim 2992 | for word in split("this\lis an\texample"): 2993 | writeLine(stdout, word) 2994 | ``` 2995 | 2996 | 2997 | ...generates this output: 2998 | 2999 | 3000 | ```nim 3001 | "this" 3002 | "is" 3003 | "an" 3004 | "example" 3005 | ``` 3006 | 3007 | 3008 | And the following code: 3009 | 3010 | 3011 | ```nim 3012 | for word in split("this:is;an$example", {';', ':', '$'}): 3013 | writeLine(stdout, word) 3014 | ``` 3015 | 3016 | 3017 | ...produces the same output as the first example. The code: 3018 | 3019 | 3020 | ```nim 3021 | let date = "2012-11-20T22:08:08.398990" 3022 | let separators = {' ', '-', ':', 'T'} 3023 | for number in split(date, separators): 3024 | writeLine(stdout, number) 3025 | ``` 3026 | 3027 | 3028 | ...results in: 3029 | 3030 | 3031 | ```nim 3032 | "2012" 3033 | "11" 3034 | "20" 3035 | "22" 3036 | "08" 3037 | "08.398990" 3038 | ``` 3039 | 3040 | 3041 | 3042 | 3043 | See also: 3044 | * `rsplit iterator<#rsplit.i,string,set[char],int>`_ 3045 | * `splitLines iterator<#splitLines.i,string>`_ 3046 | * `splitWhitespace iterator<#splitWhitespace.i,string,int>`_ 3047 | * `split proc<#split,string,set[char],int>`_ 3048 | 3049 | 3050 | ____ 3051 | 3052 | ## iterator split* 3053 | 3054 | ```nim 3055 | iterator split*(s: string, sep: string, maxsplit: int = -1): string = 3056 | ``` 3057 | 3058 | Splits the string `s` into substrings using a string separator. 3059 | 3060 | Substrings are separated by the string `sep`. The code: 3061 | 3062 | 3063 | ```nim 3064 | for word in split("thisDATAisDATAcorrupted", "DATA"): 3065 | writeLine(stdout, word) 3066 | ``` 3067 | 3068 | 3069 | Results in: 3070 | 3071 | 3072 | ```nim 3073 | "this" 3074 | "is" 3075 | "corrupted" 3076 | ``` 3077 | 3078 | 3079 | 3080 | 3081 | See also: 3082 | * `rsplit iterator<#rsplit.i,string,string,int,bool>`_ 3083 | * `splitLines iterator<#splitLines.i,string>`_ 3084 | * `splitWhitespace iterator<#splitWhitespace.i,string,int>`_ 3085 | * `split proc<#split,string,string,int>`_ 3086 | 3087 | 3088 | ____ 3089 | 3090 | ## iterator rsplit* 3091 | 3092 | ```nim 3093 | iterator rsplit*(s: string, sep: char, maxsplit: int = -1): string = 3094 | ``` 3095 | 3096 | Splits the string `s` into substrings from the right using a string separator. Works exactly the same as `split iterator <#split.i,string,char,int>`_ except in reverse order. 3097 | 3098 | 3099 | ```nim 3100 | for piece in "foo:bar".rsplit(':'): 3101 | echo piece 3102 | ``` 3103 | 3104 | 3105 | Results in: 3106 | 3107 | 3108 | ```nim 3109 | "bar" 3110 | "foo" 3111 | ``` 3112 | 3113 | 3114 | Substrings are separated from the right by the char `sep`. 3115 | 3116 | 3117 | 3118 | See also: 3119 | * `split iterator<#split.i,string,char,int>`_ 3120 | * `splitLines iterator<#splitLines.i,string>`_ 3121 | * `splitWhitespace iterator<#splitWhitespace.i,string,int>`_ 3122 | * `rsplit proc<#rsplit,string,char,int>`_ 3123 | 3124 | 3125 | ____ 3126 | 3127 | ## iterator rsplit* 3128 | 3129 | ```nim 3130 | iterator rsplit*(s: string, seps: set[char] = Whitespace, maxsplit: int = -1): string = 3131 | ``` 3132 | 3133 | Splits the string `s` into substrings from the right using a string separator. Works exactly the same as `split iterator <#split.i,string,char,int>`_ except in reverse order. 3134 | 3135 | 3136 | ```nim 3137 | for piece in "foo bar".rsplit(WhiteSpace): 3138 | echo piece 3139 | ``` 3140 | 3141 | 3142 | Results in: 3143 | 3144 | 3145 | ```nim 3146 | "bar" 3147 | "foo" 3148 | ``` 3149 | 3150 | 3151 | Substrings are separated from the right by the set of chars `seps` 3152 | 3153 | 3154 | 3155 | See also: 3156 | * `split iterator<#split.i,string,set[char],int>`_ 3157 | * `splitLines iterator<#splitLines.i,string>`_ 3158 | * `splitWhitespace iterator<#splitWhitespace.i,string,int>`_ 3159 | * `rsplit proc<#rsplit,string,set[char],int>`_ 3160 | 3161 | 3162 | ____ 3163 | 3164 | ## iterator rsplit* 3165 | 3166 | ```nim 3167 | iterator rsplit*(s: string, sep: string, maxsplit: int = -1, keepSeparators: bool = false): string = 3168 | ``` 3169 | 3170 | Splits the string `s` into substrings from the right using a string separator. Works exactly the same as `split iterator <#split.i,string,string,int>`_ except in reverse order. 3171 | 3172 | 3173 | ```nim 3174 | for piece in "foothebar".rsplit("the"): 3175 | echo piece 3176 | ``` 3177 | 3178 | 3179 | Results in: 3180 | 3181 | 3182 | ```nim 3183 | "bar" 3184 | "foo" 3185 | ``` 3186 | 3187 | 3188 | Substrings are separated from the right by the string `sep` 3189 | 3190 | 3191 | 3192 | See also: 3193 | * `split iterator<#split.i,string,string,int>`_ 3194 | * `splitLines iterator<#splitLines.i,string>`_ 3195 | * `splitWhitespace iterator<#splitWhitespace.i,string,int>`_ 3196 | * `rsplit proc<#rsplit,string,string,int>`_ 3197 | 3198 | 3199 | ____ 3200 | 3201 | ## iterator splitLines* 3202 | 3203 | ```nim 3204 | iterator splitLines*(s: string, keepEol = false): string = 3205 | ``` 3206 | 3207 | Splits the string `s` into its containing lines. 3208 | 3209 | Every `character literal `_ newline combination (CR, LF, CR-LF) is supported. The result strings contain no trailing end of line characters unless parameter ``keepEol`` is set to ``true``. 3210 | 3211 | Example: 3212 | 3213 | 3214 | ```nim 3215 | for line in splitLines("\nthis\nis\nan\n\nexample\n"): 3216 | writeLine(stdout, line) 3217 | ``` 3218 | 3219 | 3220 | Results in: 3221 | 3222 | 3223 | ```nim 3224 | "" 3225 | "this" 3226 | "is" 3227 | "an" 3228 | "" 3229 | "example" 3230 | "" 3231 | ``` 3232 | 3233 | 3234 | 3235 | 3236 | See also: 3237 | * `splitWhitespace iterator<#splitWhitespace.i,string,int>`_ 3238 | * `splitLines proc<#splitLines,string>`_ 3239 | 3240 | 3241 | ____ 3242 | 3243 | ## iterator splitWhitespace* 3244 | 3245 | ```nim 3246 | iterator splitWhitespace*(s: string, maxsplit: int = -1): string = 3247 | ``` 3248 | 3249 | Splits the string ``s`` at whitespace stripping leading and trailing whitespace if necessary. If ``maxsplit`` is specified and is positive, no more than ``maxsplit`` splits is made. 3250 | 3251 | The following code: 3252 | 3253 | 3254 | ```nim 3255 | let s = " foo \t bar baz " 3256 | for ms in [-1, 1, 2, 3]: 3257 | echo "------ maxsplit = ", ms, ":" 3258 | for item in s.splitWhitespace(maxsplit=ms): 3259 | echo '"', item, '"' 3260 | ``` 3261 | 3262 | 3263 | ...results in: 3264 | 3265 | 3266 | ```nim 3267 | ------ maxsplit = -1: 3268 | "foo" 3269 | "bar" 3270 | "baz" 3271 | ------ maxsplit = 1: 3272 | "foo" 3273 | "bar baz " 3274 | ------ maxsplit = 2: 3275 | "foo" 3276 | "bar" 3277 | "baz " 3278 | ------ maxsplit = 3: 3279 | "foo" 3280 | "bar" 3281 | "baz" 3282 | ``` 3283 | 3284 | 3285 | 3286 | 3287 | See also: 3288 | * `splitLines iterator<#splitLines.i,string>`_ 3289 | * `splitWhitespace proc<#splitWhitespace,string,int>`_ 3290 | 3291 | 3292 | ____ 3293 | 3294 | ## iterator tokenize* 3295 | 3296 | ```nim 3297 | iterator tokenize*(s: string, seps: set[char] = Whitespace): tuple[ token: string, isSep: bool] = 3298 | ``` 3299 | 3300 | Tokenizes the string `s` into substrings. 3301 | 3302 | Substrings are separated by a substring containing only `seps`. Example: 3303 | 3304 | 3305 | ```nim 3306 | for word in tokenize(" this is an example "): 3307 | writeLine(stdout, word) 3308 | ``` 3309 | 3310 | 3311 | Results in: 3312 | 3313 | 3314 | ```nim 3315 | (" ", true) 3316 | ("this", false) 3317 | (" ", true) 3318 | ("is", false) 3319 | (" ", true) 3320 | ("an", false) 3321 | (" ", true) 3322 | ("example", false) 3323 | (" ", true) 3324 | 3325 | 3326 | 3327 | ____ 3328 | 3329 | -------------------------------------------------------------------------------- /tests/test.nim: -------------------------------------------------------------------------------- 1 | import std/sha1, osproc, strutils, unittest 2 | 3 | test "complex to md5": 4 | discard execCmd("./src/nimtomd -o:tests/complex.md -ow tests/complex.nim") 5 | check secureHashFile("tests/complex.md") == parseSecureHash("FAE6854CAB76F6583C1CFF9D4F2B722302D5F9DC") 6 | 7 | test "standard lib strutils to md5": 8 | discard execCmd("./src/nimtomd -o:tests/strutils.md -ow tests/strutils.nim") 9 | check secureHashFile("tests/strutils.md") == parseSecureHash("E171D12BFD0CE2D43E16A0A91D04FCEDB01476B9") --------------------------------------------------------------------------------