Skip to content

Instantly share code, notes, and snippets.

@shumbo
Created July 1, 2018 14:00
Show Gist options
  • Save shumbo/408840d5fbaa63ff6e330870cb53a134 to your computer and use it in GitHub Desktop.
Save shumbo/408840d5fbaa63ff6e330870cb53a134 to your computer and use it in GitHub Desktop.
Hugo image helper
const v = require('vorpal')();
const sharp = require('sharp');
const child_process = require('child_process');
const fs = require('fs');
const path = require('path');
const max_page_width = 967; // possible maximum page width
const max_width = { // list of sizes
full: Math.ceil(max_page_width * 2), // full width
normal: Math.ceil(max_page_width * 2 * 0.6), // 60%
small: Math.ceil(max_page_width * 2 * 0.4), // 40%
thumbnail: Math.ceil(1110 * 2) // thumbnail
};
const postType = 'posts';
const now = new Date();
const zeroPadding = (x, l = 2) => String(x).padStart(l, '0');
const yearString = now.getFullYear();
const monthString = zeroPadding(now.getMonth() + 1);
const dateString = zeroPadding(now.getDate());
const imageDestination = `./static/uploads/${yearString}/${monthString}`;
v.delimiter('blog$').show();
v.command('hi', 'say hi').action(function(args, callback) {
this.log('hi');
callback();
});
/**
* Make sure specified file is readable
* @param {string} p path to the file or directory
*/
function fileExistCheck(p) {
return new Promise((resolve, reject) =>
fs.access(p, fs.constants.R_OK, error => {
if (error) {
reject(error);
} else {
resolve();
}
})
);
}
/**
* Copy image while resizing to the specified size
* @param {string} input Input file path
* @param {string} output Output file path
* @param {string | number} size Size in name or number(width)
*/
function resizeCopy(input, output, size) {
const maxWidth =
typeof size == 'string'
? max_width[size]
: typeof size == 'number'
? size
: 1200;
return sharp(input)
.resize(maxWidth)
.max()
.toFile(output);
}
/**
* Read text file
* @param {string} p
* @param {string} encoding
*/
function readTextFile(p, encoding = 'utf-8') {
return new Promise((resolve, reject) =>
fs.readFile(
p,
{ encoding },
(err, data) => (err ? reject(err) : resolve(data))
)
);
}
/**
*
* @param {string} p path to the file
* @param {string} data data to write
* @param {string} encoding
*/
function writeTextFile(p, data, encoding = 'utf-8') {
return new Promise((resolve, reject) => {
fs.writeFile(p, data, { encoding }, err => (err ? reject(err) : resolve()));
});
}
/**
* Resolve absolute path that can be used as url
* @param {string} p Path to the file
*/
function resolveAbsoluteUrl(p) {
return p.replace('./static', '');
}
/**
* mkdir -p
* @param {string} p
*/
function mkdirp(p) {
return new Promise((resolve, reject) =>
child_process.exec(
`mkdir -p ${p}`,
error => (error ? reject(error) : resolve())
)
);
}
v.command(
'image <path> [size]',
'add image to appropriate directory and resize if needed'
)
.option('-o, --original', 'Do not resize and save original')
.action(function(args, callback) {
const tasks = async () => {
this.log('start image tasks', args);
const imgpath = args['path'];
const original = args.options['original'] || false;
const filename = path.basename(imgpath);
if (!filename) {
return; // path must be a file
}
// make sure file exists and readable
await fileExistCheck(imgpath);
let size = Object.keys(max_width).includes(args.size) ? args.size : null;
if (!size && !original) {
size = (await new Promise((resolve, reject) => {
this.prompt(
{
type: 'list',
name: 'size',
message: 'Which size would you want to resize to?',
choices: Object.keys(max_width)
},
resolve
);
})).size;
}
this.log('Creating directory...');
await mkdirp(imageDestination);
let copiedImagePath;
if (args.options['original']) {
this.log('Copying file...');
copiedImagePath = imageDestination + `/${filename}`;
await new Promise((resolve, reject) =>
fs.copyFile(
imgpath,
copiedImagePath,
error => (error ? reject(error) : resolve())
)
);
} else {
this.log('Resize and copy...');
const elements = filename.split('.');
elements.splice(-1, 0, size);
copiedImagePath = imageDestination + `/${elements.join('.')}`;
await resizeCopy(imgpath, copiedImagePath, size);
}
// Create snippet
if (size == 'thumbnail') {
this.log('Append the following to the top of the markdown:');
const url = resolveAbsoluteUrl(copiedImagePath);
this.log(`thumbnail: "${url}"`);
} else {
this.log('Append the following to where you want the image to be:');
const url = resolveAbsoluteUrl(copiedImagePath);
this.log(`![](${url})`);
}
};
tasks()
.then(callback)
.catch(e => {
this.log(e);
this.ui.cancel();
});
});
v.command('post <permalink> [title] [thumbnail]').action(function(
args,
callback
) {
const tasks = async () => {
this.log('Create file with hugo...');
const mdfile = `${postType}/${yearString}${monthString}${dateString}-${
args['permalink']
}.md`;
await new Promise((resolve, reject) =>
child_process.exec(
`hugo new ${mdfile}`,
error => (error ? reject(error) : resolve())
)
);
const mdpath = path.resolve(`./content/${mdfile}`);
await fileExistCheck(mdpath);
if (args['thumbnail']) {
const imgpath = args['thumbnail'];
try {
await fileExistCheck(imgpath);
await mkdirp(imageDestination);
const filename = path.basename(imgpath);
if (!filename) {
throw new Error('A directory is specified as a thumbnail'); // path must be a file
}
const elements = filename.split('.');
const size = 'thumbnail';
elements.splice(-1, 0, size);
const copiedImagePath = imageDestination + `/${elements.join('.')}`;
await resizeCopy(imgpath, copiedImagePath, size);
const thumbnailUrl = resolveAbsoluteUrl(copiedImagePath);
// write to file
const current = await readTextFile(mdpath);
const lines = current.split('\n');
lines.splice(1, 0, `thumbnail: "${resolveAbsoluteUrl(thumbnailUrl)}"`);
await writeTextFile(mdpath, lines.join('\n'));
} catch (e) {
this.log('Failed to create thumbnail: ', e);
}
}
if (args['title']) {
const content = (await readTextFile(mdpath)).split('\n').map(line => {
if(line.startsWith('title:')) {
line = `title: "${args['title']}"`
}
return line;
}).join('\n');
await writeTextFile(mdpath, content);
}
};
tasks()
.then(callback)
.catch(e => {
this.log(e);
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment