Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(parseFileSize): Added parseFileSize function to parse a human readable file size to number of bytes #769

Merged
merged 1 commit into from Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
96 changes: 95 additions & 1 deletion __tests__/humanFileSize.spec.ts
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'

import { formatFileSize } from '../lib/humanfilesize'
import { formatFileSize, parseFileSize } from '../lib/humanfilesize'

describe('humanFileSize', () => {
describe('formatFileSize', () => {
Expand Down Expand Up @@ -101,3 +101,97 @@ describe('humanFileSize', () => {
})
})
})

describe('parseFileSize', () => {
it('should return null on error', () => {
const invalid = [
'',
'a',
'.',
'kb',
'1.1.2',
'10ob',
'1z',
]

for (const line of invalid) {
expect(parseFileSize(line)).toBeNull()
}
})

it('can parse base 2', () => {
const values = {
'2kib': 2048,
'2mib': 2 * (1024 ** 2),
'2gib': 2 * (1024 ** 3),
'2tib': 2 * (1024 ** 4),
'2pib': 2 * (1024 ** 5),
}

for (const [text, value] of Object.entries(values)) {
expect(parseFileSize(text)).toBe(value)
}
})

it('can parse base 10', () => {
const values = {
'2kb': 2000,
'2mb': 2 * (1000 ** 2),
'2gb': 2 * (1000 ** 3),
'2tb': 2 * (1000 ** 4),
'2pb': 2 * (1000 ** 5),
}

for (const [text, value] of Object.entries(values)) {
expect(parseFileSize(text)).toBe(value)
}
})

it('parses missing binary prefixes if force is set true', () => {
const values = {
'2kb': 2048,
'2mb': 2 * (1024 ** 2),
'2gb': 2 * (1024 ** 3),
'2tb': 2 * (1024 ** 4),
'2pb': 2 * (1024 ** 5),
}

for (const [text, value] of Object.entries(values)) {
expect(parseFileSize(text, true)).toBe(value)
}
})

it('can parse with spaces', () => {
const values = {
'2kb': 2000,
'2 kb': 2000,
' 2kb': 2000,
'2kb ': 2000,
' 2 k b ': 2000,
'2kib': 2048,
'2 kIb': 2048,
' 2kib': 2048,
'2Kib ': 2048,
' 2 k i b ': 2048,
}

for (const [text, value] of Object.entries(values)) {
expect(parseFileSize(text)).toBe(value)
}
})

it('can parse decimals', () => {
const values = {
'2kb': 2000,
'2.2kb': 2200,
'2.kb': 2000,
'.2kb ': 200,
',2kb': 200,
'2,2kb': 2200,
}

for (const [text, value] of Object.entries(values)) {
expect(parseFileSize(text)).toBe(value)
}
})
})
37 changes: 37 additions & 0 deletions lib/humanfilesize.ts
Expand Up @@ -64,3 +64,40 @@

return relativeSize + ' ' + readableFormat
}

/**
* Returns a file size in bytes from a humanly readable string
* Note: `b` and `B` are both parsed as bytes and not as bit or byte.
*
* @param {string} value file size in human-readable format
* @param {boolean} forceBinary for backwards compatibility this allows values to be base 2 (so 2KB means 2048 bytes instead of 2000 bytes)
* @return {number} or null if string could not be parsed
*/
export function parseFileSize(value: string, forceBinary = false) {
try {
value = `${value}`.toLocaleLowerCase().replaceAll(/\s+/g, '').replaceAll(',', '.')
} catch (e) {
return null

Check warning on line 80 in lib/humanfilesize.ts

View check run for this annotation

Codecov / codecov/patch

lib/humanfilesize.ts#L80

Added line #L80 was not covered by tests
}

const match = value.match(/^([0-9]*(\.[0-9]*)?)([kmgtp]?)(i?)b?$/)
// ignore not found, missing pre- and decimal, empty number
if (match === null || match[1] === '.' || match[1] === '') {
return null
}

const bytesArray = {
'': 0,
k: 1,
m: 2,
g: 3,
t: 4,
p: 5,
e: 6,
}

const decimalString = `${match[1]}`
const base = (match[4] === 'i' || forceBinary) ? 1024 : 1000

return Math.round(Number.parseFloat(decimalString) * (base ** bytesArray[match[3]]))
}