├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ ├── linter.yml │ └── tests.yml ├── .gitignore ├── .snapshots ├── TestApplication_Ban ├── TestApplication_Blue ├── TestApplication_Bool ├── TestApplication_Charles ├── TestApplication_Color ├── TestApplication_Gray ├── TestApplication_Green ├── TestApplication_Orange ├── TestApplication_Purple ├── TestApplication_Red ├── Test_CanSendAStringToRay └── Test_CanSendAnArrayToRay ├── CHANGELOG.md ├── LICENSE.md ├── Makefile ├── README.md ├── SECURITY.md ├── color_test.go ├── colors.go ├── go.mod ├── go.sum ├── payloads ├── BoolPayload.go ├── ClearAllPayload.go ├── ClearScreenPayload.go ├── ColorPayload.go ├── CreateLockPayload.go ├── CustomPayload.go ├── DumpPayload.go ├── HideAppPayload.go ├── HidePayload.go ├── HtmlPayload.go ├── ImagePayload.go ├── JsonStringPayload.go ├── LogPayload.go ├── NewScreenPayload.go ├── NotifyPayload.go ├── NullPayload.go ├── Payload.go ├── RemovePayload.go ├── ShowAppPayload.go ├── SizePayload.go ├── StringPayload.go └── TimePayload.go ├── payloads_test.go ├── playground └── playground.go ├── ray.go ├── ray_test.go ├── sizes.go └── utils ├── client.go └── stacktrace.go /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | insert_final_newline = true 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | indent_style = tab 8 | indent_size = 4 9 | 10 | [{Makefile,go.mod,go.sum,*.go,.gitmodules}] 11 | indent_style = tab 12 | indent_size = 4 13 | 14 | [*.md] 15 | indent_size = 4 16 | trim_trailing_whitespace = false 17 | 18 | eclint_indent_style = unset 19 | 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: octoper 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Screenshots** 20 | If applicable, add screenshots to help explain your problem. 21 | 22 | **Platform (please complete the following information):** 23 | - OS: [e.g. iOS] 24 | - Go version [e.g.1.5, 1.16] 25 | - Ray module version [e.g. 0.0.3] 26 | - Ray App Version [e.g. 1.13] 27 | 28 | **Additional context** 29 | Add any other context about the problem here. 30 | -------------------------------------------------------------------------------- /.github/workflows/linter.yml: -------------------------------------------------------------------------------- 1 | name: "Golang CI Lint" 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - main 8 | pull_request: 9 | jobs: 10 | golangci: 11 | name: lint 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: golangci-lint 16 | uses: golangci/golangci-lint-action@v2 17 | with: 18 | # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version 19 | version: v1.37.1 -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | name: "Tests" 3 | env: 4 | GO111MODULE: on 5 | 6 | jobs: 7 | test: 8 | strategy: 9 | matrix: 10 | go-version: [1.x, 1.16.x] 11 | platform: [ubuntu-latest, macos-latest] 12 | runs-on: ${{ matrix.platform }} 13 | 14 | steps: 15 | - uses: actions/setup-go@v1 16 | with: 17 | go-version: ${{ matrix.go-version }} 18 | - uses: actions/checkout@v2 19 | 20 | - name: Cache go modules 21 | uses: actions/cache@v1 22 | with: 23 | path: ~/go/pkg/mod 24 | key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }} 25 | restore-keys: ${{ runner.os }}-go- 26 | 27 | - name: Run go test 28 | run: go test -v ./... 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage.out 2 | -------------------------------------------------------------------------------- /.snapshots/TestApplication_Ban: -------------------------------------------------------------------------------- 1 | [{"type":"custom","content":{"content":"🕶","label":""},"origin":{"file":"","line_number":""}}] 2 | -------------------------------------------------------------------------------- /.snapshots/TestApplication_Blue: -------------------------------------------------------------------------------- 1 | [{"type":"color","content":{"color":"blue"},"origin":{"file":"","line_number":""}}] 2 | -------------------------------------------------------------------------------- /.snapshots/TestApplication_Bool: -------------------------------------------------------------------------------- 1 | [{"type":"custom","content":{"content":false,"label":"Boolean"},"origin":{"file":"","line_number":""}}] 2 | -------------------------------------------------------------------------------- /.snapshots/TestApplication_Charles: -------------------------------------------------------------------------------- 1 | [{"type":"custom","content":{"content":"🎶 🎹 🎷 🕺","label":""},"origin":{"file":"","line_number":""}}] 2 | -------------------------------------------------------------------------------- /.snapshots/TestApplication_Color: -------------------------------------------------------------------------------- 1 | [{"type":"color","content":{"color":"green"},"origin":{"file":"","line_number":""}}] 2 | -------------------------------------------------------------------------------- /.snapshots/TestApplication_Gray: -------------------------------------------------------------------------------- 1 | [{"type":"color","content":{"color":"gray"},"origin":{"file":"","line_number":""}}] 2 | -------------------------------------------------------------------------------- /.snapshots/TestApplication_Green: -------------------------------------------------------------------------------- 1 | [{"type":"color","content":{"color":"green"},"origin":{"file":"","line_number":""}}] 2 | -------------------------------------------------------------------------------- /.snapshots/TestApplication_Orange: -------------------------------------------------------------------------------- 1 | [{"type":"color","content":{"color":"orange"},"origin":{"file":"","line_number":""}}] 2 | -------------------------------------------------------------------------------- /.snapshots/TestApplication_Purple: -------------------------------------------------------------------------------- 1 | [{"type":"color","content":{"color":"purple"},"origin":{"file":"","line_number":""}}] 2 | -------------------------------------------------------------------------------- /.snapshots/TestApplication_Red: -------------------------------------------------------------------------------- 1 | [{"type":"color","content":{"color":"red"},"origin":{"file":"","line_number":""}}] 2 | -------------------------------------------------------------------------------- /.snapshots/Test_CanSendAStringToRay: -------------------------------------------------------------------------------- 1 | [{"type":"custom","content":{"content":"hey","label":""},"origin":{"file":"","line_number":""}}] 2 | -------------------------------------------------------------------------------- /.snapshots/Test_CanSendAnArrayToRay: -------------------------------------------------------------------------------- 1 | [{"type":"color","content":{"color":"green"},"origin":{"file":"","line_number":""}}] 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to Ray Go module will be documented in this file. 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Vaggelis Yfantis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build_playground: clean_playground 2 | go build -o playground/bin/main playground/playground.go 3 | 4 | playground: 5 | go run playground/playground.go 6 | 7 | clean_playground: 8 | rm -R playground/bin/ 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Debug your Go with Ray to fix problems faster 2 | 3 | ![Latest Version](https://img.shields.io/github/v/tag/octoper/go-ray) 4 | ![Go version](https://img.shields.io/github/go-mod/go-version/octoper/go-ray) 5 | ![Tests](https://github.com/octoper/go-ray/workflows/Tests/badge.svg) 6 | [![Golang CI Lint](https://github.com/octoper/go-ray/actions/workflows/linter.yml/badge.svg)](https://github.com/octoper/go-ray/actions/workflows/linter.yml) 7 | ![License](https://img.shields.io/github/license/octoper/go-ray) 8 | [![Go Reference](https://pkg.go.dev/badge/github.com/octoper/go-ray.svg)](https://pkg.go.dev/github.com/octoper/go-ray) 9 | [![Buy us a tree](https://img.shields.io/badge/Treeware-%F0%9F%8C%B3-lightgreen)](https://plant.treeware.earth/octoper/go-ray) 10 | 11 | This module can be installed in any Go application to send messages to the Ray app. 12 | 13 | ## Install 14 | 15 | When using Go Modules, you do not need to install anything to start using Ray with your Go program. Import the module 16 | and the go will automatically download the latest version of the module when you next build your program. 17 | 18 | ```go 19 | import ( 20 | "github.com/octoper/go-ray" 21 | ) 22 | ``` 23 | 24 | With or without Go Modules, to use the latest version of the SDK, run: 25 | 26 | `go get github.com/octoper/go-ray` 27 | 28 | Consult the [Go documentation on Modules](https://github.com/golang/go/wiki/Modules#how-to-upgrade-and-downgrade-dependencies) for more information on how to manage dependencies. 29 | 30 | ## Documentation 31 | 32 | You can find the full documentation on [our documentation site](https://spatie.be/docs/ray). 33 | 34 | ## Testing 35 | 36 | ```bash 37 | go test -v 38 | ``` 39 | 40 | ## Changelog 41 | 42 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 43 | 44 | ## Contributing 45 | 46 | Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details. 47 | 48 | ## Contributing 49 | 50 | Bug reports and pull requests are welcome on GitHub at https://github.com/octoper/go-ray. This project is intended to be 51 | a safe, welcoming space for collaboration, and contributors are expected to adhere to 52 | the [code of conduct](https://github.com/spatie/ray/blob/master/CODE_OF_CONDUCT.md). 53 | 54 | ## Contributing 55 | 56 | Please see [CODE_OF_CONDUCT](.github/CODE_OF_CONDUCT.md) for details. 57 | 58 | ## Security Vulnerabilities 59 | 60 | Please review [our security policy](SECURITY.md) on how to report security vulnerabilities. 61 | 62 | ## Credits 63 | 64 | - [Vaggelis Yfantis](https://github.com/octoper) 65 | - [All Contributors](../../contributors) 66 | 67 | ## License 68 | 69 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 70 | 71 | ## Treeware 72 | 73 | You're free to use this package, but if it makes it to your production environment you are required to buy the world a tree. 74 | 75 | It’s now common knowledge that one of the best tools to tackle the climate crisis and keep our temperatures from rising above 1.5C is to plant trees. If you support this package and contribute to the Treeware forest you’ll be creating employment for local families and restoring wildlife habitats. 76 | 77 | You can buy trees here [offset.earth/treeware](https://plant.treeware.earth/octoper/go-ray) 78 | 79 | Read more about Treeware at [treeware.earth](http://treeware.earth) 80 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | If you discover any security related issues, please email me@octoper.me instead of using the issue tracker. 4 | -------------------------------------------------------------------------------- /color_test.go: -------------------------------------------------------------------------------- 1 | package ray 2 | 3 | import ( 4 | "github.com/bradleyjkemp/cupaloy/v2" 5 | "testing" 6 | ) 7 | 8 | func TestApplication_Red(t *testing.T) { 9 | // t.Parallel() 10 | 11 | ray := Ray().Red() 12 | 13 | result, _ := ray.SentJsonPayloads() 14 | 15 | cupaloy.SnapshotT(t, result) 16 | } 17 | 18 | 19 | func TestApplication_Green(t *testing.T) { 20 | // t.Parallel() 21 | 22 | ray := Ray().Green() 23 | 24 | result, _ := ray.SentJsonPayloads() 25 | 26 | cupaloy.SnapshotT(t, result) 27 | } 28 | 29 | func TestApplication_Blue(t *testing.T) { 30 | // t.Parallel() 31 | 32 | ray := Ray().Blue() 33 | 34 | result, _ := ray.SentJsonPayloads() 35 | 36 | cupaloy.SnapshotT(t, result) 37 | } 38 | 39 | func TestApplication_Purple(t *testing.T) { 40 | // t.Parallel() 41 | 42 | ray := Ray().Purple() 43 | 44 | result, _ := ray.SentJsonPayloads() 45 | 46 | cupaloy.SnapshotT(t, result) 47 | } 48 | 49 | func TestApplication_Orange(t *testing.T) { 50 | // t.Parallel() 51 | 52 | ray := Ray().Orange() 53 | 54 | result, _ := ray.SentJsonPayloads() 55 | 56 | cupaloy.SnapshotT(t, result) 57 | } 58 | 59 | func TestApplication_Gray(t *testing.T) { 60 | // t.Parallel() 61 | 62 | ray := Ray().Gray() 63 | 64 | result, _ := ray.SentJsonPayloads() 65 | 66 | cupaloy.SnapshotT(t, result) 67 | } 68 | 69 | -------------------------------------------------------------------------------- /colors.go: -------------------------------------------------------------------------------- 1 | package ray 2 | 3 | import "github.com/octoper/go-ray/payloads" 4 | 5 | // Green Color 6 | func (r *application) Green() *application { 7 | return r.SendRequest(payloads.NewColorPayload("green")) 8 | } 9 | 10 | // Orange Color 11 | func (r *application) Orange() *application { 12 | return r.SendRequest(payloads.NewColorPayload("orange")) 13 | } 14 | 15 | // Red Color 16 | func (r *application) Red() *application { 17 | return r.SendRequest(payloads.NewColorPayload("red")) 18 | } 19 | 20 | // Blue Color 21 | func (r *application) Blue() *application { 22 | return r.SendRequest(payloads.NewColorPayload("blue")) 23 | } 24 | 25 | // Purple Color 26 | func (r *application) Purple() *application { 27 | return r.SendRequest(payloads.NewColorPayload("purple")) 28 | } 29 | 30 | // Gray Color 31 | func (r *application) Gray() *application { 32 | return r.SendRequest(payloads.NewColorPayload("gray")) 33 | } -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/octoper/go-ray 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/bradleyjkemp/cupaloy/v2 v2.6.0 7 | github.com/davecgh/go-spew v1.1.1 8 | github.com/gomarkdown/markdown v0.0.0-20210208175418-bda154fe17d8 9 | github.com/google/uuid v1.2.0 10 | github.com/stretchr/testify v1.7.0 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bradleyjkemp/cupaloy/v2 v2.6.0 h1:knToPYa2xtfg42U3I6punFEjaGFKWQRXJwj0JTv4mTs= 2 | github.com/bradleyjkemp/cupaloy/v2 v2.6.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/gomarkdown/markdown v0.0.0-20210208175418-bda154fe17d8 h1:nWU6p08f1VgIalT6iZyqXi4o5cZsz4X6qa87nusfcsc= 7 | github.com/gomarkdown/markdown v0.0.0-20210208175418-bda154fe17d8/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU= 8 | github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= 9 | github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 10 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 11 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 12 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 13 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 14 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 15 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 16 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 17 | golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ= 18 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 19 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 20 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 21 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 22 | -------------------------------------------------------------------------------- /payloads/BoolPayload.go: -------------------------------------------------------------------------------- 1 | package payloads 2 | 3 | // NewBoolPayload creates a new Bool Payload 4 | func NewBoolPayload(bool bool) Payload { 5 | return NewCustomPayload(bool, "Boolean") 6 | } 7 | -------------------------------------------------------------------------------- /payloads/ClearAllPayload.go: -------------------------------------------------------------------------------- 1 | package payloads 2 | 3 | // NewClearAllPayload creates a new Clear All Payload 4 | func NewClearAllPayload() Payload{ 5 | return Payload { 6 | Type: "clear_all", 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /payloads/ClearScreenPayload.go: -------------------------------------------------------------------------------- 1 | package payloads 2 | 3 | // NewClearScreenPayload creates a new Clear Screen Payload 4 | func NewClearScreenPayload() Payload { 5 | return Payload { 6 | Type: "new_screen", 7 | Content: "", 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /payloads/ColorPayload.go: -------------------------------------------------------------------------------- 1 | package payloads 2 | 3 | // NewColorPayload creates a new Color Payload 4 | func NewColorPayload(color string) Payload { 5 | return Payload { 6 | Type: "color", 7 | Content: map[string]string { 8 | "color": color, 9 | }, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /payloads/CreateLockPayload.go: -------------------------------------------------------------------------------- 1 | package payloads 2 | 3 | // NewCreateLockPayload creates a new Create Lock Payload 4 | func NewCreateLockPayload(name string) Payload { 5 | return Payload { 6 | Type: "create_lock", 7 | Content: map[string]string { 8 | "name": name, 9 | }, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /payloads/CustomPayload.go: -------------------------------------------------------------------------------- 1 | package payloads 2 | 3 | // NewCustomPayload creates a new Custom Payload 4 | func NewCustomPayload(content interface{}, label string) Payload { 5 | return Payload { 6 | Type: "custom", 7 | Content: map[string]interface{} { 8 | "content": content, 9 | "label": label, 10 | }, 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /payloads/DumpPayload.go: -------------------------------------------------------------------------------- 1 | package payloads 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/davecgh/go-spew/spew" 7 | "github.com/gomarkdown/markdown" 8 | ) 9 | 10 | // NewDumpPayload creates a new Dump Payload 11 | func NewDumpPayload(value interface{}) Payload { 12 | md := []byte("``` \n" + spew.Sdump(value) + "\n```") 13 | output := markdown.ToHTML(md, nil, nil) 14 | styledOutput := strings.Replace(string(output), "
", strings.Join([]string{
15 | 		`
`,
16 | 		``,
17 | 	}, ""), 1)
18 | 	return NewCustomPayload(styledOutput, "")
19 | }
20 | 


--------------------------------------------------------------------------------
/payloads/HideAppPayload.go:
--------------------------------------------------------------------------------
1 | package payloads
2 | 
3 | // NewHideAppPayload creates a new Hide App Payload
4 | func NewHideAppPayload() Payload {
5 | 	return Payload {
6 | 		Type: "hide_app",
7 | 	}
8 | }
9 | 


--------------------------------------------------------------------------------
/payloads/HidePayload.go:
--------------------------------------------------------------------------------
1 | package payloads
2 | 
3 | // NewHidePayload creates a new Hide Payload
4 | func NewHidePayload() Payload {
5 | 	return Payload {
6 | 		Type: "hide",
7 | 	}
8 | }
9 | 


--------------------------------------------------------------------------------
/payloads/HtmlPayload.go:
--------------------------------------------------------------------------------
1 | package payloads
2 | 
3 | // NewHtmlPayload creates a new HTML Payload
4 | func NewHtmlPayload(html string) Payload {
5 | 	return NewCustomPayload(html, "HTML")
6 | }
7 | 


--------------------------------------------------------------------------------
/payloads/ImagePayload.go:
--------------------------------------------------------------------------------
 1 | package payloads
 2 | 
 3 | import (
 4 | 	"strings"
 5 | )
 6 | 
 7 | // NewImagePayload creates a new Image Payload
 8 | func NewImagePayload(location string) Payload {
 9 | 	location = strings.ReplaceAll(location, "\"", "")
10 | 
11 | 	return NewCustomPayload("\"\"", "Image")
12 | }
13 | 


--------------------------------------------------------------------------------
/payloads/JsonStringPayload.go:
--------------------------------------------------------------------------------
 1 | package payloads
 2 | 
 3 | import "encoding/json"
 4 | 
 5 | // NewJsonStringPayload creates a new Json String Payload
 6 | func NewJsonStringPayload(value interface{}) Payload {
 7 | 	jsonValue, err := json.Marshal(value)
 8 | 
 9 | 	if err != nil {
10 | 		panic(err)
11 | 	}
12 | 
13 | 	return Payload {
14 | 		Type: "json_string",
15 | 		Content: map[string]interface{} {
16 | 			"value": string(jsonValue),
17 | 		},
18 | 	}
19 | }
20 | 


--------------------------------------------------------------------------------
/payloads/LogPayload.go:
--------------------------------------------------------------------------------
 1 | package payloads
 2 | 
 3 | // NewLogPayload creates a new Log Payload
 4 | func NewLogPayload(values ...interface{}) Payload {
 5 | 	return Payload {
 6 | 		Type: "log",
 7 | 		Content: map[string]interface{} {
 8 | 			"values": values,
 9 | 		},
10 | 	}
11 | }
12 | 


--------------------------------------------------------------------------------
/payloads/NewScreenPayload.go:
--------------------------------------------------------------------------------
 1 | package payloads
 2 | 
 3 | // NewNewScreenPayload creates a new New Screen Payload
 4 | func NewNewScreenPayload(name string) Payload {
 5 | 	return Payload {
 6 | 		Type: "new_screen",
 7 | 		Content: map[string]string {
 8 | 			"name": name,
 9 | 		},
10 | 	}
11 | }
12 | 


--------------------------------------------------------------------------------
/payloads/NotifyPayload.go:
--------------------------------------------------------------------------------
 1 | package payloads
 2 | 
 3 | // NewNotifyPayload creates a new Notify Payload
 4 | func NewNotifyPayload(value string) Payload {
 5 | 	return Payload {
 6 | 		Type: "notify",
 7 | 		Content: map[string]interface{} {
 8 | 			"value": value,
 9 | 		},
10 | 	}
11 | }
12 | 


--------------------------------------------------------------------------------
/payloads/NullPayload.go:
--------------------------------------------------------------------------------
1 | package payloads
2 | 
3 | // NewNullPayload creates a new Null Payload
4 | func NewNullPayload() Payload {
5 | 	return NewCustomPayload(nil, "Null")
6 | }
7 | 


--------------------------------------------------------------------------------
/payloads/Payload.go:
--------------------------------------------------------------------------------
1 | package payloads
2 | 
3 | // Payload struct
4 | type Payload struct {
5 | 	Type string `json:"type"`
6 | 	Content interface{} `json:"content"`
7 | 	Origin interface{} `json:"origin"`
8 | }
9 | 


--------------------------------------------------------------------------------
/payloads/RemovePayload.go:
--------------------------------------------------------------------------------
1 | package payloads
2 | 
3 | // NewRemovePayload creates a new Remove Payload
4 | func NewRemovePayload() Payload {
5 | 	return Payload {
6 | 		Type: "remove",
7 | 	}
8 | }
9 | 


--------------------------------------------------------------------------------
/payloads/ShowAppPayload.go:
--------------------------------------------------------------------------------
1 | package payloads
2 | 
3 | // NewShowAppPayload creates a new Show App Payload
4 | func NewShowAppPayload() Payload {
5 | 	return Payload {
6 | 		Type: "show_app",
7 | 	}
8 | }
9 | 


--------------------------------------------------------------------------------
/payloads/SizePayload.go:
--------------------------------------------------------------------------------
 1 | package payloads
 2 | 
 3 | // NewSizePayload creates a new Size Payload
 4 | func NewSizePayload(color string) Payload{
 5 | 	return Payload{
 6 | 		Type: "size",
 7 | 		Content: map[string]string {
 8 | 			"size": color,
 9 | 		},
10 | 	}
11 | }
12 | 


--------------------------------------------------------------------------------
/payloads/StringPayload.go:
--------------------------------------------------------------------------------
1 | package payloads
2 | 
3 | // NewStringPayload creates a new String Payload
4 | func NewStringPayload(value string) Payload {
5 | 	return NewCustomPayload(value, "String")
6 | }
7 | 


--------------------------------------------------------------------------------
/payloads/TimePayload.go:
--------------------------------------------------------------------------------
 1 | package payloads
 2 | 
 3 | import "time"
 4 | 
 5 | // NewTimePayload creates a new Time Payload
 6 | func NewTimePayload(time time.Time, format string) Payload {
 7 | 	timezoneName, _ := time.Zone()
 8 | 
 9 | 	return Payload {
10 | 		Type: "carbon",
11 | 		Content: map[string]interface{} {
12 | 			"formatted": time.Format(format),
13 | 			"timestamp": time.Unix(),
14 | 			"timezone": timezoneName,
15 | 		},
16 | 	}
17 | }
18 | 


--------------------------------------------------------------------------------
/payloads_test.go:
--------------------------------------------------------------------------------
 1 | package ray
 2 | 
 3 | import (
 4 | 	"github.com/bradleyjkemp/cupaloy/v2"
 5 | 	"testing"
 6 | )
 7 | 
 8 | func Test_CanSendAStringToRay(t *testing.T) {
 9 | 	ray := Ray("hey")
10 | 
11 | 	result, _ := ray.SentJsonPayloads()
12 | 
13 | 	cupaloy.SnapshotT(t, result)
14 | }
15 | 
16 | func Test_CanSendAnArrayToRay(t *testing.T) {
17 | 	ray := Ray([]int{2,4,6}).Color("green")
18 | 
19 | 	result, _ := ray.SentJsonPayloads()
20 | 
21 | 	cupaloy.SnapshotT(t, result)
22 | }
23 | 
24 | func TestApplication_Bool(t *testing.T) {
25 | 
26 | 	ray := Ray().Bool(false)
27 | 
28 | 	result, _ := ray.SentJsonPayloads()
29 | 
30 | 	cupaloy.SnapshotT(t, result)
31 | }
32 | 
33 | func TestApplication_Ban(t *testing.T) {
34 | 
35 | 	ray := Ray().Ban()
36 | 
37 | 	result, _ := ray.SentJsonPayloads()
38 | 
39 | 	cupaloy.SnapshotT(t, result)
40 | }
41 | 
42 | func TestApplication_Color(t *testing.T) {
43 | 	t.Parallel()
44 | 
45 | 	ray := Ray().Color("green")
46 | 
47 | 	result, _ := ray.SentJsonPayloads()
48 | 
49 | 	cupaloy.SnapshotT(t, result)
50 | }
51 | 
52 | func TestApplication_Charles(t *testing.T) {
53 | 	t.Parallel()
54 | 
55 | 	ray := Ray().Charles()
56 | 
57 | 	result, _ := ray.SentJsonPayloads()
58 | 
59 | 	cupaloy.SnapshotT(t, result)
60 | }


--------------------------------------------------------------------------------
/playground/playground.go:
--------------------------------------------------------------------------------
 1 | //nolint
 2 | package main
 3 | 
 4 | import (
 5 | 	"github.com/octoper/go-ray"
 6 | )
 7 | 
 8 | func main() {
 9 | 	ray.Ray().Ban()
10 | }
11 | 


--------------------------------------------------------------------------------
/ray.go:
--------------------------------------------------------------------------------
  1 | package ray
  2 | 
  3 | import (
  4 | 	"crypto/md5"
  5 | 	"encoding/hex"
  6 | 	"encoding/json"
  7 | 	"github.com/google/uuid"
  8 | 	payloads "github.com/octoper/go-ray/payloads"
  9 | 	"github.com/octoper/go-ray/utils"
 10 | 	"os"
 11 | 	"strconv"
 12 | 	"time"
 13 | )
 14 | 
 15 | // Callable mocks a function that returns a boolean
 16 | type Callable = func() bool
 17 | 
 18 | type application struct {
 19 | 	uuid         string
 20 | 	host         string
 21 | 	port         int
 22 | 	enabled      bool
 23 | 	sentPayloads []payloads.Payload
 24 | 	client utils.Client
 25 | }
 26 | 
 27 | type request struct {
 28 | 	Uuid     string            `json:"uuid"`
 29 | 	Payloads interface{}       `json:"payloads"`
 30 | 	Meta     map[string]string `json:"meta"`
 31 | }
 32 | 
 33 | var applicationConfig = application{
 34 | 	host:    "127.0.0.1",
 35 | 	port:    23517,
 36 | 	enabled: true,
 37 | 	client: *utils.NewClient(),
 38 | }
 39 | 
 40 | // NewRay creates a New Ray instance
 41 | func NewRay() *application {
 42 | 	app := applicationConfig
 43 | 	return &app
 44 | }
 45 | 
 46 | // Ray Creates a new insatnce of the application also can receive values to send to Ray
 47 | func Ray(values ...interface{}) *application {
 48 | 	r := NewRay()
 49 | 
 50 | 	r.uuid = uuid.New().String()
 51 | 
 52 | 	if values != nil {
 53 | 		r.Send(values...)
 54 | 	}
 55 | 
 56 | 	return r
 57 | }
 58 | 
 59 | // Send Values
 60 | func (r *application) Send(values ...interface{}) *application {
 61 | 	var payloadsMap []payloads.Payload
 62 | 
 63 | 	for _, payload := range values {
 64 | 		switch payload.(type) { // nolint:gosimple
 65 | 		case bool:
 66 | 			payloadsMap = append(payloadsMap, payloads.NewBoolPayload(payload.(bool)))
 67 | 		case nil:
 68 | 			payloadsMap = append(payloadsMap, payloads.NewNullPayload())
 69 | 		case string, int, int32, int64, float32, float64, complex64, complex128, uint, uint8, uint16, uint32, uint64:
 70 | 			payloadsMap = append(payloadsMap, payloads.NewCustomPayload(payload, ""))
 71 | 		default:
 72 | 			payloadsMap = append(payloadsMap, payloads.NewDumpPayload(payload))
 73 | 		}
 74 | 	}
 75 | 
 76 | 	return r.SendRequest(payloadsMap...)
 77 | }
 78 | 
 79 | // Get the UUID
 80 | func (r *application) Uuid() string {
 81 | 	return r.uuid
 82 | }
 83 | 
 84 | // Get the UUID
 85 | func (r *application) Client() *utils.Client {
 86 | 	r.client.SetHost(r.Host())
 87 | 	r.client.SetPort(r.Port())
 88 | 	return &r.client
 89 | }
 90 | 
 91 | // Get the port application is running
 92 | func (r *application) Port() int {
 93 | 	return r.port
 94 | }
 95 | 
 96 | // Set the port application is running
 97 | func (r *application) SetPort(port int) {
 98 | 	applicationConfig.port = port
 99 | }
100 | 
101 | // Get the host application is running
102 | func (r *application) Host() string {
103 | 	return r.host
104 | }
105 | 
106 | // Set the host application is running
107 | func (r *application) SetHost(host string) {
108 | 	applicationConfig.host = host
109 | }
110 | 
111 | // Get Sent Payloads as Json
112 | func (r *application) SentJsonPayloads() ([]byte, error) {
113 | 	return json.Marshal(applicationConfig.sentPayloads)
114 | }
115 | 
116 | // Enable sending payloads to Ray
117 | func (r *application) Enable() {
118 | 	applicationConfig.enabled = true
119 | }
120 | 
121 | // Prevents sending payloads to Ray
122 | func (r *application) Disable() {
123 | 	applicationConfig.enabled = false
124 | }
125 | 
126 | // Check if Ray is enabled
127 | func (r *application) Enabled() bool {
128 | 	return applicationConfig.enabled
129 | }
130 | 
131 | // Check if Ray is disabled
132 | func (r *application) Disabled() bool {
133 | 	return !applicationConfig.enabled
134 | }
135 | 
136 | // Create New Screen
137 | func (r *application) NewScreen(name string) *application {
138 | 	return r.SendRequest(payloads.NewNewScreenPayload(name))
139 | }
140 | 
141 | // Color
142 | func (r *application) Color(color string) *application {
143 | 	return r.SendRequest(payloads.NewColorPayload(color))
144 | }
145 | 
146 | // Send custom payload
147 | func (r *application) SendCustom(content interface{}, label string) *application {
148 | 	return r.SendRequest(payloads.NewCustomPayload(content, label))
149 | }
150 | 
151 | // Size
152 | func (r *application) Size(size string) *application {
153 | 	return r.SendRequest(payloads.NewSizePayload(size))
154 | }
155 | 
156 | // Hide
157 | func (r *application) Hide() *application {
158 | 	return r.SendRequest(payloads.NewHidePayload())
159 | }
160 | 
161 | // Hide App
162 | func (r *application) HideApp() *application {
163 | 	return r.SendRequest(payloads.NewHideAppPayload())
164 | }
165 | 
166 | // Show App
167 | func (r *application) ShowApp() *application {
168 | 	return r.SendRequest(payloads.NewShowAppPayload())
169 | }
170 | 
171 | // Clear Screen
172 | func (r *application) ClearScreen() *application {
173 | 	return r.SendRequest(payloads.NewClearScreenPayload())
174 | }
175 | 
176 | // Clear All
177 | func (r *application) ClearAll() *application {
178 | 	return r.SendRequest(payloads.NewClearAllPayload())
179 | }
180 | 
181 | // HTML
182 | func (r *application) Html(html string) *application {
183 | 	return r.SendRequest(payloads.NewHtmlPayload(html))
184 | }
185 | 
186 | // Notify
187 | func (r *application) Notify(text string) *application {
188 | 	return r.SendRequest(payloads.NewNotifyPayload(text))
189 | }
190 | 
191 | // Boolean
192 | func (r *application) Bool(bool bool) *application {
193 | 	return r.SendRequest(payloads.NewBoolPayload(bool))
194 | }
195 | 
196 | // Null
197 | func (r *application) Null() *application {
198 | 	return r.SendRequest(payloads.NewNullPayload())
199 | }
200 | 
201 | // Charles
202 | func (r *application) Charles() *application {
203 | 	return r.Send("🎶 🎹 🎷 🕺")
204 | }
205 | 
206 | // String
207 | func (r *application) String(str string) *application {
208 | 	return r.SendRequest(payloads.NewStringPayload(str))
209 | }
210 | 
211 | //Time
212 | func (r *application) Time(time time.Time) *application {
213 | 	return r.SendRequest(payloads.NewTimePayload(time, "2006-01-02 15:04:05"))
214 | }
215 | 
216 | // Time with Format
217 | func (r *application) TimeWithFormat(time time.Time, format string) *application {
218 | 	return r.SendRequest(payloads.NewTimePayload(time, format))
219 | }
220 | 
221 | // Pause code execution
222 | func (r *application) Pause() *application {
223 | 	hash := md5.New()
224 | 	_, err := hash.Write([]byte(time.Now().String()))
225 | 
226 | 	if err != nil {
227 | 		panic(err)
228 | 	}
229 | 
230 | 	lockName := hash.Sum(nil)
231 | 
232 | 	r.SendRequest(payloads.NewCreateLockPayload(hex.EncodeToString(lockName)))
233 | 
234 | 	for {
235 | 		time.Sleep(time.Second);
236 | 
237 | 		lockExistsClient := r.Client().LockExists(hex.EncodeToString(lockName))
238 | 		if !lockExistsClient.Active {
239 | 			break
240 | 		}
241 | 	}
242 | 
243 | 	return r
244 | }
245 | 
246 | // Sends the provided value(s) encoded as a JSON string using json.Marshal.
247 | func (r *application) ToJson(jsons ...interface{}) *application {
248 | 	for _, jsonValue := range jsons {
249 | 		r.SendRequest(payloads.NewJsonStringPayload(jsonValue))
250 | 	}
251 | 	return r
252 | }
253 | 
254 | // Image
255 | func (r *application) Image(value string) *application {
256 | 	return r.SendRequest(payloads.NewImagePayload(value))
257 | }
258 | 
259 | // Ban
260 | func (r *application) Ban() *application {
261 | 	return r.Send("🕶")
262 | }
263 | 
264 | // Die
265 | func (r *application) Die() {
266 | 	r.DieWithStatusCode(1)
267 | }
268 | 
269 | // Die with Status Code
270 | func (r *application) DieWithStatusCode(status int) {
271 | 	os.Exit(status)
272 | }
273 | 
274 | // Remove
275 | func (r *application) Remove() *application {
276 | 	return r.SendRequest(payloads.NewRemovePayload())
277 | }
278 | 
279 | // Show When
280 | func (r *application) ShowWhen(show interface{}) *application {
281 | 	switch show.(type) { // nolint:gosimple
282 | 	case Callable:
283 | 		show = show.(Callable)()
284 | 	}
285 | 
286 | 	if !show.(bool) {
287 | 		return r.Remove()
288 | 	}
289 | 
290 | 	return r
291 | }
292 | 
293 | // Show If
294 | func (r *application) ShowIf(show interface{}) *application {
295 | 	return r.ShowWhen(show)
296 | }
297 | 
298 | // Remove When
299 | func (r *application) RemoveWhen(show interface{}) *application {
300 | 	switch show.(type) { // nolint:gosimple
301 | 	case Callable:
302 | 		show = show.(Callable)()
303 | 	}
304 | 
305 | 	if show.(bool) {
306 | 		return r.Remove()
307 | 	}
308 | 
309 | 	return r
310 | }
311 | 
312 | // Remove If
313 | func (r *application) RemoveIf(show interface{}) *application {
314 | 	return r.RemoveWhen(show)
315 | }
316 | 
317 | // Set the host application is running
318 | func (r *application) SendRequest(ResponsePayloads ...payloads.Payload) *application {
319 | 	var payloadsMap []payloads.Payload
320 | 
321 | 	// stack := utils.NewStacktrace()
322 | 	stackAbsPath := ""
323 | 	stackLineNo := ""
324 | 
325 | 	/*if len(stack.Frames) > 0 {
326 | 		stackAbsPath = stack.Frames[0].AbsPath
327 | 		stackLineNo = strconv.Itoa(stack.Frames[0].Lineno)
328 | 	}*/
329 | 
330 | 	for _, payload := range ResponsePayloads {
331 | 		payload.Origin = map[string]string{
332 | 			"file":        stackAbsPath,
333 | 			"line_number": stackLineNo,
334 | 		}
335 | 
336 | 		payloadsMap = append(payloadsMap, payload)
337 | 	}
338 | 
339 | 	applicationConfig.sentPayloads = payloadsMap
340 | 
341 | 	requestPayload := request{
342 | 		Uuid:     r.Uuid(),
343 | 		Payloads: payloadsMap,
344 | 		Meta: map[string]string{
345 | 			"ray_package_version": "0.1.4",
346 | 		},
347 | 	}
348 | 
349 | 	if !r.enabled {
350 | 		return r
351 | 	}
352 | 
353 | 	client := r.Client()
354 | 
355 | 	_, err := client.Sent(requestPayload)
356 | 
357 | 	//Handle Error
358 | 	if err != nil {
359 | 		panic("Couldn't connect to Ray It doesn't seem to be running at " + Ray().Host() + ":" + strconv.Itoa(Ray().Port()))
360 | 	}
361 | 
362 | 	return r
363 | }
364 | 
365 | 


--------------------------------------------------------------------------------
/ray_test.go:
--------------------------------------------------------------------------------
 1 | package ray
 2 | 
 3 | import (
 4 | 	"github.com/stretchr/testify/assert"
 5 | 	"os"
 6 | 	"testing"
 7 | )
 8 | 
 9 | func TestMain(m *testing.M) {
10 | 	Ray().Disable()
11 | 
12 | 	os.Exit(m.Run())
13 | }
14 | 
15 | func TestApplication_SetHost(t *testing.T) {
16 | 	t.Parallel()
17 | 
18 | 	Ray().SetHost("192.168.1.255")
19 | 
20 | 	assert.Equal(t, "192.168.1.255", Ray().Host())
21 | }
22 | 
23 | 
24 | func TestApplication_SetPort(t *testing.T) {
25 | 	t.Parallel()
26 | 
27 | 	Ray().SetPort(27154)
28 | 
29 | 	assert.Equal(t, 27154, Ray().Port())
30 | }
31 | 
32 | 


--------------------------------------------------------------------------------
/sizes.go:
--------------------------------------------------------------------------------
 1 | package ray
 2 | 
 3 | import "github.com/octoper/go-ray/payloads"
 4 | 
 5 | func (r *application) Small() *application {
 6 | 	return r.SendRequest(payloads.NewSizePayload("sm"))
 7 | }
 8 | 
 9 | func (r *application) Large() *application {
10 | 	return r.SendRequest(payloads.NewSizePayload("lg"))
11 | }
12 | 


--------------------------------------------------------------------------------
/utils/client.go:
--------------------------------------------------------------------------------
 1 | package utils
 2 | 
 3 | import (
 4 | 	"bytes"
 5 | 	"encoding/json"
 6 | 	"io"
 7 | 	"net/http"
 8 | 	"os"
 9 | 	"strconv"
10 | )
11 | 
12 | // Client struct
13 | type Client struct {
14 | 	host string
15 | 	port int
16 | }
17 | 
18 | // LockRespnse struct
19 | type LockRespnse struct {
20 | 	Name          string `json:"name"`
21 | 	Active        bool   `json:"active"`
22 | 	StopExecution bool   `json:"stop_execution"`
23 | 	GroupUuid     string `json:"displayed_on_group_uuid"`
24 | }
25 | 
26 | 
27 | // NewClient creates a Client instance
28 | func NewClient() *Client {
29 | 	return &Client{}
30 | }
31 | 
32 | // SetPort Sets the port
33 | func (c *Client) SetPort(port int) int {
34 | 	c.port = port
35 | 	return c.port
36 | }
37 | 
38 | // SetHost Sets the host
39 | func (c *Client) SetHost(host string) string {
40 | 	c.host = host
41 | 	return c.host
42 | }
43 | 
44 | 
45 | // Sent sents the give payload to Ray
46 | func (c *Client) Sent(requestPayload interface{}) (*http.Response, error) {
47 | 	requestJson, _ := json.Marshal(requestPayload)
48 | 
49 | 	responseBody := bytes.NewBuffer(requestJson)
50 | 
51 | 	//Make a request to Ray
52 | 	resp, err := http.Post("http://" + c.host + ":" + strconv.Itoa(c.port), "application/json", responseBody)
53 | 
54 | 	if err != nil {
55 | 		panic(err)
56 | 	}
57 | 
58 | 	defer resp.Body.Close()
59 | 
60 | 	return resp, err
61 | }
62 | 
63 | // LockExists checks if lock exists
64 | func (c *Client) LockExists(lockName string) LockRespnse {
65 | 	//Make a request to Ray
66 | 	resp, err := http.Get("http://" + c.host + ":" + strconv.Itoa(c.port) + "/locks/"+ lockName)
67 | 
68 | 	if err != nil {
69 | 		panic(err)
70 | 	}
71 | 
72 | 	body, bodyErr := io.ReadAll(resp.Body)
73 | 
74 | 	if bodyErr != nil {
75 | 		panic(bodyErr)
76 | 	}
77 | 
78 | 	res := LockRespnse{}
79 | 	if err := json.Unmarshal([]byte(body), &res); err != nil {
80 | 		panic(err)
81 | 	}
82 | 
83 | 	if res.StopExecution {
84 | 		os.Exit(1)
85 | 	}
86 | 
87 | 	return res
88 | }
89 | 
90 | 


--------------------------------------------------------------------------------
/utils/stacktrace.go:
--------------------------------------------------------------------------------
  1 | package utils
  2 | 
  3 | /**
  4 |  * Thanks to Sentry Team and Sentry Go SDK
  5 |  *
  6 |  * @see https://github.com/getsentry/sentry-go/blob/master/stacktrace.go
  7 |  */
  8 | 
  9 | import (
 10 | 	"go/build"
 11 | 	"path/filepath"
 12 | 	"reflect"
 13 | 	"runtime"
 14 | 	"strings"
 15 | )
 16 | 
 17 | const unknown string = "unknown"
 18 | 
 19 | // The module download is split into two parts: downloading the go.mod and downloading the actual code.
 20 | // If you have dependencies only needed for tests, then they will show up in your go.mod,
 21 | // and go get will download their go.mods, but it will not download their code.
 22 | // The test-only dependencies get downloaded only when you need it, such as the first time you run go test.
 23 | //
 24 | // https://github.com/golang/go/issues/26913#issuecomment-411976222
 25 | 
 26 | // Stacktrace holds information about the frames of the stack.
 27 | type Stacktrace struct {
 28 | 	Frames        []Frame `json:"frames,omitempty"`
 29 | 	FramesOmitted []uint  `json:"frames_omitted,omitempty"`
 30 | }
 31 | 
 32 | // NewStacktrace creates a stacktrace using runtime.Callers.
 33 | func NewStacktrace() *Stacktrace {
 34 | 	pcs := make([]uintptr, 100)
 35 | 	n := runtime.Callers(1, pcs)
 36 | 
 37 | 	if n == 0 {
 38 | 		return nil
 39 | 	}
 40 | 
 41 | 	frames := extractFrames(pcs[:n])
 42 | 	frames = filterFrames(frames)
 43 | 
 44 | 	stacktrace := Stacktrace{
 45 | 		Frames: frames,
 46 | 	}
 47 | 
 48 | 	return &stacktrace
 49 | }
 50 | 
 51 | // TODO: Make it configurable so that anyone can provide their own implementation?
 52 | // Use of reflection allows us to not have a hard dependency on any given
 53 | // package, so we don't have to import it.
 54 | 
 55 | // ExtractStacktrace creates a new Stacktrace based on the given error.
 56 | func ExtractStacktrace(err error) *Stacktrace {
 57 | 	method := extractReflectedStacktraceMethod(err)
 58 | 
 59 | 	var pcs []uintptr
 60 | 
 61 | 	if method.IsValid() {
 62 | 		pcs = extractPcs(method)
 63 | 	} else {
 64 | 		pcs = extractXErrorsPC(err)
 65 | 	}
 66 | 
 67 | 	if len(pcs) == 0 {
 68 | 		return nil
 69 | 	}
 70 | 
 71 | 	frames := extractFrames(pcs)
 72 | 	frames = filterFrames(frames)
 73 | 
 74 | 	stacktrace := Stacktrace{
 75 | 		Frames: frames,
 76 | 	}
 77 | 
 78 | 	return &stacktrace
 79 | }
 80 | 
 81 | func extractReflectedStacktraceMethod(err error) reflect.Value {
 82 | 	var method reflect.Value
 83 | 
 84 | 	// https://github.com/pingcap/errors
 85 | 	methodGetStackTracer := reflect.ValueOf(err).MethodByName("GetStackTracer")
 86 | 	// https://github.com/pkg/errors
 87 | 	methodStackTrace := reflect.ValueOf(err).MethodByName("StackTrace")
 88 | 	// https://github.com/go-errors/errors
 89 | 	methodStackFrames := reflect.ValueOf(err).MethodByName("StackFrames")
 90 | 
 91 | 	if methodGetStackTracer.IsValid() {
 92 | 		stacktracer := methodGetStackTracer.Call(make([]reflect.Value, 0))[0]
 93 | 		stacktracerStackTrace := reflect.ValueOf(stacktracer).MethodByName("StackTrace")
 94 | 
 95 | 		if stacktracerStackTrace.IsValid() {
 96 | 			method = stacktracerStackTrace
 97 | 		}
 98 | 	}
 99 | 
100 | 	if methodStackTrace.IsValid() {
101 | 		method = methodStackTrace
102 | 	}
103 | 
104 | 	if methodStackFrames.IsValid() {
105 | 		method = methodStackFrames
106 | 	}
107 | 
108 | 	return method
109 | }
110 | 
111 | func extractPcs(method reflect.Value) []uintptr {
112 | 	var pcs []uintptr
113 | 
114 | 	stacktrace := method.Call(make([]reflect.Value, 0))[0]
115 | 
116 | 	if stacktrace.Kind() != reflect.Slice {
117 | 		return nil
118 | 	}
119 | 
120 | 	for i := 0; i < stacktrace.Len(); i++ {
121 | 		pc := stacktrace.Index(i)
122 | 
123 | 		if pc.Kind() == reflect.Uintptr {
124 | 			pcs = append(pcs, uintptr(pc.Uint()))
125 | 			continue
126 | 		}
127 | 
128 | 		if pc.Kind() == reflect.Struct {
129 | 			field := pc.FieldByName("ProgramCounter")
130 | 			if field.IsValid() && field.Kind() == reflect.Uintptr {
131 | 				pcs = append(pcs, uintptr(field.Uint()))
132 | 				continue
133 | 			}
134 | 		}
135 | 	}
136 | 
137 | 	return pcs
138 | }
139 | 
140 | // extractXErrorsPC extracts program counters from error values compatible with
141 | // the error types from golang.org/x/xerrors.
142 | //
143 | // It returns nil if err is not compatible with errors from that package or if
144 | // no program counters are stored in err.
145 | func extractXErrorsPC(err error) []uintptr {
146 | 	// This implementation uses the reflect package to avoid a hard dependency
147 | 	// on third-party packages.
148 | 
149 | 	// We don't know if err matches the expected type. For simplicity, instead
150 | 	// of trying to account for all possible ways things can go wrong, some
151 | 	// assumptions are made and if they are violated the code will panic. We
152 | 	// recover from any panic and ignore it, returning nil.
153 | 	//nolint: errcheck
154 | 	defer func() { recover() }()
155 | 
156 | 	field := reflect.ValueOf(err).Elem().FieldByName("frame") // type Frame struct{ frames [3]uintptr }
157 | 	field = field.FieldByName("frames")
158 | 	field = field.Slice(1, field.Len()) // drop first pc pointing to xerrors.New
159 | 	pc := make([]uintptr, field.Len())
160 | 	for i := 0; i < field.Len(); i++ {
161 | 		pc[i] = uintptr(field.Index(i).Uint())
162 | 	}
163 | 	return pc
164 | }
165 | 
166 | // Frame represents a function call and it's metadata. Frames are associated
167 | // with a Stacktrace.
168 | type Frame struct {
169 | 	Function    string                 `json:"function,omitempty"`
170 | 	Symbol      string                 `json:"symbol,omitempty"`
171 | 	Module      string                 `json:"module,omitempty"`
172 | 	Package     string                 `json:"package,omitempty"`
173 | 	Filename    string                 `json:"filename,omitempty"`
174 | 	AbsPath     string                 `json:"abs_path,omitempty"`
175 | 	Lineno      int                    `json:"lineno,omitempty"`
176 | 	Colno       int                    `json:"colno,omitempty"`
177 | 	PreContext  []string               `json:"pre_context,omitempty"`
178 | 	ContextLine string                 `json:"context_line,omitempty"`
179 | 	PostContext []string               `json:"post_context,omitempty"`
180 | 	InApp       bool                   `json:"in_app,omitempty"`
181 | 	Vars        map[string]interface{} `json:"vars,omitempty"`
182 | }
183 | 
184 | // NewFrame assembles a stacktrace frame out of runtime.Frame.
185 | func NewFrame(f runtime.Frame) Frame {
186 | 	var abspath, relpath string
187 | 	// NOTE: f.File paths historically use forward slash as path separator even
188 | 	// on Windows, though this is not yet documented, see
189 | 	// https://golang.org/issues/3335. In any case, filepath.IsAbs can work with
190 | 	// paths with either slash or backslash on Windows.
191 | 	switch {
192 | 	case f.File == "":
193 | 		relpath = unknown
194 | 		// Leave abspath as the empty string to be omitted when serializing
195 | 		// event as JSON.
196 | 		abspath = ""
197 | 	case filepath.IsAbs(f.File):
198 | 		abspath = f.File
199 | 		// TODO: in the general case, it is not trivial to come up with a
200 | 		// "project relative" path with the data we have in run time.
201 | 		// We shall not use filepath.Base because it creates ambiguous paths and
202 | 		// affects the "Suspect Commits" feature.
203 | 		// For now, leave relpath empty to be omitted when serializing the event
204 | 		// as JSON. Improve this later.
205 | 		relpath = ""
206 | 	default:
207 | 		// f.File is a relative path. This may happen when the binary is built
208 | 		// with the -trimpath flag.
209 | 		relpath = f.File
210 | 		// Omit abspath when serializing the event as JSON.
211 | 		abspath = ""
212 | 	}
213 | 
214 | 	function := f.Function
215 | 	var pkg string
216 | 
217 | 	if function != "" {
218 | 		pkg, function = splitQualifiedFunctionName(function)
219 | 	}
220 | 
221 | 	frame := Frame{
222 | 		AbsPath:  abspath,
223 | 		Filename: relpath,
224 | 		Lineno:   f.Line,
225 | 		Module:   pkg,
226 | 		Function: function,
227 | 	}
228 | 
229 | 	frame.InApp = isInAppFrame(frame)
230 | 
231 | 	return frame
232 | }
233 | 
234 | // splitQualifiedFunctionName splits a package path-qualified function name into
235 | // package name and function name. Such qualified names are found in
236 | // runtime.Frame.Function values.
237 | func splitQualifiedFunctionName(name string) (pkg string, fun string) {
238 | 	pkg = packageName(name)
239 | 	fun = strings.TrimPrefix(name, pkg+".")
240 | 	return
241 | }
242 | 
243 | func extractFrames(pcs []uintptr) []Frame {
244 | 	var frames []Frame
245 | 	callersFrames := runtime.CallersFrames(pcs)
246 | 
247 | 	for {
248 | 		callerFrame, more := callersFrames.Next()
249 | 
250 | 		frames = append([]Frame{
251 | 			NewFrame(callerFrame),
252 | 		}, frames...)
253 | 
254 | 		if !more {
255 | 			break
256 | 		}
257 | 	}
258 | 
259 | 	return frames
260 | }
261 | 
262 | // filterFrames filters out stack frames that are not meant to be reported to
263 | // Sentry. Those are frames internal to the SDK or Go.
264 | func filterFrames(frames []Frame) []Frame {
265 | 	if len(frames) == 0 {
266 | 		return nil
267 | 	}
268 | 
269 | 	filteredFrames := make([]Frame, 0, len(frames))
270 | 
271 | 	for _, frame := range frames {
272 | 		// Skip Go internal frames.
273 | 		if frame.Module == "runtime" || frame.Module == "testing" {
274 | 			continue
275 | 		}
276 | 		// Skip Sentry internal frames, except for frames in _test packages (for testing).
277 | 		if strings.HasPrefix(frame.Module, "github.com/octoper/go-ray") &&
278 | 			!strings.HasSuffix(frame.Module, "_test") {
279 | 			continue
280 | 		}
281 | 		filteredFrames = append(filteredFrames, frame)
282 | 	}
283 | 
284 | 	return filteredFrames
285 | }
286 | 
287 | func isInAppFrame(frame Frame) bool {
288 | 	if strings.HasPrefix(frame.AbsPath, build.Default.GOROOT) ||
289 | 		strings.Contains(frame.Module, "vendor") ||
290 | 		strings.Contains(frame.Module, "third_party") {
291 | 		return false
292 | 	}
293 | 
294 | 	return true
295 | }
296 | 
297 | // packageName returns the package part of the symbol name, or the empty string
298 | // if there is none.
299 | // It replicates https://golang.org/pkg/debug/gosym/#Sym.PackageName, avoiding a
300 | // dependency on debug/gosym.
301 | func packageName(name string) string {
302 | 	// A prefix of "type." and "go." is a compiler-generated symbol that doesn't belong to any package.
303 | 	// See variable reservedimports in cmd/compile/internal/gc/subr.go
304 | 	if strings.HasPrefix(name, "go.") || strings.HasPrefix(name, "type.") {
305 | 		return ""
306 | 	}
307 | 
308 | 	pathend := strings.LastIndex(name, "/")
309 | 	if pathend < 0 {
310 | 		pathend = 0
311 | 	}
312 | 
313 | 	if i := strings.Index(name[pathend:], "."); i != -1 {
314 | 		return name[:pathend+i]
315 | 	}
316 | 	return ""
317 | }
318 | 


--------------------------------------------------------------------------------