src/lib/util.js
import Fs from 'fs'
import Path from 'path'
/**
* Elements of block.
* @type {Array}
*/
const BlockElements = [
'address', 'article', 'aside', 'audio', 'blockquote', 'body', 'canvas',
'center', 'dd', 'dir', 'div', 'dl', 'dt', 'fieldset', 'figcaption',
'figure', 'footer', 'form', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'header', 'hgroup', 'hr', 'html', 'isindex', 'li', 'main', 'menu', 'nav',
'noframes', 'noscript', 'ol', 'output', 'p', 'pre', 'section', 'table',
'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'ul'
]
/**
* Elements of void.
* @type {Array.<String>}
*/
const VoidElements = [
'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input',
'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr'
]
/**
* Provides utility function.
*/
export default class Util {
/**
* This method returns the first index at which a given element can be found in the array
*
* @param {Array} arr Array.
* @param {Object} obj Element to locate in the array.
*
* @return {Number} If the success index, otherwise -1.
*/
static arrayIndexOf (arr, obj) {
return Array.prototype.indexOf.call(arr, obj)
}
/**
* Escape a regexp syntaxes.
*
* @param {String} str Original string.
*
* @return {String} Escaped string.
*
* @see https://stackoverflow.com/questions/1144783/how-to-replace-all-occurrences-of-a-string-in-javascript
*/
static escapeRegExp (str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}
/**
* Check the existence of a file or folder.
*
* @param {String} path Path of the file or folder.
*
* @return {Boolean} True if exists. Otherwise false.
*/
static existsSync (path) {
try {
Fs.accessSync(Path.resolve(path), Fs.F_OK)
return true
} catch (err) {
return false
}
}
/**
* Converts the value of the Date object to its equivalent string representation using the specified format.
*
* @param {Date} date Date and time. Default is current date and time.
* @param {String} format Date and time format string. Default is "YYYY-MM-DD hh:mm:ss.SSS".
*
* @return {String} Formatted string.
*
* @see http://qiita.com/osakanafish/items/c64fe8a34e7221e811d0
*/
static formatDate (date, format) {
if (!(date)) {
date = new Date()
}
if (typeof format !== 'string') {
format = 'YYYY-MM-DD hh:mm:ss.SSS'
}
const Y = date.getFullYear()
const M = date.getMonth()
const D = date.getDate()
const h = date.getHours()
const m = date.getMinutes()
const s = date.getSeconds()
if (Number.isNaN(Y) || Number.isNaN(M) || Number.isNaN(D) || Number.isNaN(h) || Number.isNaN(m) || Number.isNaN(s)) {
return null
}
// Zero padding
let f = format
f = f.replace(/YYYY/g, Y)
f = f.replace(/MM/g, ('0' + (M + 1)).slice(-2))
f = f.replace(/DD/g, ('0' + D).slice(-2))
f = f.replace(/hh/g, ('0' + h).slice(-2))
f = f.replace(/mm/g, ('0' + m).slice(-2))
f = f.replace(/ss/g, ('0' + s).slice(-2))
// Single digit
f = f.replace(/M/g, M + 1)
f = f.replace(/D/g, D)
f = f.replace(/h/g, h)
f = f.replace(/m/g, m)
f = f.replace(/s/g, s)
if (f.match(/S/g)) {
let ms = date.getMilliseconds()
if (!(Number.isNaN(ms))) {
ms = ('00' + ms).slice(-3)
for (let i = 0, max = f.match(/S/g).length; i < max; ++i) {
f = f.replace(/S/, ms.substring(i, i + 1))
}
}
}
return f
}
/**
* Check the node of a block element.
*
* @param {Node} node Node.
*
* @return {Boolean} Block element if "true".
*/
static isBlockElement (node) {
return BlockElements.indexOf(node.nodeName.toLowerCase()) !== -1
}
/**
* Check the node of a void element.
*
* @param {Node} node Node.
*
* @return {Boolean} Void element if "true".
*/
static isVoidElement (node) {
return VoidElements.indexOf(node.nodeName.toLowerCase()) !== -1
}
/**
* Asynchronous mkdir(2). No arguments other than a possible exception are given to the completion callback.
* mode defaults to 0o777.
*
* @param {String} path Directory path.
*
* @return {Boolean} Success if "true".
*/
static mkdirSync (path) {
const dir = Path.resolve(path)
if (Util.existsSync(dir)) {
return true
}
Fs.mkdirSync(dir)
return Util.existsSync(dir)
}
/**
* Get the HTML string of an element with its contents converted.
*
* @param {Node} node DOM node.
* @param {String} content Text.
*
* @return {String} HTML text.
*/
static outerHTML (node, content) {
const clone = node.cloneNode(false)
if (clone.outerHTML) {
return clone.outerHTML.replace('><', '>' + content + '<')
}
return content
}
/**
* Get a datetime from WordPress XML GMT
*
* @param {String} gmt String of GMT.
*
* @return {Object} Datetime.
*/
static datetimeFromWpGMT (gmt) {
const datetime = gmt.split(' ')
const date = datetime[0].split('-')
return {
year: date[0],
month: date[1],
day: date[2],
time: datetime[1]
}
}
/**
* Remove whitespace from both sides of a string.
*
* @param {String} str String.
*
* @return {String} New string.1
*/
static trim (str) {
return str.replace(/^[ \r\n\t]+|[ \r\n\t]+$/g, '')
}
/**
* If the file or folder to the same path on exists, generates a unique path that does not duplicate.
* e.g. "./test" to "test-1", "./test.md" to "./test-1.md"
*
* @param {String} path Original path.
* @param {Number} min The minimum value of the sequential number. Defailt is 1.
* @param {Number} max The maximum value of the sequential number. Defailt is 256.
*
* @return {String} Success is the unique path (full path), failure is null. If not duplicate returns the original path.
*/
static uniquePathWithSequentialNumber (path, min = 1, max = 256) {
const originalPath = Path.resolve(path)
if (!(Util.existsSync(originalPath) && typeof min === 'number' && typeof max === 'number' && 0 <= min && min < max)) {
return originalPath
}
const ext = Path.extname(originalPath)
const base = Path.basename(originalPath, ext)
const parent = Path.dirname(originalPath)
for (let i = min; i <= max; ++i) {
const name = base + '-' + i + ext
const uniquePath = Path.join(parent, name)
if (!(Util.existsSync(uniquePath))) {
return uniquePath
}
}
return null
}
}