-
Notifications
You must be signed in to change notification settings - Fork 316
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: add pretty print * docs: improve Cl.prettyPrint docs * refactor: review
- Loading branch information
1 parent
04b96d1
commit ae25ad9
Showing
3 changed files
with
266 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
/* | ||
Format Clarity Values into Clarity style readable strings | ||
eg: | ||
`Cl.uint(1)` => u1 | ||
`Cl.list(Cl.uint(1))` => (list u1) | ||
`Cl.tuple({ id: u1 })` => { id: u1 } | ||
*/ | ||
|
||
import { bytesToHex } from '@stacks/common'; | ||
import { ClarityType, ClarityValue, ListCV, TupleCV, principalToString } from '.'; | ||
|
||
function formatSpace(space: number, depth: number, end = false) { | ||
if (!space) return ' '; | ||
return `\n${' '.repeat(space * (depth - (end ? 1 : 0)))}`; | ||
} | ||
|
||
/** | ||
* @description format List clarity values in clarity style strings | ||
* with the ability to prettify the result with line break end space indentation | ||
* @example | ||
* ```ts | ||
* formatList(Cl.list([Cl.uint(1)])) | ||
* // (list u1) | ||
* | ||
* formatList(Cl.list([Cl.uint(1)]), 2) | ||
* // (list | ||
* // u1 | ||
* // ) | ||
* ``` | ||
*/ | ||
function formatList(cv: ListCV, space: number, depth = 1): string { | ||
if (cv.list.length === 0) return '(list)'; | ||
|
||
const spaceBefore = formatSpace(space, depth, false); | ||
const endSpace = space ? formatSpace(space, depth, true) : ''; | ||
|
||
const items = cv.list.map(v => prettyPrintWithDepth(v, space, depth)).join(spaceBefore); | ||
|
||
return `(list${spaceBefore}${items}${endSpace})`; | ||
} | ||
|
||
/** | ||
* @description format Tuple clarity values in clarity style strings | ||
* with the ability to prettify the result with line break end space indentation | ||
* @example | ||
* ```ts | ||
* formatTuple(Cl.tuple({ id: Cl.uint(1) })) | ||
* // { id: u1 } | ||
* | ||
* formatTuple(Cl.tuple({ id: Cl.uint(1) }, 2)) | ||
* // { | ||
* // id: u1 | ||
* // } | ||
* ``` | ||
*/ | ||
function formatTuple(cv: TupleCV, space: number, depth = 1): string { | ||
if (Object.keys(cv.data).length === 0) return '{}'; | ||
|
||
const items: string[] = []; | ||
for (const [key, value] of Object.entries(cv.data)) { | ||
items.push(`${key}: ${prettyPrintWithDepth(value, space, depth)}`); | ||
} | ||
|
||
const spaceBefore = formatSpace(space, depth, false); | ||
const endSpace = formatSpace(space, depth, true); | ||
|
||
return `{${spaceBefore}${items.join(`,${spaceBefore}`)}${endSpace}}`; | ||
} | ||
|
||
function exhaustiveCheck(param: never): never { | ||
throw new Error(`invalid clarity value type: ${param}`); | ||
} | ||
|
||
// the exported function should not expose the `depth` argument | ||
function prettyPrintWithDepth(cv: ClarityValue, space = 0, depth: number): string { | ||
if (cv.type === ClarityType.BoolFalse) return 'false'; | ||
if (cv.type === ClarityType.BoolTrue) return 'true'; | ||
|
||
if (cv.type === ClarityType.Int) return cv.value.toString(); | ||
if (cv.type === ClarityType.UInt) return `u${cv.value.toString()}`; | ||
|
||
if (cv.type === ClarityType.StringASCII) return `"${cv.data}"`; | ||
if (cv.type === ClarityType.StringUTF8) return `u"${cv.data}"`; | ||
|
||
if (cv.type === ClarityType.PrincipalContract) return `'${principalToString(cv)}`; | ||
if (cv.type === ClarityType.PrincipalStandard) return `'${principalToString(cv)}`; | ||
|
||
if (cv.type === ClarityType.Buffer) return `0x${bytesToHex(cv.buffer)}`; | ||
|
||
if (cv.type === ClarityType.OptionalNone) return 'none'; | ||
if (cv.type === ClarityType.OptionalSome) | ||
return `(some ${prettyPrintWithDepth(cv.value, space, depth)})`; | ||
|
||
if (cv.type === ClarityType.ResponseOk) | ||
return `(ok ${prettyPrintWithDepth(cv.value, space, depth)})`; | ||
if (cv.type === ClarityType.ResponseErr) | ||
return `(err ${prettyPrintWithDepth(cv.value, space, depth)})`; | ||
|
||
if (cv.type === ClarityType.List) { | ||
return formatList(cv, space, depth + 1); | ||
} | ||
if (cv.type === ClarityType.Tuple) { | ||
return formatTuple(cv, space, depth + 1); | ||
} | ||
|
||
// make sure that we exhausted all ClarityTypes | ||
exhaustiveCheck(cv); | ||
} | ||
|
||
/** | ||
* @description format clarity values in clarity style strings | ||
* with the ability to prettify the result with line break end space indentation | ||
* @param cv The Clarity Value to format | ||
* @param space The indentation size of the output string. There's no indentation and no line breaks if space = 0 | ||
* @example | ||
* ```ts | ||
* prettyPrint(Cl.tuple({ id: Cl.some(Cl.uint(1)) })) | ||
* // { id: (some u1) } | ||
* | ||
* prettyPrint(Cl.tuple({ id: Cl.uint(1) }, 2)) | ||
* // { | ||
* // id: u1 | ||
* // } | ||
* ``` | ||
*/ | ||
export function prettyPrint(cv: ClarityValue, space = 0): string { | ||
return prettyPrintWithDepth(cv, space, 0); | ||
} |
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,136 @@ | ||
import { Cl } from '../src'; | ||
|
||
describe.only('test format of Stacks.js clarity values into clarity style strings', () => { | ||
it('formats basic types', () => { | ||
expect(Cl.prettyPrint(Cl.bool(true))).toStrictEqual('true'); | ||
expect(Cl.prettyPrint(Cl.bool(false))).toStrictEqual('false'); | ||
expect(Cl.prettyPrint(Cl.none())).toStrictEqual('none'); | ||
|
||
expect(Cl.prettyPrint(Cl.int(1))).toStrictEqual('1'); | ||
expect(Cl.prettyPrint(Cl.int(10n))).toStrictEqual('10'); | ||
|
||
expect(Cl.prettyPrint(Cl.stringAscii('hello world!'))).toStrictEqual('"hello world!"'); | ||
expect(Cl.prettyPrint(Cl.stringUtf8('hello world!'))).toStrictEqual('u"hello world!"'); | ||
}); | ||
|
||
it('formats principal', () => { | ||
const addr = 'ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG'; | ||
|
||
expect(Cl.prettyPrint(Cl.standardPrincipal(addr))).toStrictEqual( | ||
"'ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG" | ||
); | ||
expect(Cl.prettyPrint(Cl.contractPrincipal(addr, 'contract'))).toStrictEqual( | ||
"'ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG.contract" | ||
); | ||
}); | ||
|
||
it('formats optional some', () => { | ||
expect(Cl.prettyPrint(Cl.some(Cl.uint(1)))).toStrictEqual('(some u1)'); | ||
expect(Cl.prettyPrint(Cl.some(Cl.stringAscii('btc')))).toStrictEqual('(some "btc")'); | ||
expect(Cl.prettyPrint(Cl.some(Cl.stringUtf8('stx 🚀')))).toStrictEqual('(some u"stx 🚀")'); | ||
}); | ||
|
||
it('formats reponse', () => { | ||
expect(Cl.prettyPrint(Cl.ok(Cl.uint(1)))).toStrictEqual('(ok u1)'); | ||
expect(Cl.prettyPrint(Cl.error(Cl.uint(1)))).toStrictEqual('(err u1)'); | ||
expect(Cl.prettyPrint(Cl.ok(Cl.some(Cl.uint(1))))).toStrictEqual('(ok (some u1))'); | ||
expect(Cl.prettyPrint(Cl.ok(Cl.none()))).toStrictEqual('(ok none)'); | ||
}); | ||
|
||
it('formats buffer', () => { | ||
expect(Cl.prettyPrint(Cl.buffer(Uint8Array.from([98, 116, 99])))).toStrictEqual('0x627463'); | ||
expect(Cl.prettyPrint(Cl.bufferFromAscii('stx'))).toStrictEqual('0x737478'); | ||
}); | ||
|
||
it('formats lists', () => { | ||
expect(Cl.prettyPrint(Cl.list([1, 2, 3].map(Cl.int)))).toStrictEqual('(list 1 2 3)'); | ||
expect(Cl.prettyPrint(Cl.list([1, 2, 3].map(Cl.uint)))).toStrictEqual('(list u1 u2 u3)'); | ||
expect(Cl.prettyPrint(Cl.list(['a', 'b', 'c'].map(Cl.stringUtf8)))).toStrictEqual( | ||
'(list u"a" u"b" u"c")' | ||
); | ||
|
||
expect(Cl.prettyPrint(Cl.list([]))).toStrictEqual('(list)'); | ||
}); | ||
|
||
it('can prettify lists on multiple lines', () => { | ||
const list = Cl.list([1, 2, 3].map(Cl.int)); | ||
expect(Cl.prettyPrint(list)).toStrictEqual('(list 1 2 3)'); | ||
expect(Cl.prettyPrint(list, 2)).toStrictEqual('(list\n 1\n 2\n 3\n)'); | ||
|
||
expect(Cl.prettyPrint(Cl.list([]), 2)).toStrictEqual('(list)'); | ||
}); | ||
|
||
it('formats tuples', () => { | ||
expect(Cl.prettyPrint(Cl.tuple({ counter: Cl.uint(10) }))).toStrictEqual('{ counter: u10 }'); | ||
expect( | ||
Cl.prettyPrint(Cl.tuple({ counter: Cl.uint(10), state: Cl.ok(Cl.stringUtf8('valid')) })) | ||
).toStrictEqual('{ counter: u10, state: (ok u"valid") }'); | ||
|
||
expect(Cl.prettyPrint(Cl.tuple({}))).toStrictEqual('{}'); | ||
}); | ||
|
||
it('can prettify tuples on multiple lines', () => { | ||
const tuple = Cl.tuple({ counter: Cl.uint(10) }); | ||
|
||
expect(Cl.prettyPrint(tuple)).toStrictEqual('{ counter: u10 }'); | ||
expect(Cl.prettyPrint(tuple, 2)).toStrictEqual('{\n counter: u10\n}'); | ||
|
||
expect(Cl.prettyPrint(Cl.tuple({}), 2)).toStrictEqual('{}'); | ||
}); | ||
|
||
it('prettifies nested list and tuples', () => { | ||
// test that the right indentation level is applied for nested composite types | ||
const addr = 'ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG'; | ||
const value = Cl.tuple({ | ||
id: Cl.uint(1), | ||
messageAscii: Cl.stringAscii('hello world'), | ||
someMessageUtf8: Cl.some(Cl.stringUtf8('hello world')), | ||
items: Cl.some( | ||
Cl.list([ | ||
Cl.ok( | ||
Cl.tuple({ | ||
id: Cl.uint(1), | ||
owner: Cl.some(Cl.standardPrincipal(addr)), | ||
valid: Cl.ok(Cl.uint(2)), | ||
history: Cl.some(Cl.list([Cl.uint(1), Cl.uint(2)])), | ||
}) | ||
), | ||
Cl.ok( | ||
Cl.tuple({ | ||
id: Cl.uint(2), | ||
owner: Cl.none(), | ||
valid: Cl.error(Cl.uint(1000)), | ||
history: Cl.none(), | ||
}) | ||
), | ||
]) | ||
), | ||
}); | ||
|
||
const expected = `{ | ||
id: u1, | ||
messageAscii: "hello world", | ||
someMessageUtf8: (some u"hello world"), | ||
items: (some (list | ||
(ok { | ||
id: u1, | ||
owner: (some 'ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG), | ||
valid: (ok u2), | ||
history: (some (list | ||
u1 | ||
u2 | ||
)) | ||
}) | ||
(ok { | ||
id: u2, | ||
owner: none, | ||
valid: (err u1000), | ||
history: none | ||
}) | ||
)) | ||
}`; | ||
|
||
const result = Cl.prettyPrint(value, 2); | ||
expect(result).toStrictEqual(expected); | ||
}); | ||
}); |