Skip to content

Commit

Permalink
added tests
Browse files Browse the repository at this point in the history
  • Loading branch information
wernerglinka committed Feb 7, 2022
1 parent f68cc67 commit e6ad84a
Show file tree
Hide file tree
Showing 64 changed files with 5,703 additions and 56 deletions.
7 changes: 0 additions & 7 deletions History.md

This file was deleted.

8 changes: 0 additions & 8 deletions Makefile

This file was deleted.

318 changes: 283 additions & 35 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,57 +1,305 @@
var path = require('path');
var extname = path.extname;
var yaml = require('js-yaml');
'use strict';

const {promises: {readFile, readdir}} = require('fs');
const path = require('path');
const extension = path.extname;
const yaml = require('js-yaml');
const toml = require('toml');


/**
* @typedef Options
* @property {String} key
*/

/** @type {Options} */
const defaults = {}

/**
* Normalize plugin options
* @param {Options} [options]
* @returns {Object}
*/
function normalizeOptions(options) {
return Object.assign({}, defaults, options || {});
}

/**
* Expose `plugin`.
* YAML to JSON
* @param {*} string - YAML file
* @returns .json string
*/
function yamlToJSON(string) {
try {
return yaml.load(string);
} catch (e) {
console.log(e);
}
}

module.exports = plugin;
/**
* TOML to JSON
* @param {*} string - TOML file
* @returns .json string
*/
function tomlToJSON(string) {
try {
return JSON.parse(JSON.stringify(toml.parse(string)));
} catch (e) {
console.log(e);
}
}

/**
* Supported metadata parsers.
* getExternalFile
* Reads file content in either .json, .yaml, .yml or .toml format
* @param {*} filePath
* @returns Content of the file in .json
*/
async function getExternalFile(filePath) {
const fileExtension = extension(filePath);
const fileBuffer = await readFile(filePath);
let fileContent;

switch (fileExtension) {
case ".yaml" :
case ".yml" :
fileContent = yamlToJSON(fileBuffer);
break;
case ".toml" :
fileContent = tomlToJSON(fileBuffer);
break;
case ".json" :
fileContent = JSON.parse(fileBuffer.toString()); // remove line breaks etc from the filebuffer
break;
default:
fileContent = JSON.parse(fileBuffer.toString());
}

var parsers = {
'.json': JSON.parse,
'.yaml': yaml.safeLoad,
'.yml': yaml.safeLoad
return fileContent;
};

/**
* Metalsmith plugin to hide drafts from the output.
*
* @param {Object} opts
* @return {Function}
* getDirectoryFiles
* @param {*} directoryPath
* @returns List of all files in the directory
*/
async function getDirectoryFiles(directoryPath) {
const fileList = await readdir(directoryPath);
return await getDirectoryFilesContent(directoryPath, fileList);

function plugin(opts){
opts = opts || {};
};

return function(files, metalsmith, done){
var metadata = metalsmith.metadata();
var exts = Object.keys(parsers);
for (var key in opts) {
var file = opts[key].replace(/(\/|\\)/g, path.sep);
var ext = extname(file);
if (!~exts.indexOf(ext)) throw new Error('unsupported metadata type "' + ext + '"');
if (!metadata[key] || files[file]) {
if (!files[file]) throw new Error('file "' + file + '" not found');
/**
* getDirectoryFilesContent
* @param {*} directoryPath
* @param {*} fileList
* @returns The content of all files in a directory
*/
async function getDirectoryFilesContent(directoryPath, fileList) {
const fileContent = await fileList.map(async file => {
return await getExternalFile(path.join(path.join(directoryPath, file)));
});
return await Promise.all(fileContent);
};

var parse = parsers[ext];
var str = files[file].contents.toString();
delete files[file];
/**
* getFileObject
* @param {*} filePath
* @param {*} optionKey
* @param {*} allMetadata
* @returns promise to push metafile object to metalsmith metadata object
*/
async function getFileObject(filePath, optionKey, allMetadata) {
return getExternalFile(filePath)
.then(fileBuffer => {
allMetadata[optionKey] = fileBuffer;
});
}

try {
var data = parse(str);
} catch (e) {
return done(new Error('malformed data in "' + file + '"'));
/**
* getDirectoryObject
* @param {*} directoryPath
* @param {*} optionKey
* @param {*} allMetadata
* @returns promise to push concatenated metafile object of all directory files to metalsmith metadata object
*/
async function getDirectoryObject(directoryPath, optionKey, allMetadata) {
return getDirectoryFiles(directoryPath)
.then(fileBuffers => {
const groupMetadata = [];
fileBuffers.forEach(fileBuffer => {
groupMetadata.push(JSON.parse(JSON.stringify(fileBuffer)));
})

if (groupMetadata.length) {
allMetadata[optionKey] = groupMetadata;
}
else {
console.log(`No files found in this directory "${key}"`);
}

})
.catch(error => {
console.error(error.message);
process.exit(1);
});
};


/**
* A Metalsmith plugin to read files with metadata
*
* Files containing metadata must be located in the Metalsmith root directory.
* Content of files located in the Metalsmith source directory (local files) is readily available
* in the files object while files outside the source directory (external files) are read fropm disk.
*
* Files are specified via option entries like: site: "./data/siteMetadata.json"
* The resulting meta object will then be something like this:
* {
* site: {
* "title":"New MetalsmithStarter",
* "description":"Metalsmith Starter Website",
* "author":"werner@glinka.co",
* "siteURL":"https://newmsnunjucks.netlify.app/",
* ...
* }
*
* Directories may also be specified like this: example: "./data/example". In this case
* the plugin will read all files in the directory and concatenate them into a single file object.
*
*
* @param {Options} options
* @returns {import('metalsmith').Plugin}
*/

function initMetameta(options){
options = normalizeOptions(options);

return function metameta(files, metalsmith, done){
const allMetadata = metalsmith.metadata();

// array to hold all active promises during external file reads. Will be
// used with Promise.allSettled to invoke done()
const allPromises = [];

// loop over all options/metadata files/directories
Object.keys(options).forEach(function(optionKey) {

// check if file is located inside the metalsmith source directory
const metaFilePath = options[optionKey];

// convention: "./" inside, "../" outside of metasmith source
const isLocal = metaFilePath.startsWith("./");
const isExternal = metaFilePath.startsWith("../");

// flag to be reset when valid filepath is detected
let validFilepath = false;

/*
* if file or directory is local we can get the metadata from the metalsmith file object
*/
if (isLocal) {
// get object key from the options
const key = metaFilePath.slice(2);
let metadata;

// check if the optionKey element has a file exension
const fileExtension = extension(metaFilePath);
if ( fileExtension ) {
if ( fileExtension === ".json" || fileExtension === ".yaml" || fileExtension === ".yml" || fileExtension === ".toml") {
// get the data from file object
try {
metadata = files[key].contents.toString();
} catch (error) {
console.log("Could not find file in files object");
return done(error);
}

if ( fileExtension === ".yaml" || fileExtension === ".yml" ) {
metadata = JSON.stringify(yamlToJSON(metadata));
}

if ( fileExtension === ".toml" ) {
metadata = JSON.stringify(toml.parse(metadata));
}



// to temp meta object
allMetadata[optionKey] = JSON.parse(metadata);
// ... and remove this file from the metalsmith build process
delete files[key];

// indicate filepath is valid
validFilepath = true;
}
} else {
// assume this is a directory, all files in this directory will be concatenated into one
// metadata object
const groupMetadata = [];
Object.keys(files).forEach(function(file) {
if (file.includes(key)) {
groupMetadata.push(JSON.parse(files[file].contents.toString()));
}
});

if (groupMetadata.length) {
allMetadata[optionKey] = groupMetadata;
}
else {
console.log(`No files found in this directory "${key}"`);
}

// indicate filepath is valid
validFilepath = true;
}
}

/*
* if file or directory is external we get the metadata from respective files
*/
if (isExternal) {
// get object key
const key = metaFilePath.slice(3);

metadata[key] = data;
// check if the optionKey has a file exension
const fileExtension = extension(metaFilePath);
if ( fileExtension ) {
if ( fileExtension === ".json" || fileExtension === ".yaml" || fileExtension === ".yml" || fileExtension === ".toml") {
// read external file content and store in metadata object
const filePath = path.join(metalsmith._directory, key);
const extFilePromise = getFileObject(filePath, optionKey, allMetadata)

// add this promise to allPromises array. Will be later used with Promise.allSettled to invoke done()
allPromises.push(extFilePromise);

// indicate filepath is valid
validFilepath = true;
}
} else {
// assume this is a directory
// get content of all files in this directory, concatenated into one metadata object
const directoryPath = path.join(metalsmith._directory, key);
const extDirectoryPromise = getDirectoryObject(directoryPath, optionKey, allMetadata);

// add this promise to allPromises array. Will be later used with Promise.allSettled to invoke done()
allPromises.push(extDirectoryPromise);

// indicate filepath is valid
validFilepath = true;
}
}
}

done();
if (!validFilepath) {
const error = `${metaFilePath} is not a valid metafile path. Path must be relative to Metalsmith root`;
done(error);
}
});

// Promise.allSettled is used to invoke done()
Promise.allSettled(allPromises).then(() => done());
};
}

module.exports = initMetameta;
Loading

0 comments on commit e6ad84a

Please sign in to comment.