Rot13 cli in javascript


#1

My nephew got a coding book for Christmas. It’s an illustrated book on the basic concepts of coding, there is no programming done in the book, just the concepts. He also learned about ROT13 from some friends and was hand writing his own “secret language” with it by hand. So I jump at the chance to create a little program that could do the work for him, and used it as a teaching opportunity for him and I. I was also inspired by something I read on twitter from @progmofo about coding being a creative endeavor and that all the cool things that he coded, were done on fun hacks while watching TV. Which actually helped me approach the new lessons in the book and this with a lot less internal resistance.

Here’s the code:

const commandLineArgs = require('command-line-args');
const commandLineUsage = require('command-line-usage');
const chalk = require('chalk');

const optionDefinitions = [
    { name: 'help', alias: 'h', type: Boolean },
    { name: 'in', type: String, defaultOption: true }
];

const sections = [
    {
        header: "Cesar's Cipher",
        content: "Encrypt or decrypt strings"
    }, {
        header: 'Options',
        optionList: [
            {
                name: 'in',
                typeLabel: '{underline file}',
                description: 'The input string. {underline must be surrounded in quotes.}'
            }, {
                name: 'help',
                description: 'Print the usage guide.'
            }
        ]
    }
];

const options = commandLineArgs(optionDefinitions);
const usage = commandLineUsage(sections);

const rot13 = (str) => {
    let codes = [];
    
    
    for(let letter of str) {
        codes.push(letter.toUpperCase().charCodeAt());
    }

    let decode = (code) => {
        if(code < 65 || code > 90) {
            return code;
        } else if(code < 78) {
            return code + 13;
        } else {
            return code - 13;
        }
    };

    codes = codes.map(decode).map(code => String.fromCharCode(code)).join('');
    
    return codes;
};

if (options.help) {
    console.log(usage);
} else if(options.in) {
    console.log(chalk.blue(rot13(options.in)));
} else {
    console.log(chalk.red("You didn't provide anything."));
}

#2

Nice! It’s interesting that you have that for-of loop to convert the string to uppercase numbers. You’d think you could use the .map() function on it too and have something like:

let codes = str.map((c) => c.toUpperCase().charCodeAt());

But it turns out that String isn’t a sequence type, so the best you can do is:

str.split('').map((c) => c.toUpperCase().charCodeAt());

Kind of dumb to call split with nothing on it, but I think you can do this:

str.toUpperCase().split('').map((c) => c.charCodeAt())

Which would be slightly more efficient since you’d just uppercase the whole string in one move, then split it into chars, then convert to an array of numbers with map.


#3

@zedshaw Ah. thank you for pointing that out! I took it further to eliminate a bunch of the code in the rot13 function.

const rot13 = (str) => {
    return str.toUpperCase()
              .split('')
              .map((c) => {
                c = c.charCodeAt();
                if (c < 65 || c > 90) {
                    return String.fromCharCode(c);
                } else if (c < 78) {
                    return String.fromCharCode(c + 13);
                } else {
                    return String.fromCharCode(c - 13);
                }
              })
              .join('');
};

I didn’t even think about splitting the string into an Array because at the time I was just thinking “this is a string”, and closed my mind how I could transform it better.


#4

Boom! That’s what I’m talking about. This style of JS (known as “functional JS”) is considered the most reliable way to write it. Ok, now I want you to take a look at this little block:

                c = c.charCodeAt();
                if (c < 65 || c > 90) {
                    return String.fromCharCode(c);
                } else if (c < 78) {
                    return String.fromCharCode(c + 13);
                } else {
                    return String.fromCharCode(c - 13);
                }

That first line bothers me. Use let and don’t reuse the c variable.

Next, see how the if-statement has a lot of duplicated code? What you’re really doing here is determining the number to add or subtract from the character code. You could pull this into a function, and then have:

return c.charCodeAt() + rot13calc();

Then your rot13calc() function just does something like this:

                if (c < 65 || c > 90) {
                    return 0;
                } else if (c < 78) {
                    return 13
                } else {
                    return -13;
                }

Once you do that you might start to see this has a math behind it and could simplify even more.

Incidentally, this thing we’re doing is called “code golf” and it’s where people pass a chunk of code back and forth pushing it down the path of better (or more and more clever).


#5

Alright. Here is my response, with your changes applied:

const rot13 = (str) => {
    
    // internal helper funcitons:
    // More succinct function for String.fromCharCode();
    const codeToStr = code => String.fromCharCode(code);
    
    // calculation function for character code
    const rot13Calc = (code) => {
        if (code < 65 || code > 90) {
            return 0;
        } else if(code < 78) {
            return 13;
        } else {
            return -13;
        }
    }

    return str.toUpperCase()
              .split('')
              .map((c) => {
                let cCode = c.charCodeAt();
                return codeToStr(cCode + rot13Calc(cCode));
              })
              .join('');

};

I declared the helper functions locally within the main function. I felt that this was the smartest thing to do since they wouldn’t be needed anywhere else.

Also, at one point I was thinking of using the following method for Function objects in javascript, on the string array. However, in the end it would have made the code more complex.

Lastly, I’m torn between leaving the code as above, or I could further reduce the lines of code in the return statement by writing it like so:

    return str.toUpperCase()
                  .split('')
                  .map(c => codeToStr(c.charCodeAt() + rot13Calc(c.charCodeAt())))
                  .join('');

#6

Alright about the only things I can see are:

  1. Do you know about the ternary operator?
c < 78 ? 13 : -13;
  1. Why not this:
return str.toUpperCase()
              .split('')
              .map(c => c.charCodeAt())
              .map(c => codeToStr(c + rot13Calc(c)))
              .join('');
  1. Right now you’re converting everything, but skipping the chars outside the rot13 range. You could use filter to just remove them:
return str.toUpperCase()
              .split('')
              .map(c => c.charCodeAt())
              .filter(c => c >= 65 || c =< 90)
              .map(c => codeToStr(c + rot13Calc(c)))
              .join('');
  1. Then add in Mr Ternary:
return str.toUpperCase()
              .split('')
              .map(c => c.charCodeAt())
              .filter(c => c >= 65 || c =< 90)
              .map(c => codeToString(c + (c < 78 ? 13 : -13)))
              .join('');
  1. Then finally, wellllll you can even put the codeToString in one more map:
return str.toUpperCase()
              .split('')
              .map(c => c.charCodeAt())
              .filter(c => c >= 65 || c =< 90)
              .map(c => c + (c < 78 ? 13 : -13))
              .map(codeToString)
              .join('');
  1. So then why do we need codeToString?
return str.toUpperCase()
              .split('')
              .map(c => c.charCodeAt())
              .filter(c => c >= 65 || c =< 90)
              .map(c => c + (c < 78 ? 13 : -13))
              .map(String.fromCharCode)
              .join('');

I only tested the last one, and it prints some strange unicode trailing things, but otherwise works. The way to think about this is you want each stage to do some kind of transform in small steps. You apply functions to data to transform in steps from one form to another.


#7

I was doing some research for my cut.js project and I was looking to find a way to retain delimiter from the String.prototype.split() function (Answer: you put it in a variable. whodathunkit!). I learned that the .split() function is not unicode safe. I think that is why in your implementation that there are trailing unicode characters.

I haven’t tested this, I plan to and will post confirmation ASAP.

For your edification:


#8

That is very interesting, because if .split() is not unicode safe, but all JS strings are unicode by default, then…how is that possible? Also, .split is the only way to get the bytes unless maybe you use the Buffer() class? That’s about the only other thing I could think of.