This article appears in Nuggets on September 22, 2021

First put NPM package links: www.npmjs.com/package/com… www.npmjs.com/package/com…

Initialize the

First, create a Git repository for code versioning

git init -y
Copy the code

Initialize the NPM project

npm init -y
Copy the code

In the generated package.json file, set the package name package.name, called command-encoding, and set the license, author, and repository fields

For NPM, implementing a command-line tool is as simple as adding a bin field to your package.json file with key for your command and value for the corresponding entry file

Such as:

{
  "bin": {
    "ce": "./bin/command-encoding.js"."command-encoding": "./bin/command-encoding.js"}}Copy the code

We then create a new bin folder in the project root and create the command-encoding.js file in it

The first line of this file requires a comment #! /usr/bin/env node

To facilitate debugging, we need to execute the NPM link command and ask NPM to create a soft link for us, linking the current directory to the NPM global folder. Ce. Exe (Windows) command-encoding-exe is automatically generated in NPM global folder. When we execute ce command in terminal, /bin/command-encoding.js with Node./bin/command-encoding.js

Command Parameter parsing

As a command line tool, it is necessary to parse the parameters of the command

In Node, when executing script files in Node, parameters and other information can be retrieved from process objects such as process.argv, but this is too cumbersome. It is better to parse commands and parameters in a package written by someone else

We install the COMMANDER module

npm install commander -S
Copy the code

How do you use it? I’m going to introduce it, and then I’m going to get its instance program

const { Command } = require("commander");
const program = new Command();
Copy the code

Then register the commands and parameters

// Register the command
const command = commandprogram.command("encode");
command.option(
  // Register parameters
  "-t, --type <encoding type>".// Parameter shorthand and full name can be separated by a comma
  "Fill in a code type".// This is a description of the parameters
  "base64" // This is the default value of the argument
);
command.action((options) = > {
  // The logic to execute when this command is triggered
  console.log(options);
});
Copy the code

Finally, the parsed parameters are called

program.parse();
Copy the code

Let’s try it at the command line

Ce --help # show encode encode --help # show encode encodeCopy the code

Since we are writing a Base64 encoder by hand, we need an input parameter

command.option(
  // Re-register the parameters above
  "-i, --input <encoding input>"."String to transcode"
);
Copy the code

{type: ‘base64’, input: ‘This is base64 encoding’}

Base64 encoding parsing

Let’s review base64 encoding

We know that every character can be represented in binary, and that a byte is eight bits of binary

This is a base64 code test

This 11101000 10111111 10011001 is 11100110 10011000 10101111 test 11100110 10110101 10001011 test 11101000 10101111 10010101 b 01100010 A 01100001 S 01110011 E 01100101 6 00110110 4 00110100 11100111 10111100 10010110 Code 11100111 10100000 10000001Copy the code

Base64 encoding actually operates on binary data by dividing every three bytes into groups of 24 binary digits, which are then divided into four six-bit binary digits

Why is there a 6-bit copy, since the 6-bit binary number represents a decimal number up to 64, calculated in the command line node

The standard encoding table of Base64 consists of 26 uppercase letters, 26 lowercase letters, 0-9, and + /, representing the decimal numbers 0-63 respectively

So if a character is converted to a decimal number 0, 20, 30, 61, 25, 10 (NPX command-encoding decode -i ATd9YJ), the corresponding encoding should be ATd9YJ

So let’s see how this in this test base64 encoding character is converted to base64 encoding, in UTF8 encoding, it’s three bytes, eight bits of binary, and we need to divide it into four equal parts, right

11101000 10111111 10011001 -> 111010 001011 111110 011001, how to convert base 2 to base 10

You can calculate 1 * 2^5 + 1 * 2^4 + 1 * 2^3 + 0 * 2^2+ 1 * 2^1 + 0 * 2^0 to get 58, then 58, 11, 62, 25, and the base64 encoding is 6L+Z

Standard Base64 encodings may have an = sign at the end, and if the last group is less than four, or there are several more, an equal sign is added at the end, so base64 encodings end with 0-2 equals signs

Implement Base64 encoding

Base64 encoding is possible in the callback function in the action registered in the above code

First convert the input to a hexadecimal buffer

command.action((options) = > {
  const buffer = Buffer.from(options.input);
});
Copy the code

Then convert buffer to a long string in base 2

/* A function that complements a string and calculates how many more characters are needed to fill from the given minimum length to the given string length, and returns the string to be filled */
const fillStr = (min, str, filledStr = "0") = >
  new Array(min - str.length).fill(filledStr).join("");

command.action((options) = > {
  const buffer = Buffer.from(options.input);

  // A decimal array
  const _10Arr = Array.from(buffer);

  // _2Str will get a string like "111010001011111110011001"
  let _2Str = _10Arr.reduce((prevTotal, curr) = > {
    // Convert base 10 to base 2
    let _cur2Str = curr.toString(2);

    If there are less than 8 bits, add 0 to the front
    if (_cur2Str.length < 8) {
      _cur2Str = fillStr(8, _cur2Str) + _cur2Str;
    }

    // Then join them together
    return prevTotal + _cur2Str;
  }, "");
});
Copy the code

Next, we process the _2Str string, clipping it six bits at a time until it stops, so we can process it with a while loop

while (_2Str) {
  // Extract the first six bits of _2Str, and only the first six bits are processed in each loop
  let current2Str = _2Str.slice(0.6);
  // Then remove the first 6 bits of the operation in _2Str
  // _2Str is a string assigned after the sixth bit of _2Str
  _2Str = _2Str.substr(6, _2Str.length);
  // current2Str may be smaller than 6 bits. In this case, 0 needs to be added
  // If the last time is less than 6 bits, add 0 to the end
  if (current2Str.length < 6) {
    current2Str += fillStr(6, current2Str); }}Copy the code

With the converted binary data, we can convert it to decimal and then, based on the encoding table, convert it to the corresponding encoding character

// Declare a variable to store the encoded Base64 result string
let result = "";
const chars =
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
while (_2Str) {
  let current2Str = _2Str.slice(0.6);

  _2Str = _2Str.substr(6, _2Str.length);

  if (current2Str.length < 6) {
    current2Str += fillStr(6, current2Str);
  }

  // Convert to base 10 and append to result in turn

  result += chars[parseInt(current2Str, 2)];
}

// When the loop is complete, the result is the encoded Base64 string
Copy the code

However, this is not the end, we still need to judge whether we need to add equal signs. In the end, if there are less than 4 copies, we need to add equal signs for the remaining copies

So we can figure out how many more binary numbers we need, if each of them is 6, how many more binary numbers we need to add over 6, and we can figure out how many more equal signs we need to add

How do you figure out how many more bytes are needed to make a set of four?

We came up with the idea of mod, using the total length of the current binary % (4 * 6) to get how many binaries there are in the last group, and then using 4 * 6 – the last group to get the binaries that need to be supplemented

let _2StrLength = 0;

while (_2Str) {
  // xxxx
  _2StrLength += current2Str.length;
  // xxxx
}

// How many binaries are there in a group
const groupLength = 4 * 6;
// The last group of binary data
const _2DataInLastGroup = _2StrLength % groupLength;
// The number of binary numbers to be added
const _2LengthNeed = groupLength - (_2Left || groupLength);
// Get the number of equals signs
const equalCount = _2LengthNeed / 6;
// Add the final result
result += fillStr(result.length + equalCount, result, "=");
Copy the code

Console. log(result) outputs the encoding result on the console.

Other BASEX encoding

Can base64 encoders be more generic?

Like base32 encoding, base16 encoding?

If you know how Base32, base16 encoding works, you’ll find it easy

In Base64 encoding, the encoding table has 64 characters, and the decimal numbers that can be represented are from 0 to 63, so the corresponding binary number does not exceed 6 bits

In Base32 encoding, the encoding table has 32 characters. The decimal number that can be represented is from 0 to 31, and the corresponding binary number cannot exceed 5 bits

Similarly, in Base16 encoding, the encoding table has 16 characters. The decimal number that can be represented is from 0 to 15, and the corresponding binary number cannot exceed 4 bits

We have a relationship that looks like this

64 -> 6, 32 -> 5, 16 -> 4

Can this relationship be computed in code? We can do this with a recurrence

const calcBaseNum = (int, flag = 0, total = 0) = > {
  const current = 1 * Math.pow(2, flag++) + total;
  if (int > current) return calcBaseNum(int, flag, current);
  else return flag - 1;
};
const baseNum = calcBaseNum(64); / / 6
Copy the code

Encoding tables can be configured

const chars = {
  64: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".32: "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".16: "0123456789ABCDEF"};Copy the code

Then, there is still the calculation method of the complement equal sign rule. Among the three encodings, the only difference may be the value of groupLength mentioned above

In base64, groupLength === 4 * 6

In fact, this number is the least common multiple of 6 and 8, which is 24. Similarly, in base32 encoding, the least common multiple of 5 and 8 is 40, and in Base16 encoding, the least common multiple of 4 and 8 is 8, so there is no supplementary equal sign

How do you find the greatest common multiple of two numbers?

We can first find their least common divisor using toss and turn division

Divide by dividing one number by another (you don't need to know the size), taking the remainder, dividing the dividend by the remainder, taking the remainder, dividing the new dividend by the remainder, taking the remainder, until the remainder is 0, and the final dividend is the greatest common divisor *@param {number} number1
 * @param {number} number2* /
function getGreatestCommonDivisor(number1, number2) {
  if (!Array.from(arguments).every(isNumber))
    throw new TypeError(
      "Only type of number can be got greatest common divisor!"
    );
  if (number2 == 0) return number1;

  return getGreatestCommonDivisor(number2, number1 % number2);
}
Copy the code

And then, the greatest common multiple is two numbers multiplied and divided by the greatest common divisor

/** * find the least common multiple * least common multiple = multiply two numbers divided by the greatest common divisor *@param {number} number1
 * @param {number} number2* /
function getLowestCommonMultiple(number1, number2) {
  if (!Array.from(arguments).every(isNumber))
    throw new TypeError(
      "Only type of number can be got lowest common multiple!"
    );

  const gcd = getGreatestCommonDivisor(number1, number2);
  if(gcd ! = =0) {
    return (number1 * number2) / gcd;
  }
  return 0;
}
Copy the code

GroupLength = getLowestCommonMultiple(calcBaseNum(64 /* here or 32, 16 */), 8)

Based on the base64 encoded code above, we can derive the complete code for the public logic of these three encodings

const chars = {
  64: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".32: "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".16: "0123456789ABCDEF"};function createEncodingBaseInt(int, chars) {
  const baseNum = calcBaseNum(int);
  // Maximum common multiple, LCM/baseNum Number of 8bits in each group, base64 -> 4 / base32 -> 8 / base16 -> 2
  const lcm = getLowestCommonMultiple(baseNum, 8);
  /** * encode base x *@param {Buffer | string} Input buffer Data or string */
  return function encode(input) {
    let buffer = input;

    // If buffer is not a buffer object, it is converted to a buffer object
    if(! Buffer.isBuffer(buffer)) { buffer = Buffer.from(buffer); }// Convert the buffer object to an array
    const _10Arr = Array.from(buffer);
    // bufferOfStr generates an entire binary string
    let _2Str = _10Arr.reduce((prevTotal, curr) = > {
      // Convert base 10 to base 2
      let _cur2Str = curr.toString(2);

      If there are less than 8 bits, add 0 to the front
      if (_cur2Str.length < 8) {
        _cur2Str = fillStr(8, _cur2Str) + _cur2Str;
      }

      return prevTotal + _cur2Str;
    }, "");

    // Declare a result string. EncodeBase64 returns result
    let result = "";
    let _2CurrentLength = 0;

    // When _2Str is empty, the loop stops and the loop operates on _2Str
    while (_2Str) {
      // Extract the first 6 bits of _2Str
      let current2Str = _2Str.slice(0, baseNum);

      // If the last time is less than 6 bits, add 0 to the end
      if (current2Str.length < baseNum) {
        current2Str += fillStr(baseNum, current2Str);
      }

      _2CurrentLength += current2Str.length;

      // _2Str is a string assigned after the sixth bit of _2Str
      _2Str = _2Str.substr(baseNum, _2Str.length);
      // Converts the 2-base current2Str to the 10-base index of the encoded string, looking for characters in it, and adding to result
      result += chars[parseInt(current2Str, 2)];
    }

    // add an equal sign
    // Use the maximum common multiple to calculate the number of bytes required to complete the composition, and then divide by baseNum to calculate the number of groups required
    const equalCount = (lcm - (_2CurrentLength % lcm || lcm)) / baseNum;

    result += fillStr(result.length + equalCount, result, "=");

    // The loop ends, returning the generated Base64 encoding
    return result;
  };
}

const encode = createEncodingBaseInt(64, chars[64]);

console.log(encode("This is testing base64 encoding.")); // 6L+Z5piv5rWL6K+VYmFzZTY057yW56CB
Copy the code

Decoding of base64, Base32 and Base16

Knowing the principles of the three encoding methods, it is not difficult to deduce their decoding principles, which is to use the encoding table to convert the encoded characters into decimal numbers

Then, the decimal number is converted to the binary number, which is combined every 8 bits and converted back to the original value. The equal sign can be ignored

So let’s cut the crap and paste the code

/** * create a mapping table between characters and decimal numbers *@param {string} str 
 * @returns {Map} * /
function createCharIndexMap(str) {
    const map = new Map(a)if(! isString(str))return map
    for (let i = 0; i < str.length; i ++) {
        map.set(str[i], i)
    }
    return map
}
function createDecodingBaseInt(int, charsMap) {
  const baseNum = calcBaseNum(int);
  /** * decode base x */
  return function decode(input) {
    if(! isString(input)) input = Buffer.from(input).toString();let _2Str = "";

    for (let i = 0; i < input.length; i++) {
      const char = input[i];
      if (char === "=") continue;

      const charIndex = charsMap.get(char);

      if (charIndex === undefined)
        throw new Error("error: unsupport input: " + char);

      let _current2Str = charIndex.toString(2);

      _current2Str.length < baseNum &&
        (_current2Str = fillStr(baseNum, _current2Str) + _current2Str);
      _2Str += _current2Str;
    }
    const _10Arr = [];

    while (_2Str) {
      let current2Str = _2Str.slice(0.8);
      _10Arr.push(parseInt(current2Str, 2));

      _2Str = _2Str.substr(8, _2Str.length);
    }

    return Buffer.from(_10Arr);
  };
}

const decode = createDecodingBaseInt(64, createCharIndexMap(chars[64]))

console.log(decode("6L+Z5piv5rWL6K+VYmFzZTY057yW56CB")) // Base64 encoding is tested
Copy the code

Base64 encoding in the data URL

In development, we can often see the figure of data URL. For example, data URL can be directly placed in the SRC attribute of img tag, and data URL can also be directly placed in the URL () of background attribute in CSS

You can also access it directly by entering it in the browser

In contrast to normal Base64 encoding, it simply prefixes the file type (and encoding)

We can introduce the mine module

npm install mime -S
Copy the code

Myme.gettype (path.extName (XXXX)) = mime.getType(path.extName (XXXX))

The resulting MimeType is then spelled in front of the Base64 encoding

let base64Str = "6L+Z5piv5rWL6K+VYmFzZTY057yW56CB"
base64Str = `data:${mimetype}; base64,${base64Str}`
Copy the code

And you’re done!

How do I publish a project to NPM?

First you need an NPM account

Sign up www.npmjs.com/signup

Then log in to NPM in the project

npm login
Copy the code

Note that the address points to the NPM source

Such as: NRM use NPM or NPM login – registry=https://registry.npmjs.org

You can use

npm version patch
Copy the code

Create a patch version, and note that this creates a tag in your local Git

And publish it directly

npm publish --registry=https://registry.npmjs.org
Copy the code

You can configure the commands to be executed before or after publication in package.json

Such as:

{
    "scripts": {
        "postpublish": "git push origin master"}}Copy the code

Then go to www.npmjs.com/ to search for your own package, or download NPM install [your package name]

other

Remember to use NPM unlink to remove soft links after debugging

Code address: github.com/kawayiLinLi…

Original text: yzl. Xyz/Lin / 2021/09…