UNPKG

4.41 kBJavaScriptView Raw
1'use strict'
2
3const { dirname } = require('path')
4const { mkdir, rm, stat } = require('fs').promises
5const { createHash } = require('crypto')
6const { createWriteStream } = require('fs')
7const http = require('http')
8const https = require('https')
9const { unlink } = require('fs/promises')
10
11const recursive = { recursive: true }
12
13const stripUrlHash = url => url.split('#')[0]
14
15const filename = url => {
16 const hash = createHash('shake256', {
17 outputLength: 8
18 })
19 hash.update(stripUrlHash(url))
20 return hash.digest('base64')
21 .replace(/=/g, '')
22 .replace(/\+/g, '_')
23 .replace(/\//g, '$')
24}
25
26const cleanDir = async dir => {
27 try {
28 await stat(dir)
29 await rm(dir, recursive)
30 } catch (err) {
31 // Ignore
32 }
33}
34
35const $op = Symbol('pad.op')
36const $x = Symbol('pad.x')
37const $lt = Symbol('pad.lt')
38const $w = Symbol('pad.w')
39function pad (width) {
40 if (!width) {
41 width = process.stdout.columns || 80
42 }
43 const ops = {
44 [$x] (widthLeft) {
45 return ''.padStart(widthLeft, this.text)
46 },
47 [$lt] (widthLeft) {
48 const { text, padding } = this
49 if (text.length <= widthLeft) {
50 return text.padEnd(widthLeft, padding)
51 }
52 return '...' + text.substring(text.length - widthLeft + 3)
53 },
54 [$w] (widthLeft, result, opIndex) {
55 const { text } = this
56 if (text.length < widthLeft && !text.includes('\n')) {
57 return text.padEnd(widthLeft, ' ')
58 }
59 const lines = []
60 text.split(/\r?\n/).forEach(line => {
61 if (line.length <= widthLeft) {
62 lines.push(line.padEnd(widthLeft, ' '))
63 } else {
64 for (let offset = 0; offset < line.length; offset += widthLeft - 1) {
65 const part = line.slice(offset, offset + widthLeft - 1)
66 if (part.length < widthLeft - 1) {
67 lines.push(part.padEnd(widthLeft, ' '))
68 } else {
69 lines.push(`${part}↵`)
70 }
71 }
72 }
73 })
74 const before = result.slice(0, opIndex).join('')
75 const after = result.slice(opIndex + 1).join('')
76 return lines.join(after + '\n' + before)
77 }
78 }
79 return (strings, ...values) => {
80 const result = []
81 let op
82 let opIndex
83 const length = strings.reduce((total, string, index) => {
84 result.push(string)
85 total += string.length
86 let value = values[index]
87 if (value === null || value === undefined) {
88 return total
89 }
90 if (value[$op]) {
91 if (opIndex !== undefined) {
92 throw new Error('Only one operator is allowed')
93 }
94 op = value
95 opIndex = result.length
96 result.push(value)
97 } else {
98 if (typeof value !== 'string') {
99 value = value.toString()
100 }
101 result.push(value)
102 total += value.length
103 }
104 return total
105 }, 0)
106 if (op !== undefined) {
107 const widthLeft = width - length
108 result[opIndex] = ops[op[$op]].call(op, widthLeft, result, opIndex)
109 }
110 return result.join('')
111 }
112}
113
114pad.x = (text) => ({ [$op]: $x, text })
115pad.lt = (text, padding = ' ') => ({ [$op]: $lt, text, padding })
116pad.w = (text) => ({ [$op]: $w, text })
117
118function allocPromise () {
119 let resolve
120 let reject
121 const promise = new Promise((_resolve, _reject) => {
122 resolve = _resolve
123 reject = _reject
124 })
125 return { promise, resolve, reject }
126}
127
128async function download (url, filename) {
129 const { hostname, port, origin } = new URL(url)
130 const options = {
131 hostname,
132 port,
133 path: url.substring(origin.length),
134 method: 'GET'
135 }
136 const protocol = url.startsWith('https:') ? https : http
137 await mkdir(dirname(filename), recursive)
138 const output = createWriteStream(filename)
139 const { promise, resolve, reject } = allocPromise()
140 const request = protocol.request(options, async response => {
141 if (response.statusCode !== 200) {
142 reject(response.statusCode)
143 output.end()
144 await unlink(filename)
145 return
146 }
147 response.on('error', reject)
148 response.on('end', resolve)
149 response.pipe(output)
150 })
151 request.on('error', reject)
152 request.end()
153 return promise
154}
155
156module.exports = {
157 stripUrlHash,
158 filename,
159 cleanDir,
160 createDir: dir => mkdir(dir, recursive),
161 recreateDir: dir => cleanDir(dir).then(() => mkdir(dir, recursive)),
162 extractPageUrl: headers => headers['x-page-url'],
163 allocPromise,
164 noop () {},
165 pad,
166 download
167}