Skip to content

Commit

Permalink
Add support for pretty priting CBOR values (#6)
Browse files Browse the repository at this point in the history
Also hex representation is always displayed now so that users can easily copy it.
  • Loading branch information
boreq authored Sep 20, 2024
1 parent 56bf289 commit 8b74fb7
Show file tree
Hide file tree
Showing 19 changed files with 384 additions and 131 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.22
go-version: 1.23
id: go

- name: Check out code into the Go module directory
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,12 @@ To view `bolt.database` using Bolt UI execute the following command:
The security features can be disabled by using command line flags if you are
using the program locally.

## Building

### Frontend

1. You need to downgrade to Node v16 to build the frontend project.
2. Use `_tools/build_frontend.sh`.

[actions]: https://github.com/boreq/bolt-ui/actions
[screenshot]: https://user-images.githubusercontent.com/1935975/128639070-6c335b7a-26d9-4575-ae94-2250e31149c1.png
44 changes: 44 additions & 0 deletions display/cbor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package display

import (
"bufio"
"bytes"
"github.com/acarl005/stripansi"
"github.com/boreq/errors"
"github.com/fxamacker/cbor/v2"
refmtcbor "github.com/polydawn/refmt/cbor"
refmtpretty "github.com/polydawn/refmt/pretty"
refmtshared "github.com/polydawn/refmt/shared"
)

type PrettifierCBOR struct {
}

func NewPrettifierCBOR() *PrettifierCBOR {
return &PrettifierCBOR{}
}

func (p PrettifierCBOR) Prettify(b []byte) (string, error) {
if err := cbor.Wellformed(b); err != nil {
return "", errors.Wrap(err, "invalid cbor")
}
return cborToText(b)
}

// from https://github.com/boreq/bolt-ui/pull/2
func cborToText(dataCBOR []byte) (string, error) {
var buf bytes.Buffer
bufWriter := bufio.NewWriter(&buf)
err := refmtshared.TokenPump{
TokenSource: refmtcbor.NewDecoder(refmtcbor.DecodeOptions{}, bytes.NewReader(dataCBOR)),
TokenSink: refmtpretty.NewEncoder(bufWriter),
}.Run()
if err != nil {
return "", errors.Wrap(err, "tokenpump run failed")
}
err = bufWriter.Flush()
if err != nil {
return "", errors.Wrap(err, "error flushing the buffer")
}
return stripansi.Strip(buf.String()), nil
}
25 changes: 25 additions & 0 deletions display/json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package display

import (
"bytes"
"encoding/json"
"github.com/boreq/errors"
)

type PrettifierJSON struct {
}

func NewPrettifierJSON() *PrettifierJSON {
return &PrettifierJSON{}
}

func (p PrettifierJSON) Prettify(b []byte) (string, error) {
if json.Valid(b) {
buf := &bytes.Buffer{}
if err := json.Indent(buf, b, "", " "); err != nil {
return "", errors.Wrap(err, "error indenting")
}
return buf.String(), nil
}
return "", errors.New("invalid json")
}
63 changes: 63 additions & 0 deletions display/pretty.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package display

import (
"github.com/boreq/errors"
)

type ContentType struct {
s string
}

var (
ContentTypeJSON ContentType = ContentType{"json"}
ContentTypeCBOR ContentType = ContentType{"cbor"}
ContentTypeString ContentType = ContentType{"string"}
)

type Prettifier interface {
Prettify(b []byte) (string, error)
}

type Prettified struct {
Type ContentType
Value string
}

type prettifier struct {
Prettifier Prettifier
ContentType ContentType
}

type Pretty struct {
prettifiers []prettifier
}

func NewPretty() *Pretty {
return &Pretty{prettifiers: []prettifier{
{
Prettifier: NewPrettifierCBOR(),
ContentType: ContentTypeCBOR,
},
{
Prettifier: NewPrettifierJSON(),
ContentType: ContentTypeJSON,
},
{
Prettifier: NewPrettifierString(),
ContentType: ContentTypeString,
},
}}
}

func (p *Pretty) Print(b []byte) (Prettified, error) {
for _, prettifier := range p.prettifiers {
v, err := prettifier.Prettifier.Prettify(b)
if err == nil {
return Prettified{
Type: prettifier.ContentType,
Value: v,
}, nil
}
}
return Prettified{}, errors.New("no prettifiers completed successfully")
}
61 changes: 61 additions & 0 deletions display/pretty_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package display_test

import (
"github.com/boreq/bolt-ui/display"
"github.com/fxamacker/cbor/v2"
"github.com/stretchr/testify/require"
"testing"
)

func TestPretty(t *testing.T) {
cbor, err := cbor.Marshal(struct {
Field1 string `cbor:"1,keyasint"`
Field2 int `cbor:"2,keyasint"`
}{
Field1: "string",
Field2: 123,
})
require.NoError(t, err)

testCases := []struct {
Name string
Bytes []byte
Result display.Prettified
}{
{
Name: "json",
Bytes: []byte(`{"some":"json"}`),
Result: display.Prettified{
Type: display.ContentTypeJSON,
Value: `{
"some": "json"
}`,
},
},
{
Name: "cbor",
Bytes: cbor,
Result: display.Prettified{
Type: display.ContentTypeCBOR,
Value: "Map<len:2> {\n\r\t1: \"string\"\n\r\t2: 123\n\r}\n\r",
},
},
{
Name: "string",
Bytes: []byte("some_string"),
Result: display.Prettified{
Type: display.ContentTypeString,
Value: "some_string",
},
},
}

for _, testCase := range testCases {
t.Run(testCase.Name, func(t *testing.T) {
p := display.NewPretty()
result, err := p.Print(testCase.Bytes)
require.NoError(t, err)
require.Equal(t, testCase.Result, result)
})
}
}
29 changes: 29 additions & 0 deletions display/string.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package display

import (
"github.com/boreq/errors"
"unicode"
)

type PrettifierString struct {
}

func NewPrettifierString() *PrettifierString {
return &PrettifierString{}
}

func (p *PrettifierString) Prettify(b []byte) (string, error) {
if p.canDisplayAsString(b) {
return string(b), nil
}
return "", errors.New("can't display as string")
}

func (p *PrettifierString) canDisplayAsString(b []byte) bool {
for _, rne := range string(b) {
if !unicode.IsGraphic(rne) && !unicode.IsSpace(rne) {
return false
}
}
return true
}
4 changes: 4 additions & 0 deletions frontend/src/components/Value.scss
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@
font-family: monospace;
}

.value-header {
color: $text-color-dimmed;
}

.value-empty {
color: $text-color-dimmed;
}
Expand Down
27 changes: 17 additions & 10 deletions frontend/src/components/Value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,35 +19,42 @@ export default class Value extends Vue {
return 'nil';
}

if (this.entry.value.str) {
return 'string';
if (this.entry.value.pretty) {
return this.entry.value.pretty.content_type;
}

return 'hex';
return 'unknown';
}

get formatTooltip(): string {
if (!this.entry.value) {
return 'The value is empty.';
}

if (this.entry.value.str) {
return 'Displaying the value as string.';
if (this.entry.value.pretty) {
return `Recognized content type ${this.entry.value.pretty.content_type} for pretty printing.`;
}

return 'Display the bytes using hexadecimal encoding.';
return 'Pretty printing is unavailable due to unrecognized content type of this value.';
}

get valueString(): string {
get valuePretty(): string {
if (!this.entry.value) {
return null;
}

if (this.entry.value.str) {
return this.entry.value.str;
if (this.entry.value.pretty) {
return this.entry.value.pretty.value;
}

return this.entry.value.hex;
return null;
}

get valueHex(): string {
if (!this.entry.value) {
return null;
}

return this.entry.value.hex;
}
}
12 changes: 11 additions & 1 deletion frontend/src/components/Value.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,17 @@
</div>

<div class="value-string" v-if="entry.value">
{{ valueString }}
<div v-if="valuePretty">
<div class="value-header">
Pretty printed
</div>
<pre class="value-string"><code>{{ valuePretty }}</code></pre>
</div>

<div class="value-header">
Raw value as hex
</div>
<pre class="value-string"><code>{{ valueHex }}</code></pre>
</div>

<div class="value-empty" v-else>
Expand Down
7 changes: 6 additions & 1 deletion frontend/src/dto/Entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,10 @@ export class Key {

export class Value {
hex: string;
str: string;
pretty: Pretty;
}

export class Pretty {
content_type: string;
value: string;
}
Loading

0 comments on commit 8b74fb7

Please sign in to comment.