text: const Minipass = require('minipass')
  const columnify = require('columnify')
  
  // This module consumes package data in the following format:
  //
  // {
  //   name: String,
  //   description: String,
  //   maintainers: [{ username: String, email: String }],
  //   keywords: String | [String],
  //   version: String,
  //   date: Date // can be null,
  // }
  //
  // The returned stream will format this package data
  // into a byte stream of formatted, displayable output.
  
  module.exports = (opts) => {
    return opts.json ? new JSONOutputStream() : new TextOutputStream(opts)
  }
  
  class JSONOutputStream extends Minipass {
    #didFirst = false
  
    write (obj) {
      if (!this.#didFirst) {
        super.write('[\n')
        this.#didFirst = true
      } else {
        super.write('\n,\n')
      }
  
      return super.write(JSON.stringify(obj))
    }
  
    end () {
      super.write(this.#didFirst ? ']\n' : '\n[]\n')
      super.end()
    }
  }
  
  class TextOutputStream extends Minipass {
    constructor (opts) {
      super()
      this._opts = opts
      this._line = 0
    }
  
    write (pkg) {
      return super.write(prettify(pkg, ++this._line, this._opts))
    }
  }
  
  function prettify (data, num, opts) {
    var truncate = !opts.long
  
    var pkg = normalizePackage(data, opts)
  
    var columns = ['name', 'description', 'author', 'date', 'version', 'keywords']
  
    if (opts.parseable) {
      return columns.map(function (col) {
        return pkg[col] && ('' + pkg[col]).replace(/\t/g, ' ')
      }).join('\t')
    }
  
    // stdout in tap is never a tty
    /* istanbul ignore next */
    const maxWidth = process.stdout.isTTY ? process.stdout.getWindowSize()[0] : Infinity
    let output = columnify(
      [pkg],
      {
        include: columns,
        showHeaders: num <= 1,
        columnSplitter: ' | ',
        truncate: truncate,
        config: {
          name: { minWidth: 25, maxWidth: 25, truncate: false, truncateMarker: '' },
          description: { minWidth: 20, maxWidth: 20 },
          author: { minWidth: 15, maxWidth: 15 },
          date: { maxWidth: 11 },
          version: { minWidth: 8, maxWidth: 8 },
          keywords: { maxWidth: Infinity },
        },
      }
    ).split('\n').map(line => line.slice(0, maxWidth)).join('\n')
  
    if (opts.color) {
      output = highlightSearchTerms(output, opts.args)
    }
  
    return output
  }
  
  var colors = [31, 33, 32, 36, 34, 35]
  var cl = colors.length
  
  function addColorMarker (str, arg, i) {
    var m = i % cl + 1
    var markStart = String.fromCharCode(m)
    var markEnd = String.fromCharCode(0)
  
    if (arg.charAt(0) === '/') {
      return str.replace(
        new RegExp(arg.slice(1, -1), 'gi'),
        bit => markStart + bit + markEnd
      )
    }
  
    // just a normal string, do the split/map thing
    var pieces = str.toLowerCase().split(arg.toLowerCase())
    var p = 0
  
    return pieces.map(function (piece) {
      piece = str.slice(p, p + piece.length)
      var mark = markStart +
                 str.slice(p + piece.length, p + piece.length + arg.length) +
                 markEnd
      p += piece.length + arg.length
      return piece + mark
    }).join('')
  }
  
  function colorize (line) {
    for (var i = 0; i < cl; i++) {
      var m = i + 1
      var color = '\u001B[' + colors[i] + 'm'
      line = line.split(String.fromCharCode(m)).join(color)
    }
    var uncolor = '\u001B[0m'
    return line.split('\u0000').join(uncolor)
  }
  
  function highlightSearchTerms (str, terms) {
    terms.forEach(function (arg, i) {
      str = addColorMarker(str, arg, i)
    })
  
    return colorize(str).trim()
  }
  
  function normalizePackage (data, opts) {
    return {
      name: data.name,
      description: data.description,
      author: data.maintainers.map((m) => `=${m.username}`).join(' '),
      keywords: Array.isArray(data.keywords)
        ? data.keywords.join(' ')
        : typeof data.keywords === 'string'
          ? data.keywords.replace(/[,\s]+/, ' ')
          : '',
      version: data.version,
      date: (data.date &&
            (data.date.toISOString() // remove time
              .split('T').join(' ')
              .replace(/:[0-9]{2}\.[0-9]{3}Z$/, ''))
              .slice(0, -5)) ||
            'prehistoric',
    }
  }
  