import type { PhpRuntimeValue } from '../_helpers/_phpTypes.ts'
type SprintfValue = PhpRuntimeValue
export function sprintf(format: string, ...args: SprintfValue[]): string | false {
const regex = /%%|%(?:(\d+)\$)?((?:[-+#0 ]|'[\s\S])*)(\d+)?(?:\.(\d*))?([\s\S])/g const callArgs: SprintfValue[] = [format, ...args] let i = 1
const _pad = function (str: string, len: number, chr: string, leftJustify: boolean): string { if (!chr) { chr = ' ' } const padding = str.length >= len ? '' : new Array((1 + len - str.length) >>> 0).join(chr) return leftJustify ? str + padding : padding + str }
const justify = function ( value: string, prefix: string, leftJustify: boolean, minWidth: number, padChar: string, ): string { const diff = minWidth - value.length if (diff > 0) { if (!leftJustify && padChar === '0') { value = [value.slice(0, prefix.length), _pad('', diff, '0', true), value.slice(prefix.length)].join('') } else { value = _pad(value, minWidth, padChar, leftJustify) } } return value }
const _formatBaseX = function ( value: SprintfValue, base: number, leftJustify: boolean, minWidth: number, precision: number | undefined, padChar: string, ): string { const number = Number(value) >>> 0 const padded = _pad(number.toString(base), precision || 0, '0', false) return justify(padded, '', leftJustify, minWidth, padChar) }
const _formatString = function ( value: string, leftJustify: boolean, minWidth: number, precision: number | null | undefined, customPadChar: string, ) { if (precision !== null && precision !== undefined) { value = value.slice(0, precision) } return justify(value, '', leftJustify, minWidth, customPadChar) }
const doFormat = function ( substring: string, argIndex: string | undefined, modifiers: string, minWidthRaw: string | undefined, precisionRaw: string | undefined, specifier: string, ): string { let number = 0 let prefix = '' let value: SprintfValue
if (substring === '%%') { return '%' }
let padChar = ' ' let leftJustify = false let positiveNumberPrefix = '' let j = 0 let l = 0
for (j = 0, l = modifiers.length; j < l; j++) { switch (modifiers.charAt(j)) { case ' ': case '0': padChar = modifiers.charAt(j) break case '+': positiveNumberPrefix = '+' break case '-': leftJustify = true break case "'": if (j + 1 < l) { padChar = modifiers.charAt(j + 1) j++ } break } }
let minWidthNum = 0 if (!minWidthRaw) { minWidthNum = 0 } else { minWidthNum = +minWidthRaw }
if (!isFinite(minWidthNum)) { throw new Error('Width must be finite') }
let precisionNum: number | undefined if (!precisionRaw) { precisionNum = specifier === 'd' ? 0 : 'fFeE'.includes(specifier) ? 6 : undefined } else { precisionNum = +precisionRaw }
if (argIndex && +argIndex === 0) { throw new Error('Argument number must be greater than zero') }
if (argIndex && +argIndex >= callArgs.length) { throw new Error('Too few arguments') }
value = argIndex ? callArgs[+argIndex] : callArgs[i++]
switch (specifier) { case '%': return '%' case 's': return _formatString(String(value), leftJustify, minWidthNum, precisionNum, padChar) case 'c': return _formatString(String.fromCharCode(+String(value)), leftJustify, minWidthNum, precisionNum, padChar) case 'b': return _formatBaseX(value, 2, leftJustify, minWidthNum, precisionNum, padChar) case 'o': return _formatBaseX(value, 8, leftJustify, minWidthNum, precisionNum, padChar) case 'x': return _formatBaseX(value, 16, leftJustify, minWidthNum, precisionNum, padChar) case 'X': return _formatBaseX(value, 16, leftJustify, minWidthNum, precisionNum, padChar).toUpperCase() case 'u': return _formatBaseX(value, 10, leftJustify, minWidthNum, precisionNum, padChar) case 'i': case 'd': number = +String(value) || 0 number = Math.round(number - (number % 1)) prefix = number < 0 ? '-' : positiveNumberPrefix value = prefix + _pad(String(Math.abs(number)), precisionNum ?? 0, '0', false)
if (leftJustify && padChar === '0') { padChar = ' ' } return justify(String(value), prefix, leftJustify, minWidthNum, padChar) case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': { number = +String(value) prefix = number < 0 ? '-' : positiveNumberPrefix const methodIndex = 'efg'.indexOf(specifier.toLowerCase()) const absNumber = Math.abs(number) let floatResult = '' if (methodIndex === 0) { floatResult = absNumber.toExponential(precisionNum) } else if (methodIndex === 1) { floatResult = absNumber.toFixed(precisionNum) } else { floatResult = absNumber.toPrecision(precisionNum) } const justified = justify(prefix + floatResult, prefix, leftJustify, minWidthNum, padChar) return 'eEfFgG'.indexOf(specifier) % 2 === 0 ? justified : justified.toUpperCase() } default: return '' } }
try { return format.replace(regex, doFormat) } catch (_err) { return false } }
|