Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* dev: CSpell IO layer * fix: Add encoding to reads * fix: add `data:` url encoder
- Loading branch information
Showing
13 changed files
with
167 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`index api 1`] = ` | ||
Array [ | ||
"CSpellIONode => function", | ||
"asyncIterableToArray => function", | ||
"encodeDataUrl => function", | ||
"getStat => function", | ||
"getStatSync => function", | ||
"readFile => function", | ||
"readFileSync => function", | ||
"toDataUrl => function", | ||
"writeToFile => function", | ||
"writeToFileIterable => function", | ||
"writeToFileIterableP => function", | ||
] | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export function toError(e: unknown): Error { | ||
if (e instanceof Error) return e; | ||
if (typeof e === 'object' && e && typeof (e as Error).message === 'string') { | ||
return e as Error; | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
return Error(e && (e as any).toString()); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,2 @@ | ||
import { format } from 'util'; | ||
|
||
export function toError(e: unknown): Error { | ||
if (e instanceof Error) return e; | ||
if (typeof e === 'object' && e && (e as Error).message) { | ||
return e as Error; | ||
} | ||
return Error(format(e)); | ||
} | ||
export { toError } from './error'; | ||
export { ErrorNotImplemented } from './ErrorNotImplemented'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export type BufferEncoding = | ||
| 'ascii' | ||
| 'utf8' | ||
| 'utf-8' | ||
| 'utf16le' | ||
| 'ucs2' | ||
| 'ucs-2' | ||
| 'base64' | ||
| 'base64url' | ||
| 'latin1' | ||
| 'hex'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { toDataUrl, encodeDataUrl, decodeDataUrl } from './dataUrl'; | ||
|
||
describe('dataUrl', () => { | ||
test.each` | ||
data | mediaType | attributes | expected | ||
${'Hello, World!'} | ${'text/plain'} | ${undefined} | ${'data:text/plain;charset=utf8,Hello%2C%20World!'} | ||
${'Hello, World!'} | ${'text/plain'} | ${[['filename', 'hello.txt']]} | ${'data:text/plain;charset=utf8;filename=hello.txt,Hello%2C%20World!'} | ||
${'Hello, World! %%%%$$$$,,,,'} | ${'text/plain'} | ${undefined} | ${'data:text/plain;charset=utf8;base64,SGVsbG8sIFdvcmxkISAlJSUlJCQkJCwsLCw'} | ||
${Buffer.from('Hello, World!')} | ${'text/plain'} | ${[['filename', 'hello.txt']]} | ${'data:text/plain;filename=hello.txt;base64,SGVsbG8sIFdvcmxkIQ' /* cspell:disable-line */} | ||
${'☸☹☺☻☼☾☿'} | ${'text/plain'} | ${undefined} | ${'data:text/plain;charset=utf8;base64,4pi44pi54pi64pi74pi84pi-4pi_'} | ||
${'Hello, World!'} | ${'application/vnd.cspell'} | ${undefined} | ${'data:application/vnd.cspell;charset=utf8,Hello%2C%20World!'} | ||
`('encodeDataUrl $data', ({ data, mediaType, attributes, expected }) => { | ||
const url = encodeDataUrl(data, mediaType, attributes); | ||
expect(url).toEqual(expected); | ||
const urlObj = toDataUrl(data, mediaType, attributes); | ||
expect(urlObj.toString()).toEqual(url); | ||
}); | ||
|
||
test.each` | ||
url | expected | ||
${'data:text/plain;charset=utf8,Hello%2C%20World!'} | ${{ mediaType: 'text/plain', encoding: 'utf8', data: Buffer.from('Hello, World!'), attributes: new Map([['charset', 'utf8']]) }} | ||
${'data:text/plain;charset=utf8;filename=hello.txt,Hello%2C%20World!'} | ${{ mediaType: 'text/plain', encoding: 'utf8', data: Buffer.from('Hello, World!'), attributes: new Map([['charset', 'utf8'], ['filename', 'hello.txt']]) }} | ||
${'data:text/plain;charset=utf8;base64,SGVsbG8sIFdvcmxkISAlJSUlJCQkJCwsLCw'} | ${{ mediaType: 'text/plain', encoding: 'utf8', data: Buffer.from('Hello, World! %%%%$$$$,,,,'), attributes: new Map([['charset', 'utf8']]) }} | ||
${'data:text/plain;base64,SGVsbG8sIFdvcmxkIQ' /* cspell:disable-line */} | ${{ mediaType: 'text/plain', data: Buffer.from('Hello, World!'), attributes: new Map() }} | ||
${'data:text/plain;charset=utf8;base64,4pi44pi54pi64pi74pi84pi-4pi_'} | ${{ mediaType: 'text/plain', encoding: 'utf8', data: Buffer.from('☸☹☺☻☼☾☿'), attributes: new Map([['charset', 'utf8']]) }} | ||
`('encodeDataUrl $url', ({ url, expected }) => { | ||
const data = decodeDataUrl(url); | ||
expect(data).toEqual(expected); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
/** | ||
* Generates a string of the following format: | ||
* | ||
* `data:[mediaType][;charset=<encoding>[;base64],<data>` | ||
* | ||
* - `encoding` - defaults to `utf8` for text data | ||
* @param data | ||
* @param mediaType - The mediaType is a [MIME](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) type string | ||
* @param attributes - Additional attributes | ||
*/ | ||
export function encodeDataUrl( | ||
data: string | Buffer, | ||
mediaType: string, | ||
attributes?: Iterable<readonly [string, string]> | undefined | ||
): string { | ||
if (typeof data === 'string') return encodeString(data, mediaType, attributes); | ||
const attribs = encodeAttributes(attributes || []); | ||
return `data:${mediaType}${attribs};base64,${data.toString('base64url')}`; | ||
} | ||
|
||
export function toDataUrl( | ||
data: string | Buffer, | ||
mediaType: string, | ||
attributes?: Iterable<[string, string]> | undefined | ||
): URL { | ||
return new URL(encodeDataUrl(data, mediaType, attributes)); | ||
} | ||
|
||
function encodeString( | ||
data: string, | ||
mediaType: string | undefined, | ||
attributes: Iterable<readonly [string, string]> | undefined | ||
): string { | ||
mediaType = mediaType || 'text/plain'; | ||
attributes = attributes || []; | ||
const asUrlComp = encodeURIComponent(data); | ||
const asBase64 = Buffer.from(data).toString('base64url'); | ||
const useBase64 = asBase64.length < asUrlComp.length - 7; | ||
const encoded = useBase64 ? asBase64 : asUrlComp; | ||
// Ensure charset is first. | ||
const attribMap = new Map([['charset', 'utf8'] as readonly [string, string]].concat([...attributes])); | ||
attribMap.set('charset', 'utf8'); // Make sure it is always `utf8`. | ||
const attribs = encodeAttributes(attribMap); | ||
return `data:${mediaType}${attribs}${useBase64 ? ';base64' : ''},${encoded}`; | ||
} | ||
|
||
export interface DecodedDataUrl { | ||
data: Buffer; | ||
mediaType: string; | ||
encoding?: string | undefined; | ||
attributes: Map<string, string>; | ||
} | ||
|
||
function encodeAttributes(attributes: Iterable<readonly [string, string]>): string { | ||
return [...attributes].map(([key, value]) => `;${key}=${encodeURIComponent(value)}`).join(''); | ||
} | ||
|
||
const dataUrlRegExHead = /^data:(?<mediaType>[^;,]*)(?<attributes>(?:;[^=]+=[^;,]*)*)(?<base64>;base64)?$/; | ||
|
||
export function decodeDataUrl(url: string): DecodedDataUrl { | ||
const [head, encodedData] = url.split(',', 2); | ||
if (!head || encodedData === undefined) throw Error('Not a data url'); | ||
const match = head.match(dataUrlRegExHead); | ||
if (!match || !match.groups) throw Error('Not a data url'); | ||
const mediaType = match.groups['mediaType'] || ''; | ||
const rawAttributes = (match.groups['attributes'] || '') | ||
.split(';') | ||
.filter((a) => !!a) | ||
.map((entry) => entry.split('=', 2)) | ||
.map(([key, value]) => [key, decodeURIComponent(value)] as [string, string]); | ||
const attributes = new Map(rawAttributes); | ||
const encoding = attributes.get('charset'); | ||
const isBase64 = !!match.groups['base64']; | ||
const data = isBase64 ? Buffer.from(encodedData, 'base64url') : Buffer.from(decodeURIComponent(encodedData)); | ||
return { mediaType, data, encoding, attributes }; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters