Repository: danielgtaylor/restish
Pull request #79 (2022-02-24 21:44:15)

feat: print displayable unicode body when possible
Previously if an unknown content type was encountered then the body would be printed as hex characters. This detects displayable unicode characters (and whitespace, and BOM) and prints them as text whenever possible. This is much more user-friendly than random hex values for things that may just be JSON or YAML or text with an incorrect or missing content type.

codecov[bot]: # [Codecov](https://codecov.io/gh/danielgtaylor/restish/pull/79?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Daniel+G.+Taylor) Report
> Merging [#79](https://codecov.io/gh/danielgtaylor/restish/pull/79?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Daniel+G.+Taylor) (425fac9) into [main](https://codecov.io/gh/danielgtaylor/restish/commit/a799f40933f7a9c151f29adde99652791a60f340?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Daniel+G.+Taylor) (a799f40) will **decrease** coverage by `0.40%`.
> The diff coverage is `6.25%`.

[![Impacted file tree graph](https://codecov.io/gh/danielgtaylor/restish/pull/79/graphs/tree.svg?width=650&height=150&src=pr&token=1BMagYibk9&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Daniel+G.+Taylor)](https://codecov.io/gh/danielgtaylor/restish/pull/79?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Daniel+G.+Taylor)

```diff
@@            Coverage Diff             @@
##             main      #79      +/-   ##
==========================================
- Coverage   55.68%   55.27%   -0.41%     
==========================================
  Files          20       20              
  Lines        1916     1932      +16     
==========================================
+ Hits         1067     1068       +1     
- Misses        703      716      +13     
- Partials      146      148       +2     
```


| [Impacted Files](https://codecov.io/gh/danielgtaylor/restish/pull/79?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Daniel+G.+Taylor) | Coverage Δ | |
|---|---|---|
| [cli/formatter.go](https://codecov.io/gh/danielgtaylor/restish/pull/79/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Daniel+G.+Taylor#diff-Y2xpL2Zvcm1hdHRlci5nbw==) | `40.52% <6.25%> (-3.16%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/danielgtaylor/restish/pull/79?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Daniel+G.+Taylor).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Daniel+G.+Taylor)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/danielgtaylor/restish/pull/79?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Daniel+G.+Taylor). Last update [a799f40...425fac9](https://codecov.io/gh/danielgtaylor/restish/pull/79?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Daniel+G.+Taylor). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Daniel+G.+Taylor).


Patch:
diff --git a/cli/formatter.go b/cli/formatter.go
index 18dc67e..2307ad3 100644
--- a/cli/formatter.go
+++ b/cli/formatter.go
@@ -10,6 +10,8 @@ import (
 	"reflect"
 	"sort"
 	"strings"
+	"unicode"
+	"unicode/utf8"
 
 	"github.com/alecthomas/chroma"
 	"github.com/alecthomas/chroma/quick"
@@ -23,6 +25,12 @@ import (
 	"github.com/eliukblau/pixterm/pkg/ansimage"
 )
 
+// DisplayRanges includes all viewable Unicode characters along with white
+// space.
+var DisplayRanges = []*unicode.RangeTable{
+	unicode.L, unicode.M, unicode.N, unicode.P, unicode.S, unicode.White_Space,
+}
+
 func init() {
 	// Simple 256-color theme for JSON/YAML output in a terminal.
 	styles.Register(chroma.MustNewStyle("cli-dark", chroma.StyleEntries{
@@ -101,6 +109,42 @@ func makeJSONSafe(obj interface{}, normalizeNumbers bool) interface{} {
 	return obj
 }
 
+// printable returns true if the given body can be printed to a terminal
+// based on displayable unicode character ranges and whitespace. If true,
+// then the body is also returned as a byte slice ready to be written to
+// stdout.
+func printable(body interface{}) ([]byte, bool) {
+	if b, ok := body.([]byte); ok {
+		// This was not a known format we could parse, and was not likely an
+		// image. If it looks like displayable text, then let's try to display
+		// it as such, up to 100KiB.
+		if len(b) < 102400 && utf8.Valid(b) {
+			display := true
+			for i, r := range string(b) {
+				if i == 0 && r == '\uFEFF' {
+					// Skip unicode BOM
+					continue
+				}
+				if i > 100 {
+					// Only examine the first 100 bytes, which is long enough to
+					// detect non-printable characters in most file preambles or
+					// magic number file signatures.
+					break
+				}
+				if !unicode.In(r, DisplayRanges...) {
+					display = false
+					break
+				}
+			}
+
+			if display {
+				return b, true
+			}
+		}
+	}
+	return nil, false
+}
+
 // Highlight a block of data with the given lexer.
 func Highlight(lexer string, data []byte) ([]byte, error) {
 	sb := &strings.Builder{}
@@ -252,6 +296,11 @@ func (f *DefaultFormatter) Format(resp Response) error {
 				}
 			}
 
+			if b, ok := printable(resp.Body); ok {
+				e = b
+				handled = true
+			}
+
 			if !handled {
 				if s, ok := resp.Body.(string); ok {
 					text += "\n" + s


Test patch:
diff --git a/cli/formatter_test.go b/cli/formatter_test.go
new file mode 100644
index 0000000..04c6243
--- /dev/null
+++ b/cli/formatter_test.go
@@ -0,0 +1,35 @@
+package cli
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestPrintable(t *testing.T) {
+	// Printable with BOM
+	var body interface{} = []byte("\uFEFF\t\r\n Just a tést!.$%^{}/")
+	_, ok := printable(body)
+	assert.True(t, ok)
+
+	// Non-printable
+	body = []byte{0}
+	_, ok = printable(body)
+	assert.False(t, ok)
+
+	// Long printable
+	tmp := make([]byte, 150)
+	for i := 0; i < 150; i++ {
+		tmp[i] = 'a'
+	}
+	_, ok = printable(tmp)
+	assert.True(t, ok)
+
+	// Too long
+	tmp = make([]byte, 1000000)
+	for i := 0; i < 1000000; i++ {
+		tmp[i] = 'a'
+	}
+	_, ok = printable(tmp)
+	assert.False(t, ok)
+}