Son CV dans un terminal web en Javascript!
https://terminal-cv.gregandev.fr
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
333 lines
9.3 KiB
333 lines
9.3 KiB
/*
|
|
control character types:
|
|
1 - metadata
|
|
2 - symbols
|
|
6 - false
|
|
7 - true
|
|
8- 16 - negative doubles
|
|
16-24 positive doubles
|
|
27 - String starts with a character 27 or less or is an empty string
|
|
0 - multipart separator
|
|
> 27 normal string characters
|
|
*/
|
|
/*
|
|
* Convert arbitrary scalar values to buffer bytes with type preservation and type-appropriate ordering
|
|
*/
|
|
|
|
const float64Array = new Float64Array(2)
|
|
const int32Array = new Int32Array(float64Array.buffer, 0, 4)
|
|
const uint8Array6 = new Uint8Array(float64Array.buffer, 2, 6)
|
|
const uint8Array8 = new Uint8Array(float64Array.buffer, 0, 8)
|
|
let nullTerminate = false
|
|
let textEncoder
|
|
try {
|
|
textEncoder = new TextEncoder()
|
|
} catch (error) {}
|
|
|
|
/*
|
|
* Convert arbitrary scalar values to buffer bytes with type preservation and type-appropriate ordering
|
|
*/
|
|
export function writeKey(key, target, position, inSequence) {
|
|
let targetView = target.dataView
|
|
if (!targetView)
|
|
targetView = target.dataView = new DataView(target.buffer, target.byteOffset, ((target.byteLength + 3) >> 2) << 2)
|
|
switch (typeof key) {
|
|
case 'string':
|
|
let strLength = key.length
|
|
let c1 = key.charCodeAt(0)
|
|
if (!(c1 >= 28)) // escape character
|
|
target[position++] = 27
|
|
if (strLength < 0x40) {
|
|
let i, c2
|
|
for (i = 0; i < strLength; i++) {
|
|
c1 = key.charCodeAt(i)
|
|
if (c1 < 0x80) {
|
|
target[position++] = c1
|
|
} else if (c1 < 0x800) {
|
|
target[position++] = c1 >> 6 | 0xc0
|
|
target[position++] = c1 & 0x3f | 0x80
|
|
} else if (
|
|
(c1 & 0xfc00) === 0xd800 &&
|
|
((c2 = key.charCodeAt(i + 1)) & 0xfc00) === 0xdc00
|
|
) {
|
|
c1 = 0x10000 + ((c1 & 0x03ff) << 10) + (c2 & 0x03ff)
|
|
i++
|
|
target[position++] = c1 >> 18 | 0xf0
|
|
target[position++] = c1 >> 12 & 0x3f | 0x80
|
|
target[position++] = c1 >> 6 & 0x3f | 0x80
|
|
target[position++] = c1 & 0x3f | 0x80
|
|
} else {
|
|
target[position++] = c1 >> 12 | 0xe0
|
|
target[position++] = c1 >> 6 & 0x3f | 0x80
|
|
target[position++] = c1 & 0x3f | 0x80
|
|
}
|
|
}
|
|
} else {
|
|
if (target.utf8Write)
|
|
position += target.utf8Write(key, position)
|
|
else
|
|
position += textEncoder.encodeInto(key, target.subarray(position)).written
|
|
if (position > target.length - 4)
|
|
throw new RangeError('String does not fit in target buffer')
|
|
}
|
|
break
|
|
case 'number':
|
|
float64Array[0] = key
|
|
let lowInt = int32Array[0]
|
|
let highInt = int32Array[1]
|
|
let length
|
|
if (key < 0) {
|
|
targetView.setInt32(position + 4, ~((lowInt >>> 4) | (highInt << 28)))
|
|
targetView.setInt32(position + 0, (highInt ^ 0x7fffffff) >>> 4)
|
|
targetView.setInt32(position + 8, ((lowInt & 0xf) ^ 0xf) << 4, true) // just always do the null termination here
|
|
return position + 9
|
|
} else if ((lowInt & 0xf) || inSequence) {
|
|
length = 9
|
|
} else if (lowInt & 0xfffff)
|
|
length = 8
|
|
else if (lowInt || (highInt & 0xf))
|
|
length = 6
|
|
else
|
|
length = 4
|
|
// switching order to go to little endian
|
|
targetView.setInt32(position + 0, (highInt >>> 4) | 0x10000000)
|
|
targetView.setInt32(position + 4, (lowInt >>> 4) | (highInt << 28))
|
|
// if (length == 9 || nullTerminate)
|
|
targetView.setInt32(position + 8, (lowInt & 0xf) << 4, true)
|
|
return position + length;
|
|
case 'object':
|
|
if (key) {
|
|
if (key instanceof Array) {
|
|
for (let i = 0, l = key.length; i < l; i++) {
|
|
if (i > 0)
|
|
target[position++] = 0
|
|
position = writeKey(key[i], target, position, true)
|
|
}
|
|
break
|
|
} else if (key instanceof Uint8Array) {
|
|
target.set(key, position)
|
|
position += key.length
|
|
break
|
|
} else {
|
|
throw new Error('Unable to serialize object as a key')
|
|
}
|
|
} else // null
|
|
target[position++] = 0
|
|
break
|
|
case 'boolean':
|
|
targetView.setUint32(position++, key ? 7 : 6, true)
|
|
return position
|
|
case 'bigint':
|
|
return writeKey(Number(key), target, position, inSequence)
|
|
case 'undefined':
|
|
return position
|
|
// undefined is interpreted as the absence of a key, signified by zero length
|
|
case 'symbol':
|
|
target[position++] = 2
|
|
return writeKey(key.description, target, position, inSequence)
|
|
default:
|
|
throw new Error('Can not serialize key of type ' + typeof key)
|
|
}
|
|
if (nullTerminate && !inSequence)
|
|
targetView.setUint32(position, 0)
|
|
return position
|
|
}
|
|
|
|
let position
|
|
export function readKey(buffer, start, end, inSequence) {
|
|
buffer[end] = 0 // make sure it is null terminated
|
|
position = start
|
|
let controlByte = buffer[position]
|
|
let value
|
|
if (controlByte < 24) {
|
|
if (controlByte < 8) {
|
|
position++
|
|
if (controlByte == 6) {
|
|
value = false
|
|
} else if (controlByte == 7) {
|
|
value = true
|
|
} else if (controlByte == 0) {
|
|
value = null
|
|
} else if (controlByte == 2) {
|
|
value = Symbol.for(readString(buffer))
|
|
} else
|
|
return Uint8Array.prototype.slice.call(buffer, start, end)
|
|
} else {
|
|
let dataView = buffer.dataView || (buffer.dataView = new DataView(buffer.buffer, buffer.byteOffset, ((buffer.byteLength + 3) >> 2) << 2))
|
|
let highInt = dataView.getInt32(position) << 4
|
|
let size = end - position
|
|
let lowInt
|
|
if (size > 4) {
|
|
lowInt = dataView.getInt32(position + 4)
|
|
highInt |= lowInt >>> 28
|
|
if (size <= 6) { // clear the last bits
|
|
lowInt &= -0x1000
|
|
}
|
|
lowInt = lowInt << 4
|
|
if (size > 8) {
|
|
lowInt = lowInt | buffer[position + 8] >> 4
|
|
}
|
|
} else
|
|
lowInt = 0
|
|
if (controlByte < 16) {
|
|
// negative gets negated
|
|
highInt = highInt ^ 0x7fffffff
|
|
lowInt = ~lowInt
|
|
}
|
|
int32Array[1] = highInt
|
|
int32Array[0] = lowInt
|
|
value = float64Array[0]
|
|
position += 9
|
|
}
|
|
} else {
|
|
if (controlByte == 27) {
|
|
position++
|
|
}
|
|
value = readString(buffer)
|
|
/*let strStart = position
|
|
let strEnd = end
|
|
for (; position < end; position++) {
|
|
if (buffer[position] == 0) {
|
|
break
|
|
}
|
|
}
|
|
value = buffer.toString('utf8', strStart, position++)*/
|
|
}
|
|
while (position < end) {
|
|
if (buffer[position] === 0)
|
|
position++
|
|
if (inSequence) {
|
|
encoder.position = position
|
|
return value
|
|
}
|
|
let nextValue = readKey(buffer, position, end, true)
|
|
if (value instanceof Array) {
|
|
value.push(nextValue)
|
|
} else
|
|
value = [ value, nextValue ]
|
|
}
|
|
return value
|
|
}
|
|
export const enableNullTermination = () => nullTerminate = true
|
|
|
|
export const encoder = {
|
|
writeKey,
|
|
readKey,
|
|
enableNullTermination,
|
|
}
|
|
export const toBufferKey = (key) => {
|
|
let buffer = Buffer.alloc(2048)
|
|
return buffer.slice(0, writeKey(key, buffer, 0, 2048) + 1)
|
|
}
|
|
export const fromBufferKey = (sourceBuffer) => {
|
|
return readKey(sourceBuffer, 0, sourceBuffer.length - 1)
|
|
}
|
|
const fromCharCode = String.fromCharCode
|
|
function makeStringBuilder() {
|
|
let stringBuildCode = '(source) => {'
|
|
let previous = []
|
|
for (let i = 0; i < 0x30; i++) {
|
|
let v = fromCharCode((i & 0xf) + 97) + fromCharCode((i >> 4) + 97)
|
|
stringBuildCode += `
|
|
let ${v} = source[position++]
|
|
if (${v} === 0)
|
|
return fromCharCode(${previous})
|
|
else if (${v} >= 0x80)
|
|
${v} = finishUtf8(${v}, source)
|
|
`
|
|
previous.push(v)
|
|
if (i == 1000000) // this just exists to prevent rollup from doing dead code elimination on finishUtf8
|
|
finishUtf8()
|
|
}
|
|
stringBuildCode += `return fromCharCode(${previous}) + readString(source)}`
|
|
return stringBuildCode
|
|
}
|
|
|
|
let pendingSurrogate
|
|
function finishUtf8(byte1, src) {
|
|
if ((byte1 & 0xe0) === 0xc0) {
|
|
// 2 bytes
|
|
const byte2 = src[position++] & 0x3f
|
|
return ((byte1 & 0x1f) << 6) | byte2
|
|
} else if ((byte1 & 0xf0) === 0xe0) {
|
|
// 3 bytes
|
|
const byte2 = src[position++] & 0x3f
|
|
const byte3 = src[position++] & 0x3f
|
|
return ((byte1 & 0x1f) << 12) | (byte2 << 6) | byte3
|
|
} else if ((byte1 & 0xf8) === 0xf0) {
|
|
// 4 bytes
|
|
if (pendingSurrogate) {
|
|
byte1 = pendingSurrogate
|
|
pendingSurrogate = null
|
|
position += 3
|
|
return byte1
|
|
}
|
|
const byte2 = src[position++] & 0x3f
|
|
const byte3 = src[position++] & 0x3f
|
|
const byte4 = src[position++] & 0x3f
|
|
let unit = ((byte1 & 0x07) << 0x12) | (byte2 << 0x0c) | (byte3 << 0x06) | byte4
|
|
if (unit > 0xffff) {
|
|
unit -= 0x10000
|
|
unit = 0xdc00 | (unit & 0x3ff)
|
|
pendingSurrogate = ((unit >>> 10) & 0x3ff) | 0xd800
|
|
position -= 4 // reset so we can return the next part of the surrogate pair
|
|
}
|
|
return unit
|
|
} else {
|
|
return byte1
|
|
}
|
|
}
|
|
|
|
const readString = eval(makeStringBuilder())
|
|
|
|
export function compareKeys(a, b) {
|
|
// compare with type consistency that matches binary comparison
|
|
if (typeof a == 'object') {
|
|
if (!a) {
|
|
return b == null ? 0 : -1
|
|
}
|
|
if (a.compare) {
|
|
if (b == null) {
|
|
return 1
|
|
} else if (b.compare) {
|
|
return a.compare(b)
|
|
} else {
|
|
return -1
|
|
}
|
|
}
|
|
let arrayComparison
|
|
if (b instanceof Array) {
|
|
let i = 0
|
|
while((arrayComparison = compareKeys(a[i], b[i])) == 0 && i <= a.length) {
|
|
i++
|
|
}
|
|
return arrayComparison
|
|
}
|
|
arrayComparison = compareKeys(a[0], b)
|
|
if (arrayComparison == 0 && a.length > 1)
|
|
return 1
|
|
return arrayComparison
|
|
} else if (typeof a == typeof b) {
|
|
if (typeof a === 'symbol') {
|
|
a = Symbol.keyFor(a)
|
|
b = Symbol.keyFor(b)
|
|
}
|
|
return a < b ? -1 : a === b ? 0 : 1
|
|
}
|
|
else if (typeof b == 'object') {
|
|
if (b instanceof Array)
|
|
return -compareKeys(b, a)
|
|
return 1
|
|
} else {
|
|
return typeOrder[typeof a] < typeOrder[typeof b] ? -1 : 1
|
|
}
|
|
}
|
|
const typeOrder = {
|
|
symbol: 0,
|
|
undefined: 1,
|
|
boolean: 2,
|
|
number: 3,
|
|
string: 4
|
|
}
|
|
export const MINIMUM_KEY = null
|
|
export const MAXIMUM_KEY = new Uint8Array([0xff]) |