Enhancing Functionality – Adding Features and Flexibility
In this tutorial, we’ll transform our simple Markdown-to-HTML converter into a versatile and flexible CLI tool. By adding the ability to handle multiple tasks and options, we’re going to take this project from “nice and simple” to “powerful and practical.” We’ll also make sure it’s user-friendly, handles errors like a champ, and allows customization with configuration options.
Handling Command-Line Arguments
Okay, so our first CLI tool works, but right now it’s like a restaurant with only one item on the menu. Sure, it gets the job done, but it’s not very flexible. To make our tool more useful, we’re going to give users some options. Imagine if they could pass commands like --output
, --help
, or even run commands on multiple files at once! This will turn your one-trick pony into a versatile powerhouse.
But how do we go about handling these different inputs? Enter yargs and commander.js—two popular libraries that make it super easy to manage command-line arguments in Node.js. For this tutorial, let’s go with commander.js because it’s lightweight, powerful, and pretty straightforward.
Step 1: Install Commander.js
First, we need to install the commander package:
npm install commander
Commander.js will let us define different commands and options that users can pass when running our tool. Now, let’s dive into the code and integrate it into our project.
Step 2: Updating the Code to Handle Arguments
Open up your index.js
file, and let’s add some basic commands.
#!/usr/bin/env node
const { program } = require('commander');
const fs = require('fs');
const path = require('path');
// Define our program commands
program
.version('1.0.0')
.description('Markdown to HTML converter CLI tool')
.option('-o, --output <file>', 'Specify the output file')
.option('-h, --help', 'Display help for the command')
.parse(process.argv);
// Get the input file and output options
const inputFile = program.args[0];
const outputFile = program.output || 'output.html';
if (!inputFile) {
console.error('Error: No input file provided');
process.exit(1);
}
// Read the input file and process it
const markdownContent = fs.readFileSync(inputFile, 'utf-8');
const htmlContent = markdownToHtml(markdownContent);
// Write the HTML to the output file
fs.writeFileSync(outputFile, htmlContent);
console.log(`Converted ${inputFile} to ${outputFile}`);
// Function to convert Markdown to HTML
function markdownToHtml(markdown) {
// Dummy function for now. We'll improve this later.
return `<html><body>${markdown}</body></html>`;
}
Step 3: Breaking Down the Code
Let’s unpack what’s happening here.
- program.version(): This command sets the version of your tool. It’s not required, but it’s a nice touch, and it’s good practice for CLI tools.
- program.option(): We use this to define the options our tool accepts. In this case, we added an
--output
option that lets the user specify an output file. If no output file is provided, it defaults tooutput.html
. - program.parse(): This processes the arguments passed when the tool is run.
- program.args[0]: This grabs the first argument passed, which is the input file (the Markdown file that needs converting).
- Error handling: We check if the input file was provided. If not, we throw an error message and exit the process.
At this stage, you can run your tool like this:
md2html input.md --output result.html
This will take input.md
, convert it to HTML, and write the result to result.html
. If you don’t specify --output
, it’ll save the HTML to output.html
by default.
Error Handling and Validation
Next up, let’s give our tool some “manners.” By that, I mean we need to handle errors and validate user inputs to make sure everything goes smoothly. A good CLI tool should not crash or leave users guessing what went wrong.
Step 1: Validating Input Files
What if the user passes a file that doesn’t exist? Or worse, what if they pass an invalid file type? We don’t want our tool to just give up silently. Instead, let’s check for common mistakes and provide clear feedback.
Add this block of code to your index.js
to handle some basic validations:
// Check if the input file exists
if (!fs.existsSync(inputFile)) {
console.error(`Error: File "${inputFile}" does not exist.`);
process.exit(1);
}
// Check if the input file is a Markdown file
if (path.extname(inputFile) !== '.md') {
console.error(`Error: "${inputFile}" is not a Markdown file.`);
process.exit(1);
}
Now, if the user tries to run the tool with an invalid or non-existent file, they’ll get an error message instead of a mysterious crash.
Step 2: Adding Help Messages
Another way to make your tool user-friendly is by providing helpful messages when things go wrong—or better yet, when users ask for help. With commander.js, adding a help message is super simple.
Just update your program
definition like this:
program
.version('1.0.0')
.description('Markdown to HTML converter CLI tool')
.option('-o, --output <file>', 'Specify the output file')
.on('--help', function () {
console.log('');
console.log('Example:');
console.log(' $ md2html input.md --output result.html');
})
.parse(process.argv);
Now, when users run:
md2html --help
They’ll see a help message with examples on how to use the tool. Giving users clear instructions is key to making sure they don’t get frustrated.
Reading and Writing Files with Node.js
Now we’re going to get into the meat of this tool: reading the Markdown file and converting it to HTML. For that, we’ll use Node.js’s file system (fs) module, which lets us work with files right from the terminal.
Step 1: Reading the Markdown File
Let’s start by reading the user’s input file. The following line of code takes care of that:
const markdownContent = fs.readFileSync(inputFile, 'utf-8');
- fs.readFileSync(): This reads the content of the file synchronously (blocking). We pass in the file path (
inputFile
) and specify the encoding (utf-8
) so that we get the file content as a string.
Step 2: Writing the Output File
Once we’ve read the Markdown file, the next step is to convert it to HTML and save the result. Here’s how we do that:
fs.writeFileSync(outputFile, htmlContent);
console.log(`Converted ${inputFile} to ${outputFile}`);
- fs.writeFileSync(): This writes the content of
htmlContent
to the file specified byoutputFile
.
Adding Configuration Options
Wouldn’t it be nice if users could save their settings and not have to pass the same arguments every time they run your tool? That’s where configuration files come in. We can give users the ability to specify default options (like the default output folder) in a config file.
Step 1: Creating a Config File
Let’s create a simple configuration file format using JSON. The config file will store things like the default output directory or file extension.
Create a new file called md2html.config.json
in your project folder, and add this sample content:
{
"outputDir": "./dist",
"fileExtension": ".html"
}
Step 2: Reading the Config File
Now let’s update our tool to read from this config file. Add this code to your index.js
:
let config = {
outputDir: './',
fileExtension: '.html'
};
if (fs.existsSync('md2html.config.json')) {
const configFile = fs.readFileSync('md2html.config.json');
config = JSON.parse(configFile);
}
const outputFile = path.join(config.outputDir, path.basename(inputFile, '.md') + config.fileExtension);
This code does a few things:
- If
md2html.config.json
exists, it reads the file and updates theconfig
object with its values. - We use the
outputDir
andfileExtension
from the config to generate the output file path.
Step 3: Using Configuration Options
Now when users run the tool, they don’t have to specify an output file if they don’t want to. They can rely on the defaults in their config file instead.
For example, if a user has this in their config file:
{
"outputDir": "./output",
"fileExtension": ".html"
}
They can simply run:
md2html input.md
And the tool will save the output to ./output/input.html
without needing any extra arguments.
Takeaway
Look at you—you’ve come a long way from that basic script we started with in the last tutorial. Now you’ve got a CLI tool that can handle arguments, validate inputs, and read/write files. Plus, it’s got some manners thanks to error handling and helpful messages. Your tool can even be customized by users via configuration files, making it super flexible and professional.
At this point, you’re basically a CLI master. And trust me, there’s no turning back from here—you’ll want to start automating everything now!
Next up, we’ll make your tool even fancier by adding testing and publishing it as a real, open-source project.