Skip to content

Commit

Permalink
Merge pull request #179 from kenji-miyake/support-multi-inputs
Browse files Browse the repository at this point in the history
feat: support multi inputs
  • Loading branch information
tcort authored Jan 21, 2022
2 parents b13b6c2 + 41caf76 commit da43429
Showing 1 changed file with 148 additions and 107 deletions.
255 changes: 148 additions & 107 deletions markdown-link-check
Original file line number Diff line number Diff line change
Expand Up @@ -18,85 +18,108 @@ const statusLabels = {
error: chalk.yellow('⚠'),
};

const opts = {};
let filenameForOutput = '';
let stream = process.stdin; // read from stdin unless a filename is given
class Input {
constructor(filenameForOutput, stream) {
this.filenameForOutput = filenameForOutput;
this.stream = stream;
}
};

function commaSeparatedCodesList(value, dummyPrevious) {
return value.split(',').map(function(item) {
return parseInt(item, 10);
return parseInt(item, 10);
});
}

program
.version(pkg.version)
.option('-p, --progress', 'show progress bar')
.option('-c, --config [config]', 'apply a config file (JSON), holding e.g. url specific header configuration')
.option('-q, --quiet', 'displays errors only')
.option('-v, --verbose', 'displays detailed error information')
.option('-a, --alive <code>', 'comma separated list of HTTP codes to be considered as alive', commaSeparatedCodesList)
.option('-r, --retry', 'retry after the duration indicated in \'retry-after\' header when HTTP code is 429')
.arguments('[filenameOrUrl]')
.action(function (filenameOrUrl) {
filenameForOutput = filenameOrUrl;
if (/https?:/.test(filenameOrUrl)) {
stream = needle.get(filenameOrUrl);
stream.on('error', function (error) {
console.error(chalk.red('\nERROR: Unable to connect! Please provide a valid URL as an argument.'));
process.exit(1);
});
stream.on('response', function (response) {
if (response.statusCode === 404) {
console.error(chalk.red('\nERROR: 404 - File not found! Please provide a valid URL as an argument.'));
process.exit(1);
}
});
try { // extract baseUrl from supplied URL
const parsed = url.parse(filenameOrUrl);
delete parsed.search;
delete parsed.hash;
if (parsed.pathname.lastIndexOf('/') !== -1) {
parsed.pathname = parsed.pathname.substr(0, parsed.pathname.lastIndexOf('/') + 1);
}
opts.baseUrl = url.format(parsed);
} catch (err) { /* ignore error */
function getInputs() {
const opts = {};
const inputs = [];

program
.version(pkg.version)
.option('-p, --progress', 'show progress bar')
.option('-c, --config [config]', 'apply a config file (JSON), holding e.g. url specific header configuration')
.option('-q, --quiet', 'displays errors only')
.option('-v, --verbose', 'displays detailed error information')
.option('-a, --alive <code>', 'comma separated list of HTTP codes to be considered as alive', commaSeparatedCodesList)
.option('-r, --retry', 'retry after the duration indicated in \'retry-after\' header when HTTP code is 429')
.arguments('[filenamesOrUrls...]')
.action(function (filenamesOrUrls) {
let filenameForOutput;
let stream;

if (!filenamesOrUrls.length) {
// read from stdin unless a filename is given
inputs.push(new Input(filenameForOutput, process.stdin))
}
} else {
fs.stat(filenameOrUrl, function(error , stats){
if (!error && stats.isDirectory()){
console.error(chalk.red('\nERROR: ' + filenameOrUrl + ' is a directory! Please provide a valid filename as an argument.'));
process.exit(1);

for (const filenameOrUrl of filenamesOrUrls) {
filenameForOutput = filenameOrUrl;
if (/https?:/.test(filenameOrUrl)) {
stream = needle.get(filenameOrUrl);
stream.on('error', function (error) {
console.error(chalk.red('\nERROR: Unable to connect! Please provide a valid URL as an argument.'));
process.exit(1);
});
stream.on('response', function (response) {
if (response.statusCode === 404) {
console.error(chalk.red('\nERROR: 404 - File not found! Please provide a valid URL as an argument.'));
process.exit(1);
}
});
try { // extract baseUrl from supplied URL
const parsed = url.parse(filenameOrUrl);
delete parsed.search;
delete parsed.hash;
if (parsed.pathname.lastIndexOf('/') !== -1) {
parsed.pathname = parsed.pathname.substr(0, parsed.pathname.lastIndexOf('/') + 1);
}
opts.baseUrl = url.format(parsed);
} catch (err) { /* ignore error */
}
} else {
fs.stat(filenameOrUrl, function(error , stats){
if (!error && stats.isDirectory()){
console.error(chalk.red('\nERROR: ' + filenameOrUrl + ' is a directory! Please provide a valid filename as an argument.'));
process.exit(1);
}
});
opts.baseUrl = 'file://' + path.dirname(path.resolve(filenameOrUrl));
stream = fs.createReadStream(filenameOrUrl);
}

inputs.push(new Input(filenameForOutput, stream))
}
});
opts.baseUrl = 'file://' + path.dirname(path.resolve(filenameOrUrl));
stream = fs.createReadStream(filenameOrUrl);
}
}
).parse(process.argv);

opts.showProgressBar = (program.progress === true); // force true or undefined to be true or false.
opts.quiet = (program.quiet === true);
opts.verbose = (program.verbose === true);
opts.retryOn429 = (program.retry === true);
opts.aliveStatusCodes = program.alive;
// set the projectBaseUrl to the current working directory, so that `{{BASEURL}}` can be resolved to the project root.
opts.projectBaseUrl = `file://${process.cwd()}`;

return [inputs, opts];
}

async function processInput(filenameForOutput, stream, opts) {
let markdown = ''; // collect the markdown data, then process it

}).parse(process.argv);

opts.showProgressBar = (program.progress === true); // force true or undefined to be true or false.
opts.quiet = (program.quiet === true);
opts.verbose = (program.verbose === true);
opts.retryOn429 = (program.retry === true);
opts.aliveStatusCodes = program.alive;
// set the projectBaseUrl to the current working directory, so that `{{BASEURL}}` can be resolved to the project root.
opts.projectBaseUrl = `file://${process.cwd()}`;

let markdown = ''; // collect the markdown data, then process it

stream
.on('data', function (chunk) {
markdown += chunk.toString();
})
.on('error', function(error) {
if (error.code === 'ENOENT') {
console.error(chalk.red('\nERROR: File not found! Please provide a valid filename as an argument.'));
} else {
console.error(chalk.red(error));
stream.on('error', function(error) {
if (error.code === 'ENOENT') {
console.error(chalk.red('\nERROR: File not found! Please provide a valid filename as an argument.'));
} else {
console.error(chalk.red(error));
}
return process.exit(1);
})

for await (const chunk of stream) {
markdown += chunk.toString();
}
return process.exit(1);
})
.on('end', function () {

if (filenameForOutput) {
console.log(chalk.cyan('\nFILE: ' + filenameForOutput));
}
Expand All @@ -121,8 +144,6 @@ stream
opts.retryCount = config.retryCount;
opts.fallbackRetryDelay = config.fallbackRetryDelay;
opts.aliveStatusCodes = config.aliveStatusCodes;

runMarkdownLinkCheck(markdown, opts);
});
}
else {
Expand All @@ -131,47 +152,67 @@ stream
}
});
}
else {
runMarkdownLinkCheck(markdown, opts);
}
});

function runMarkdownLinkCheck(markdown, opts) {
markdownLinkCheck(markdown, opts, function (err, results) {
if (err) {
console.error(chalk.red('\n ERROR: something went wrong!'));
console.error(err.stack);
process.exit(1);
}

if (results.length === 0 && !opts.quiet) {
console.log(chalk.yellow(' No hyperlinks found!'));
}
results.forEach(function (result) {
// Skip messages for non-deadlinks in quiet mode.
if (opts.quiet && result.status !== 'dead') {
return;
await runMarkdownLinkCheck(markdown, opts).catch(() => reject());
resolve();
}

async function runMarkdownLinkCheck(markdown, opts) {
return new Promise((resolve, reject) => {
markdownLinkCheck(markdown, opts, function (err, results) {
if (err) {
console.error(chalk.red('\n ERROR: something went wrong!'));
console.error(err.stack);
reject();
}

if (opts.verbose) {
if (result.err) {
console.log(' [%s] %s → Status: %s %s', statusLabels[result.status], result.link, result.statusCode, result.err);
} else {
console.log(' [%s] %s → Status: %s', statusLabels[result.status], result.link, result.statusCode);
}
if (results.length === 0 && !opts.quiet) {
console.log(chalk.yellow(' No hyperlinks found!'));
}
else {
console.log(' [%s] %s', statusLabels[result.status], result.link);
results.forEach(function (result) {
// Skip messages for non-deadlinks in quiet mode.
if (opts.quiet && result.status !== 'dead') {
return;
}

if (opts.verbose) {
if (result.err) {
console.log(' [%s] %s → Status: %s %s', statusLabels[result.status], result.link, result.statusCode, result.err);
} else {
console.log(' [%s] %s → Status: %s', statusLabels[result.status], result.link, result.statusCode);
}
}
else {
console.log(' [%s] %s', statusLabels[result.status], result.link);
}
});
console.log('\n %s links checked.', results.length);
if (results.some((result) => result.status === 'dead')) {
let deadLinks = results.filter(result => { return result.status === 'dead'; });
console.error(chalk.red('\n ERROR: %s dead links found!'), deadLinks.length);
deadLinks.forEach(function (result) {
console.log(' [%s] %s → Status: %s', statusLabels[result.status], result.link, result.statusCode);
});
reject();
}

resolve();
});
console.log('\n %s links checked.', results.length);
if (results.some((result) => result.status === 'dead')) {
let deadLinks = results.filter(result => { return result.status === 'dead'; });
console.error(chalk.red('\n ERROR: %s dead links found!'), deadLinks.length);
deadLinks.forEach(function (result) {
console.log(' [%s] %s → Status: %s', statusLabels[result.status], result.link, result.statusCode);
});
process.exit(1);
}
});
}

async function main() {
const [inputs, opts] = getInputs();

let isOk = true;
for (const input of inputs) {
await processInput(input.filenameForOutput, input.stream, opts)
.catch(() => { isOk = false; });
}

if (!isOk) {
process.exit(1);
}
}

main();

0 comments on commit da43429

Please sign in to comment.