From 2c844ae9f2f5be0afdd17a9463afc22b8be49725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radu=20Lucu=C8=9B?= Date: Tue, 19 Mar 2024 13:55:08 +0200 Subject: [PATCH] misc: update location output, add custom area printer (#96) * misc: update location output, add custom area printer * update AreaClear --- .github/workflows/release.yml | 2 +- .github/workflows/tests.yml | 2 +- README.md | 42 +++--- go.mod | 32 +---- go.sum | 137 ++---------------- view/context.go | 3 - view/default.go | 4 +- view/default_test.go | 20 +-- view/infinite.go | 47 +++---- view/infinite_test.go | 257 ++++++++++++++-------------------- view/json.go | 2 +- view/json_test.go | 1 + view/latency.go | 31 ++-- view/latency_test.go | 47 +++---- view/output.go | 97 ++++--------- view/output_test.go | 38 +++-- view/printer.go | 96 ++++++++++++- view/summary.go | 2 +- view/summary_test.go | 9 +- 19 files changed, 356 insertions(+), 513 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a350d8b..d2be30d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,7 +22,7 @@ jobs: - uses: actions/setup-go@v5 with: - go-version: ">=1.21.3" + go-version: ">=1.22" cache: true - uses: goreleaser/goreleaser-action@v4 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 347ca9b..a8bafa2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - go: ["1.21"] + go: ["1.22"] os: [ubuntu-latest, macOS-latest, windows-latest] name: ${{ matrix.os }} Go ${{ matrix.go }} Tests steps: diff --git a/README.md b/README.md index 63c5e8d..c2ac5b3 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ For example, if you want to run ping from a probe in Seattle that is also part o ```bash globalping ping google.com from Comcast+Seattle -> NA, US, (WA), Seattle, ASN:7922, Comcast Cable Communications, LLC +> Seattle (WA), US, NA, Comcast Cable Communications, LLC (AS33650) PING (142.250.217.78) 56(84) bytes of data. 64 bytes from sea09s29-in-f14.1e100.net (142.250.217.78): icmp_seq=1 ttl=58 time=14.0 ms 64 bytes from sea09s29-in-f14.1e100.net (142.250.217.78): icmp_seq=2 ttl=58 time=14.5 ms @@ -141,22 +141,22 @@ With the following command, we execute four ping commands at four different loca ```bash globalping ping google.com from Amazon,Germany,USA,Dallas --limit 4 --latency -> AS, KR, Seoul, ASN:16509, Amazon.com, Inc. (aws-ap-northeast-2) +> Seoul, KR, AS, Amazon.com, Inc. (AS16509) (aws-ap-northeast-2) Min: 33.163 ms Max: 33.256 ms Avg: 33.22 ms -> EU, DE, Frankfurt, ASN:16276, OVH SAS +> Frankfurt, DE, EU, DE, OVH SAS (AS16276) Min: 1.221 ms Max: 1.291 ms Avg: 1.264 ms -> NA, US, (IL), Chicago, ASN:174, Cogent Communications +> Chicago (IL), US, NA, Cogent Communications (AS174) Min: 112.405 ms Max: 112.686 ms Avg: 112.528 ms -> NA, US, (TX), Dallas, ASN:393336, Catalyst Host LLC +> Dallas (TX), US, NA, Catalyst Host LLC (AS393336) Min: 1.579 ms Max: 1.588 ms Avg: 1.584 ms @@ -179,7 +179,7 @@ Include a link at the bottom of your results using the `--share` flag to view an ```bash globalping dns google.com from gcp-asia-south1 --share -> AS, IN, Mumbai, ASN:396982, Google LLC (gcp-asia-south1) +> Mumbai, IN, AS, Google LLC (AS396982) (gcp-asia-south1) ; <<>> DiG 9.16.37-Debian <<>> -t A google.com -p 53 -4 +timeout=3 +tries=2 +nocookie +nsid ;; global options: +cmd ;; Got answer: @@ -207,7 +207,7 @@ You can select the same probes used in a previous measurement by passing the mea ```bash globalping dns google.com from rvasVvKnj48cxNjC -> AS, IN, Mumbai, ASN:396982, Google LLC (gcp-asia-south1) +> Mumbai, IN, AS, Google LLC (AS396982) (gcp-asia-south1) ; <<>> DiG 9.16.42-Debian <<>> -t A google.com -p 53 -4 +timeout=3 +tries=2 +nocookie +nosplit +nsid ;; global options: +cmd ;; Got answer: @@ -234,43 +234,43 @@ Use `[@1 | first, @2 ... @-2, @-1 | last | previous]` to select the probes from ```bash globalping ping google.com from USA --latency -> NA, US, (VA), Ashburn, ASN:213230, Hetzner Online GmbH +> Ashburn (VA), US, NA, Hetzner Online GmbH (AS213230) Min: 7.314 ms Max: 7.413 ms Avg: 7.359 ms globalping ping google.com from Germany --latency -> EU, DE, Falkenstein, ASN:24940, Hetzner Online GmbH +> Falkenstein, DE, EU, Hetzner Online GmbH (AS24940) Min: 4.87 ms Max: 4.936 ms Avg: 4.911 ms globalping ping google.com from previous --latency -> EU, DE, Falkenstein, ASN:24940, Hetzner Online GmbH +> Falkenstein, DE, EU, Hetzner Online GmbH (AS24940) Min: 4.87 ms Max: 4.936 ms Avg: 4.911 ms globalping ping google.com from @-1 --latency -> EU, DE, Falkenstein, ASN:24940, Hetzner Online GmbH +> Falkenstein, DE, EU, Hetzner Online GmbH (AS24940) Min: 4.87 ms Max: 4.936 ms Avg: 4.911 ms globalping ping google.com from @-2 --latency -> NA, US, (VA), Ashburn, ASN:213230, Hetzner Online GmbH +> Ashburn (VA), US, NA, Hetzner Online GmbH (AS213230) Min: 7.314 ms Max: 7.413 ms Avg: 7.359 ms globalping ping google.com from first --latency -> NA, US, (VA), Ashburn, ASN:213230, Hetzner Online GmbH +> Ashburn (VA), US, NA, Hetzner Online GmbH (AS213230) Min: 7.314 ms Max: 7.413 ms Avg: 7.359 ms globalping ping google.com from @1 --latency -> NA, US, (VA), Ashburn, ASN:213230, Hetzner Online GmbH +> Ashburn (VA), US, NA, Hetzner Online GmbH (AS213230) Min: 7.314 ms Max: 7.413 ms Avg: 7.359 ms @@ -287,7 +287,7 @@ This means that eventually you will run out of credits and the test will stop. ```bash globalping ping cdn.jsdelivr.net from Europe --infinite -> EU, GB, London, ASN:40676, Psychz Networks +> London, GB, EU, Psychz Networks (AS40676) PING cdn.jsdelivr.net (151.101.1.229) 56(84) bytes of data. 64 bytes from 151.101.1.229 (151.101.1.229): icmp_seq=1 ttl=59 time=0.54 ms 64 bytes from 151.101.1.229 (151.101.1.229): icmp_seq=2 ttl=59 time=0.42 ms @@ -298,12 +298,12 @@ If you select multiple probes when using `--infinite` the output will change to ```bash globalping ping cdn.jsdelivr.net from Europe --limit 5 --infinite -Location | Sent | Loss | Last | Min | Avg | Max -EU, GB, London, ASN:16276, OVH SAS | 22 | 0.00% | 3.33 ms | 3.07 ms | 3.20 ms | 3.33 ms -EU, DE, Falkenstein, ASN:24940, Hetzner Online GmbH | 22 | 0.00% | 5.41 ms | 5.30 ms | 5.78 ms | 13.1 ms -EU, AT, Vienna, ASN:57169, EDIS GmbH | 22 | 0.00% | 0.47 ms | 0.46 ms | 0.56 ms | 0.88 ms -EU, SE, Stockholm, ASN:20473, The Constant Company, LLC | 22 | 0.00% | 1.03 ms | 0.83 ms | 1.15 ms | 4.66 ms -EU, ES, Madrid, ASN:47787, EDGOO NETWORKS LLC | 22 | 0.00% | 0.24 ms | 0.13 ms | 0.26 ms | 0.42 ms +Location | Sent | Loss | Last | Min | Avg | Max +London, GB, EU, OVH SAS (AS16276) | 22 | 0.00% | 3.33 ms | 3.07 ms | 3.20 ms | 3.33 ms +Falkenstein, DE, EU, Hetzner Online GmbH (AS24940) | 22 | 0.00% | 5.41 ms | 5.30 ms | 5.78 ms | 13.1 ms +Vienna, AT, EU, EDIS GmbH (AS57169) | 22 | 0.00% | 0.47 ms | 0.46 ms | 0.56 ms | 0.88 ms +Stockholm, SE, EU, The Constant Company, LLC (AS20473) | 22 | 0.00% | 1.03 ms | 0.83 ms | 1.15 ms | 4.66 ms +Madrid, ES, EU, EDGOO NETWORKS LLC (AS47787) | 22 | 0.00% | 0.24 ms | 0.13 ms | 0.26 ms | 0.42 ms ^C ``` diff --git a/go.mod b/go.mod index f550336..ed93d5a 100644 --- a/go.mod +++ b/go.mod @@ -1,49 +1,31 @@ module github.com/jsdelivr/globalping-cli -go 1.21 - -toolchain go1.21.3 +go 1.22 require ( github.com/andybalholm/brotli v1.1.0 - github.com/charmbracelet/lipgloss v0.9.1 - github.com/icza/backscanner v0.0.0-20230330133933-bf6beb754c70 + github.com/icza/backscanner v0.0.0-20240221180818-f23e3ba0e79f github.com/mattn/go-runewidth v0.0.15 github.com/pkg/errors v0.9.1 - github.com/pterm/pterm v0.12.78 github.com/shirou/gopsutil v3.21.11+incompatible github.com/spf13/cobra v1.8.0 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 go.uber.org/mock v0.4.0 + golang.org/x/term v0.18.0 ) require ( - atomicgo.dev/cursor v0.2.0 // indirect - atomicgo.dev/keyboard v0.2.9 // indirect - atomicgo.dev/schedule v0.1.0 // indirect - github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/containerd/console v1.0.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-ole/go-ole v1.3.0 // indirect - github.com/gookit/color v1.5.4 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/kr/pretty v0.3.1 // indirect - github.com/lithammer/fuzzysearch v1.1.8 // indirect - github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/muesli/reflow v0.3.0 // indirect - github.com/muesli/termenv v0.15.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rivo/uniseg v0.4.4 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/tklauser/go-sysconf v0.3.13 // indirect github.com/tklauser/numcpus v0.7.0 // indirect - github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - github.com/yusufpapurcu/wmi v1.2.3 // indirect - golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect - golang.org/x/sys v0.16.0 // indirect - golang.org/x/term v0.16.0 // indirect - golang.org/x/text v0.14.0 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + golang.org/x/sys v0.18.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 602fea7..9f59a1a 100644 --- a/go.sum +++ b/go.sum @@ -1,53 +1,18 @@ -atomicgo.dev/assert v0.0.2 h1:FiKeMiZSgRrZsPo9qn/7vmr7mCsh5SZyXY4YGYiYwrg= -atomicgo.dev/assert v0.0.2/go.mod h1:ut4NcI3QDdJtlmAxQULOmA13Gz6e2DWbSAS8RUOmNYQ= -atomicgo.dev/cursor v0.2.0 h1:H6XN5alUJ52FZZUkI7AlJbUc1aW38GWZalpYRPpoPOw= -atomicgo.dev/cursor v0.2.0/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU= -atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8= -atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ= -atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs= -atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU= -github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs= -github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8= -github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII= -github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k= -github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI= -github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c= -github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE= -github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4= -github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= -github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= -github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= -github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg= -github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I= -github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= -github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= -github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= -github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= -github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= -github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= -github.com/icza/backscanner v0.0.0-20230330133933-bf6beb754c70 h1:xrd41BUTgqxyYFfFwGdt/bnwS8KNYzPraj8WgvJ5NWk= -github.com/icza/backscanner v0.0.0-20230330133933-bf6beb754c70/go.mod h1:GYeBD1CF7AqnKZK+UCytLcY3G+UKo0ByXX/3xfdNyqQ= +github.com/icza/backscanner v0.0.0-20240221180818-f23e3ba0e79f h1:EKPpaKkARuHjoV/ZKzk3vqbSJXULRSivDCQhL+tF77Y= +github.com/icza/backscanner v0.0.0-20240221180818-f23e3ba0e79f/go.mod h1:GYeBD1CF7AqnKZK+UCytLcY3G+UKo0ByXX/3xfdNyqQ= github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6 h1:8UsGZ2rr2ksmEru6lToqnXgA8Mz1DP11X4zSJ159C3k= github.com/icza/mighty v0.0.0-20180919140131-cfd07d671de6/go.mod h1:xQig96I1VNBDIWGCdTt54nHt6EeI639SmHycLYL7FkA= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= -github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= -github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= -github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -55,121 +20,43 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= -github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= -github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= -github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= -github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= -github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI= -github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg= -github.com/pterm/pterm v0.12.30/go.mod h1:MOqLIyMOgmTDz9yorcYbcw+HsgoZo3BQfg2wtl3HEFE= -github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEejaWgXU= -github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE= -github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8= -github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s= -github.com/pterm/pterm v0.12.78 h1:QTWKaIAa4B32GKwqVXtu9m1DUMgWw3VRljMkMevX+b8= -github.com/pterm/pterm v0.12.78/go.mod h1:1v/gzOF1N0FsjbgTHZ1wVycRkKiatFvJSJC4IGaQAAo= -github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= -github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= -github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4= github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0= github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4= github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY= -github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= -github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= -golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= -golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/view/context.go b/view/context.go index bdba0dc..cd33fb8 100644 --- a/view/context.go +++ b/view/context.go @@ -3,8 +3,6 @@ package view import ( "math" "time" - - "github.com/pterm/pterm" ) type Context struct { @@ -35,7 +33,6 @@ type Context struct { RecordToSession bool // Record measurement to session history - Area *pterm.AreaPrinter Hostname string IsHeaderPrinted bool AggregatedStats []*MeasurementStats diff --git a/view/default.go b/view/default.go index c08cfbc..afdab10 100644 --- a/view/default.go +++ b/view/default.go @@ -16,7 +16,7 @@ func (v *viewer) outputDefault(id string, data *globalping.Measurement, m *globa } // Output slightly different format if state is available - v.printer.Println(generateProbeInfo(result, !v.ctx.CIMode)) + v.printer.Println(v.getProbeInfo(result)) if v.isBodyOnlyHttpGet(m) { v.printer.Println(strings.TrimSpace(result.Result.RawBody)) @@ -26,6 +26,6 @@ func (v *viewer) outputDefault(id string, data *globalping.Measurement, m *globa } if v.ctx.Share { - v.printer.Println(formatWithLeadingArrow(shareMessage(id), !v.ctx.CIMode)) + v.printer.Println(v.getShareMessage(id)) } } diff --git a/view/default_test.go b/view/default_test.go index 710eb5b..af538c2 100644 --- a/view/default_test.go +++ b/view/default_test.go @@ -69,10 +69,10 @@ func Test_Output_Default_HTTP_Get(t *testing.T) { viewer.Output(measurementID1, m) - assert.Equal(t, `> EU, DE, Berlin, ASN:123, Network 1 + assert.Equal(t, `> Berlin, DE, EU, Network 1 (AS123) Body 1 -> NA, US, (NY), New York, ASN:567, Network 2 +> New York (NY), US, NA, Network 2 (AS567) Body 2 `, w.String()) } @@ -135,10 +135,10 @@ func Test_Output_Default_HTTP_Get_Share(t *testing.T) { viewer.Output(measurementID1, m) - assert.Equal(t, fmt.Sprintf(`> EU, DE, Berlin, ASN:123, Network 1 + assert.Equal(t, fmt.Sprintf(`> Berlin, DE, EU, Network 1 (AS123) Body 1 -> NA, US, (NY), New York, ASN:567, Network 2 +> New York (NY), US, NA, Network 2 (AS567) Body 2 > View the results online: https://www.jsdelivr.com/globalping?measurement=%s `, measurementID1), w.String()) @@ -201,11 +201,11 @@ func Test_Output_Default_HTTP_Get_Full(t *testing.T) { viewer.Output(measurementID1, m) - assert.Equal(t, `> EU, DE, Berlin, ASN:123, Network 1 + assert.Equal(t, `> Berlin, DE, EU, Network 1 (AS123) Headers 1 Body 1 -> NA, US, (NY), New York, ASN:567, Network 2 +> New York (NY), US, NA, Network 2 (AS567) Headers 2 Body 2 `, w.String()) @@ -266,10 +266,10 @@ func Test_Output_Default_HTTP_Head(t *testing.T) { viewer.Output(measurementID1, m) - assert.Equal(t, `> EU, DE, Berlin, ASN:123, Network 1 + assert.Equal(t, `> Berlin, DE, EU, Network 1 (AS123) Headers 1 -> NA, US, (NY), New York, ASN:567, Network 2 +> New York (NY), US, NA, Network 2 (AS567) Headers 2 `, w.String()) } @@ -321,10 +321,10 @@ func Test_Output_Default_Ping(t *testing.T) { viewer.Output(measurementID1, m) - assert.Equal(t, `> EU, DE, Berlin, ASN:123, Network 1 + assert.Equal(t, `> Berlin, DE, EU, Network 1 (AS123) Ping Results 1 -> NA, US, (NY), New York, ASN:567, Network 2 +> New York (NY), US, NA, Network 2 (AS567) Ping Results 2 `, w.String()) } diff --git a/view/infinite.go b/view/infinite.go index c2f4c88..6c90a1e 100644 --- a/view/infinite.go +++ b/view/infinite.go @@ -10,7 +10,6 @@ import ( "github.com/jsdelivr/globalping-cli/globalping" "github.com/mattn/go-runewidth" - "github.com/pterm/pterm" ) // Table defaults @@ -54,7 +53,7 @@ func (v *viewer) outputStreamingPackets(m *globalping.Measurement) error { hm.Stats[0] = parsedOutput.Stats if !v.ctx.IsHeaderPrinted { v.ctx.Hostname = parsedOutput.Hostname - v.printer.Println(generateProbeInfo(probeMeasurement, !v.ctx.CIMode)) + v.printer.Println(v.getProbeInfo(probeMeasurement)) v.printer.Printf("PING %s (%s) %s bytes of data.\n", parsedOutput.Hostname, parsedOutput.Address, @@ -74,23 +73,18 @@ func (v *viewer) outputStreamingPackets(m *globalping.Measurement) error { } func (v *viewer) outputTableView(m *globalping.Measurement) error { - var err error if len(v.ctx.AggregatedStats) == 0 { // Initialize state v.ctx.AggregatedStats = make([]*MeasurementStats, len(m.Results)) for i := range m.Results { v.ctx.AggregatedStats[i] = NewMeasurementStats() } - // Create new writer - v.ctx.Area, err = pterm.DefaultArea.Start() - if err != nil { - return errors.New("failed to start writer: " + err.Error()) - } } hm := v.ctx.History.Find(m.ID) - o, newStats, newAggregatedStats := v.generateTable(hm, m, pterm.GetTerminalWidth()-2) + width, _ := v.printer.GetSize() + o, newStats, newAggregatedStats := v.generateTable(hm, m, width-2) hm.Stats = newStats - v.ctx.Area.Update(*o) + v.printer.AreaUpdate(o) if m.Status != globalping.StatusInProgress { v.ctx.AggregatedStats = newAggregatedStats } @@ -99,7 +93,7 @@ func (v *viewer) outputTableView(m *globalping.Measurement) error { func (v *viewer) outputFailSummary(m *globalping.Measurement) error { for i := range m.Results { - v.printer.Println(generateProbeInfo(&m.Results[i], !v.ctx.CIMode)) + v.printer.Println(v.getProbeInfo(&m.Results[i])) v.printer.Println(m.Results[i].Result.RawOutput) } return errors.New("all probes failed") @@ -158,9 +152,9 @@ func (v *viewer) generateTable(hm *HistoryItem, m *globalping.Measurement, areaW for i := range table { table[i][0] = strings.ReplaceAll(table[i][0], "\t", " ") // Replace tabs with spaces lines := strings.Split(table[i][0], "\n") // Split first column into lines - color := pterm.Reset // No color + color := ColorNone // No color if i == 0 && !v.ctx.CIMode { - color = pterm.FgLightCyan + color = ColorLightCyan } for k := range lines { width := runewidth.StringWidth(lines[k]) @@ -172,14 +166,19 @@ func (v *viewer) generateTable(hm *HistoryItem, m *globalping.Measurement, areaW } else if colMax[0] > width { lines[k] = runewidth.FillRight(lines[k], colMax[0]) } - if color != 0 { - lines[k] = pterm.NewStyle(color).Sprint(lines[k]) + if color != "" { + lines[k] = v.printer.Color(lines[k], color) } } for j := 1; j < len(table[i]); j++ { - lines[0] += colSeparator + formatValue(table[i][j], color, colMax[j], j != 0) + lines[0] += colSeparator + if j == 0 { + lines[0] += v.printer.FillRightAndColor(table[i][j], colMax[j], color) + } else { + lines[0] += v.printer.FillLeftAndColor(table[i][j], colMax[j], color) + } for k := 1; k < len(lines); k++ { - lines[k] += colSeparator + formatValue("", 0, colMax[j], false) + lines[k] += colSeparator + v.printer.FillLeft("", colMax[j]) } } for j := 0; j < len(lines); j++ { @@ -255,20 +254,6 @@ func getRowValues(stats *MeasurementStats) [7]string { } } -func formatValue(v string, color pterm.Color, width int, toRight bool) string { - for len(v) < width { - if toRight { - v = " " + v - } else { - v = v + " " - } - } - if color != 0 { - v = pterm.NewStyle(color).Sprint(v) - } - return v -} - type ParsedPingOutput struct { Hostname string Address string diff --git a/view/infinite_test.go b/view/infinite_test.go index 912aa94..69c5861 100644 --- a/view/infinite_test.go +++ b/view/infinite_test.go @@ -2,10 +2,7 @@ package view import ( "bytes" - "io" "math" - "os" - "runtime" "testing" "time" @@ -23,6 +20,7 @@ func Test_OutputInfinite_SingleProbe_InProgress(t *testing.T) { timeMock.EXPECT().Now().Return(defaultCurrentTime.Add(500 * time.Millisecond)).Times(3) ctx := createDefaultContext("ping") + ctx.CIMode = true hm := ctx.History.Find(measurementID1) w := new(bytes.Buffer) viewer := NewViewer(ctx, NewPrinter(nil, w, w), timeMock, nil) @@ -36,7 +34,7 @@ func Test_OutputInfinite_SingleProbe_InProgress(t *testing.T) { assert.NoError(t, err) assert.Equal(t, - `> EU, DE, Berlin, ASN:3320, Deutsche Telekom AG + `> Berlin, DE, EU, Deutsche Telekom AG (AS3320) PING jsdelivr.map.fastly.net (151.101.1.229) 56(84) bytes of data. `, w.String(), @@ -49,7 +47,7 @@ PING jsdelivr.map.fastly.net (151.101.1.229) 56(84) bytes of data. assert.NoError(t, err) assert.Equal(t, - `> EU, DE, Berlin, ASN:3320, Deutsche Telekom AG + `> Berlin, DE, EU, Deutsche Telekom AG (AS3320) PING jsdelivr.map.fastly.net (151.101.1.229) 56(84) bytes of data. 64 bytes from 151.101.1.229 (151.101.1.229): icmp_seq=1 ttl=56 time=12.9 ms `, @@ -64,7 +62,7 @@ PING jsdelivr.map.fastly.net (151.101.1.229) 56(84) bytes of data. assert.NoError(t, err) assert.Equal(t, - `> EU, DE, Berlin, ASN:3320, Deutsche Telekom AG + `> Berlin, DE, EU, Deutsche Telekom AG (AS3320) PING jsdelivr.map.fastly.net (151.101.1.229) 56(84) bytes of data. 64 bytes from 151.101.1.229 (151.101.1.229): icmp_seq=1 ttl=56 time=12.9 ms 64 bytes from 151.101.1.229 (151.101.1.229): icmp_seq=2 ttl=56 time=12.7 ms @@ -91,7 +89,7 @@ rtt min/avg/max/mdev = 12.711/12.854/12.952/0.103 ms` assert.NoError(t, err) assert.Equal(t, - `> EU, DE, Berlin, ASN:3320, Deutsche Telekom AG + `> Berlin, DE, EU, Deutsche Telekom AG (AS3320) PING jsdelivr.map.fastly.net (151.101.1.229) 56(84) bytes of data. 64 bytes from 151.101.1.229 (151.101.1.229): icmp_seq=1 ttl=56 time=12.9 ms 64 bytes from 151.101.1.229 (151.101.1.229): icmp_seq=2 ttl=56 time=12.7 ms @@ -114,7 +112,7 @@ PING jsdelivr.map.fastly.net (151.101.1.229) 56(84) bytes of data. assert.NoError(t, err) assert.Equal(t, - `> EU, DE, Berlin, ASN:3320, Deutsche Telekom AG + `> Berlin, DE, EU, Deutsche Telekom AG (AS3320) PING jsdelivr.map.fastly.net (151.101.1.229) 56(84) bytes of data. 64 bytes from 151.101.1.229 (151.101.1.229): icmp_seq=1 ttl=56 time=12.9 ms 64 bytes from 151.101.1.229 (151.101.1.229): icmp_seq=2 ttl=56 time=12.7 ms @@ -138,13 +136,14 @@ func Test_OutputInfinite_SingleProbe_Failed(t *testing.T) { measurement.Results[0].Result.RawOutput = `ping: cdn.jsdelivr.net.xc: Name or service not known` ctx := createDefaultContext("ping") + ctx.CIMode = true w := new(bytes.Buffer) viewer := NewViewer(ctx, NewPrinter(nil, w, w), nil, nil) err := viewer.OutputInfinite(measurement) assert.Equal(t, "all probes failed", err.Error()) assert.Equal(t, - `> EU, DE, Berlin, ASN:3320, Deutsche Telekom AG + `> Berlin, DE, EU, Deutsche Telekom AG (AS3320) ping: cdn.jsdelivr.net.xc: Name or service not known `, w.String(), @@ -154,23 +153,12 @@ ping: cdn.jsdelivr.net.xc: Name or service not known } func Test_OutputInfinite_MultipleProbes_MultipleCalls(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("skipping test on windows") // Newline issues. Should be fixed once we use custom writer - } ctrl := gomock.NewController(t) defer ctrl.Finish() - osStdout := os.Stdout - defer func() { os.Stdout = osStdout }() - timeMock := mocks.NewMockTime(ctrl) timeMock.EXPECT().Now().Return(defaultCurrentTime.Add(1 * time.Millisecond)).AnyTimes() - r, w, err := os.Pipe() - assert.NoError(t, err) - defer r.Close() - defer w.Close() - measurement := createPingMeasurement_MultipleProbes(measurementID1) measurement.Status = globalping.StatusInProgress measurement.Results[0].Result.Status = globalping.StatusInProgress @@ -178,17 +166,17 @@ func Test_OutputInfinite_MultipleProbes_MultipleCalls(t *testing.T) { ctx := createDefaultContext("ping") ctx.CIMode = true + w := new(bytes.Buffer) viewer := NewViewer(ctx, NewPrinter(nil, w, w), timeMock, nil) // Call 1 - expectedOutput := `Location | Sent | Loss | Last | Min | Avg | Max -EU, GB, London,... | 0 | 0.00% | - | - | - | - -EU, DE, Falkens... | 1 | 0.00% | 5.46 ms | 5.46 ms | 5.46 ms | 5.46 ms -EU, DE, Nurembe... | 1 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms + expectedOutput := `Location | Sent | Loss | Last | Min | Avg | Max +London, GB, EU, OVH SAS (AS0) | 0 | 0.00% | - | - | - | - +Falkenstein, DE, EU, Hetzner Online GmbH (AS0) | 1 | 0.00% | 5.46 ms | 5.46 ms | 5.46 ms | 5.46 ms +Nuremberg, DE, EU, Hetzner Online GmbH (AS0) | 1 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms ` - os.Stdout = w - viewer.OutputInfinite(measurement) - os.Stdout = osStdout + err := viewer.OutputInfinite(measurement) + assert.NoError(t, err) expectedStats := []*MeasurementStats{ NewMeasurementStats(), @@ -204,15 +192,15 @@ EU, DE, Nurembe... | 1 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms no answer yet for icmp_seq=2` // Call 2 - expectedOutput += `Location | Sent | Loss | Last | Min | Avg | Max -EU, GB, London,... | 2 | 50.00% | 17.6 ms | 17.6 ms | 17.6 ms | 17.6 ms -EU, DE, Falkens... | 1 | 0.00% | 5.46 ms | 5.46 ms | 5.46 ms | 5.46 ms -EU, DE, Nurembe... | 1 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms + expectedOutput += "\033[4A\033[0J" + + `Location | Sent | Loss | Last | Min | Avg | Max +London, GB, EU, OVH SAS (AS0) | 2 | 50.00% | 17.6 ms | 17.6 ms | 17.6 ms | 17.6 ms +Falkenstein, DE, EU, Hetzner Online GmbH (AS0) | 1 | 0.00% | 5.46 ms | 5.46 ms | 5.46 ms | 5.46 ms +Nuremberg, DE, EU, Hetzner Online GmbH (AS0) | 1 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms ` - os.Stdout = w - viewer.OutputInfinite(measurement) - os.Stdout = osStdout + err = viewer.OutputInfinite(measurement) + assert.NoError(t, err) assertMeasurementStats(t, expectedStats[0], ctx.AggregatedStats[0]) assertMeasurementStats(t, expectedStats[1], ctx.AggregatedStats[1]) @@ -231,15 +219,14 @@ no answer yet for icmp_seq=2 rtt min/avg/max/mdev = 17.006/17.333/17.648/0.321 ms` // Call 3 - expectedOutput += `Location | Sent | Loss | Last | Min | Avg | Max -EU, GB, London,... | 3 | 0.00% | 17.0 ms | 17.0 ms | 17.3 ms | 17.6 ms -EU, DE, Falkens... | 1 | 0.00% | 5.46 ms | 5.46 ms | 5.46 ms | 5.46 ms -EU, DE, Nurembe... | 1 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms + expectedOutput += "\033[4A\033[0J" + + `Location | Sent | Loss | Last | Min | Avg | Max +London, GB, EU, OVH SAS (AS0) | 3 | 0.00% | 17.0 ms | 17.0 ms | 17.3 ms | 17.6 ms +Falkenstein, DE, EU, Hetzner Online GmbH (AS0) | 1 | 0.00% | 5.46 ms | 5.46 ms | 5.46 ms | 5.46 ms +Nuremberg, DE, EU, Hetzner Online GmbH (AS0) | 1 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms ` - os.Stdout = w err = viewer.OutputInfinite(measurement) - os.Stdout = osStdout assert.NoError(t, err) expectedStats = []*MeasurementStats{ @@ -260,13 +247,7 @@ EU, DE, Nurembe... | 1 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms StartedAt: defaultCurrentTime.Add(1 * time.Millisecond), }) - os.Stdout = w err = viewer.OutputInfinite(measurement2) - os.Stdout = osStdout - assert.NoError(t, err) - w.Close() - - output, err := io.ReadAll(r) assert.NoError(t, err) expectedStats = []*MeasurementStats{ @@ -278,33 +259,22 @@ EU, DE, Nurembe... | 1 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms assertMeasurementStats(t, expectedStats[1], ctx.AggregatedStats[1]) assertMeasurementStats(t, expectedStats[2], ctx.AggregatedStats[2]) - expectedOutput += `Location | Sent | Loss | Last | Min | Avg | Max -EU, GB, London,... | 6 | 0.00% | 17.0 ms | 17.0 ms | 17.3 ms | 17.6 ms -EU, DE, Falkens... | 2 | 0.00% | 5.46 ms | 5.46 ms | 5.46 ms | 5.46 ms -EU, DE, Nurembe... | 2 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms + expectedOutput += "\033[4A\033[0J" + + `Location | Sent | Loss | Last | Min | Avg | Max +London, GB, EU, OVH SAS (AS0) | 6 | 0.00% | 17.0 ms | 17.0 ms | 17.3 ms | 17.6 ms +Falkenstein, DE, EU, Hetzner Online GmbH (AS0) | 2 | 0.00% | 5.46 ms | 5.46 ms | 5.46 ms | 5.46 ms +Nuremberg, DE, EU, Hetzner Online GmbH (AS0) | 2 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms ` - assert.Equal(t, expectedOutput, string(output)) + assert.Equal(t, expectedOutput, w.String()) } func Test_OutputInfinite_MultipleProbes_MultipleConcurentCalls(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("skipping test on windows") // Newline issues. Should be fixed once we use custom writer - } - ctrl := gomock.NewController(t) defer ctrl.Finish() - osStdout := os.Stdout - defer func() { os.Stdout = osStdout }() - timeMock := mocks.NewMockTime(ctrl) timeMock.EXPECT().Now().Return(defaultCurrentTime.Add(1 * time.Millisecond)).AnyTimes() - r, w, err := os.Pipe() - assert.NoError(t, err) - defer r.Close() - defer w.Close() - // Call 1 measurement1 := createPingMeasurement_MultipleProbes(measurementID1) measurement1.Status = globalping.StatusInProgress @@ -318,17 +288,17 @@ func Test_OutputInfinite_MultipleProbes_MultipleConcurentCalls(t *testing.T) { hm1 := ctx.History.Find(measurementID1) hm1.Status = globalping.StatusInProgress ctx.CIMode = true + w := new(bytes.Buffer) viewer := NewViewer(ctx, NewPrinter(nil, w, w), timeMock, nil) - expectedOutput := `Location | Sent | Loss | Last | Min | Avg | Max -EU, GB, London,... | 1 | 0.00% | 10.0 ms | 10.0 ms | 10.0 ms | 10.0 ms -EU, DE, Falkens... | 0 | 0.00% | - | - | - | - -EU, DE, Nurembe... | 1 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms + expectedOutput := `Location | Sent | Loss | Last | Min | Avg | Max +London, GB, EU, OVH SAS (AS0) | 1 | 0.00% | 10.0 ms | 10.0 ms | 10.0 ms | 10.0 ms +Falkenstein, DE, EU, Hetzner Online GmbH (AS0) | 0 | 0.00% | - | - | - | - +Nuremberg, DE, EU, Hetzner Online GmbH (AS0) | 1 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms ` - os.Stdout = w - viewer.OutputInfinite(measurement1) - os.Stdout = osStdout + err := viewer.OutputInfinite(measurement1) + assert.NoError(t, err) // Call 2 measurement2 := createPingMeasurement_MultipleProbes(measurementID2) @@ -344,30 +314,30 @@ EU, DE, Nurembe... | 1 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms StartedAt: defaultCurrentTime.Add(1 * time.Millisecond), }) - expectedOutput += `Location | Sent | Loss | Last | Min | Avg | Max -EU, GB, London,... | 1 | 0.00% | 10.0 ms | 10.0 ms | 10.0 ms | 10.0 ms -EU, DE, Falkens... | 1 | 0.00% | 20.0 ms | 20.0 ms | 20.0 ms | 20.0 ms -EU, DE, Nurembe... | 2 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms + expectedOutput += "\033[4A\033[0J" + + `Location | Sent | Loss | Last | Min | Avg | Max +London, GB, EU, OVH SAS (AS0) | 1 | 0.00% | 10.0 ms | 10.0 ms | 10.0 ms | 10.0 ms +Falkenstein, DE, EU, Hetzner Online GmbH (AS0) | 1 | 0.00% | 20.0 ms | 20.0 ms | 20.0 ms | 20.0 ms +Nuremberg, DE, EU, Hetzner Online GmbH (AS0) | 2 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms ` - os.Stdout = w - viewer.OutputInfinite(measurement2) - os.Stdout = osStdout + err = viewer.OutputInfinite(measurement2) + assert.NoError(t, err) // Call 3 measurement1.Results[1].Result.RawOutput = `PING jsdelivr.map.fastly.net (151.101.1.229) 56(84) bytes of data. 64 bytes from 151.101.1.229 (151.101.1.229): icmp_seq=1 ttl=60 time=20 ms 64 bytes from 151.101.1.229 (151.101.1.229): icmp_seq=2 ttl=30 time=25 ms` - expectedOutput += `Location | Sent | Loss | Last | Min | Avg | Max -EU, GB, London,... | 1 | 0.00% | 10.0 ms | 10.0 ms | 10.0 ms | 10.0 ms -EU, DE, Falkens... | 3 | 0.00% | 20.0 ms | 20.0 ms | 21.7 ms | 25.0 ms -EU, DE, Nurembe... | 2 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms + expectedOutput += "\033[4A\033[0J" + + `Location | Sent | Loss | Last | Min | Avg | Max +London, GB, EU, OVH SAS (AS0) | 1 | 0.00% | 10.0 ms | 10.0 ms | 10.0 ms | 10.0 ms +Falkenstein, DE, EU, Hetzner Online GmbH (AS0) | 3 | 0.00% | 20.0 ms | 20.0 ms | 21.7 ms | 25.0 ms +Nuremberg, DE, EU, Hetzner Online GmbH (AS0) | 2 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms ` - os.Stdout = w - viewer.OutputInfinite(measurement1) - os.Stdout = osStdout + err = viewer.OutputInfinite(measurement1) + assert.NoError(t, err) // Call 4 measurement1.Status = globalping.StatusFinished @@ -391,15 +361,15 @@ rtt min/avg/max/mdev = 10/15/25/5 ms` rtt min/avg/max/mdev = 20/25/30/5 ms` hm1.Status = globalping.StatusFinished - expectedOutput += `Location | Sent | Loss | Last | Min | Avg | Max -EU, GB, London,... | 3 | 0.00% | 25.0 ms | 10.0 ms | 16.7 ms | 25.0 ms -EU, DE, Falkens... | 4 | 0.00% | 20.0 ms | 20.0 ms | 23.8 ms | 30.0 ms -EU, DE, Nurembe... | 2 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms + expectedOutput += "\033[4A\033[0J" + + `Location | Sent | Loss | Last | Min | Avg | Max +London, GB, EU, OVH SAS (AS0) | 3 | 0.00% | 25.0 ms | 10.0 ms | 16.7 ms | 25.0 ms +Falkenstein, DE, EU, Hetzner Online GmbH (AS0) | 4 | 0.00% | 20.0 ms | 20.0 ms | 23.8 ms | 30.0 ms +Nuremberg, DE, EU, Hetzner Online GmbH (AS0) | 2 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms ` - os.Stdout = w - viewer.OutputInfinite(measurement1) - os.Stdout = osStdout + err = viewer.OutputInfinite(measurement1) + assert.NoError(t, err) // Call 5 measurement2.Results[0].Result.Status = globalping.StatusFinished @@ -412,59 +382,37 @@ EU, DE, Nurembe... | 2 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms 3 packets transmitted, 3 received, 0% packet loss, time 100ms rtt min/avg/max/mdev = 10/15/25/5 ms` - os.Stdout = w err = viewer.OutputInfinite(measurement2) - os.Stdout = osStdout - assert.NoError(t, err) - w.Close() - - output, err := io.ReadAll(r) assert.NoError(t, err) - expectedOutput += `Location | Sent | Loss | Last | Min | Avg | Max -EU, GB, London,... | 6 | 0.00% | 25.0 ms | 10.0 ms | 16.7 ms | 25.0 ms -EU, DE, Falkens... | 4 | 0.00% | 20.0 ms | 20.0 ms | 23.8 ms | 30.0 ms -EU, DE, Nurembe... | 2 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms + expectedOutput += "\033[4A\033[0J" + + `Location | Sent | Loss | Last | Min | Avg | Max +London, GB, EU, OVH SAS (AS0) | 6 | 0.00% | 25.0 ms | 10.0 ms | 16.7 ms | 25.0 ms +Falkenstein, DE, EU, Hetzner Online GmbH (AS0) | 4 | 0.00% | 20.0 ms | 20.0 ms | 23.8 ms | 30.0 ms +Nuremberg, DE, EU, Hetzner Online GmbH (AS0) | 2 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms ` - assert.Equal(t, expectedOutput, string(output)) + assert.Equal(t, expectedOutput, w.String()) } func Test_OutputInfinite_MultipleProbes(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("skipping test on windows") // Newline issues. Should be fixed once we use custom writer - } - ctrl := gomock.NewController(t) defer ctrl.Finish() - osStdout := os.Stdout - defer func() { os.Stdout = osStdout }() - measurement := createPingMeasurement_MultipleProbes(measurementID1) - r, w, err := os.Pipe() - assert.NoError(t, err) - defer r.Close() - defer w.Close() - ctx := createDefaultContext("ping") + w := new(bytes.Buffer) v := NewViewer(ctx, NewPrinter(nil, w, w), nil, nil) - os.Stdout = w - err = v.OutputInfinite(measurement) - assert.NoError(t, err) - os.Stdout = osStdout - w.Close() - - output, err := io.ReadAll(r) + err := v.OutputInfinite(measurement) assert.NoError(t, err) - expectedOutput := "\x1b[96m\x1b[96mLocation \x1b[0m\x1b[0m | \x1b[96m\x1b[96mSent\x1b[0m\x1b[0m | \x1b[96m\x1b[96m Loss\x1b[0m\x1b[0m | \x1b[96m\x1b[96m Last\x1b[0m\x1b[0m | \x1b[96m\x1b[96m Min\x1b[0m\x1b[0m | \x1b[96m\x1b[96m Avg\x1b[0m\x1b[0m | \x1b[96m\x1b[96m Max\x1b[0m\x1b[0m" + + expectedOutput := "\033[96mLocation \033[0m | \033[96mSent\033[0m | \033[96m Loss\033[0m | \033[96m Last\033[0m | \033[96m Min\033[0m | \033[96m Avg\033[0m | \033[96m Max\033[0m" + ` -EU, GB, London,... | 1 | 0.00% | 0.77 ms | 0.77 ms | 0.77 ms | 0.77 ms -EU, DE, Falkens... | 1 | 0.00% | 5.46 ms | 5.46 ms | 5.46 ms | 5.46 ms -EU, DE, Nurembe... | 1 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms +London, GB, EU, OVH SAS (AS0) | 1 | 0.00% | 0.77 ms | 0.77 ms | 0.77 ms | 0.77 ms +Falkenstein, DE, EU, Hetzner Online GmbH (AS0) | 1 | 0.00% | 5.46 ms | 5.46 ms | 5.46 ms | 5.46 ms +Nuremberg, DE, EU, Hetzner Online GmbH (AS0) | 1 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms ` - assert.Equal(t, expectedOutput, string(output)) + assert.Equal(t, expectedOutput, w.String()) assert.Equal(t, []*MeasurementStats{ {Sent: 1, Rcv: 1, Lost: 0, Loss: 0, Last: 0.77, Min: 0.77, Avg: 0.77, Max: 0.77, Time: 100, Tsum: 0.77, Tsum2: 0.5929}, @@ -484,16 +432,17 @@ func Test_OutputInfinite_MultipleProbes_All_Failed(t *testing.T) { } ctx := createDefaultContext("ping") + ctx.CIMode = true w := new(bytes.Buffer) v := NewViewer(ctx, NewPrinter(nil, w, w), nil, nil) err := v.OutputInfinite(measurement) assert.Equal(t, "all probes failed", err.Error()) - assert.Equal(t, `> EU, GB, London, ASN:0, OVH SAS + assert.Equal(t, `> London, GB, EU, OVH SAS (AS0) ping: cdn.jsdelivr.net.xc: Name or service not known -> EU, DE, Falkenstein, ASN:0, Hetzner Online GmbH +> Falkenstein, DE, EU, Hetzner Online GmbH (AS0) ping: cdn.jsdelivr.net.xc: Name or service not known -> EU, DE, Nuremberg, ASN:0, Hetzner Online GmbH +> Nuremberg, DE, EU, Hetzner Online GmbH (AS0) ping: cdn.jsdelivr.net.xc: Name or service not known `, w.String()) @@ -521,10 +470,10 @@ func Test_GenerateTable_Full(t *testing.T) { measurement := createPingMeasurement_MultipleProbes(measurementID1) table, _, stats := viewer.generateTable(hm, measurement, 500) - expectedTable := "\x1b[96m\x1b[96mLocation \x1b[0m\x1b[0m | \x1b[96m\x1b[96mSent\x1b[0m\x1b[0m | \x1b[96m\x1b[96m Loss\x1b[0m\x1b[0m | \x1b[96m\x1b[96m Last\x1b[0m\x1b[0m | \x1b[96m\x1b[96m Min\x1b[0m\x1b[0m | \x1b[96m\x1b[96m Avg\x1b[0m\x1b[0m | \x1b[96m\x1b[96m Max\x1b[0m\x1b[0m\n" + - "EU, GB, London, ASN:0, OVH SAS | 1 | 0.00% | 0.77 ms | 0.77 ms | 0.77 ms | 0.77 ms\n" + - "EU, DE, Falkenstein, ASN:0, Hetzner Online GmbH | 1 | 0.00% | 5.46 ms | 5.46 ms | 5.46 ms | 5.46 ms\n" + - "EU, DE, Nuremberg, ASN:0, Hetzner Online GmbH | 1 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms\n" + expectedTable := "\033[96mLocation \033[0m | \033[96mSent\033[0m | \033[96m Loss\033[0m | \033[96m Last\033[0m | \033[96m Min\033[0m | \033[96m Avg\033[0m | \033[96m Max\033[0m\n" + + "London, GB, EU, OVH SAS (AS0) | 1 | 0.00% | 0.77 ms | 0.77 ms | 0.77 ms | 0.77 ms\n" + + "Falkenstein, DE, EU, Hetzner Online GmbH (AS0) | 1 | 0.00% | 5.46 ms | 5.46 ms | 5.46 ms | 5.46 ms\n" + + "Nuremberg, DE, EU, Hetzner Online GmbH (AS0) | 1 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms\n" assert.Equal(t, expectedTable, *table) assert.Equal(t, []*MeasurementStats{ @@ -548,10 +497,10 @@ func Test_GenerateTable_CIMode(t *testing.T) { measurement := createPingMeasurement_MultipleProbes(measurementID1) table, _, stats := viewer.generateTable(hm, measurement, 500) - expectedTable := `Location | Sent | Loss | Last | Min | Avg | Max -EU, GB, London, ASN:0, OVH SAS | 1 | 0.00% | 0.77 ms | 0.77 ms | 0.77 ms | 0.77 ms -EU, DE, Falkenstein, ASN:0, Hetzner Online GmbH | 1 | 0.00% | 5.46 ms | 5.46 ms | 5.46 ms | 5.46 ms -EU, DE, Nuremberg, ASN:0, Hetzner Online GmbH | 1 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms + expectedTable := `Location | Sent | Loss | Last | Min | Avg | Max +London, GB, EU, OVH SAS (AS0) | 1 | 0.00% | 0.77 ms | 0.77 ms | 0.77 ms | 0.77 ms +Falkenstein, DE, EU, Hetzner Online GmbH (AS0) | 1 | 0.00% | 5.46 ms | 5.46 ms | 5.46 ms | 5.46 ms +Nuremberg, DE, EU, Hetzner Online GmbH (AS0) | 1 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms ` assert.Equal(t, expectedTable, *table) @@ -574,12 +523,12 @@ func Test_GenerateTable_OneRow_Truncated(t *testing.T) { measurement := createPingMeasurement_MultipleProbes(measurementID1) measurement.Results[1].Probe.Network = "作者聚集的原创内容平台于201 1年1月正式上线让人们更" - table, _, stats := viewer.generateTable(hm, measurement, 106) + table, _, stats := viewer.generateTable(hm, measurement, 104) - expectedTable := "\x1b[96m\x1b[96mLocation \x1b[0m\x1b[0m | \x1b[96m\x1b[96mSent\x1b[0m\x1b[0m | \x1b[96m\x1b[96m Loss\x1b[0m\x1b[0m | \x1b[96m\x1b[96m Last\x1b[0m\x1b[0m | \x1b[96m\x1b[96m Min\x1b[0m\x1b[0m | \x1b[96m\x1b[96m Avg\x1b[0m\x1b[0m | \x1b[96m\x1b[96m Max\x1b[0m\x1b[0m\n" + - "EU, GB, London, ASN:0, OVH SAS | 1 | 0.00% | 0.77 ms | 0.77 ms | 0.77 ms | 0.77 ms\n" + - "EU, DE, Falkenstein, ASN:0, 作者聚集的原创... | 1 | 0.00% | 5.46 ms | 5.46 ms | 5.46 ms | 5.46 ms\n" + - "EU, DE, Nuremberg, ASN:0, Hetzner Online GmbH | 1 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms\n" + expectedTable := "\033[96mLocation \033[0m | \033[96mSent\033[0m | \033[96m Loss\033[0m | \033[96m Last\033[0m | \033[96m Min\033[0m | \033[96m Avg\033[0m | \033[96m Max\033[0m\n" + + "London, GB, EU, OVH SAS (AS0) | 1 | 0.00% | 0.77 ms | 0.77 ms | 0.77 ms | 0.77 ms\n" + + "Falkenstein, DE, EU, 作者聚集的原创内容平... | 1 | 0.00% | 5.46 ms | 5.46 ms | 5.46 ms | 5.46 ms\n" + + "Nuremberg, DE, EU, Hetzner Online GmbH (AS0) | 1 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms\n" assert.Equal(t, expectedTable, *table) assert.Equal(t, []*MeasurementStats{ @@ -601,14 +550,14 @@ func Test_GenerateTable_MultiLine_Truncated(t *testing.T) { measurement := createPingMeasurement_MultipleProbes(measurementID1) measurement.Results[1].Probe.Network = "Hetzner Online GmbH\nLorem ipsum\nLorem ipsum dolor sit amet" - table, _, stats := viewer.generateTable(hm, measurement, 106) - - expectedTable := "\x1b[96m\x1b[96mLocation \x1b[0m\x1b[0m | \x1b[96m\x1b[96mSent\x1b[0m\x1b[0m | \x1b[96m\x1b[96m Loss\x1b[0m\x1b[0m | \x1b[96m\x1b[96m Last\x1b[0m\x1b[0m | \x1b[96m\x1b[96m Min\x1b[0m\x1b[0m | \x1b[96m\x1b[96m Avg\x1b[0m\x1b[0m | \x1b[96m\x1b[96m Max\x1b[0m\x1b[0m\n" + - "EU, GB, London, ASN:0, OVH SAS | 1 | 0.00% | 0.77 ms | 0.77 ms | 0.77 ms | 0.77 ms\n" + - "EU, DE, Falkenstein, ASN:0, Hetzner Online ... | 1 | 0.00% | 5.46 ms | 5.46 ms | 5.46 ms | 5.46 ms\n" + - "Lorem ipsum | | | | | | \n" + - "Lorem ipsum dolor sit amet | | | | | | \n" + - "EU, DE, Nuremberg, ASN:0, Hetzner Online GmbH | 1 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms\n" + table, _, stats := viewer.generateTable(hm, measurement, 99) + + expectedTable := "\033[96mLocation \033[0m | \033[96mSent\033[0m | \033[96m Loss\033[0m | \033[96m Last\033[0m | \033[96m Min\033[0m | \033[96m Avg\033[0m | \033[96m Max\033[0m\n" + + "London, GB, EU, OVH SAS (AS0) | 1 | 0.00% | 0.77 ms | 0.77 ms | 0.77 ms | 0.77 ms\n" + + "Falkenstein, DE, EU, Hetzner Online ... | 1 | 0.00% | 5.46 ms | 5.46 ms | 5.46 ms | 5.46 ms\n" + + "Lorem ipsum | | | | | | \n" + + "Lorem ipsum dolor sit amet (AS0) | | | | | | \n" + + "Nuremberg, DE, EU, Hetzner Online Gm... | 1 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms\n" assert.Equal(t, expectedTable, *table) assert.Equal(t, []*MeasurementStats{ @@ -631,10 +580,10 @@ func Test_GenerateTable_MaxTruncated(t *testing.T) { measurement := createPingMeasurement_MultipleProbes(measurementID1) table, _, stats := viewer.generateTable(hm, measurement, 0) - expectedTable := "\x1b[96m\x1b[96mLoc...\x1b[0m\x1b[0m | \x1b[96m\x1b[96mSent\x1b[0m\x1b[0m | \x1b[96m\x1b[96m Loss\x1b[0m\x1b[0m | \x1b[96m\x1b[96m Last\x1b[0m\x1b[0m | \x1b[96m\x1b[96m Min\x1b[0m\x1b[0m | \x1b[96m\x1b[96m Avg\x1b[0m\x1b[0m | \x1b[96m\x1b[96m Max\x1b[0m\x1b[0m\n" + - "EU,... | 1 | 0.00% | 0.77 ms | 0.77 ms | 0.77 ms | 0.77 ms\n" + - "EU,... | 1 | 0.00% | 5.46 ms | 5.46 ms | 5.46 ms | 5.46 ms\n" + - "EU,... | 1 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms\n" + expectedTable := "\033[96mLoc...\033[0m | \033[96mSent\033[0m | \033[96m Loss\033[0m | \033[96m Last\033[0m | \033[96m Min\033[0m | \033[96m Avg\033[0m | \033[96m Max\033[0m\n" + + "Lon... | 1 | 0.00% | 0.77 ms | 0.77 ms | 0.77 ms | 0.77 ms\n" + + "Fal... | 1 | 0.00% | 5.46 ms | 5.46 ms | 5.46 ms | 5.46 ms\n" + + "Nur... | 1 | 0.00% | 4.07 ms | 4.07 ms | 4.07 ms | 4.07 ms\n" assert.Equal(t, expectedTable, *table) assert.Equal(t, []*MeasurementStats{ diff --git a/view/json.go b/view/json.go index 33d9b32..c51bf00 100644 --- a/view/json.go +++ b/view/json.go @@ -9,7 +9,7 @@ func (v *viewer) OutputJson(id string) error { v.printer.Println(string(output)) if v.ctx.Share { - v.printer.Println(formatWithLeadingArrow(shareMessage(id), !v.ctx.CIMode)) + v.printer.Println(v.getShareMessage(id)) } v.printer.Println() diff --git a/view/json_test.go b/view/json_test.go index 437ff17..601172b 100644 --- a/view/json_test.go +++ b/view/json_test.go @@ -27,6 +27,7 @@ func Test_Output_Json(t *testing.T) { &Context{ ToJSON: true, Share: true, + CIMode: true, }, NewPrinter(nil, w, w), nil, diff --git a/view/latency.go b/view/latency.go index fd6af74..fcd00b8 100644 --- a/view/latency.go +++ b/view/latency.go @@ -16,7 +16,7 @@ func (v *viewer) OutputLatency(id string, data *globalping.Measurement) error { v.printer.Println() } - v.printer.Println(generateProbeInfo(&result, !v.ctx.CIMode)) + v.printer.Println(v.getProbeInfo(&result)) switch v.ctx.Cmd { case "ping": @@ -24,44 +24,43 @@ func (v *viewer) OutputLatency(id string, data *globalping.Measurement) error { if err != nil { return err } - v.printer.Println(v.latencyStatHeader("Min", v.ctx.CIMode) + fmt.Sprintf("%.2f ms", stats.Min)) - v.printer.Println(v.latencyStatHeader("Max", v.ctx.CIMode) + fmt.Sprintf("%.2f ms", stats.Max)) - v.printer.Println(v.latencyStatHeader("Avg", v.ctx.CIMode) + fmt.Sprintf("%.2f ms", stats.Avg)) + v.printer.Println(v.latencyStatHeader("Min") + fmt.Sprintf("%.2f ms", stats.Min)) + v.printer.Println(v.latencyStatHeader("Max") + fmt.Sprintf("%.2f ms", stats.Max)) + v.printer.Println(v.latencyStatHeader("Avg") + fmt.Sprintf("%.2f ms", stats.Avg)) case "dns": timings, err := globalping.DecodeDNSTimings(result.Result.TimingsRaw) if err != nil { return err } - v.printer.Println(v.latencyStatHeader("Total", v.ctx.CIMode) + fmt.Sprintf("%v ms", timings.Total)) + v.printer.Println(v.latencyStatHeader("Total") + fmt.Sprintf("%v ms", timings.Total)) case "http": timings, err := globalping.DecodeHTTPTimings(result.Result.TimingsRaw) if err != nil { return err } - v.printer.Println(v.latencyStatHeader("Total", v.ctx.CIMode) + fmt.Sprintf("%v ms", timings.Total)) - v.printer.Println(v.latencyStatHeader("Download", v.ctx.CIMode) + fmt.Sprintf("%v ms", timings.Download)) - v.printer.Println(v.latencyStatHeader("First byte", v.ctx.CIMode) + fmt.Sprintf("%v ms", timings.FirstByte)) - v.printer.Println(v.latencyStatHeader("DNS", v.ctx.CIMode) + fmt.Sprintf("%v ms", timings.DNS)) - v.printer.Println(v.latencyStatHeader("TLS", v.ctx.CIMode) + fmt.Sprintf("%v ms", timings.TLS)) - v.printer.Println(v.latencyStatHeader("TCP", v.ctx.CIMode) + fmt.Sprintf("%v ms", timings.TCP)) + v.printer.Println(v.latencyStatHeader("Total") + fmt.Sprintf("%v ms", timings.Total)) + v.printer.Println(v.latencyStatHeader("Download") + fmt.Sprintf("%v ms", timings.Download)) + v.printer.Println(v.latencyStatHeader("First byte") + fmt.Sprintf("%v ms", timings.FirstByte)) + v.printer.Println(v.latencyStatHeader("DNS") + fmt.Sprintf("%v ms", timings.DNS)) + v.printer.Println(v.latencyStatHeader("TLS") + fmt.Sprintf("%v ms", timings.TLS)) + v.printer.Println(v.latencyStatHeader("TCP") + fmt.Sprintf("%v ms", timings.TCP)) default: return errors.New("unexpected command for latency output: " + v.ctx.Cmd) } } if v.ctx.Share { - v.printer.Println(formatWithLeadingArrow(shareMessage(id), !v.ctx.CIMode)) + v.printer.Println(v.getShareMessage(id)) } v.printer.Println() return nil } -func (v *viewer) latencyStatHeader(title string, ci bool) string { +func (v *viewer) latencyStatHeader(title string) string { text := fmt.Sprintf("%s: ", title) - if ci { + if v.ctx.CIMode { return text - } else { - return terminalLayoutBold.Render(text) } + return v.printer.Bold(text) } diff --git a/view/latency_test.go b/view/latency_test.go index 0480ae1..c98eb1a 100644 --- a/view/latency_test.go +++ b/view/latency_test.go @@ -65,17 +65,14 @@ func Test_Output_Latency_Ping_Not_CI(t *testing.T) { err := viewer.Output(measurementID1, &globalping.MeasurementCreate{}) assert.NoError(t, err) - assert.Equal(t, `> Continent, Country, (State), City, ASN:12345, Network (tag-1) -Min: 8.00 ms -Max: 20.00 ms -Avg: 12.00 ms - -> Continent B, Country B, (State B), City B, ASN:12349, Network B -Min: 9.00 ms -Max: 22.00 ms -Avg: 15.00 ms - -`, w.String()) + assert.Equal(t, "\033[1;38;2;23;212;167m> City (State), Country, Continent, Network (AS12345) (tag-1)\033[0m\n"+ + "\033[1mMin: \033[0m8.00 ms\n"+ + "\033[1mMax: \033[0m20.00 ms\n"+ + "\033[1mAvg: \033[0m12.00 ms\n\n"+ + "\033[1;38;2;23;212;167m> City B (State B), Country B, Continent B, Network B (AS12349)\033[0m\n"+ + "\033[1mMin: \033[0m9.00 ms\n"+ + "\033[1mMax: \033[0m22.00 ms\n"+ + "\033[1mAvg: \033[0m15.00 ms\n\n", w.String()) } func Test_Output_Latency_Ping_CI(t *testing.T) { @@ -119,7 +116,7 @@ func Test_Output_Latency_Ping_CI(t *testing.T) { err := viewer.Output(measurementID1, &globalping.MeasurementCreate{}) assert.NoError(t, err) - assert.Equal(t, `> Continent, Country, (State), City, ASN:12345, Network + assert.Equal(t, `> City (State), Country, Continent, Network (AS12345) Min: 8.00 ms Max: 20.00 ms Avg: 12.00 ms @@ -167,10 +164,8 @@ func Test_Output_Latency_DNS_Not_CI(t *testing.T) { err := viewer.Output(measurementID1, &globalping.MeasurementCreate{}) assert.NoError(t, err) - assert.Equal(t, `> Continent, Country, (State), City, ASN:12345, Network -Total: 44 ms - -`, w.String()) + assert.Equal(t, "\033[1;38;2;23;212;167m> City (State), Country, Continent, Network (AS12345)\033[0m\n"+ + "\033[1mTotal: \033[0m44 ms\n\n", w.String()) } func Test_Output_Latency_DNS_CI(t *testing.T) { @@ -214,7 +209,7 @@ func Test_Output_Latency_DNS_CI(t *testing.T) { err := viewer.Output(measurementID1, &globalping.MeasurementCreate{}) assert.NoError(t, err) - assert.Equal(t, `> Continent, Country, (State), City, ASN:12345, Network + assert.Equal(t, `> City (State), Country, Continent, Network (AS12345) Total: 44 ms `, w.String()) @@ -260,15 +255,13 @@ func Test_Output_Latency_Http_Not_CI(t *testing.T) { err := viewer.Output(measurementID1, &globalping.MeasurementCreate{}) assert.NoError(t, err) - assert.Equal(t, `> Continent, Country, (State), City, ASN:12345, Network -Total: 44 ms -Download: 11 ms -First byte: 20 ms -DNS: 5 ms -TLS: 2 ms -TCP: 4 ms - -`, w.String()) + assert.Equal(t, "\033[1;38;2;23;212;167m> City (State), Country, Continent, Network (AS12345)\033[0m\n"+ + "\033[1mTotal: \033[0m44 ms\n"+ + "\033[1mDownload: \033[0m11 ms\n"+ + "\033[1mFirst byte: \033[0m20 ms\n"+ + "\033[1mDNS: \033[0m5 ms\n"+ + "\033[1mTLS: \033[0m2 ms\n"+ + "\033[1mTCP: \033[0m4 ms\n\n", w.String()) } func Test_Output_Latency_Http_CI(t *testing.T) { @@ -312,7 +305,7 @@ func Test_Output_Latency_Http_CI(t *testing.T) { err := viewer.Output(measurementID1, &globalping.MeasurementCreate{}) assert.NoError(t, err) - assert.Equal(t, `> Continent, Country, (State), City, ASN:12345, Network + assert.Equal(t, `> City (State), Country, Continent, Network (AS12345) Total: 44 ms Download: 11 ms First byte: 20 ms diff --git a/view/output.go b/view/output.go index ca78336..0b68274 100644 --- a/view/output.go +++ b/view/output.go @@ -6,20 +6,8 @@ import ( "strings" "time" - "github.com/charmbracelet/lipgloss" "github.com/jsdelivr/globalping-cli/globalping" "github.com/mattn/go-runewidth" - "github.com/pterm/pterm" -) - -var ( - // UI styles - terminalLayoutHighlight = lipgloss.NewStyle(). - Bold(true).Foreground(lipgloss.Color("#17D4A7")) - - terminalLayoutArrow = lipgloss.NewStyle().SetString(">").Bold(true).Foreground(lipgloss.Color("#17D4A7")).PaddingRight(1).String() - - terminalLayoutBold = lipgloss.NewStyle().Bold(true) ) func (v *viewer) Output(id string, m *globalping.MeasurementCreate) error { @@ -67,28 +55,9 @@ func (v *viewer) Output(id string, m *globalping.MeasurementCreate) error { func (v *viewer) liveView(id string, data *globalping.Measurement, m *globalping.MeasurementCreate) error { var err error - // Create new writer - areaPrinter, err := pterm.DefaultArea.Start() - if err != nil { - return fmt.Errorf("failed to start writer: %v", err) - } - areaPrinter.RemoveWhenDone = true + w, h := v.printer.GetSize() - defer func() { - // Stop area printer and clear area if not already done - err := areaPrinter.Stop() - if err != nil { - v.printer.Printf("failed to stop writer: %v", err) - } - }() - - w, h, err := pterm.GetTerminalSize() - if err != nil { - return fmt.Errorf("failed to get terminal size: %v", err) - } - - // String builder for output - var output strings.Builder + output := &strings.Builder{} // Poll API until the measurement is complete for data.Status == globalping.StatusInProgress { @@ -98,14 +67,13 @@ func (v *viewer) liveView(id string, data *globalping.Measurement, m *globalping return fmt.Errorf("failed to get data: %v", err) } - // Reset string builder output.Reset() // Output every result in case of multiple probes for i := range data.Results { result := &data.Results[i] // Output slightly different format if state is available - output.WriteString(generateProbeInfo(result, !v.ctx.CIMode) + "\n") + output.WriteString(v.getProbeInfo(result) + "\n") if v.isBodyOnlyHttpGet(m) { output.WriteString(strings.TrimSpace(result.Result.RawBody) + "\n\n") @@ -114,31 +82,23 @@ func (v *viewer) liveView(id string, data *globalping.Measurement, m *globalping } } - areaPrinter.Update(trimOutput(output.String(), w, h)) - } - - // Stop area printer and clear area - err = areaPrinter.Stop() - if err != nil { - return fmt.Errorf("failed to stop writer: %v", err) + v.printer.AreaUpdate(trimOutput(output, w, h)) } + v.printer.AreaClear() v.outputDefault(id, data, m) return nil } // Used to trim the output to fit the terminal in live view -func trimOutput(output string, terminalW, terminalH int) string { +func trimOutput(output *strings.Builder, terminalW, terminalH int) *string { maxW := terminalW - 4 // 4 extra chars to be safe from overflow maxH := terminalH - 4 // 4 extra lines to be safe from overflow - if maxW <= 0 || maxH <= 0 { panic("terminal width / height too limited to display results") } - text := strings.ReplaceAll(output, "\t", " ") - - // Split output into lines + text := strings.ReplaceAll(output.String(), "\t", " ") lines := strings.Split(text, "\n") if len(lines) > maxH { @@ -155,19 +115,14 @@ func trimOutput(output string, terminalW, terminalH int) string { } } - // Join lines back into a string txt := strings.Join(lines, "\n") - - return txt + return &txt } -// Also checks if the probe has a state in it in the form %s, %s, (%s), %s, ASN:%d -func generateProbeInfo(result *globalping.ProbeMeasurement, useStyling bool) string { +func (v *viewer) getProbeInfo(result *globalping.ProbeMeasurement) string { var output strings.Builder - - // Continent + Country + (State) + City + ASN + Network + (Region Tag) + output.WriteString("> ") output.WriteString(getLocationText(result)) - // Check tags to see if there's a region code if len(result.Probe.Tags) > 0 { for _, tag := range result.Probe.Tags { @@ -178,34 +133,32 @@ func generateProbeInfo(result *globalping.ProbeMeasurement, useStyling bool) str } } } - - headerWithFormat := formatWithLeadingArrow(output.String(), useStyling) - return headerWithFormat + if v.ctx.CIMode { + return output.String() + } + return v.printer.BoldWithColor(output.String(), ColorHighlight) } -func formatWithLeadingArrow(text string, useStyling bool) string { - if useStyling { - return terminalLayoutArrow + terminalLayoutHighlight.Render(text) +func (v *viewer) getShareMessage(id string) string { + m := fmt.Sprintf("> View the results online: https://www.jsdelivr.com/globalping?measurement=%s", id) + if v.ctx.CIMode { + return m } - return "> " + text + return v.printer.BoldWithColor(m, ColorHighlight) } func (v *viewer) isBodyOnlyHttpGet(m *globalping.MeasurementCreate) bool { return v.ctx.Cmd == "http" && m.Options != nil && m.Options.Request != nil && m.Options.Request.Method == "GET" && !v.ctx.Full } -func shareMessage(id string) string { - return fmt.Sprintf("View the results online: https://www.jsdelivr.com/globalping?measurement=%s", id) -} - func getLocationText(m *globalping.ProbeMeasurement) string { state := "" if m.Probe.State != "" { - state = "(" + m.Probe.State + "), " + state = " (" + m.Probe.State + ")" } - return m.Probe.Continent + - ", " + m.Probe.Country + - ", " + state + m.Probe.City + - ", ASN:" + fmt.Sprint(m.Probe.ASN) + - ", " + m.Probe.Network + return m.Probe.City + state + ", " + + m.Probe.Country + ", " + + m.Probe.Continent + ", " + + m.Probe.Network + " " + + "(AS" + fmt.Sprint(m.Probe.ASN) + ")" } diff --git a/view/output_test.go b/view/output_test.go index 0640705..636238c 100644 --- a/view/output_test.go +++ b/view/output_test.go @@ -1,6 +1,7 @@ package view import ( + "strings" "testing" "github.com/jsdelivr/globalping-cli/globalping" @@ -8,11 +9,6 @@ import ( ) var ( - testContext = Context{ - From: "New York", - Target: "1.1.1.1", - CIMode: true, - } testResult = globalping.ProbeMeasurement{ Probe: globalping.ProbeDetails{ Continent: "Continent", @@ -27,21 +23,32 @@ var ( ) func Test_HeadersBase(t *testing.T) { - assert.Equal(t, "> Continent, Country, (State), City, ASN:12345, Network", generateProbeInfo(&testResult, !testContext.CIMode)) + v := viewer{ + ctx: &Context{ + CIMode: false, + }, + } + assert.Equal(t, "\033[1;38;2;23;212;167m> City (State), Country, Continent, Network (AS12345)\033[0m", v.getProbeInfo(&testResult)) } func Test_HeadersTags(t *testing.T) { newResult := testResult newResult.Probe.Tags = []string{"tag1", "tag2"} + v := viewer{ + ctx: &Context{ + CIMode: true, + }, + } - assert.Equal(t, "> Continent, Country, (State), City, ASN:12345, Network (tag1)", generateProbeInfo(&newResult, !testContext.CIMode)) + assert.Equal(t, "> City (State), Country, Continent, Network (AS12345) (tag1)", v.getProbeInfo(&newResult)) newResult.Probe.Tags = []string{"tag", "tag2"} - assert.Equal(t, "> Continent, Country, (State), City, ASN:12345, Network (tag2)", generateProbeInfo(&newResult, !testContext.CIMode)) + assert.Equal(t, "> City (State), Country, Continent, Network (AS12345) (tag2)", v.getProbeInfo(&newResult)) } func Test_TrimOutput(t *testing.T) { - output := `> EU, GB, London, ASN:12345 + output := &strings.Builder{} + output.WriteString(`> London, GB, EU, Network (AS12345) TEST CONTENT ABCD EDF @@ -52,7 +59,7 @@ IOPU GHJKL IOPU GHJKL -LOREM IPSUM LOREM IPSUM LOREM IPSUM` +LOREM IPSUM LOREM IPSUM LOREM IPSUM`) res := trimOutput(output, 84, 11) @@ -64,23 +71,24 @@ IOPU GHJKL LOREM IPSUM LOREM IPSUM LOREM IPSUM` - assert.Equal(t, expectedRes, res) + assert.Equal(t, expectedRes, *res) } func Test_TrimOutput_CN(t *testing.T) { - output := `> EU, GB, London, ASN:12345 + output := &strings.Builder{} + output.WriteString(`> London, GB, EU, Network (AS12345) some text a 中文互联文互联网高质量的问答社区和创 作者聚集的原创内容平台于201 1年1月正式上线让人们更 好的分享 知识经验和见解到自己的解答」中文互联网高质量的问答社区和创作者聚集的原创内容平台中文互联网高质量的问答社区和创作者聚集的原创内容平台于2011年1月正式上线让人们更好的分享知识经验和见解到自己的解答」中文互联网高质量的问答社区和创作者聚集的原创内容平台于 some text e -some text f` +some text f`) res := trimOutput(output, 84, 10) - expectedRes := `> EU, GB, London, ASN:12345 + expectedRes := `> London, GB, EU, Network (AS12345) some text a 中文互联文互联网高质量的问答社区和创 作者聚集的原创内容平台于201 1年1月正式上线让人们更 好的分享 知识经验和见解到自己的解答」中文互联网高质量的问答社区和创作者聚集的原 some text e some text f` - assert.Equal(t, expectedRes, res) + assert.Equal(t, expectedRes, *res) } diff --git a/view/printer.go b/view/printer.go index 44f62ef..bc7c17a 100644 --- a/view/printer.go +++ b/view/printer.go @@ -3,14 +3,26 @@ package view import ( "fmt" "io" + "math" + "os" + "strings" - "github.com/pterm/pterm" + "golang.org/x/term" +) + +type Color string + +const ( + ColorNone Color = "" + ColorLightCyan Color = "96" + ColorHighlight Color = "38;2;23;212;167" ) type Printer struct { - InReader io.Reader - OutWriter io.Writer - ErrWriter io.Writer + InReader io.Reader + OutWriter io.Writer + ErrWriter io.Writer + areaHeight int } func NewPrinter( @@ -18,7 +30,6 @@ func NewPrinter( outWriter io.Writer, errWriter io.Writer, ) *Printer { - pterm.SetDefaultOutput(outWriter) // TODO: Set writer for AreaPrinter return &Printer{ InReader: inReader, OutWriter: outWriter, @@ -37,3 +48,78 @@ func (p *Printer) Println(a ...any) { func (p *Printer) Printf(format string, a ...any) { fmt.Fprintf(p.OutWriter, format, a...) } + +func (p *Printer) FillLeft(s string, w int) string { + if len(s) >= w { + return s + } + return strings.Repeat(" ", w-len(s)) + s +} + +func (p *Printer) FillRight(s string, w int) string { + if len(s) >= w { + return s + } + return s + strings.Repeat(" ", w-len(s)) +} + +func (p *Printer) FillLeftAndColor(s string, w int, color Color) string { + if len(s) < w { + s = strings.Repeat(" ", w-len(s)) + s + } + if color == ColorNone { + return s + } + return p.Color(s, color) +} + +func (p *Printer) FillRightAndColor(s string, w int, color Color) string { + if len(s) < w { + s += strings.Repeat(" ", w-len(s)) + } + if color == ColorNone { + return s + } + return p.Color(s, color) +} + +func (p *Printer) Color(s string, color Color) string { + return fmt.Sprintf("\033[%sm%s\033[0m", color, s) +} + +func (p *Printer) Bold(s string) string { + return fmt.Sprintf("\033[1m%s\033[0m", s) +} + +func (p *Printer) BoldWithColor(s string, color Color) string { + return fmt.Sprintf("\033[1;%sm%s\033[0m", color, s) +} + +func (p *Printer) GetSize() (width, height int) { + f, ok := p.OutWriter.(*os.File) + if !ok { + return math.MaxInt, math.MaxInt + } + w, h, _ := term.GetSize(int(f.Fd())) + if w <= 0 { + w = math.MaxInt + } + if h <= 0 { + h = math.MaxInt + } + return w, h +} + +func (p *Printer) AreaUpdate(content *string) { + p.AreaClear() + p.areaHeight = strings.Count(*content, "\n") + fmt.Fprint(p.OutWriter, *content) +} + +func (p *Printer) AreaClear() { + if p.areaHeight == 0 { + return + } + fmt.Fprintf(p.OutWriter, "\033[%dA\033[0J", p.areaHeight) + p.areaHeight = 0 +} diff --git a/view/summary.go b/view/summary.go index fb942bf..75ba422 100644 --- a/view/summary.go +++ b/view/summary.go @@ -45,7 +45,7 @@ func (v *viewer) OutputSummary() { } ids := v.ctx.History.ToString("+") if ids != "" { - v.printer.Println(formatWithLeadingArrow(shareMessage(ids), !v.ctx.CIMode)) + v.printer.Println(v.getShareMessage(ids)) } if v.ctx.MeasurementsCreated > v.ctx.History.Capacity() { v.printer.Printf("For long-running continuous mode measurements, only the last %d packets are shared.\n", diff --git a/view/summary_test.go b/view/summary_test.go index 3dd5128..6d0d0de 100644 --- a/view/summary_test.go +++ b/view/summary_test.go @@ -2,6 +2,7 @@ package view import ( "bytes" + "fmt" "math" "testing" @@ -76,7 +77,7 @@ rtt min/avg/max/mdev = 0.770/0.770/0.770/0.000 ms --- ping statistics --- 1 packets transmitted, 0 received, 100.00% packet loss, time 0ms rtt min/avg/max/mdev = -/-/-/- ms -` + formatWithLeadingArrow(shareMessage(measurementID1), true) + "\n" +` + fmt.Sprintf("\033[1;38;2;23;212;167m> View the results online: https://www.jsdelivr.com/globalping?measurement=%s\033[0m\n", measurementID1) assert.Equal(t, expectedOutput, w.String()) }) @@ -89,11 +90,12 @@ rtt min/avg/max/mdev = -/-/-/- ms } ctx.History.Push(&HistoryItem{Id: measurementID2}) ctx.Share = true + ctx.CIMode = true w := new(bytes.Buffer) viewer := NewViewer(ctx, NewPrinter(nil, w, w), nil, nil) viewer.OutputSummary() - expectedOutput := "\n" + formatWithLeadingArrow(shareMessage(measurementID1+"+"+measurementID2), true) + "\n" + expectedOutput := fmt.Sprintf("\n> View the results online: https://www.jsdelivr.com/globalping?measurement=%s+%s\n", measurementID1, measurementID2) assert.Equal(t, expectedOutput, w.String()) }) @@ -107,6 +109,7 @@ rtt min/avg/max/mdev = -/-/-/- ms }, History: history, Share: true, + CIMode: true, MeasurementsCreated: 2, Packets: 16, } @@ -114,7 +117,7 @@ rtt min/avg/max/mdev = -/-/-/- ms viewer := NewViewer(ctx, NewPrinter(nil, w, w), nil, nil) viewer.OutputSummary() - expectedOutput := "\n" + formatWithLeadingArrow(shareMessage(measurementID2), true) + + expectedOutput := fmt.Sprintf("\n> View the results online: https://www.jsdelivr.com/globalping?measurement=%s", measurementID2) + "\nFor long-running continuous mode measurements, only the last 16 packets are shared.\n" assert.Equal(t, expectedOutput, w.String()) })