4
\$\begingroup\$

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)
\$\endgroup\$
2
\$\begingroup\$

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()
\$\endgroup\$
  • \$\begingroup\$ Like the template idea, thanks for sugesting! Woukd need to familiarise with nim syntax behind it, though. \$\endgroup\$ – Evgeny Aug 4 at 11:15
  • 1
    \$\begingroup\$ @Evgeny, nim templates are powerful, an old but still valid post Introduction to Metaprogramming in Nim#templates \$\endgroup\$ – Alex Aug 4 at 12:57
1
\$\begingroup\$

An improvement in case statement:

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

let existingContent = readFile(filepath())
case command()    
    of "open":        
        let key = getpass()
        notify("Decoding")
        let newContent = xxtea.decrypt(existingContent, key)
    of "close":
        let key = getpass2()
        notify("Encoding")
        let newContent = xxtea.encrypt(existingContent, key)
write(newContent)
notify("Finished processing")

I wish I knew how to declare and assign f = xxtea.decrypt and f = xxtea.encrypt and call f in nim - would have same even more repeated code.

Also documentation is not immediately clear about a dictionary in nim, even though some light here. A dictionary can be used to encode notify() messages by command names.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.