File encoder/decoder in Nim I wrote a simple program to encode a local text file with password and decode it back. There is one text file and it is either encoded with a password or is in plain text.

I had a version in Python for this, but nim compiles to an executable, so I gave it a try.

I'm not perfectly happy with command line argument handling, as well as with some repeated code in case statement, but maybe there are other things to improve too.

import os
import system
import strutils
import terminal 

import xxtea

const FILENAME = "passwords.txt"
const USAGE = """Encrypt and decrypt a file using a password.

Usage: 
    vault open [file]
    vault close [file]

Default file:
    passwords.txt    

WARNING: this program may overwrite or delete file content without 
a possibilty of recovery. Passwords cannot be recovered. Information 
can be destroyed by repeated attempts of encryption/decryption. Use 
at you own risk. 
"""

proc getpass(message: string = "Password: "): string =
   return terminal.readPasswordFromStdin(message)

proc getpass2(): string =
    let key = getpass()
    let key2 = getpass("Repeat password: ") 
    if key == key2:
        return key    
    else: 
        quitWithMessage("Passwords do not match")

proc write(content: string): void =
    writeFile(filepath(), content)

proc notify(message: string): void =
    echo message, " ", filepath()

# cli args helpers

proc filepath(): string =
    try:
       return paramStr(2)
    except IndexError: 
       return FILENAME

proc command(): string = 
    try:
       return paramStr(1).toLowerAscii()
    except IndexError: 
       quitWithMessage(USAGE)

# flow control 

proc quitWithMessage(message: string): void =
    echo message
    quit(1)

# main

if command() notin ["open", "close", "help"]:
    quitWithMessage(USAGE)   

let existingContent = readFile(filepath())

case command()    
    of "open":        
        let key = getpass()
        let newContent = xxtea.decrypt(existingContent, key)
        write(newContent)
        notify("Decoded")
    of "close":
        let key = getpass2()
        let newContent = xxtea.encrypt(existingContent, key)
        write(newContent)
        notify("Encoded")
    of "help": 
        quitWithMessage(USAGE)
As long as the encoding and decoding are so similar you could use a template

template doCmd(gp: untyped, encdec: untyped, msg: string): void =
    let key = gp()
    let newContent = encdec(existingContent, key)
    write(newContent)
    notify(msg)


and then:

of "open":
    doCmd getpass, xxtea.decrypt, "Decoded"
of "close":
    doCmd getpass2, xxtea.encrypt, "Encoded"


You can also use a proc that accepts proc as arguments, more safe for checking the types of arguments passed, but at that point either getpass and getpass2 are declared with the same arguments or you have to pass an argument for each variant.

To pass a function as an argument just use proc and{.closure}

proc doCmd(gp: proc(m: string = "Password: "): string {.closure}, encdec: proc(b: string, k: string): string {.closure}, msg: string): void =
    let key = gp()