From 82999a267750149a6c7367b01f36b187fa827614 Mon Sep 17 00:00:00 2001 From: Danny McClanahan <1305167+cosmicexplorer@users.noreply.github.com> Date: Sat, 23 Nov 2024 01:11:36 -0500 Subject: [PATCH] add console and color support --- Cakefile | 24 +++++++--- build-support/colors.coffee | 42 +++++++++++++++++ build-support/console.coffee | 89 ++++++++++++++++++++++++++++++++++++ test/support/helpers.coffee | 2 +- 4 files changed, 150 insertions(+), 7 deletions(-) create mode 100644 build-support/colors.coffee create mode 100644 build-support/console.coffee diff --git a/Cakefile b/Cakefile index 02303a81fe..040d544644 100644 --- a/Cakefile +++ b/Cakefile @@ -1,6 +1,5 @@ assert = require 'assert' { createHash } = require 'crypto' -oldConsole = require 'console' fs = require 'fs' os = require 'os' path = require 'path' @@ -11,11 +10,24 @@ CoffeeScript = require './lib/coffeescript' helpers = require './lib/coffeescript/helpers' util = require 'util' process = require 'process' + +{ setupStyler } = require './build-support/colors' +{ setupConsole } = require './build-support/console' { spawnNodeProcess, NonZeroExit, } = require './build-support/subprocess' + +option '-l', '--level [LEVEL]', 'log level [debug < info < log(default) < warn < error]' +option null, '--no-color', 'disable colored output' + +task = (name, description, action) -> + global.task name, description, ({level = 'log', ...opts} = {}) -> + setupStyler {colors: not opts['no-color']} + setupConsole {level} + action {...opts} + sha256 = -> createHash 'sha256' checksumFile = (inPath) -> @@ -30,7 +42,7 @@ checksumFile = (inPath) -> # ANSI Terminal Colors. bold = red = green = yellow = reset = '' -unless process.env.NODE_DISABLE_COLORS +if process.stdout.hasColors() and not process.env.NODE_DISABLE_COLORS bold = '\x1B[0;1m' red = '\x1B[0;31m' green = '\x1B[0;32m' @@ -54,7 +66,7 @@ majorVersion = parseInt CoffeeScript.VERSION.split('.')[0], 10 # Log a message with a color. log = (message, color, explanation) -> - console.log color + message + reset + ' ' + (explanation or '') + console.log stylize(color)(message) + ' ' + (explanation or '') # Run a CoffeeScript through our node/coffee interpreter. @@ -74,7 +86,7 @@ buildAttestation = (inputPaths, outputPath, attestationPath, execute) -> [inputHashes..., outputHash] = attestation.split ':' if (outputHash == outputChecksum and [0...checksummedPaths.length].every (i) -> checksummedPaths[i] == inputHashes[i]) - console.debug 'success! using cache...' + console.debug "#{stylize('yellow', 'italic')('success!')} using cache..." return yes console.warn 'attestation was out of date, ...' # TODO: ????? @@ -246,11 +258,11 @@ watchAndBuildAndTest = (harmony = no) -> buildAndTest yes, harmony fs.watch 'src/', interval: 200, (eventType, filename) -> if eventType is 'change' - log "src/#{filename} changed, rebuilding..." + log "src/#{filename} changed, rebuilding...", green buildAndTest (filename is 'grammar.coffee'), harmony fs.watch 'test/', {interval: 200, recursive: yes}, (eventType, filename) -> if eventType is 'change' - log "test/#{filename} changed, rebuilding..." + log "test/#{filename} changed, rebuilding...", green buildAndTest no, harmony diff --git a/build-support/colors.coffee b/build-support/colors.coffee new file mode 100644 index 0000000000..6fdc1e64a4 --- /dev/null +++ b/build-support/colors.coffee @@ -0,0 +1,42 @@ +process = require 'process' +util = require 'util' + +class Styler + constructor: ({colors} = {}) -> + @colors = colors ? yes + + @styleTable: util.inspect.colors + + @lookupStyle: (style) => @styleTable[style] ? throw new TypeError "style not found: '#{style}'" + + @passThrough: (str) -> str + + @doStylize = (...styles) => + starts = [] + ends = [] + for style in styles + [start, end] = @lookupStyle style + starts.push start + ends.unshift end + (str) -> "\x1B[#{starts.join ';'}m#{str}\x1B[#{ends.join ';'}m" + + stylize: (...styles) -> + if @colors then @constructor.doStylize(...styles) else @constructor.passThrough + + @doReset: '\x1B[0m' + + reset: -> if @colors then @constructor.doReset else '' + + +exports.setupStyler = ({colors} = {}) -> + return if global.styler? + + if process.env.NODE_DISABLE_COLORS + colors = no + unless process.stdout.hasColors() + colors = no + + styler = new Styler {colors} + + global.styler = styler + global.stylize = (...args) -> styler.stylize ...args diff --git a/build-support/console.coffee b/build-support/console.coffee new file mode 100644 index 0000000000..e66adbee6a --- /dev/null +++ b/build-support/console.coffee @@ -0,0 +1,89 @@ +{ Console } = require 'console' +process = require 'process' + +exports.CakeConsole = class CakeConsole extends Console + @LEVELS: ['trace', 'debug', 'info', 'log', 'warn', 'error'] + @validLevels: => "[#{(@LEVELS.map (l) -> "'#{l}'").join ', '}]" + @checkLevel: (level) => + unless level in @LEVELS + throw new TypeError "argument '#{level}' was not a valid log level (should be: #{@validLevels()})" + level + + constructor: ({level, ...opts} = {}) -> + super opts + @level = @constructor.checkLevel level ? 'log' + + @getLevelNum: (l) => @LEVELS.indexOf @checkLevel l + curLevelNum: -> @constructor.getLevelNum @level + doesThisLevelApply: (l) -> @curLevelNum() <= @constructor.getLevelNum(l) + + # Always log, regardless of level. This is for terminal output not intended to be configured by + # logging level. + unconditionalLog: (...args) -> + super.log ...args + + # Define the named logging methods (.log(), .warn(), ...) by extracting them from the superclass. + trace: (...args) -> + if @doesThisLevelApply 'trace' + super ...args + + debug: (...args) -> + if @doesThisLevelApply 'debug' + super ...args + + info: (...args) -> + if @doesThisLevelApply 'info' + super ...args + + log: (...args) -> + if @doesThisLevelApply 'log' + super ...args + + warn: (...args) -> + if @doesThisLevelApply 'warn' + super ...args + + error: (...args) -> + if @doesThisLevelApply 'error' + super ...args + + # Call .dir(), but filtering by configured level. + dirLevel: (level, ...args) -> + if @doesThisLevelApply level + super.dir ...args + + # We want to be able to call .dir() as normal, but we also want to be able to call .dir.log() to + # explicitly set the logging level for .dir(). + Object.defineProperty @::, 'dir', + configurable: yes + get: -> + # By default, .dir() uses the 'log' level. + dir = (...args) -> @dirLevel 'log', ...args + Object.defineProperties dir, Object.fromEntries do => for k in @constructor.LEVELS + f = do (k) => (...args) => @dirLevel k, ...args + [k, + enumerable: yes + writable: yes + configurable: yes + value: Object.defineProperty f, 'name', + configurable: yes + value: k] + # We wouldn't normally have to set this, but Console does some wonky prototype munging: + # https://github.com/nodejs/node/blob/17fae65c72321659390c4cbcd9ddaf248accb953/lib/internal/console/constructor.js#L145-L147 + set: (dir) -> # ignore + + @stdio: ({ + stdout = process.stdout, + stderr = process.stderr, + ...opts, + } = {}) => new @ { + stdout, + stderr, + ...opts + } + + +exports.setupConsole = ({level}) -> + return if global.cakeConsole? + global.console = global.cakeConsole = CakeConsole.stdio {level} + console.info "log level = #{level}" diff --git a/test/support/helpers.coffee b/test/support/helpers.coffee index 2e73b04784..5db321944d 100644 --- a/test/support/helpers.coffee +++ b/test/support/helpers.coffee @@ -45,7 +45,7 @@ exports.inspect = (obj) -> else require('util').inspect obj, depth: 10 - colors: if process.env.NODE_DISABLE_COLORS then no else yes + colors: if process.env.NODE_DISABLE_COLORS then no else process.stdout.hasColors() # Helpers to get AST nodes for a string of code. exports.getAstRoot = getAstRoot = (code) ->