Skip to content

Commit

Permalink
Merge pull request #769 from nextcloud-libraries/feat/parseFileSize
Browse files Browse the repository at this point in the history
feat(parseFileSize): Added `parseFileSize` function to parse a human readable file size to number of bytes
  • Loading branch information
susnux committed Sep 22, 2023
2 parents 99c48b7 + e41200b commit 02439e9
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 1 deletion.
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 @@ -105,3 +105,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 @@ export function formatFileSize(size: number|string, skipSmallSizes = false, bina

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
}

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]]))
}

0 comments on commit 02439e9

Please sign in to comment.