UNPKG

yoda-seo-tips

Version:
101 lines (82 loc) 2.67 kB
import * as cheerio from 'cheerio'; import { Stream, Writable } from 'stream'; import { createReadStream, createWriteStream } from 'fs'; import { promisify } from 'util'; export type Input = string | Stream; export type Output = string | {write(msg: string): void} | null; export interface Rule { ($: CheerioStatic, report: (msg: string) => void): void; }; export class HtmlValidator { constructor(private rules: Rule[]) {} async process(input: Input, output: Output) { let inputStream = typeof input === 'string' ? createReadStream(input) : input && typeof input.on === 'function' ? input : error('input: expecting filename or readable stream as input') var writer = !output ? new ConsoleWriter : typeof output === 'string' ? new FileWriter(output) : typeof output.write === 'function' ? new StreamWriter(output) : error('output: expecting filename or writable stream'); let data = await consumeStream(inputStream); let cheerioDom = cheerio.load(data); for(let rule of this.rules) { rule(cheerioDom, msg => { writer.write(msg + '\n'); }); } await writer.close(); } } class StreamWriter { constructor(private stream: {write(msg: string): void}) {} write(msg : string) { this.stream.write(msg); } async close() {} } class ConsoleWriter { async write(msg: string) { console.log(msg.replace(/\s*$/, '')); } async close() {} } class FileWriter { error?: any; stream = createWriteStream(this.filename, {flags: 'w'}); constructor(private filename: string) { this.stream.on('error', err => { this.error = err }); } write(msg: string) { if(this.error) throw this.error; this.stream.write(msg); } async close() { if(this.error) throw this.error; return new Promise((resolve, reject) => { this.stream.on('error', reject); this.stream.on('finish', () => { this.stream.close(); this.stream.destroy(); resolve(); }); this.stream.end(); }) } } function consumeStream(stream: Stream) : Promise<string> { return new Promise((resolve, reject) => { let data = ''; stream.on('data', chunk => { data += chunk; }); stream.on('end', () => { resolve(data); }); stream.on('error', reject); }); } function error(msg: string): never { throw new Error(msg); }