Create a plain text representation of an HTML document's skeleton. Great for logging.
at main 121 lines 3.1 kB view raw
1let FLARE = true 2 3const selfClosingTags = [ 4 'area', 5 'base', 6 'br', 7 'col', 8 'command', 9 'embed', 10 'hr', 11 'img', 12 'input', 13 'keygen', 14 'link', 15 'meta', 16 'param', 17 'source', 18 'track', 19 'wbr', 20] 21 22export default function report(title, str, opts = {}) { 23 const { flare = true } = opts 24 FLARE = flare 25 26 const bones = selectorSkelly(str) 27 const lines = bones.split('\n') 28 const longestLine = lines.reduce((a, b) => (a.length > b.length ? a : b)) 29 const titleLine = `${c.pink('┌─')} ${c.b(c.blue(title))}${flare ? ' 🩻' : ''} ${c.pink('─○')}` 30 const lastLine = c.pink(`${'─'.repeat(longestLine.length + 3)}`) 31 32 return `${titleLine} 33${lines.map((line) => `${c.pink('│')} ${line}`).join('\n')} 34${lastLine}` 35} 36 37export function selectorSkelly(h) { 38 const html = h.replace(/<([^>]+)\/>/g, '<$1/>') 39 40 const tags = html.match(/<[^>]+>/g) || [] 41 let indentLevel = 0 42 let formattedHtml = '' 43 44 for (const tag of tags) { 45 const isClosing = tag.startsWith('</') 46 const tagMatch = tag.match(/^<\/?([\w-]+)/) 47 const idMatch = tag.match(/id="([^"]+)"/) 48 const classMatch = tag.match(/class="([^"]+)"/) 49 const isSelfClosing = 50 tag.endsWith('/>') || selfClosingTags.some((selfTag) => tag.startsWith(`<${selfTag}`)) 51 52 if (!isClosing && tagMatch) { 53 const tagName = tagMatch[1] 54 const idSelector = idMatch ? c.red(`#${idMatch[1]}`) : '' 55 const classSelector = classMatch ? c.orange(`.${classMatch[1].replace(/\s+/g, '.')}`) : '' 56 const selector = `${c.dim('<')}${tagName}${idSelector}${classSelector}${c.dim('>')}` 57 58 formattedHtml += `${' '.repeat(indentLevel)}${selector}\n` 59 60 if (!isSelfClosing) indentLevel++ 61 } else if (isClosing && tagMatch) { 62 indentLevel = Math.max(indentLevel - 1, 0) 63 } 64 } 65 66 return formattedHtml.trim() 67} 68 69export function tagsSkelly(h, opts = {}) { 70 const { attrs = false } = opts 71 const html = h.replace(/<([^>]+)\/>/g, '<$1/>') 72 73 const tags = html.match(/<[^>]+>/g) || [] 74 let indentLevel = 0 75 let formattedHtml = '' 76 77 for (const tag of tags) { 78 const isClosing = tag.startsWith('</') 79 const isSelfClosing = 80 tag.endsWith('/>') || selfClosingTags.some((selfTag) => tag.startsWith(`<${selfTag}`)) 81 const tagMatch = tag.match(/^<\/?([\w-]+)/) 82 const insert = attrs ? tag : tag.replace(/ .+/, '>') 83 84 if (!isClosing) { 85 if (isSelfClosing) { 86 formattedHtml += `${' '.repeat(indentLevel)}${insert}\n` 87 } else { 88 formattedHtml += `${' '.repeat(indentLevel)}${insert}\n` 89 indentLevel++ 90 } 91 } else if (tagMatch && isClosing) { 92 indentLevel = Math.max(indentLevel - 1, 0) 93 } 94 } 95 96 return formattedHtml 97} 98 99export const c = { 100 b(str) { 101 return FLARE ? `\x1b[1m${str}\x1b[22m` : str 102 }, 103 color(str, code) { 104 return FLARE ? `\x1b[${code}m${str}\x1b[0m` : str 105 }, 106 dim(str) { 107 return this.color(str, 2) 108 }, 109 red(str) { 110 return this.color(str, 31) 111 }, 112 orange(str) { 113 return this.color(str, 33) 114 }, 115 blue(str) { 116 return this.color(str, 34) 117 }, 118 pink(str) { 119 return this.color(str, 35) 120 }, 121}