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…