Skip to content

Commit

Permalink
Resolves #259: add debugger
Browse files Browse the repository at this point in the history
  • Loading branch information
webketje committed May 13, 2022
1 parent a871af6 commit c68c71d
Show file tree
Hide file tree
Showing 6 changed files with 242 additions and 9 deletions.
108 changes: 108 additions & 0 deletions lib/debug.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
const debug = require('debug')
const utf8 = require('is-utf8')
const { isString } = require('./helpers')

const streamLogHandler =
(stream) =>
(...args) =>
stream.write(require('util').format(...args) + '\n')
debug.log = streamLogHandler(process.stderr)

const options = {}
Object.defineProperties(options, {
colors: {
get() {
return debug.inspectOpts.colors
},
set(v) {
debug.inspectOpts.colors = v
}
},
handle: {
get() {
return debug.log
},
set(v) {
debug.log = v
}
}
})

// add a %b buffer formatter to print buffer contents.
// useful for checking whether the contents match rendering expectations
debug.formatters.b = function (buffer) {
if (buffer instanceof Buffer && utf8(buffer)) {
return `${buffer.toString().slice(0, 200)}...`
}
return buffer
}

/**
* @typedef {import('debug').Debugger} Debugger
* @property {import('debug').Debugger} info
* @property {import('debug').Debugger} warn
* @property {import('debug').Debugger} error
*/

/**
* Create a new [debug](https://github.com/debug-js/debug#readme) debugger
* @param {string} namespace Debugger namespace
* @returns {Debugger}
* @example
* ```js
* const debug = metalsmith.debug('metalsmith-myplugin')
* debug('a debug log') // logs 'metalsmith-myplugin a debug log'
* debug.warn('A warning') // logs 'metalsmith-myplugin:warn A warning'
* ```
*/
function Debugger(namespace) {
if (!isString(namespace)) {
throw new Error('invalid debugger namespace')
}
// ANSI colors, see https://tintin.mudhalla.net/info/256color/
// the colors have been chosen to best integrate with dark & light bg
// and provide consistent coloring of debug/error/warn/info channel messages
// debug only supports these colors if an extra module is installed, but these are already available in most terminals
// to be tested on Windows Batch

const namespacedDebug = debug(namespace)

namespacedDebug.log = (...args) => options.handle(...args)
namespacedDebug.color = 247

const warn = namespacedDebug.extend('warn')
warn.color = 178

const info = namespacedDebug.extend('info')
info.color = 247

const error = namespacedDebug.extend('error')
error.color = 196

const dbugger = Object.assign(namespacedDebug, { warn, info, error })
return dbugger
}

// We need to proxy some properties through Debugger to get access to them through metalsmith.debug.<option>
function proxy(host, target, option) {
Object.defineProperty(host, option, {
get() {
return target[option]
},
set(v) {
target[option] = v
},
configurable: false
})
}

proxy(Debugger, options, 'handle')
proxy(Debugger, options, 'colors')
proxy(Debugger, debug, 'enabled')
proxy(Debugger, debug, 'enable')
proxy(Debugger, debug, 'disable')

module.exports = {
Debugger,
fileLogHandler: streamLogHandler
}
7 changes: 6 additions & 1 deletion lib/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ function match(input, patterns, options) {
})
return micromatch(input, patterns, options).sort()
}

function writeStream(path) {
return fs.createWriteStream(path, 'utf-8')
}
/**
* Recursively remove a directory
* @param {string} p
Expand Down Expand Up @@ -181,7 +185,8 @@ const helpers = {
outputFile,
stat,
readFile,
batchAsync
batchAsync,
writeStream
}

module.exports = helpers
51 changes: 47 additions & 4 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,29 @@ const assert = require('assert')
const matter = require('gray-matter')
const Mode = require('stat-mode')
const path = require('path')
const { readdir, batchAsync, isFunction, outputFile, stat, readFile } = require('./helpers')
const { rm, isString, isBoolean, isObject, isNumber, isUndefined, match } = require('./helpers')
const {
readdir,
batchAsync,
isFunction,
outputFile,
stat,
readFile,
writeStream,
rm,
isString,
isBoolean,
isObject,
isNumber,
isUndefined,
match
} = require('./helpers')
const utf8 = require('is-utf8')
const Ware = require('ware')
const { Debugger, fileLogHandler } = require('./debug')

const symbol = {
env: Symbol('env')
env: Symbol('env'),
log: Symbol('log')
}

/**
Expand Down Expand Up @@ -109,6 +125,11 @@ function Metalsmith(directory) {
value: Object.create(null),
enumerable: false
})
Object.defineProperty(this, symbol.log, {
value: null,
enumerable: false,
writable: true
})
}

/**
Expand Down Expand Up @@ -331,6 +352,9 @@ Metalsmith.prototype.env = function (vars, value) {
}
if (!(isFunction(value) || isObject(value))) {
this[symbol.env][vars.toUpperCase()] = value
if (this.debug.enabled && this.env('DEBUG_LOG')) {
this.debug.colors = false
}
return this
}
throw new TypeError('Environment variable values can only be primitive: Number, Boolean, String or null')
Expand All @@ -342,6 +366,8 @@ Metalsmith.prototype.env = function (vars, value) {
if (isUndefined(vars)) return Object.assign(Object.create(null), this[symbol.env])
}

Metalsmith.prototype.debug = Debugger

/**
* Build with the current settings to the destination directory.
*
Expand All @@ -361,8 +387,17 @@ Metalsmith.prototype.build = function (callback) {
const clean = this.clean()
const dest = this.destination()

if (this.debug.enabled && this.env('DEBUG_LOG')) {
this[symbol.log] = writeStream(this.path(this.env('DEBUG_LOG')))
this.debug.handle = fileLogHandler(this[symbol.log])
this.debug.colors = false
}

const result = (clean ? rm(dest) : Promise.resolve()).then(this.process.bind(this)).then((files) => {
return this.write(files).then(() => files)
return this.write(files).then(() => {
if (this[symbol.log]) this[symbol.log].end()
return files
})
})

/** block required for Metalsmith 2.x callback-flow compat */
Expand Down Expand Up @@ -412,6 +447,14 @@ Metalsmith.prototype.process = function (callback) {
*/

Metalsmith.prototype.run = function (files, plugins, callback) {
let debugValue = this.env('DEBUG')
if (debugValue === false) {
this.debug.disable()
} else {
if (debugValue === true) debugValue = '*'
this.debug.enable(debugValue)
}

/** block required for Metalsmith 2.x callback-flow compat */
const last = arguments[arguments.length - 1]
callback = isFunction(last) ? last : undefined
Expand Down
4 changes: 1 addition & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
},
"files": [
"index.js",
"lib/**",
"lib/index.js",
"lib/debug.js",
"lib/helpers.js",
"bin/**",
"CHANGELOG.md",
"snapcraft.yaml"
Expand All @@ -55,6 +57,7 @@
"chalk": "^4.1.2",
"commander": "^6.2.1",
"cross-spawn": "^7.0.3",
"debug": "^4.3.3",
"gray-matter": "^4.0.3",
"is-utf8": "~0.2.0",
"micromatch": "^4.0.4",
Expand Down
76 changes: 76 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ const Mode = require('stat-mode')
const noop = function () {}
const path = require('path')
const rm = require('../lib/helpers').rm
const { fileLogHandler } = require('../lib/debug')
const fixture = path.resolve.bind(path, __dirname, 'fixtures')
const d = require('debug')

describe('Metalsmith', function () {
beforeEach(function () {
Expand Down Expand Up @@ -356,6 +358,79 @@ describe('Metalsmith', function () {
})
})

describe('#debug', function () {
it('should allow plugins to use debug instances', function (done) {
const m = Metalsmith(fixture('basic'))
let fakeStream = []
m.debug.handle = fileLogHandler({
write(arg) {
fakeStream.push(arg)
}
})

m.env('debug', true)
.use(function plugin(files, m, next) {
const debug = m.debug('metalsmith:test')
debug('A log')
debug.warn('A warning')
debug.error('An error')
debug.info('An info')
next()
})
.process(() => {
assert.deepStrictEqual(fakeStream, [
' \x1B[38;5;247;1mmetalsmith:test \x1B[0mA log \x1B[38;5;247m+0ms\x1B[0m\n',
' \x1B[38;5;178;1mmetalsmith:test:warn \x1B[0mA warning \x1B[38;5;178m+0ms\x1B[0m\n',
' \x1B[38;5;196;1mmetalsmith:test:error \x1B[0mAn error \x1B[38;5;196m+0ms\x1B[0m\n',
' \x1B[38;5;247;1mmetalsmith:test:info \x1B[0mAn info \x1B[38;5;247m+0ms\x1B[0m\n'
])
done()
})
})

it("should convert metalsmith.env('debug', true) to debug all namespaces", function (done) {
const m = Metalsmith(fixture('basic'))

let fakeStream
m.debug.handle = fileLogHandler({
write(arg) {
fakeStream = arg
}
})

m.use(function plugin(files, m, next) {
const debug = m.debug('metalsmith:test')
debug('A log')
next()
}).process(() => {
assert.strictEqual(fakeStream, ' \x1B[38;5;247;1mmetalsmith:test \x1B[0mA log \x1B[38;5;247m+0ms\x1B[0m\n')
assert.strictEqual(m.env('debug'), '*')
done()
})
})

it("should be disabled when metalsmith.env('debug') === false", function (done) {
const m = Metalsmith(fixture('basic'))
let fakeStream = null
m.debug.handle = fileLogHandler({
write(arg) {
fakeStream = arg
}
})

m.env('debug', false)
.use(function plugin(files, m, next) {
const debug = m.debug('metalsmith:test')
debug('A log')
next()
})
.process(() => {
assert.strictEqual(fakeStream, null)
done()
})
})
})

describe('#read', function () {
it('should read from a source directory', function (done) {
const m = Metalsmith(fixture('read'))
Expand All @@ -373,6 +448,7 @@ describe('Metalsmith', function () {
done()
})
})

it('should only return a promise when callback is omitted', function (done) {
const m = Metalsmith(fixture('read'))

Expand Down

0 comments on commit c68c71d

Please sign in to comment.