Skip to content

Instantly share code, notes, and snippets.

@enten
Last active August 12, 2017 00:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save enten/54c45d5056e63b30b16739aadd7de0d1 to your computer and use it in GitHub Desktop.
Save enten/54c45d5056e63b30b16739aadd7de0d1 to your computer and use it in GitHub Desktop.
adsl2
const nodeNotifier = require('node-notifier')
const {
format,
inspect
} = require('util')
const CONSOLE = Object.assign({}, console)
const DEFAULT = {
colors: !!require('supports-color'),
consoleLogLevel: 'log.',
icons: {
error: '✗',
info: '✓',
verbose: 'ℹ',
warn: '❗'
},
hr: '-',
level: 'info',
levelPrefixLength: 4,
levels: [
'error',
'warn',
'info',
'verbose',
'debug',
'silly'
],
levelsStyles: {
'log.': ['bold'],
error: ['bgRed', 'yellow'],
warn: ['bgYellow', 'red'],
info: ['bgGreen', 'blue'],
verbose: ['min', 'cyan'],
debug: ['bold', 'magenta'],
silly: ['bold', 'grey']
},
listBullet: '*',
listBulletStyles: 'dim',
listKeyStyles: 'bold',
listValueSep: '-',
listValueSepStyles: undefined,
notifTitle: `${process.pid} ${process.title}`,
notifier: nodeNotifier.notify.bind(nodeNotifier),
notifs: true,
outputs: {
},
plugins: {
},
prefix: undefined,
prefixStyles: ['dim'],
prefixes: [
getMessageLevelChunk,
getMessagePrefixChunk
],
suffixes: [
],
titlesDepth: 0,
titlesPrefix: '>',
transports: [
printMessage
],
quiet: false,
styles: {
error: ['bold', 'red'],
warn: ['bold', 'yellow'],
info: ['green'],
verbose: ['cyan'],
debug: ['magenta'],
silly: ['gray']
},
stylesDepth: 0,
tracePrefix: 'Trace:',
tracePrefixStyles: 'bold'
}
const STYLE = {
reset: (txt) => '\u001b[0m' + txt + '\u001b[22m',
bold: (txt) => '\u001b[1m' + txt + '\u001b[22m',
dim: (txt) => '\u001b[2m' + txt + '\u001b[22m',
italic: (txt) => '\u001b[3m' + txt + '\u001b[23m',
underline: (txt) => '\u001b[4m' + txt + '\u001b[24m',
blink: (txt) => '\u001b[5m' + txt + '\u001b[25m',
inverse: (txt) => '\u001b[7m' + txt + '\u001b[27m',
hidden: (txt) => '\u001b[8m' + txt + '\u001b[28m',
strikethrough: (txt) => '\u001b[9m' + txt + '\u001b[29m',
black: (txt) => '\u001b[30m' + txt + '\u001b[39m',
blackBright: (txt) => '\u001b[90m' + txt + '\u001b[39m',
bgBlack: (txt) => '\u001b[40m' + txt + '\u001b[49m',
bgBlackBright: (txt) => '\u001b[100m' + txt + '\u001b[49m',
red: (txt) => '\u001b[31m' + txt + '\u001b[39m',
redBright: (txt) => '\u001b[91m' + txt + '\u001b[39m',
bgRed: (txt) => '\u001b[41m' + txt + '\u001b[49m',
bgRedBright: (txt) => '\u001b[101m' + txt + '\u001b[49m',
green: (txt) => '\u001b[32m' + txt + '\u001b[39m',
greenBright: (txt) => '\u001b[92m' + txt + '\u001b[39m',
bgGreen: (txt) => '\u001b[42m' + txt + '\u001b[49m',
bgGreenBright: (txt) => '\u001b[102m' + txt + '\u001b[49m',
yellow: (txt) => '\u001b[33m' + txt + '\u001b[39m',
yellowBright: (txt) => '\u001b[93m' + txt + '\u001b[39m',
bgYellow: (txt) => '\u001b[43m' + txt + '\u001b[49m',
bgYellowBright: (txt) => '\u001b[103m' + txt + '\u001b[49m',
blue: (txt) => '\u001b[34m' + txt + '\u001b[39m',
blueBright: (txt) => '\u001b[94m' + txt + '\u001b[39m',
bgBlue: (txt) => '\u001b[44m' + txt + '\u001b[49m',
bgBlueBright: (txt) => '\u001b[104m' + txt + '\u001b[49m',
magenta: (txt) => '\u001b[35m' + txt + '\u001b[39m',
magentaBright: (txt) => '\u001b[95m' + txt + '\u001b[39m',
bgMagenta: (txt) => '\u001b[45m' + txt + '\u001b[49m',
bgMagentaBright: (txt) => '\u001b[105m' + txt + '\u001b[49m',
cyan: (txt) => '\u001b[36m' + txt + '\u001b[39m',
cyanBright: (txt) => '\u001b[96m' + txt + '\u001b[39m',
bgCyan: (txt) => '\u001b[46m' + txt + '\u001b[49m',
bgCyanBright: (txt) => '\u001b[106m' + txt + '\u001b[49m',
white: (txt) => '\u001b[37m' + txt + '\u001b[39m',
whiteBright: (txt) => '\u001b[97m' + txt + '\u001b[39m',
bgWhite: (txt) => '\u001b[47m' + txt + '\u001b[49m',
bgWhiteBright: (txt) => '\u001b[107m' + txt + '\u001b[49m'
}
// Fix humans
STYLE.grey = STYLE.gray = STYLE.blackBright
STYLE.bgGrey = STYLE.bgGray = STYLE.bgBlackBright
function Logger (logger) {
const consoleLogLevel = logger.consoleLogLevel || DEFAULT.consoleLogLevel
const log = (...args) => {
const msg = createMessage(logger, consoleLogLevel, {
args,
chunks: [format(...args)],
})
broadcastMessage(logger, msg)
}
Object.defineProperties(log, {
'level': {
enumerable: false,
value: consoleLogLevel
},
'logger': {
enumerable: false,
value: logger
}
})
const api = (level, ...args) => {
let printer = log
if (~logger.levels.indexOf(level) && typeof api[level] === 'function') {
printer = api[level]
} else {
args.unshift(level)
}
printer(...args)
}
const trace = (...args) => {
const msg = createMessageTrace(logger, consoleLogLevel, ...args)
broadcastMessage(logger, msg)
if (!logger.quiet) {
CONSOLE.trace(...args)
}
}
const dir = (...args) => {
const msg = createMessageInspection(logger, consoleLogLevel, ...args)
broadcastMessage(logger, msg)
}
Object.defineProperties(api, {
'configure': {
enumerable: false,
value: (key, value) => {
let opts = key
if (typeof key === 'string') {
opts = {[key]: value}
}
if (typeof opts === 'object') {
Object.assign(logger, opts)
}
return api
}
},
'dir': {
enumerable: true,
value: Object.defineProperty(dir, 'name', {value: 'dir'})
},
'inherit': {
enumerable: false,
value: inheritLogger.bind(null, logger)
},
'log': {
enumerable: true,
value: Object.defineProperty(log, 'name', {value: 'log'})
},
// 'install': {
// enumerable: false,
// value: installLoggerAPI.bind(null, api)
// },
'name': {
enumerable: false,
value: 'Logger'
},
'opts': {
enumerable: false,
value: logger
},
// 'uninstall': {
// enumerable: false,
// value: uninstallLoggerAPI.bind(null, api)
// },
'trace': {
enumerable: true,
value: Object.defineProperty(trace, 'name', {value: 'trace'})
}
})
Object.defineProperties(api, {
})
logger.levels.forEach((level) => {
const printer = Printer(logger, level)
Object.defineProperty(api, level, {
enumerable: true,
value: printer
})
})
const plugins = {
bare: createMessageBare,
dir: createMessageInspection,
}
if (logger.titlesDepth) {
for (let weight = 1; weight <= logger.titlesDepth; ++weight) {
plugins['h' + weight] = (_logger, _level, ...args) => {
args.unshift(weight)
return createMessageTitle(_logger, _level, ...args)
}
}
}
Object.assign(plugins, {
hr: createMessageSeparator,
list: createMessageList,
nocolor: createMessageNoColors,
nocolors: createMessageNoColors,
notify: createMessageNotification,
raw: createMessageRaw,
styles: createMessageStylized,
title: createMessageTitle,
ul: createMessageList
}, logger.plugins)
Object.keys(plugins).forEach((key) => {
extendLoggerAPI(api, key, plugins[key])
})
if (logger.stylesDepth) {
if (log.styles) {
bindStyles(log.styles, log.styles, logger.stylesDepth)
}
logger.levels.forEach((level) => {
const printer = api[level]
if (printer && typeof printer.styles === 'function') {
bindStyles(printer.styles, printer.styles, logger.stylesDepth)
}
})
}
;[
'time',
'timeEnd',
].forEach((key) => {
const wrapper = (...args) => {
if (!logger.quiet) {
CONSOLE[key](...args)
}
}
Object.defineProperty(api, key, {
enumerable: true,
value: Object.defineProperty(wrapper, 'name', {value: key})
})
})
api.assert = CONSOLE.assert
api.Console = CONSOLE.Console
return api
}
function Printer (logger, level) {
const printer = (...args) => {
const msg = createMessage(logger, level, {
args,
chunks: [[
format(...args),
logger.styles && logger.styles[level]
]]
})
broadcastMessage(logger, msg)
}
const trace = (...args) => {
const msg = createMessageTrace(logger, level, ...args)
broadcastMessage(logger, msg)
if (!logger.quiet && isLevelShowable(logger, level)) {
CONSOLE.trace(...args)
}
}
Object.defineProperties(printer, {
'level': {
enumerable: false,
value: level
},
'logger': {
enumerable: false,
value: logger
},
'name': {
enumerable: false,
value: level
},
'opts': {
enumerable: false,
value: {
get levelStyles () {
return getLevelPrefixStyles(logger, level)
},
get output () {
return getLevelOutput(logger, level)
},
get showable () {
return isLevelShowable(logger, level)
},
get styles () {
return getLevelStyles(logger, level)
}
}
},
'trace': {
enumerable: false,
value: Object.defineProperty(trace, 'name', {value: `${level}_trace`})
}
})
return printer
}
function bindStyles (obj, fn, depth = 1, styles = []) {
Object.keys(STYLE).forEach((key) => {
Object.defineProperty(obj, key, {
enumerable: false,
value: fn.bind(null, styles.concat(key))
})
if ((depth - 1) && depth) {
bindStyles(obj[key], fn, depth - 1, styles.concat(key))
}
})
return obj
}
function broadcastMessage (logger, msg) {
if (Array.isArray(msg)) {
return msg.map(broadcastMessage.bind(null, logger))
}
if (msg) {
logger.transports.forEach((transport) => {
transport(logger, msg)
})
}
}
function createLogger (...args) {
const opts = parseLoggerPublicOptions(...args)
return Logger(getLoggerDefaultOptions(opts))
}
function createMessage (logger, level, opts) {
return Object.assign({
args: [],
chunks: [],
level,
output: getLevelOutput(logger, level),
prefixes: [].concat(logger.prefixes || []),
showable: isLevelShowable(logger, level),
suffixes: [].concat(logger.suffixes || [])
}, opts)
}
function createMessageBare (logger, level, ...args) {
return createMessage(logger, level, {
args,
chunks: [format(...args)],
raw2: true,
prefixes: null,
suffixes: null
})
}
function createMessageInspection (logger, level, ...args) {
let [obj, opts] = args
let title
if (typeof opts === 'string') {
opts = {title: opts}
}
opts = Object.assign({
colors: logger.colors
}, opts)
if (opts.title) {
title = opts.title
}
const messages = []
const inspection = inspect(obj, opts)
const msg = createMessage(logger, level, {
args,
chunks: [[opts.newLine ? '\n' + inspection : inspection]],
inspection,
suffixes: []
})
if (opts.bare) {
msg.prefixes.length = 0
}
if (title) {
messages.push(createMessageTitle(logger, level, title))
}
messages.push(msg)
return messages
}
function createMessageList (logger, level, ...args) {
const messages = []
if (typeof args[0] === 'string') {
const title = args.shift()
messages.push(createMessageTitle(logger, level, title))
}
const styles = getLevelStyles(logger, level)
const list = args.shift()
const listBullet = logger.listBullet || DEFAULT.listBullet
const listBulletStyles = [].concat(logger.listBulletStyles || DEFAULT.listBulletStyles, styles)
const listKeyStyles = [].concat(logger.listKeyStyles || DEFAULT.listKeyStyles, styles)
const listValueSep = logger.listValueSep || DEFAULT.listValueSep
const listValueSepStyles = [].concat(logger.listValueSepStyles || DEFAULT.listValueSepStyles, styles)
Object.keys(list).forEach((key) => {
const value = inspect(list[key], {colors: logger.colors})
const msg = createMessage(logger, level, {
args,
list,
chunks: [
[listBullet, listBulletStyles],
[key, listKeyStyles],
[listValueSep, listValueSepStyles],
[value]
],
suffixes: []
})
messages.push(msg)
})
return messages
}
function createMessageNoColors (logger, level, ...args) {
return createMessage(logger, level, {
args,
colors: false
})
}
function createMessageNotification (logger, level, opts, notifCb) {
if (typeof opts === 'string') {
opts = {message: opts}
}
if (typeof notifCb === 'string') {
opts.open = notifCb
notifCb = undefined
}
if (!opts.message) {
return
}
const styles = getLevelStyles(logger, level)
const chunks = []
let {icon, message, title} = opts
if (!opts.icon) {
icon = logger.icons && logger.icons[level]
}
if (!title) {
title = logger.notifTitle || DEFAULT.notifTitle
opts.title = title
}
opts.title = [].concat(level, opts.title).join(' - ')
if (icon) {
chunks.push([icon, styles])
opts.message = [icon, opts.message].join(' ')
}
chunks.push([
message,
['bold'].concat(styles || [])
])
const msg = createMessage(logger, level, {
args: [message],
chunks,
notif: opts,
notifCb
})
return msg
}
function createMessageRaw (logger, level, ...args) {
return createMessage(logger, level, {
args,
chunks: [format(...args)],
raw: true
})
}
function createMessageSeparator (logger, level, ...args) {
const pattern = args.length ? format(...args) : logger.hr || DEFAULT.hr
const msg = createMessage(logger, level, {args, suffixes: []})
const prefixLength = getChunksLength(msg.prefixes, {logger, msg})
const lineSize = process.stdout.columns - (prefixLength + Number(!!prefixLength))
const orphean = lineSize % pattern.length
let hr = pattern.repeat((lineSize / pattern.length) | 0)
if (orphean) {
hr += pattern.slice(0, orphean)
}
msg.chunks.push([hr, getLevelStyles(logger, level)])
msg.hr = hr
return msg
}
function createMessageStylized (logger, level, ...args) {
const styles = args.shift()
const text = format(...args)
return createMessage(logger, level, {
args,
chunks: [[text, styles]],
styles
})
}
function createMessageTitle (logger, level, ...args) {
let title = 1
if (typeof args[0] === 'number') {
title = args.shift()
}
const prefix = (logger.titlesPrefix || DEFAULT.titlesPrefix).repeat(title)
const styles = getLevelStyles(logger, level)
const titleStyles = [].concat(styles || [])
let text = format(...args)
title < 3 && titleStyles.unshift('bold')
title > 4 && titleStyles.unshift('dim')
const m = createMessage(logger, level, {
args,
chunks: [
[prefix, titleStyles],
[text, titleStyles]
],
suffixes: []
})
return m
}
function createMessageTrace(logger, level, ...args) {
const styles = getLevelStyles(logger, level)
return createMessage(logger, level, {
args,
chunks: [
[
logger.tracePrefix || DEFAULT.tracePrefix,
[].concat(logger.tracePrefixStyles || DEFAULT.tracePrefixStyles, styles)
],
[
format(...args),
styles
],
],
suffixes: [],
trace: true
})
}
function extendLoggerAPI (api, key, plugin) {
const {opts: logger} = api
const consoleLogLevel = logger.consoleLogLevel || DEFAULT.consoleLogLevel
const extensions = {}
if (api.log) {
extensions.log = extendPrinterAPI(api.log, key, plugin)
}
logger.levels.forEach((level) => {
if (api[level]) {
extensions[level] = extendPrinterAPI(api[level], key, plugin)
}
})
return extensions
}
function extendPrinterAPI (api, key, plugin) {
const {level, logger} = api
const callPlugin = plugin.bind(null, logger, level)
const extension = (...args) => {
const msg = callPlugin(...args)
broadcastMessage(logger, msg)
}
Object.defineProperty(extension, 'name', {
enumerable: false,
value: [level, key].join('_')
})
Object.defineProperty(api, key, {
enumerable: false,
value: extension
})
return extension
}
function getChunksLength (chunks, ...args) {
return resolveChunks(chunks, ...args)
.map((chunk) => chunk[0]).join(' ')
.length
}
function getLevelOutput (logger, level) {
const {outputs} = logger
return outputs && outputs[level] || (CONSOLE[level] ? level : 'log')
}
function getLevelPrefix (logger, level) {
return level && level.substring(0, logger.levelPrefixLength || DEFAULT.levelPrefixLength)
}
function getLevelPrefixStyles (logger, level) {
return [].concat(
(logger.levelsStyles && logger.levelsStyles[level])
|| (logger.styles && logger.styles[level])
|| []
)
}
function getLevelStyles (logger, level) {
return [].concat(logger.styles[level] || [])
}
function getLoggerDefaultOptions (opts) {
opts = Object.assign({}, opts)
opts.colors = opts.colors != null ? opts.colors : DEFAULT.colors
opts.consoleLogLevel = opts.consoleLogLevel || DEFAULT.consoleLogLevel
opts.icons = Object.assign({}, DEFAULT.icons, opts.icons)
opts.hr = opts.hr || DEFAULT.hr
opts.level = opts.level || DEFAULT.level
opts.levels = opts.levels || DEFAULT.levels
opts.levelPrefixLength = opts.levelPrefixLength != null ? opts.levelPrefixLength : DEFAULT.levelPrefixLength
opts.levelsStyles = Object.assign({}, DEFAULT.levelsStyles, opts.levelsStyles)
opts.listBullet = opts.listBullet || DEFAULT.listBullet
opts.listBulletStyles = [].concat(opts.listBulletStyles || DEFAULT.listBulletStyles)
opts.listKeyStyles = [].concat(opts.listKeyStyles || DEFAULT.listKeyStyles)
opts.listValueSep = opts.listValueSep || DEFAULT.listValueSep
opts.listValueSepStyles = [].concat(opts.listValueSepStyles || DEFAULT.listValueSepStyles)
opts.notifTitle = opts.notifTitle || DEFAULT.notifTitle
// opts.notifier = opts.notifier || DEFAULT.notifier
opts.notifs = opts.notifs != null ? opts.notifs : DEFAULT.notifs
opts.outputs = Object.assign({}, DEFAULT.outputs, opts.outputs)
opts.plugins = Object.assign({}, DEFAULT.plugins, opts.plugins)
opts.prefix = opts.prefix || DEFAULT.prefix
opts.prefixStyles = [].concat(opts.prefixStyles || DEFAULT.prefixStyles)
opts.prefixes = [].concat(opts.prefixes || DEFAULT.prefixes)
opts.suffixes = [].concat(opts.suffixes || DEFAULT.suffixes)
opts.titlesDepth = opts.titlesDepth != null ? opts.titlesDepth : DEFAULT.titlesDepth
opts.titlesPrefix = opts.titlesPrefix || DEFAULT.titlesPrefix
opts.transports = [].concat(opts.transports || DEFAULT.transports)
opts.quiet = opts.quiet != null ? opts.quiet : DEFAULT.quiet
opts.styles = Object.assign({}, DEFAULT.styles, opts.styles)
opts.stylesDepth = opts.stylesDepth != null ? opts.stylesDepth : DEFAULT.stylesDepth
opts.tracePrefix = opts.tracePrefix || DEFAULT.tracePrefix
opts.tracePrefixStyles = [].concat(opts.tracePrefixStyles || DEFAULT.tracePrefixStyles)
return opts
}
function getMessageLevelChunk ({logger, msg}) {
return [
getLevelPrefix(logger, msg.level),
getLevelPrefixStyles(logger, msg.level)
]
}
function getMessagePrefixChunk ({logger}) {
if (logger.prefix) {
return [
logger.prefix,
logger.prefixStyles || DEFAULT.prefixStyles
]
}
}
function inheritLogger (logger, ...args) {
const opts = Object.assign({}, logger, parseLoggerPublicOptions(...args))
return Logger(opts)
}
function installLoggerAPI (loggerApi, obj) {
obj = obj || console
Object.keys(loggerApi).forEach((key) => {
obj[key] = loggerApi[key]
})
obj.notify = loggerApi.notify
}
function isLevelShowable (logger, testLevel) {
let {level, levels} = logger
if (logger.consoleLogLevel === testLevel) {
return true
}
if (typeof level === 'string') {
level = levels.indexOf(level)
}
if (level === -1) {
return true
}
if (typeof testLevel === 'string') {
testLevel = levels.indexOf(testLevel)
}
return testLevel <= level
}
function parseLoggerPublicOptions (opts = {}) {
if (typeof opts === 'string') {
opts = {level: opts}
}
opts = Object.assign({}, opts)
if (typeof arguments[1] === 'string' || typeof arguments[1] === 'function') {
opts.prefix = arguments[1]
}
if (typeof arguments[1] === 'boolean') {
opts.colors = arguments[1]
}
if (typeof arguments[1] === 'object') {
Object.assign(opts, arguments[1])
}
if (typeof arguments[2] === 'boolean') {
opts.colors = arguments[2]
}
if (typeof arguments[2] === 'object') {
Object.assign(opts, arguments[2])
}
return opts
}
function printMessage (logger, msg) {
if (logger.quiet || !msg.showable) {
return
}
if (msg.notif && logger.notifs) {
;(logger.notifier || DEFAULT.notifier)(msg.notif, msg.notifCb)
}
const colors = msg.colors != null ? msg.colors : logger.colors
const chunks = resolveChunks([].concat(
msg.prefixes || [],
msg.chunks || [],
msg.suffixes || []
), {logger, msg})
.map((chunk) => {
// if (typeof chunk === 'string' && logger.colors) {
// chunk = stylizeText(logger.styles[msg.level], chunk)
// }
if (Array.isArray(chunk)) {
chunk = colors ? stylizeText(chunk[1], chunk[0]) : chunk[0]
}
return chunk
})
;(CONSOLE[msg.output] || CONSOLE.log)(...chunks)
}
function resolveChunk (chunk, ...args) {
if (typeof chunk === 'function') {
chunk = chunk(...args)
}
if (Array.isArray(chunk)) {
chunk = chunk.map((x) => typeof x === 'function' ? x(...args) : x)
}
return chunk
}
function resolveChunks (chunks, ...args) {
return []
.concat(chunks || [])
.map((chunk) => {
return resolveChunk(chunk, ...args)
})
.filter((chunk) => chunk)
}
function stylizeText (styles, text) {
if (!styles || !text) {
return text
}
styles = [].concat(styles || []).reverse()
return styles.reduce((acc, name) => {
const style = STYLE[name]
if (style) {
acc = style(acc)
}
return acc
}, text)
}
function uninstallLoggerAPI (loggerApi, obj) {
obj = obj || console
Object.keys(loggerApi).forEach((level) => {
if (CONSOLE[level]) {
obj[level] = CONSOLE[level]
} else {
delete obj[level]
}
delete obj.notify
})
}
module.exports = Object.assign(createLogger.bind(), {
CONSOLE,
DEFAULT,
STYLE,
Logger,
Printer,
bindStyles,
broadcastMessage,
createLogger,
createMessage,
createMessageBare,
createMessageInspection,
createMessageList,
createMessageNoColors,
createMessageNotification,
createMessageRaw,
createMessageSeparator,
createMessageStylized,
createMessageTitle,
createMessageTrace,
extendLoggerAPI,
extendPrinterAPI,
getChunksLength,
getLevelOutput,
getLevelPrefix,
getLevelPrefixStyles,
getLevelStyles,
getLoggerDefaultOptions,
getMessageLevelChunk,
getMessagePrefixChunk,
inheritLogger,
installLoggerAPI,
isLevelShowable,
parseLoggerPublicOptions,
printMessage,
resolveChunk,
resolveChunks,
stylizeText,
uninstallLoggerAPI
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment