├── README.MD ├── nim.cfg ├── tests ├── config.nims └── test1.nim ├── .gitignore ├── untab.nimble └── src └── untab.nim /README.MD: -------------------------------------------------------------------------------- 1 | ## Replace '\t' with ' ' 2 | -------------------------------------------------------------------------------- /nim.cfg: -------------------------------------------------------------------------------- 1 | --verbosity:0 2 | --hints:off 3 | -d:release 4 | --opt:size 5 | -------------------------------------------------------------------------------- /tests/config.nims: -------------------------------------------------------------------------------- 1 | switch("path", "../src") 2 | switch("define", "debug") -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | tests/* 3 | !tests/config.nims 4 | !tests/test1.nim 5 | -------------------------------------------------------------------------------- /untab.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | 3 | version = "0.1.0" 4 | author = "Dan Kov" 5 | description = "Replace tabs with spaces" 6 | license = "MIT" 7 | srcDir = "src" 8 | bin = @["untab"] 9 | skipFiles = @["test1.nim"] 10 | 11 | # Dependencies 12 | 13 | requires "nim >= 1.6.2" 14 | requires "fusion" 15 | requires "result" 16 | 17 | from system/io import readFile 18 | task make, "Build project": 19 | exec "nim c --outDir:build src/untab.nim" 20 | withDir "build": 21 | echo "Stripping" 22 | let kbBeforeStrip = float(readFile("untab.exe").len) / 1024.0 23 | exec "strip untab.exe" 24 | echo $kbBeforeStrip & " -> " & $(float(readFile("untab.exe").len) / 1024.0) 25 | -------------------------------------------------------------------------------- /tests/test1.nim: -------------------------------------------------------------------------------- 1 | import unittest 2 | import results 3 | import untab 4 | import os 5 | import strutils 6 | 7 | test "always pass": 8 | check true 9 | 10 | test "open file": 11 | let result = openFileCase("tests/blank.txt") 12 | check result.isOk 13 | 14 | test "open directory": 15 | let result = openFileCase("tests") 16 | check result.error == "tests is a directory" 17 | 18 | test "open a nonexisting file": 19 | let result = openFileCase("idontexist") 20 | check result.error == "idontexist does not exist" 21 | 22 | # const filepath = "tests/tmp.txt" 23 | # test "process a file": 24 | # writeFile(filepath, "\t\t") 25 | # test1param(filepath) 26 | # check readFile(filepath) == " ".repeat(2 * 4) 27 | 28 | # test "process a file with custom space number": 29 | # writeFile(filepath, "\t\t") 30 | # test2param(filepath, 12) 31 | # check readFile(filepath) == " ".repeat(2 * 12) 32 | 33 | # test "process a file with custom space number and output file": 34 | # let customout = "tests/out.txt" 35 | # writeFile(filepath, "\t\t") 36 | # test3param(filepath, 2, customout) 37 | # check readFile(customout) == " ".repeat(8) 38 | -------------------------------------------------------------------------------- /src/untab.nim: -------------------------------------------------------------------------------- 1 | import options 2 | import std/os 3 | import results 4 | import strutils 5 | import std/strformat 6 | import fusion/matching 7 | 8 | {.experimental: "caseStmtMacros".} 9 | 10 | let eprint = proc (s: string) = stderr.writeLine(s) 11 | 12 | proc openFileCase*(path: string): Result[File, string] = 13 | let filename = splitPath(path).tail 14 | if fileExists(path): 15 | ok open(path, FileMode.fmReadWriteExisting) 16 | elif dirExists(path): 17 | err(filename & " is a directory") 18 | else: 19 | err(filename & " does not exist") 20 | 21 | 22 | proc main(path: string, output: Option[string], spaces: Natural) = 23 | let result = openFileCase(path) 24 | if not result.isOk: 25 | echo result.error 26 | else: 27 | let file = result[] 28 | let contents = file.readAll 29 | var newContents = "" 30 | 31 | var replaced = 0; 32 | var i = 0 33 | for char in contents: 34 | if char == '\t': 35 | inc replaced 36 | newContents &= ' '.repeat spaces 37 | else: 38 | newContents &= char 39 | inc i 40 | 41 | if replaced != 0: 42 | let (head, tail) = splitPath(path) 43 | let filename = case output: 44 | of Some(@str): str 45 | else: tail 46 | let outputPath = joinPath(head, filename); 47 | writeFile(outputPath, newContents) 48 | 49 | echo "replaced " & $replaced & " characters." 50 | 51 | 52 | # parse arguments 53 | # defaults 54 | when isMainModule: 55 | let usage = fmt"""usage: {splitPath(getAppFilename()).tail} [spaces per tab] [output file]""" 56 | 57 | let args = commandLineParams() 58 | var infile = 59 | try: args[0] 60 | except: 61 | echo usage 62 | quit(0) 63 | var spaces: Natural = 64 | try: args[1].parseInt() 65 | except: 4 66 | var outfile = 67 | try: some(args[2]) 68 | except: none(string) 69 | 70 | case paramCount(): 71 | of 1..3: main(infile, outfile, spaces) 72 | else: eprint("Too many arguments"); echo usage 73 | else: 74 | const OUTFILE = none(string) 75 | const SPACES: Natural = 4 76 | proc test1param*(infile: string) = 77 | main(infile, OUTFILE, SPACES) 78 | proc test2param*(infile: string, spaces: Natural) = 79 | main(infile, OUTFILE, spaces) 80 | proc test3param*(infile: string, spaces: Natural, outfile: string) = 81 | main(infile, some(outfile), spaces) 82 | --------------------------------------------------------------------------------