diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d99e11a..b2cab50 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,30 +6,29 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - go-version: [1.21.x] + go-version: [1.22.x] os: [macos-latest, ubuntu-latest] steps: - - uses: actions/checkout@v4 - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: "1.21.x" + - uses: actions/checkout@v4 + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: "1.22.x" - - name: Go Environment - run: go env + - name: Go Environment + run: go env - - name: Verify Go Modules - run: go mod verify + - name: Verify Go Modules + run: go mod verify - - name: Build - run: go build -v ./... + - name: Build + run: go build -v ./... - - name: Run tests with Race Detector - run: go test -race -vet=off ./... + - name: Run tests with Race Detector + run: go test -race -vet=off ./... - - name: Install staticcheck - run: go install honnef.co/go/tools/cmd/staticcheck@latest - - - name: Run staticcheck - run: staticcheck ./... + - name: Install staticcheck + run: go install honnef.co/go/tools/cmd/staticcheck@latest + - name: Run staticcheck + run: staticcheck ./... diff --git a/.gitignore b/.gitignore index 7362a83..35e3401 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ tmp *.pid coverage coverage.data +coverage.out build/* *.pbxuser *.mode1v3 diff --git a/Makefile b/Makefile index 180e896..68abf1b 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,14 @@ default: test install hype test: go test -timeout 10s -race -cover ./... +cov: + go test -coverprofile=coverage.out ./... + go tool cover -html=coverage.out + +hypecov: + go test -coverprofile=coverage.out . + go tool cover -html=coverage.out + install: go install -v ./cmd/hype diff --git a/binding/testdata/toc/01-one/simple/src/greet/go.mod b/binding/testdata/toc/01-one/simple/src/greet/go.mod index 21cc482..2287625 100644 --- a/binding/testdata/toc/01-one/simple/src/greet/go.mod +++ b/binding/testdata/toc/01-one/simple/src/greet/go.mod @@ -1,3 +1,3 @@ module demo -go 1.18 +go 1.22 diff --git a/binding/testdata/toc/01-one/src/greet/go.mod b/binding/testdata/toc/01-one/src/greet/go.mod index 21cc482..2287625 100644 --- a/binding/testdata/toc/01-one/src/greet/go.mod +++ b/binding/testdata/toc/01-one/src/greet/go.mod @@ -1,3 +1,3 @@ module demo -go 1.18 +go 1.22 diff --git a/binding/testdata/toc/02-two/simple/src/greet/go.mod b/binding/testdata/toc/02-two/simple/src/greet/go.mod index 21cc482..2287625 100644 --- a/binding/testdata/toc/02-two/simple/src/greet/go.mod +++ b/binding/testdata/toc/02-two/simple/src/greet/go.mod @@ -1,3 +1,3 @@ module demo -go 1.18 +go 1.22 diff --git a/binding/testdata/toc/02-two/src/greet/go.mod b/binding/testdata/toc/02-two/src/greet/go.mod index 21cc482..2287625 100644 --- a/binding/testdata/toc/02-two/src/greet/go.mod +++ b/binding/testdata/toc/02-two/src/greet/go.mod @@ -1,3 +1,3 @@ module demo -go 1.18 +go 1.22 diff --git a/binding/testdata/toc/03-three/simple/src/greet/go.mod b/binding/testdata/toc/03-three/simple/src/greet/go.mod index 21cc482..2287625 100644 --- a/binding/testdata/toc/03-three/simple/src/greet/go.mod +++ b/binding/testdata/toc/03-three/simple/src/greet/go.mod @@ -1,3 +1,3 @@ module demo -go 1.18 +go 1.22 diff --git a/binding/testdata/toc/03-three/src/greet/go.mod b/binding/testdata/toc/03-three/src/greet/go.mod index 21cc482..2287625 100644 --- a/binding/testdata/toc/03-three/src/greet/go.mod +++ b/binding/testdata/toc/03-three/src/greet/go.mod @@ -1,3 +1,3 @@ module demo -go 1.18 +go 1.22 diff --git a/body.go b/body.go index 918227c..2d71e66 100644 --- a/body.go +++ b/body.go @@ -2,7 +2,6 @@ package hype import ( "encoding/json" - "fmt" ) // Body is a container for all the elements in a document. @@ -23,9 +22,9 @@ func (b *Body) MarshalJSON() ([]byte, error) { return nil, err } - m["type"] = fmt.Sprintf("%T", b) + m["type"] = toType(b) - return json.Marshal(m) + return json.MarshalIndent(m, "", " ") } // AsPage returns the body as a Page. diff --git a/body_test.go b/body_test.go index b876e54..6995455 100644 --- a/body_test.go +++ b/body_test.go @@ -17,5 +17,16 @@ func Test_Body_AsPage(t *testing.T) { p := body.AsPage() r.NotNil(p) r.Equal(body.Element, p.Element) +} + +func Test_Body_MarshalJSON(t *testing.T) { + t.Parallel() + + body := &Body{ + Element: NewEl("body", nil), + } + + body.Nodes = append(body.Nodes, Text("hello")) + testJSON(t, "body", body) } diff --git a/cmd.go b/cmd.go index f6ac2bb..05d56ce 100644 --- a/cmd.go +++ b/cmd.go @@ -17,10 +17,10 @@ import ( type Cmd struct { *Element - Args []string `json:"args,omitempty"` - Env []string `json:"env,omitempty"` - ExpectedExit int `json:"expected_exit,omitempty"` - Timeout time.Duration `json:"timeout,omitempty"` + Args []string + Env []string + ExpectedExit int + Timeout time.Duration res *CmdResult } @@ -38,7 +38,7 @@ func (c *Cmd) MarshalJSON() ([]byte, error) { return nil, err } - m["type"] = fmt.Sprintf("%T", c) + m["type"] = toType(c) m["expected_exit"] = c.ExpectedExit m["timeout"] = c.Timeout.String() @@ -54,7 +54,7 @@ func (c *Cmd) MarshalJSON() ([]byte, error) { m["result"] = c.res } - return json.Marshal(m) + return json.MarshalIndent(m, "", " ") } func (c *Cmd) MD() string { @@ -109,18 +109,18 @@ func (c *Cmd) Execute(ctx context.Context, doc *Document) error { switch c.ExpectedExit { case -1: if res.Exit == 0 { - return fmt.Errorf("unexpected exit code: %d: %w", res.Exit, err) + return c.newError(err) } default: if res.Exit != c.ExpectedExit { - return fmt.Errorf("unexpected exit code: %d: %w", res.Exit, err) + return c.newError(err) } } } cres, err := NewCmdResult(doc.Parser, c, res) if err != nil { - return err + return c.newError(err) } c.Lock() @@ -131,6 +131,27 @@ func (c *Cmd) Execute(ctx context.Context, doc *Document) error { return nil } +func (c *Cmd) newError(err error) error { + if c == nil { + return err + } + + re, ok := err.(clam.RunError) + if !ok { + return CmdError{ + RunError: clam.RunError{ + Err: err, + }, + Filename: c.Filename, + } + } + + return CmdError{ + RunError: re, + Filename: c.Filename, + } +} + func NewCmd(el *Element) (*Cmd, error) { if el == nil { return nil, ErrIsNil("element") diff --git a/cmd/hype/cli/encode_test.go b/cmd/hype/cli/encode_test.go index 4055652..ea55b14 100644 --- a/cmd/hype/cli/encode_test.go +++ b/cmd/hype/cli/encode_test.go @@ -95,7 +95,11 @@ func Test_Encode_JSON(t *testing.T) { act := bb.Out.String() act = strings.TrimSpace(act) - // fmt.Println(act) + // fp := filepath.Join(root, tc.exp) + // f, err := os.Create(fp) + // r.NoError(err) + // f.Write([]byte(act)) + // r.NoError(f.Close()) r.Equal(exp, act) diff --git a/cmd/hype/cli/testdata/encode/json/success/execute-file.json b/cmd/hype/cli/testdata/encode/json/success/execute-file.json index 9a4ff36..b882c24 100644 --- a/cmd/hype/cli/testdata/encode/json/success/execute-file.json +++ b/cmd/hype/cli/testdata/encode/json/success/execute-file.json @@ -1,27 +1,71 @@ { + "filename": "module.md", "id": "execute file", "nodes": [ { - "file": "module.md", + "atom": "", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "", + "data_atom": "", + "namespace": "", + "node_type": "html.DocumentNode", + "type": "html.Node" + }, "nodes": [ { "atom": "html", - "file": "module.md", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "html", + "data_atom": "html", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, "nodes": [ { "atom": "head", - "file": "module.md", - "type": "*hype.Element" + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "head", + "data_atom": "head", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [], + "tag": "\u003chead\u003e", + "type": "hype.Element" }, [ { "atom": "body", - "file": "module.md", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "body", + "data_atom": "body", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, "nodes": [ [ { "atom": "page", - "file": "module.md", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "page", + "data_atom": "", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, "nodes": [ { "text": "\n", @@ -30,7 +74,15 @@ [ { "atom": "h1", - "file": "module.md", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "h1", + "data_atom": "h1", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, "level": 1, "nodes": [ { @@ -38,6 +90,7 @@ "type": "hype.Text" } ], + "tag": "\u003ch1\u003e", "type": "hype.Heading" } ], @@ -51,14 +104,29 @@ "attributes": { "id": "123" }, - "file": "module.md", + "filename": "module.md", + "html_node": { + "attributes": [ + { + "Namespace": "", + "Key": "id", + "Val": "123" + } + ], + "data": "echo", + "data_atom": "", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, "nodes": [ { "text": "HELLO!", "type": "hype.Text" } ], - "type": "*hype.Element" + "tag": "\u003cecho id=\"123\"\u003e", + "type": "hype.Element" } ], { @@ -66,8 +134,9 @@ "type": "hype.Text" } ], + "tag": "\u003cpage\u003e", "title": "JSON Encoding", - "type": "*hype.Page" + "type": "hype.Page" } ], { @@ -75,24 +144,26 @@ "type": "hype.Text" } ], - "type": "*hype.Body" + "tag": "\u003cbody\u003e", + "type": "hype.Body" } ] ], - "type": "*hype.Element" + "tag": "\u003chtml\u003e", + "type": "hype.Element" } ], - "type": "*hype.Element" + "tag": "", + "type": "hype.Element" } ], "parser": { - "type": "*hype.Parser", + "type": "hype.Parser", "section": 1, - "snippets": {} + "contents": "# JSON Encoding\n\n\u003cecho id=\"123\"\u003eHello!\u003c/echo\u003e\n" }, "section_id": 1, "snippets": {}, "title": "JSON Encoding", - "type": "*hype.Document", - "filename": "module.md" + "type": "hype.Document" } \ No newline at end of file diff --git a/cmd/hype/cli/testdata/encode/json/success/parse-file.json b/cmd/hype/cli/testdata/encode/json/success/parse-file.json index 9e740cc..e5f80fc 100644 --- a/cmd/hype/cli/testdata/encode/json/success/parse-file.json +++ b/cmd/hype/cli/testdata/encode/json/success/parse-file.json @@ -1,27 +1,71 @@ { + "filename": "module.md", "id": "parse file", "nodes": [ { - "file": "module.md", + "atom": "", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "", + "data_atom": "", + "namespace": "", + "node_type": "html.DocumentNode", + "type": "html.Node" + }, "nodes": [ { "atom": "html", - "file": "module.md", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "html", + "data_atom": "html", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, "nodes": [ { "atom": "head", - "file": "module.md", - "type": "*hype.Element" + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "head", + "data_atom": "head", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [], + "tag": "\u003chead\u003e", + "type": "hype.Element" }, [ { "atom": "body", - "file": "module.md", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "body", + "data_atom": "body", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, "nodes": [ [ { "atom": "page", - "file": "module.md", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "page", + "data_atom": "", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, "nodes": [ { "text": "\n", @@ -30,7 +74,15 @@ [ { "atom": "h1", - "file": "module.md", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "h1", + "data_atom": "h1", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, "level": 1, "nodes": [ { @@ -38,6 +90,7 @@ "type": "hype.Text" } ], + "tag": "\u003ch1\u003e", "type": "hype.Heading" } ], @@ -51,14 +104,29 @@ "attributes": { "id": "123" }, - "file": "module.md", + "filename": "module.md", + "html_node": { + "attributes": [ + { + "Namespace": "", + "Key": "id", + "Val": "123" + } + ], + "data": "echo", + "data_atom": "", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, "nodes": [ { "text": "Hello!", "type": "hype.Text" } ], - "type": "*hype.Element" + "tag": "\u003cecho id=\"123\"\u003e", + "type": "hype.Element" } ], { @@ -66,8 +134,9 @@ "type": "hype.Text" } ], + "tag": "\u003cpage\u003e", "title": "JSON Encoding", - "type": "*hype.Page" + "type": "hype.Page" } ], { @@ -75,24 +144,26 @@ "type": "hype.Text" } ], - "type": "*hype.Body" + "tag": "\u003cbody\u003e", + "type": "hype.Body" } ] ], - "type": "*hype.Element" + "tag": "\u003chtml\u003e", + "type": "hype.Element" } ], - "type": "*hype.Element" + "tag": "", + "type": "hype.Element" } ], "parser": { - "type": "*hype.Parser", + "type": "hype.Parser", "section": 1, - "snippets": {} + "contents": "# JSON Encoding\n\n\u003cecho id=\"123\"\u003eHello!\u003c/echo\u003e\n" }, "section_id": 1, "snippets": {}, "title": "JSON Encoding", - "type": "*hype.Document", - "filename": "module.md" + "type": "hype.Document" } \ No newline at end of file diff --git a/cmd/hype/cli/testdata/latex/file/simple/src/greet/go.mod b/cmd/hype/cli/testdata/latex/file/simple/src/greet/go.mod index 21cc482..2287625 100644 --- a/cmd/hype/cli/testdata/latex/file/simple/src/greet/go.mod +++ b/cmd/hype/cli/testdata/latex/file/simple/src/greet/go.mod @@ -1,3 +1,3 @@ module demo -go 1.18 +go 1.22 diff --git a/cmd/hype/cli/testdata/latex/file/src/greet/go.mod b/cmd/hype/cli/testdata/latex/file/src/greet/go.mod index 21cc482..2287625 100644 --- a/cmd/hype/cli/testdata/latex/file/src/greet/go.mod +++ b/cmd/hype/cli/testdata/latex/file/src/greet/go.mod @@ -1,3 +1,3 @@ module demo -go 1.18 +go 1.22 diff --git a/cmd/hype/cli/testdata/latex/multi/one/simple/src/greet/go.mod b/cmd/hype/cli/testdata/latex/multi/one/simple/src/greet/go.mod index 21cc482..2287625 100644 --- a/cmd/hype/cli/testdata/latex/multi/one/simple/src/greet/go.mod +++ b/cmd/hype/cli/testdata/latex/multi/one/simple/src/greet/go.mod @@ -1,3 +1,3 @@ module demo -go 1.18 +go 1.22 diff --git a/cmd/hype/cli/testdata/latex/multi/one/src/greet/go.mod b/cmd/hype/cli/testdata/latex/multi/one/src/greet/go.mod index 21cc482..2287625 100644 --- a/cmd/hype/cli/testdata/latex/multi/one/src/greet/go.mod +++ b/cmd/hype/cli/testdata/latex/multi/one/src/greet/go.mod @@ -1,3 +1,3 @@ module demo -go 1.18 +go 1.22 diff --git a/cmd/hype/cli/testdata/latex/multi/three/simple/src/greet/go.mod b/cmd/hype/cli/testdata/latex/multi/three/simple/src/greet/go.mod index 21cc482..2287625 100644 --- a/cmd/hype/cli/testdata/latex/multi/three/simple/src/greet/go.mod +++ b/cmd/hype/cli/testdata/latex/multi/three/simple/src/greet/go.mod @@ -1,3 +1,3 @@ module demo -go 1.18 +go 1.22 diff --git a/cmd/hype/cli/testdata/latex/multi/three/src/greet/go.mod b/cmd/hype/cli/testdata/latex/multi/three/src/greet/go.mod index 21cc482..2287625 100644 --- a/cmd/hype/cli/testdata/latex/multi/three/src/greet/go.mod +++ b/cmd/hype/cli/testdata/latex/multi/three/src/greet/go.mod @@ -1,3 +1,3 @@ module demo -go 1.18 +go 1.22 diff --git a/cmd/hype/cli/testdata/latex/multi/two/simple/src/greet/go.mod b/cmd/hype/cli/testdata/latex/multi/two/simple/src/greet/go.mod index 21cc482..2287625 100644 --- a/cmd/hype/cli/testdata/latex/multi/two/simple/src/greet/go.mod +++ b/cmd/hype/cli/testdata/latex/multi/two/simple/src/greet/go.mod @@ -1,3 +1,3 @@ module demo -go 1.18 +go 1.22 diff --git a/cmd/hype/cli/testdata/latex/multi/two/src/greet/go.mod b/cmd/hype/cli/testdata/latex/multi/two/src/greet/go.mod index 21cc482..2287625 100644 --- a/cmd/hype/cli/testdata/latex/multi/two/src/greet/go.mod +++ b/cmd/hype/cli/testdata/latex/multi/two/src/greet/go.mod @@ -1,3 +1,3 @@ module demo -go 1.18 +go 1.22 diff --git a/cmd/hype/cli/testdata/latex/simple/simple/src/greet/go.mod b/cmd/hype/cli/testdata/latex/simple/simple/src/greet/go.mod index 21cc482..2287625 100644 --- a/cmd/hype/cli/testdata/latex/simple/simple/src/greet/go.mod +++ b/cmd/hype/cli/testdata/latex/simple/simple/src/greet/go.mod @@ -1,3 +1,3 @@ module demo -go 1.18 +go 1.22 diff --git a/cmd/hype/cli/testdata/latex/simple/src/greet/go.mod b/cmd/hype/cli/testdata/latex/simple/src/greet/go.mod index 21cc482..2287625 100644 --- a/cmd/hype/cli/testdata/latex/simple/src/greet/go.mod +++ b/cmd/hype/cli/testdata/latex/simple/src/greet/go.mod @@ -1,3 +1,3 @@ module demo -go 1.18 +go 1.22 diff --git a/cmd/hype/cli/toc.go b/cmd/hype/cli/toc.go index 54a2a92..1d6c9cd 100644 --- a/cmd/hype/cli/toc.go +++ b/cmd/hype/cli/toc.go @@ -28,8 +28,6 @@ func (cmd *TOC) Main(ctx context.Context, pwd string, args []string) error { cmd.FS = os.DirFS(path) } - fmt.Printf("TODO >> toc.go:32 path %[1]T %[1]v\n", path) - p := hype.NewParser(cmd.FS) docs, err := p.ParseFolder(path) diff --git a/cmd_error.go b/cmd_error.go new file mode 100644 index 0000000..2285365 --- /dev/null +++ b/cmd_error.go @@ -0,0 +1,48 @@ +package hype + +import ( + "encoding/json" + + "github.com/markbates/clam" +) + +type CmdError struct { + clam.RunError + Filename string +} + +func (ce CmdError) MarshalJSON() ([]byte, error) { + mm := map[string]any{ + "args": ce.Args, + "error": errForJSON(ce.Err), + "exit": ce.Exit, + "filename": ce.Filename, + "output": string(ce.Output), + "root": ce.Dir, + "type": toType(ce), + } + + return json.MarshalIndent(mm, "", " ") +} + +func (ce CmdError) Error() string { + return toError(ce) +} + +func (ce CmdError) As(target any) bool { + ex, ok := target.(*CmdError) + if !ok { + return ce.RunError.As(target) + } + + (*ex) = ce + return true +} + +func (ce CmdError) Is(target error) bool { + if _, ok := target.(CmdError); ok { + return true + } + + return ce.RunError.Is(target) +} diff --git a/cmd_error_test.go b/cmd_error_test.go new file mode 100644 index 0000000..fcd0833 --- /dev/null +++ b/cmd_error_test.go @@ -0,0 +1,58 @@ +package hype + +import ( + "errors" + "fmt" + "io" + "testing" + + "github.com/markbates/clam" + "github.com/stretchr/testify/require" +) + +func Test_CmdError(t *testing.T) { + t.Parallel() + r := require.New(t) + + oce := CmdError{ + RunError: clam.RunError{ + Err: ExecuteError{ + Err: io.EOF, + }, + }, + } + + wrapped := fmt.Errorf("error: %w", oce) + + r.True(oce.As(&CmdError{}), oce) + r.True(oce.Is(oce), oce) + r.True(oce.Unwrap() == io.EOF, oce) + + r.True(errors.As(wrapped, &CmdError{}), wrapped) + r.True(errors.As(wrapped, &ExecuteError{}), wrapped) + + r.True(errors.Is(wrapped, CmdError{}), wrapped) + r.True(errors.Is(wrapped, ExecuteError{}), wrapped) + r.True(errors.Is(wrapped, io.EOF), wrapped) + + err := errors.Unwrap(oce) + r.Equal(io.EOF, err) +} + +func Test_CmdError_MarshalJSON(t *testing.T) { + t.Parallel() + + ce := CmdError{ + RunError: clam.RunError{ + Args: []string{"echo", "hello"}, + Env: []string{"FOO=bar", "BAR=baz"}, + Err: io.EOF, + Exit: 1, + Output: []byte("foo\nbar\nbaz\n"), + Dir: "/tmp", + }, + Filename: "foo.go", + } + + testJSON(t, "cmd_error", ce) +} diff --git a/cmd_result.go b/cmd_result.go index 4407379..93c9641 100644 --- a/cmd_result.go +++ b/cmd_result.go @@ -36,20 +36,21 @@ func (c *CmdResult) MarshalJSON() ([]byte, error) { return nil, err } - m["type"] = fmt.Sprintf("%T", c) - - if c.Result != nil { - y := struct { - *clam.Result - Type string `json:"type,omitempty"` - }{ - Result: c.Result, - Type: fmt.Sprintf("%T", c.Result), - } - m["result"] = y + m["type"] = toType(c) + + if c.Result == nil { + return json.MarshalIndent(m, "", " ") } - return json.Marshal(m) + m["args"] = c.Args + m["dir"] = c.Dir + m["duration"] = c.Duration.String() + m["err"] = errForJSON(c.Err) + m["exit"] = c.Exit + m["stderr"] = string(c.Stderr) + m["stdout"] = string(c.Stdout) + + return json.MarshalIndent(m, "", " ") } func (c *CmdResult) MD() string { diff --git a/cmd_result_test.go b/cmd_result_test.go index 651f827..00202df 100644 --- a/cmd_result_test.go +++ b/cmd_result_test.go @@ -1 +1,30 @@ package hype + +import ( + "io" + "testing" + "time" + + "github.com/markbates/clam" +) + +func Test_CmdResult_MarshalJSON(t *testing.T) { + t.Parallel() + + cr := &CmdResult{ + Element: NewEl("cmd", nil), + Result: &clam.Result{ + Args: []string{"echo", "hello"}, + Dir: "/tmp", + Duration: time.Second, + Env: []string{"FOO=bar", "BAR=baz"}, + Err: io.EOF, + Exit: 1, + Stderr: []byte("nothing"), + Stdout: []byte("foo\nbar\nbaz\n"), + }, + } + + testJSON(t, "cmd_result", cr) + +} diff --git a/cmd_test.go b/cmd_test.go index ab046e8..b078dd3 100644 --- a/cmd_test.go +++ b/cmd_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/markbates/clam" "github.com/stretchr/testify/require" ) @@ -93,6 +94,8 @@ func Test_Cmd_Execute_UnexpectedExit(t *testing.T) { err := c.Execute(ctx, doc) r.Error(err) + r.True(errors.Is(err, CmdError{})) + c.ExpectedExit = 1 err = c.Execute(ctx, doc) @@ -151,4 +154,35 @@ func Test_Cmd_Execute_Timeout(t *testing.T) { err := c.Execute(ctx, doc) r.Error(err) + r.True(errors.Is(err, CmdError{})) +} + +func Test_Cmd_MarshalJSON(t *testing.T) { + t.Parallel() + + res := &CmdResult{ + Element: NewEl("result", nil), + Result: &clam.Result{ + Args: []string{"echo", "hello"}, + Dir: "testdata/commands", + Env: []string{"FOO=bar", "BAR=baz"}, + Err: errors.New("this is an error"), + Exit: 1, + Stderr: []byte("this is stderr"), + Stdout: []byte("this is stdout"), + Duration: time.Second, + }, + } + + c := &Cmd{ + Element: NewEl("cmd", nil), + Args: []string{"echo", "hello"}, + Env: []string{"FOO=bar", "BAR=baz"}, + ExpectedExit: 1, + Timeout: time.Second, + res: res, + } + + testJSON(t, "cmd", c) + } diff --git a/comment.go b/comment.go index c5ea70d..d9a7119 100644 --- a/comment.go +++ b/comment.go @@ -8,10 +8,11 @@ import ( type Comment string func (c Comment) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]any{ - "type": fmt.Sprintf("%T", c), + m := map[string]any{ "text": string(c), - }) + "type": toType(c), + } + return json.MarshalIndent(m, "", " ") } func (tn Comment) Children() Nodes { diff --git a/comment_test.go b/comment_test.go index 45ff9f3..1924b62 100644 --- a/comment_test.go +++ b/comment_test.go @@ -27,3 +27,12 @@ func Test_Comment(t *testing.T) { r.True(ok) r.Equal("", comment.String()) } + +func Test_Comment_MarshalJSON(t *testing.T) { + t.Parallel() + + comment := Comment("hello") + + testJSON(t, "comment", comment) + +} diff --git a/document.go b/document.go index 428778e..10602a8 100644 --- a/document.go +++ b/document.go @@ -14,17 +14,17 @@ import ( var _ Node = &Document{} type Document struct { - fs.FS `json:"-"` - sync.RWMutex `json:"-"` - - ID string `json:"id,omitempty"` - Nodes Nodes `json:"nodes,omitempty"` - Parser *Parser `json:"parser,omitempty"` // Parser used to create the document - Root string `json:"root,omitempty"` - SectionID int `json:"section_id,omitempty"` - Snippets Snippets `json:"snippets,omitempty"` - Title string `json:"title,omitempty"` - Filename string `json:"filename,omitempty"` + fs.FS + sync.RWMutex + + ID string + Nodes Nodes + Parser *Parser // Parser used to create the document + Root string + SectionID int + Snippets Snippets + Title string + Filename string } func (doc *Document) MarshalJSON() ([]byte, error) { @@ -32,29 +32,37 @@ func (doc *Document) MarshalJSON() ([]byte, error) { return nil, ErrIsNil("document") } + snips := struct { + Rules map[string]string `json:"rules,omitempty"` + Snippets map[string]map[string]Snippet `json:"snippets,omitempty"` + }{ + Rules: doc.Snippets.rules, + Snippets: doc.Snippets.snippets, + } + x := struct { - ID string `json:"id,omitempty"` - Nodes Nodes `json:"nodes,omitempty"` - Parser *Parser `json:"parser,omitempty"` // Parser used to create the document - Root string `json:"root,omitempty"` - SectionID int `json:"section_id,omitempty"` - Snippets Snippets `json:"snippets,omitempty"` - Title string `json:"title,omitempty"` - Type string `json:"type"` - Filename string `json:"filename,omitempty"` + Filename string `json:"filename,omitempty"` + ID string `json:"id,omitempty"` + Nodes Nodes `json:"nodes,omitempty"` + Parser *Parser `json:"parser,omitempty"` + Root string `json:"root,omitempty"` + SectionID int `json:"section_id,omitempty"` + Snippets any `json:"snippets,omitempty"` + Title string `json:"title,omitempty"` + Type string `json:"type"` }{ - Type: fmt.Sprintf("%T", doc), + Filename: doc.Filename, + ID: doc.ID, + Nodes: doc.Nodes, Parser: doc.Parser, Root: doc.Root, SectionID: doc.SectionID, - Snippets: doc.Snippets, + Snippets: snips, Title: doc.Title, - Nodes: doc.Nodes, - ID: doc.ID, - Filename: doc.Filename, + Type: toType(doc), } - return json.Marshal(x) + return json.MarshalIndent(x, "", " ") } func (doc *Document) Pages() ([]*Page, error) { @@ -63,16 +71,17 @@ func (doc *Document) Pages() ([]*Page, error) { } pages := ByType[*Page](doc.Nodes) + if len(pages) > 0 { + return pages, nil + } - if len(pages) == 0 { - body, err := doc.Body() - if err != nil { - return nil, err - } - - pages = append(pages, body.AsPage()) + body, err := doc.Body() + if err != nil { + return nil, err } + pages = append(pages, body.AsPage()) + return pages, nil } @@ -120,12 +129,16 @@ func (doc *Document) String() string { // Execute the Document with the given context. // Any child nodes that implement the PreExecuter, // ExecutableNode, or PostExecuter interfaces will be executed. -func (doc *Document) Execute(ctx context.Context) error { +func (doc *Document) Execute(ctx context.Context) (err error) { if doc == nil { return ErrIsNil("document") } - err := doc.Children().PreExecute(ctx, doc) + defer func() { + err = doc.ensureExecuteError(err) + }() + + err = doc.Children().PreExecute(ctx, doc) if err != nil { return err } @@ -209,3 +222,30 @@ func (doc *Document) MD() string { return strings.Join(bodies, "\n---\n") } + +func (doc *Document) ensureExecuteError(err error) error { + if err == nil { + return nil + } + + if _, ok := err.(ExecuteError); ok { + return err + } + + if doc == nil { + return err + } + + var contents []byte + if doc.Parser != nil { + contents = doc.Parser.Contents + } + + return ExecuteError{ + Contents: contents, + Document: doc, + Err: err, + Filename: doc.Filename, + Root: doc.Root, + } +} diff --git a/document_test.go b/document_test.go index 3677562..60d46da 100644 --- a/document_test.go +++ b/document_test.go @@ -2,10 +2,10 @@ package hype import ( "context" + "errors" "io/fs" "strings" "testing" - "testing/fstest" "time" "github.com/stretchr/testify/require" @@ -13,80 +13,45 @@ import ( func Test_Document_Execute(t *testing.T) { t.Parallel() - r := require.New(t) - - mod := `# Page 1 - - - -` - - second := `# Second Page - -` - - cab := fstest.MapFS{ - "module.md": &fstest.MapFile{ - Data: []byte(mod), - }, - "second/second.md": &fstest.MapFile{ - Data: []byte(second), - }, - } - - p := NewParser(cab) - p.NodeParsers["foo"] = func(p *Parser, el *Element) (Nodes, error) { - x := executeNode{ - Element: el, - } - x.ExecuteFn = func(ctx context.Context, d *Document) error { - time.Sleep(time.Millisecond * 10) - - x.Lock() - x.Nodes = append(x.Nodes, Text("baz")) - x.Unlock() - return nil - } - - x.Nodes = append(x.Nodes, Text("bar")) - return Nodes{x}, nil - } - - doc, err := p.ParseFile("module.md") - r.NoError(err) + t.Run("success", func(t *testing.T) { + r := require.New(t) + p := testParser(t, "testdata/doc/execution/success") - ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*100) - defer cancel() + doc, err := p.ParseFile("module.md") + r.NoError(err) - go func() { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) defer cancel() err = doc.Execute(ctx) r.NoError(err) - }() - <-ctx.Done() + act := doc.String() + act = strings.TrimSpace(act) - r.NotEqual(context.DeadlineExceeded, ctx.Err()) + exp := "\n

Command

\n\n
$ echo Hello World\n\nHello World
\n
\n" - act := doc.String() - // fmt.Println(act) + r.Equal(exp, act) + }) - exp := ` -

Page 1

+ t.Run("failure", func(t *testing.T) { + r := require.New(t) + p := testParser(t, "testdata/doc/execution/failure") -barbaz -
- -

Second Page

+ doc, err := p.ParseFile("module.md") + r.NoError(err) -barbaz -
+ ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) + defer cancel() -` + err = doc.Execute(ctx) + r.Error(err) - r.Equal(exp, act) + _, ok := err.(ExecuteError) + r.True(ok, err) + r.True(errors.Is(err, ExecuteError{}), err) + }) } @@ -119,3 +84,50 @@ func Test_Document_MD(t *testing.T) { r.Equal(exp, act) } + +func Test_Document_MarshalJSON(t *testing.T) { + t.Parallel() + r := require.New(t) + + p := testParser(t, "testdata/doc/snippets") + p.DocIDGen = func() (string, error) { + return "1", nil + } + + ctx := context.Background() + + doc, err := p.ParseExecuteFile(ctx, "module.md") + r.NoError(err) + + testJSON(t, "document", doc) +} + +func Test_Document_Pages(t *testing.T) { + t.Parallel() + r := require.New(t) + + p := testParser(t, "testdata/doc/pages") + + doc, err := p.ParseFile("module.md") + r.NoError(err) + + pages, err := doc.Pages() + r.NoError(err) + + r.Len(pages, 3) +} + +func Test_Document_Pages_NoPages(t *testing.T) { + t.Parallel() + r := require.New(t) + + p := testParser(t, "testdata/doc/simple") + + doc, err := p.ParseFile("module.md") + r.NoError(err) + + pages, err := doc.Pages() + r.NoError(err) + + r.Len(pages, 1) +} diff --git a/element.go b/element.go index 08470f4..deff361 100644 --- a/element.go +++ b/element.go @@ -23,7 +23,14 @@ type Element struct { HTMLNode *html.Node Nodes Nodes Parent Node - FileName string // only set when Parser.ParseFile() is used + Filename string // only set when Parser.ParseFile() is used +} + +// FileName returns the filename of the element. +// This is only set when Parser.ParseFile() is used. +func (el *Element) FileName() string { + // Note: this function was added to satisfy an interface. + return el.Filename } func (el *Element) JSONMap() (map[string]any, error) { @@ -35,31 +42,57 @@ func (el *Element) JSONMap() (map[string]any, error) { defer el.RUnlock() m := map[string]any{ - "type": fmt.Sprintf("%T", el), + "atom": el.Atom(), + "attributes": map[string]string{}, + "filename": el.Filename, + "nodes": Nodes{}, + "tag": el.StartTag(), + "type": toType(el), } - if len(el.FileName) > 0 { - m["file"] = el.FileName + if len(el.Nodes) > 0 { + m["nodes"] = el.Nodes } - if el.Attributes != nil && el.Attributes.Len() > 0 { + if el.Attributes.Len() > 0 { m["attributes"] = el.Attributes } - nodes := el.Nodes + hn := el.HTMLNode + if hn == nil { + return m, nil + } - if len(nodes) > 0 { - m["nodes"] = nodes + hnm := map[string]any{ + "data": hn.Data, + "data_atom": hn.DataAtom.String(), + "namespace": hn.Namespace, + "type": toType(hn), } - hn := el.HTMLNode + switch hn.Type { + case html.ErrorNode: + hnm["node_type"] = "html.ErrorNode" + case html.TextNode: + hnm["node_type"] = "html.TextNode" + case html.DocumentNode: + hnm["node_type"] = "html.DocumentNode" + case html.ElementNode: + hnm["node_type"] = "html.ElementNode" + case html.CommentNode: + hnm["node_type"] = "html.CommentNode" + case html.DoctypeNode: + hnm["node_type"] = "html.DoctypeNode" + case html.RawNode: + hnm["node_type"] = "html.RawNode" + } - if hn != nil { - if len(hn.Data) > 0 { - m["atom"] = hn.Data - } + if len(hn.Attr) > 0 { + hnm["attributes"] = hn.Attr } + m["html_node"] = hnm + return m, nil } @@ -73,7 +106,7 @@ func (el *Element) MarshalJSON() ([]byte, error) { return nil, err } - return json.Marshal(m) + return json.MarshalIndent(m, "", " ") } func (el *Element) Format(f fmt.State, verb rune) { @@ -88,8 +121,8 @@ func (el *Element) Format(f fmt.State, verb rune) { return } - if len(el.FileName) > 0 { - fmt.Fprintf(f, "file://%s: ", el.FileName) + if len(el.Filename) > 0 { + fmt.Fprintf(f, "file://%s: ", el.Filename) } fmt.Fprintf(f, "%s", st) @@ -99,33 +132,6 @@ func (el *Element) Format(f fmt.State, verb rune) { } } -func (el *Element) Clone() (*Element, error) { - if el == nil { - return nil, ErrIsNil("element") - } - - ats, err := el.Attributes.Clone() - if err != nil { - return nil, err - } - - nel := &Element{ - Attributes: ats, - HTMLNode: &html.Node{ - Attr: el.HTMLNode.Attr, - Data: el.HTMLNode.Data, - DataAtom: el.HTMLNode.DataAtom, - Namespace: el.HTMLNode.Namespace, - Type: el.HTMLNode.Type, - }, - Nodes: el.Nodes, - Parent: el.Parent, - FileName: el.FileName, - } - - return nel, nil -} - func (el *Element) Atom() Atom { if el.HTMLNode == nil { return Atom("") @@ -147,6 +153,10 @@ func (el *Element) HTML() *html.Node { // StartTag returns the start tag for the element. // For example, for an element with an Atom of "div", the start tag would be "
". func (el *Element) StartTag() string { + if el == nil { + return "" + } + a := el.Atom() if len(a) == 0 { return "" @@ -185,6 +195,10 @@ func (el *Element) EndTag() string { // String returns StartTag() + Children().String() + EndTag() func (el *Element) String() string { + if el == nil { + return "" + } + s := el.StartTag() s += el.Children().String() s += el.EndTag() @@ -224,7 +238,7 @@ func NewEl[T ~string](at T, parent Node) *Element { var fn string if e, ok := parent.(*Element); ok { - fn = e.FileName + fn = e.Filename } return &Element{ @@ -235,7 +249,7 @@ func NewEl[T ~string](at T, parent Node) *Element { DataAtom: atom.Lookup([]byte(string(at))), }, Parent: parent, - FileName: fn, + Filename: fn, } } @@ -244,13 +258,13 @@ func (el *Element) updateFileName(dir string) { return } - if strings.HasPrefix(el.FileName, dir) { + if strings.HasPrefix(el.Filename, dir) { return } el.Lock() defer el.Unlock() - el.FileName = filepath.Join(dir, el.FileName) + el.Filename = filepath.Join(dir, el.Filename) } func (el *Element) Set(k string, v string) error { diff --git a/element_test.go b/element_test.go index a0cd2a6..493bc4a 100644 --- a/element_test.go +++ b/element_test.go @@ -110,3 +110,16 @@ func Test_Element_String(t *testing.T) { } } + +func Test_Element_MarshalJSON(t *testing.T) { + t.Parallel() + r := require.New(t) + + parent := NewEl("body", nil) + el := NewEl("div", parent) + err := el.Set("class", "foo") + r.NoError(err) + + testJSON(t, "element", el) + +} diff --git a/errors.go b/errors.go index 1568e8f..60a48e4 100644 --- a/errors.go +++ b/errors.go @@ -1,6 +1,7 @@ package hype import ( + "encoding/json" "fmt" ) @@ -25,3 +26,31 @@ func WrapNodeErr(n Node, err error) error { return fmt.Errorf("%T: %w", n, err) } + +func errForJSON(err error) any { + if err == nil { + return nil + } + + if _, ok := err.(json.Marshaler); ok { + return err + } + + return err.Error() +} + +func toError(err error) string { + if err == nil { + return "" + } + + if _, ok := err.(json.Marshaler); ok { + b, err := json.MarshalIndent(err, "", " ") + if err != nil { + return "error marshalling to json: " + err.Error() + } + return string(b) + } + + return err.Error() +} diff --git a/execute.go b/execute.go index 8fc9537..c1cf783 100644 --- a/execute.go +++ b/execute.go @@ -19,7 +19,10 @@ type WaitGrouper interface { Go(fn func() error) } -func (list Nodes) Execute(wg WaitGrouper, ctx context.Context, d *Document) error { +func (list Nodes) Execute(wg WaitGrouper, ctx context.Context, d *Document) (err error) { + if d == nil { + return ErrIsNil("document") + } for _, n := range list { @@ -31,16 +34,42 @@ func (list Nodes) Execute(wg WaitGrouper, ctx context.Context, d *Document) erro continue } + name := d.Filename + + if n, ok := n.(interface{ FileName() string }); ok { + name = n.FileName() + } + cn, ok := n.(ExecutableNode) if ok { wg.Go(func() error { - return cn.Execute(ctx, d) + err := cn.Execute(ctx, d) + if err != nil { + var contents []byte + if d.Parser != nil { + contents = d.Parser.Contents + } + return ExecuteError{ + Contents: contents, + Document: d, + Err: err, + Filename: name, + Root: d.Root, + } + } + return nil }) } err := n.Children().Execute(wg, ctx, d) if err != nil { - return err + return ExecuteError{ + Contents: d.Parser.Contents, + Document: d, + Err: err, + Filename: name, + Root: d.Root, + } } } diff --git a/execute_error.go b/execute_error.go new file mode 100644 index 0000000..80355c5 --- /dev/null +++ b/execute_error.go @@ -0,0 +1,57 @@ +package hype + +import ( + "encoding/json" + "errors" +) + +type ExecuteError struct { + Err error + Contents []byte + Document *Document + Filename string + Root string +} + +func (pe ExecuteError) MarshalJSON() ([]byte, error) { + mm := map[string]any{ + "contents": string(pe.Contents), + "document": pe.Document, + "error": errForJSON(pe.Err), + "filename": pe.Filename, + "root": pe.Root, + "type": toType(pe), + } + + return json.MarshalIndent(mm, "", " ") +} + +func (pe ExecuteError) Error() string { + return toError(pe) +} + +func (pe ExecuteError) Unwrap() error { + if _, ok := pe.Err.(unwrapper); ok { + return errors.Unwrap(pe.Err) + } + + return pe.Err +} + +func (pe ExecuteError) As(target any) bool { + ex, ok := target.(*ExecuteError) + if !ok { + return errors.As(pe.Err, target) + } + + (*ex) = pe + return true +} + +func (pe ExecuteError) Is(target error) bool { + if _, ok := target.(ExecuteError); ok { + return true + } + + return errors.Is(pe.Err, target) +} diff --git a/execute_error_test.go b/execute_error_test.go new file mode 100644 index 0000000..303d23e --- /dev/null +++ b/execute_error_test.go @@ -0,0 +1,126 @@ +package hype + +import ( + "context" + "errors" + "fmt" + "io" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_ExecuteError(t *testing.T) { + t.Parallel() + r := require.New(t) + + oce := ExecuteError{ + Err: io.EOF, + } + + wrapped := fmt.Errorf("error: %w", oce) + + r.True(oce.As(&ExecuteError{}), oce) + r.True(oce.Is(oce), oce) + r.True(oce.Unwrap() == io.EOF, oce) + + var ce ExecuteError + r.True(errors.As(wrapped, &ce), wrapped) + + ce = ExecuteError{} + r.True(errors.Is(wrapped, ce), wrapped) + + err := errors.Unwrap(oce) + r.Equal(io.EOF, err) +} + +func Test_Execute_Errors(t *testing.T) { + t.Parallel() + + tp := func() *Parser { + p := testParser(t, "testdata/parser/errors/execute") + + p.NodeParsers["foo"] = func(p *Parser, el *Element) (Nodes, error) { + n := newExecuteNode(t, func(ctx context.Context, d *Document) error { + return fmt.Errorf("boom") + }) + + return Nodes{n}, nil + } + + return p + } + + type inFn func() error + + ctx := context.Background() + + tcs := []struct { + name string + in inFn + }{ + { + name: "ParseExecute", + in: func() error { + p := tp() + _, err := p.ParseExecute(ctx, strings.NewReader("")) + return err + }, + }, + { + name: "ParseExecuteFile", + in: func() error { + p := tp() + _, err := p.ParseExecuteFile(ctx, "module.md") + return err + }, + }, + { + name: "Document.Execute", + in: func() error { + p := tp() + d, err := p.ParseFile("module.md") + if err != nil { + return err + } + + return d.Execute(ctx) + }, + }, + } + + for _, tc := range tcs { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + r := require.New(t) + + err := tc.in() + r.Error(err) + + var ce ExecuteError + r.True(errors.As(err, &ce), err) + r.NotNil(ce.Document, err) + + ce = ExecuteError{} + r.True(errors.Is(err, ce), err) + }) + } +} + +func Test_ExecuteError_MarshalJSON(t *testing.T) { + t.Parallel() + + ee := ExecuteError{ + Err: io.EOF, + Filename: "module.md", + Root: "testdata/parser/errors/execute", + Document: &Document{ + Title: "My Title", + }, + Contents: []byte("foo"), + } + + testJSON(t, "execute_error", ee) +} diff --git a/execute_test.go b/execute_test.go index 66ef08b..9505956 100644 --- a/execute_test.go +++ b/execute_test.go @@ -2,6 +2,7 @@ package hype import ( "context" + "errors" "fmt" "testing" @@ -83,13 +84,13 @@ func Test_Nodes_Execute_Errors(t *testing.T) { nodes := Nodes{tc.node} wg := &errgroup.Group{} - err := nodes.Execute(wg, nil, nil) + err := nodes.Execute(wg, nil, &Document{}) r.NoError(err) err = wg.Wait() r.Error(err) - r.Contains(err.Error(), "boom") + r.True(errors.Is(err, ExecuteError{}), err) }) } } diff --git a/fenced_code.go b/fenced_code.go index 8c46418..859c6d9 100644 --- a/fenced_code.go +++ b/fenced_code.go @@ -29,29 +29,9 @@ func (code *FencedCode) MarshalJSON() ([]byte, error) { m["lang"] = lang } - m["type"] = fmt.Sprintf("%T", code) + m["type"] = toType(code) - return json.Marshal(m) -} - -func (code *FencedCode) StartTag() string { - if code == nil || code.Element == nil { - return "" - } - - return code.Element.StartTag() -} - -func (code *FencedCode) EndTag() string { - if code == nil || code.Element == nil { - return "" - } - - return "" -} - -func (code *FencedCode) String() string { - return code.StartTag() + code.Children().String() + code.EndTag() + return json.MarshalIndent(m, "", " ") } func (code *FencedCode) MD() string { diff --git a/fenced_code_test.go b/fenced_code_test.go new file mode 100644 index 0000000..d03a940 --- /dev/null +++ b/fenced_code_test.go @@ -0,0 +1,23 @@ +package hype + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_FencedCode_MarshalJSON(t *testing.T) { + t.Parallel() + + r := require.New(t) + + code := &FencedCode{ + Element: NewEl("code", nil), + } + code.Nodes = append(code.Nodes, Text("var x = 1")) + + r.NoError(code.Set("language", "go")) + + testJSON(t, "fenced_code", code) + +} diff --git a/figcaption.go b/figcaption.go index dc635b2..a383164 100644 --- a/figcaption.go +++ b/figcaption.go @@ -2,7 +2,6 @@ package hype import ( "encoding/json" - "fmt" "strings" ) @@ -23,9 +22,9 @@ func (fc *Figcaption) MarshalJSON() ([]byte, error) { return nil, err } - m["type"] = fmt.Sprintf("%T", fc) + m["type"] = toType(fc) - return json.Marshal(m) + return json.MarshalIndent(m, "", " ") } func (fc *Figcaption) MD() string { diff --git a/figcaption_test.go b/figcaption_test.go new file mode 100644 index 0000000..42b038d --- /dev/null +++ b/figcaption_test.go @@ -0,0 +1,15 @@ +package hype + +import "testing" + +func Test_Figcaption_MarshalJSON(t *testing.T) { + t.Parallel() + + fig := &Figcaption{ + Element: NewEl("figcaption", nil), + } + fig.Nodes = append(fig.Nodes, Text("This is a caption")) + + testJSON(t, "figcaption", fig) + +} diff --git a/figure.go b/figure.go index db2103e..bc9d0f4 100644 --- a/figure.go +++ b/figure.go @@ -31,12 +31,12 @@ func (f *Figure) MarshalJSON() ([]byte, error) { return nil, err } - m["type"] = fmt.Sprintf("%T", f) + m["type"] = toType(f) m["pos"] = f.Pos m["section_id"] = f.SectionID m["style"] = f.Style() - return json.Marshal(m) + return json.MarshalIndent(m, "", " ") } func (f *Figure) MD() string { diff --git a/figure_test.go b/figure_test.go new file mode 100644 index 0000000..a04ddb8 --- /dev/null +++ b/figure_test.go @@ -0,0 +1,18 @@ +package hype + +import "testing" + +func Test_Figure_MarshalJSON(t *testing.T) { + t.Parallel() + + f := &Figure{ + Element: NewEl("figure", nil), + Pos: 1, + SectionID: 2, + style: "figure", + } + f.Nodes = append(f.Nodes, Text("This is a figure")) + + testJSON(t, "figure", f) + +} diff --git a/go.mod b/go.mod index 1eacd00..f65e5b5 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,17 @@ module github.com/gopherguides/hype -go 1.21 +go 1.22 + +toolchain go1.22.0 require ( github.com/gobuffalo/flect v1.0.2 github.com/gofrs/uuid/v5 v5.0.0 - github.com/markbates/clam v0.0.0-20220808175708-ef60f46826fb + github.com/markbates/clam v0.0.0-20240219024730-b98cdab94ec3 github.com/markbates/cleo v0.0.0-20230821202903-72220ef5f7f0 github.com/markbates/fsx v1.3.0 github.com/markbates/garlic v1.0.0 + github.com/markbates/hepa v0.0.0-20211129002629-856d16f89b9d github.com/markbates/iox v0.0.0-20230829013604-e0813da73cc6 github.com/markbates/plugins v0.0.0-20230821202759-9443baa9b3df github.com/markbates/sweets v0.0.0-20210926032915-062eb9bcc0e5 @@ -24,6 +27,6 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 // indirect + golang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 73bdfc2..07696b1 100644 --- a/go.sum +++ b/go.sum @@ -7,14 +7,16 @@ github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M= github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/markbates/clam v0.0.0-20220808175708-ef60f46826fb h1:9V50gZSw997pZqgLCjhyApFsS8JA1sIZmD+3+MLJcl4= -github.com/markbates/clam v0.0.0-20220808175708-ef60f46826fb/go.mod h1:5bD7elUD4b404zK6eOn4Q0wKJYb0mxL6yDeQZSRn9Hk= +github.com/markbates/clam v0.0.0-20240219024730-b98cdab94ec3 h1:dD7cV95C3jWtuBfJycK6D8uuli0FJK4md9RSRV2/LHU= +github.com/markbates/clam v0.0.0-20240219024730-b98cdab94ec3/go.mod h1:sLg6WXuDjdORMJaWZHBuzW6IcfjaYLaktEtdXxdM/Sk= github.com/markbates/cleo v0.0.0-20230821202903-72220ef5f7f0 h1:x3wg9UbZFp4yB94Fm8MR9xKiJP0i1XeInUWFg/6/sqM= github.com/markbates/cleo v0.0.0-20230821202903-72220ef5f7f0/go.mod h1:c3X0dnyY5ctGk3G9/h9J/oSA+NX8nt9cLKkFgrWBJ8M= github.com/markbates/fsx v1.3.0 h1:eunGc5BKa7ELGDnsN1Fe8LBH1/ZSeDmAbqIx2DAQRtM= github.com/markbates/fsx v1.3.0/go.mod h1:t7HbKJBmGY9+Wz+4kZI+67Utu3vYLcxKMjfUzz/eOA4= github.com/markbates/garlic v1.0.0 h1:IOsPWAMbH2p/jnh2OYXTous891NSAkXQdosToJvlg+E= github.com/markbates/garlic v1.0.0/go.mod h1:k4nxVa4o0HPTVgFiL6IZV8JAfJP9xXqxOkbzEZWO+oQ= +github.com/markbates/hepa v0.0.0-20211129002629-856d16f89b9d h1:+2OKR5x2iAJTQ2/u8Uyk1hOnJ8pneNV298dhJ0DO0nU= +github.com/markbates/hepa v0.0.0-20211129002629-856d16f89b9d/go.mod h1:YCs1lt0wCv6j/HTzT2V/bUWKgdXlOXwTz0118HvMGAo= github.com/markbates/iox v0.0.0-20230829013604-e0813da73cc6 h1:z/d6wuXj40wjOVq9dnpYJVNlO7PFuBK0vz0eyzJ70lk= github.com/markbates/iox v0.0.0-20230829013604-e0813da73cc6/go.mod h1:q+P6BLqC21cbcmL82OdIO3A9VFCjq9fRQ+ije7T85ow= github.com/markbates/plugins v0.0.0-20230821202759-9443baa9b3df h1:FO5PuTGqpiILO5BUK0doGhzOF3Hyb9UZ5Ec/LjUBPPw= @@ -41,8 +43,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 h1:/RIbNt/Zr7rVhIkQhooTxCxFcdWLGIKnZA4IXNFSrvo= -golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= +golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE= +golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= diff --git a/heading.go b/heading.go index 38fc4e8..1624a91 100644 --- a/heading.go +++ b/heading.go @@ -12,7 +12,11 @@ type Heading struct { level int } -func (h Heading) MarshalJSON() ([]byte, error) { +func (h *Heading) MarshalJSON() ([]byte, error) { + if h == nil { + return nil, ErrIsNil("heading") + } + h.RLock() defer h.RUnlock() @@ -21,27 +25,27 @@ func (h Heading) MarshalJSON() ([]byte, error) { return nil, err } - m["type"] = fmt.Sprintf("%T", h) + m["type"] = toType(h) m["level"] = h.level - return json.Marshal(m) + return json.MarshalIndent(m, "", " ") } -func (h Heading) MD() string { +func (h *Heading) MD() string { x := strings.Repeat("#", h.level) return fmt.Sprintf("%s %s", x, h.Children().MD()) } -func (h Heading) Level() int { +func (h *Heading) Level() int { return h.level } -func (h Heading) Format(f fmt.State, verb rune) { +func (h *Heading) Format(f fmt.State, verb rune) { switch verb { case 'v': - if len(h.FileName) > 0 { - fmt.Fprintf(f, "file://%s: ", h.FileName) + if len(h.Filename) > 0 { + fmt.Fprintf(f, "file://%s: ", h.Filename) } fmt.Fprintf(f, "%s", h.String()) default: diff --git a/heading_test.go b/heading_test.go index 9ec6dec..051d6a7 100644 --- a/heading_test.go +++ b/heading_test.go @@ -22,3 +22,15 @@ func Test_NewHeading(t *testing.T) { r.Equal("

", h.String()) r.Equal(1, h.Level()) } + +func Test_Heading_MarshalJSON(t *testing.T) { + t.Parallel() + + h := &Heading{ + Element: NewEl("h1", nil), + level: 1, + } + h.Nodes = append(h.Nodes, Text("This is a heading")) + + testJSON(t, "heading", h) +} diff --git a/hype_test.go b/hype_test.go index c4b54ad..57cbf9b 100644 --- a/hype_test.go +++ b/hype_test.go @@ -2,6 +2,7 @@ package hype import ( "context" + "encoding/json" "fmt" "io/fs" "os" @@ -80,6 +81,36 @@ func testModule(t testing.TB, root string) { } } +func testJSON(t testing.TB, gold string, val json.Marshaler) { + t.Helper() + + r := require.New(t) + + b, err := val.MarshalJSON() + r.NoError(err) + + act := string(b) + act = strings.TrimSpace(act) + + // fmt.Println(act) + + fp := filepath.Join("json", gold+".json") + + // f, err := os.Create(filepath.Join("testdata", fp)) + // r.NoError(err) + // f.Write(b) + // f.Close() + + b, err = fs.ReadFile(os.DirFS("testdata"), fp) + r.NoError(err) + + exp := string(b) + exp = strings.TrimSpace(exp) + + r.Equal(exp, act) + +} + func Test_Testdata_Auto_Modules(t *testing.T) { t.Parallel() diff --git a/image.go b/image.go index 44fdb31..f6cb3df 100644 --- a/image.go +++ b/image.go @@ -3,7 +3,6 @@ package hype import ( "context" "encoding/json" - "fmt" "io/fs" "strings" ) @@ -22,9 +21,9 @@ func (i *Image) MarshalJSON() ([]byte, error) { return nil, err } - m["type"] = fmt.Sprintf("%T", i) + m["type"] = toType(i) - return json.Marshal(m) + return json.MarshalIndent(m, "", " ") } func (i *Image) MD() string { diff --git a/image_test.go b/image_test.go new file mode 100644 index 0000000..fd74504 --- /dev/null +++ b/image_test.go @@ -0,0 +1,22 @@ +package hype + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_Image_MarshalJSON(t *testing.T) { + t.Parallel() + + r := require.New(t) + + i := &Image{ + Element: NewEl("img", nil), + } + + err := i.Set("src", "https://example.com/image.jpg") + r.NoError(err) + + testJSON(t, "image", i) +} diff --git a/include.go b/include.go index 17512f6..69f4847 100644 --- a/include.go +++ b/include.go @@ -28,13 +28,13 @@ func (inc *Include) MarshalJSON() ([]byte, error) { return nil, err } - m["type"] = fmt.Sprintf("%T", inc) + m["type"] = toType(inc) if inc.dir != "" { m["dir"] = inc.dir } - return json.Marshal(m) + return json.MarshalIndent(m, "", " ") } func (inc *Include) PostParse(p *Parser, d *Document, err error) error { @@ -121,7 +121,11 @@ func NewInclude(p *Parser, el *Element) (*Include, error) { } if err := RestripeFigureIDs(inc.Nodes, fn); err != nil { - return nil, err + return nil, ParseError{ + Err: err, + Filename: p.Filename, + Root: p.Root, + } } return inc, nil diff --git a/include_test.go b/include_test.go index 850b034..24fb5e5 100644 --- a/include_test.go +++ b/include_test.go @@ -1,12 +1,91 @@ package hype import ( + "context" + "errors" "testing" "testing/fstest" "github.com/stretchr/testify/require" ) +func Test_Include_Parse_Errors(t *testing.T) { + t.Parallel() + + tcs := []struct { + root string + filename string + }{ + { + root: "testdata/includes/broken", + filename: "module.md", + }, + } + + for _, tc := range tcs { + t.Run(tc.root, func(t *testing.T) { + r := require.New(t) + p := testParser(t, tc.root) + + ctx := context.Background() + + _, err := p.ParseExecuteFile(ctx, "module.md") + r.Error(err) + + var ee ParseError + r.True(errors.As(err, &ee)) + + r.Equal(tc.filename, ee.Filename) + r.Equal(tc.root, ee.Root) + + }) + } + +} + +func Test_Include_Cmd_Errors(t *testing.T) { + t.Parallel() + + tcs := []struct { + root string + filename string + }{ + { + root: "testdata/includes/toplevel", + filename: "module.md", + }, + { + root: "testdata/includes/sublevel", + filename: "below/b.md", + }, + } + + for _, tc := range tcs { + t.Run(tc.root, func(t *testing.T) { + r := require.New(t) + p := testParser(t, tc.root) + + ctx := context.Background() + + _, err := p.ParseExecuteFile(ctx, "module.md") + r.Error(err) + + var ee ExecuteError + r.True(errors.As(err, &ee)) + + r.Equal(tc.filename, ee.Filename) + r.Equal(tc.root, ee.Root) + + var ce CmdError + r.True(errors.As(ee.Err, &ce)) + + r.Equal(tc.filename, ce.Filename) + r.Equal(-1, ce.Exit) + }) + } + +} + func Test_Include(t *testing.T) { t.Parallel() r := require.New(t) @@ -112,3 +191,19 @@ func Test_Include_Nested(t *testing.T) { r.Equal(exp, act) } + +func Test_Include_MarshalJSON(t *testing.T) { + t.Parallel() + + r := require.New(t) + + inc := &Include{ + Element: NewEl("include", nil), + dir: "testdata/includes", + } + + err := inc.Set("src", "sub.md") + r.NoError(err) + + testJSON(t, "include", inc) +} diff --git a/inline_code.go b/inline_code.go index 7468fe6..112908e 100644 --- a/inline_code.go +++ b/inline_code.go @@ -24,25 +24,9 @@ func (code *InlineCode) MarshalJSON() ([]byte, error) { return nil, err } - m["type"] = fmt.Sprintf("%T", code) + m["type"] = toType(code) - return json.Marshal(m) -} - -func (code *InlineCode) StartTag() string { - if code == nil || code.Element == nil { - return "" - } - - return code.Element.StartTag() -} - -func (code *InlineCode) EndTag() string { - if code == nil || code.Element == nil { - return "" - } - - return code.Element.EndTag() + return json.MarshalIndent(m, "", " ") } func (code *InlineCode) String() string { diff --git a/inline_code_test.go b/inline_code_test.go new file mode 100644 index 0000000..2504b6f --- /dev/null +++ b/inline_code_test.go @@ -0,0 +1,16 @@ +package hype + +import "testing" + +func Test_InlineCode_MarshalJSON(t *testing.T) { + t.Parallel() + + il := &InlineCode{ + Element: NewEl("code", nil), + } + + il.Nodes = append(il.Nodes, Text("var x = 1")) + + testJSON(t, "inline_code", il) + +} diff --git a/json_test.go b/json_test.go deleted file mode 100644 index 57ae722..0000000 --- a/json_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package hype - -import ( - "context" - "encoding/json" - "os" - "strings" - "testing" - "time" - - "github.com/stretchr/testify/require" -) - -func Test_Document_JSON(t *testing.T) { - t.Skip() - - t.Parallel() - r := require.New(t) - - root := "testdata/doc/to_md" - - p := testParser(t, root) - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - doc, err := p.ParseExecuteFile(ctx, "module.md") - // doc, err := p.ParseFile("module.md") - r.NoError(err) - - b, err := json.MarshalIndent(doc, "", " ") - r.NoError(err) - - act := string(b) - act = strings.TrimSpace(act) - - // fmt.Println(act) - f, err := os.Create("testdata/doc/to_md/module.json") - r.NoError(err) - defer f.Close() - - _, err = f.WriteString(act) - r.NoError(err) - - exp := `` - - r.Equal(exp, act) - -} diff --git a/li.go b/li.go index 44b606a..5e654df 100644 --- a/li.go +++ b/li.go @@ -3,12 +3,12 @@ package hype import ( "bytes" "encoding/json" - "fmt" ) type LI struct { - Type string *Element + + Type string } func (li *LI) MarshalJSON() ([]byte, error) { @@ -21,13 +21,13 @@ func (li *LI) MarshalJSON() ([]byte, error) { return nil, err } - m["type"] = fmt.Sprintf("%T", li) + m["type"] = toType(li) if li.Type != "" { m["list-type"] = li.Type } - return json.Marshal(m) + return json.MarshalIndent(m, "", " ") } func (li *LI) MD() string { diff --git a/li_test.go b/li_test.go new file mode 100644 index 0000000..a189653 --- /dev/null +++ b/li_test.go @@ -0,0 +1,17 @@ +package hype + +import "testing" + +func Test_LI_MarshalJSON(t *testing.T) { + t.Parallel() + + li := &LI{ + Element: NewEl("li", nil), + Type: "ul", + } + + li.Nodes = append(li.Nodes, Text("This is a list item")) + + testJSON(t, "li", li) + +} diff --git a/link.go b/link.go index d699f3c..8ddd620 100644 --- a/link.go +++ b/link.go @@ -23,7 +23,7 @@ func (l *Link) MarshalJSON() ([]byte, error) { return nil, err } - m["type"] = fmt.Sprintf("%T", l) + m["type"] = toType(l) h, err := l.Href() if err != nil { @@ -32,7 +32,7 @@ func (l *Link) MarshalJSON() ([]byte, error) { m["url"] = h - return json.Marshal(m) + return json.MarshalIndent(m, "", " ") } func (l *Link) Href() (string, error) { diff --git a/link_test.go b/link_test.go new file mode 100644 index 0000000..367328c --- /dev/null +++ b/link_test.go @@ -0,0 +1,23 @@ +package hype + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_Link_MarshalJSON(t *testing.T) { + t.Parallel() + + r := require.New(t) + + link := &Link{ + Element: NewEl("a", nil), + } + link.Nodes = append(link.Nodes, Text("This is a link")) + + err := link.Set("href", "https://example.com") + r.NoError(err) + + testJSON(t, "link", link) +} diff --git a/md.go b/md.go index 9ecf677..932aeb8 100644 --- a/md.go +++ b/md.go @@ -2,6 +2,7 @@ package hype import ( "bytes" + "fmt" "io" "github.com/gopherguides/hype/mdx" @@ -22,13 +23,13 @@ func Markdown() PreParseFn { b, err := io.ReadAll(r) if err != nil { - return nil, err + return nil, fmt.Errorf("markdown: %w", err) } b = bytes.ReplaceAll(b, []byte("\\n"), []byte(" \n")) b, err = md.Parse(b) if err != nil { - return nil, err + return nil, fmt.Errorf("markdown: %w", err) } b = bytes.ReplaceAll(b, []byte("’"), []byte("'")) diff --git a/metadata.go b/metadata.go index 69fd69e..324b068 100644 --- a/metadata.go +++ b/metadata.go @@ -29,10 +29,10 @@ func (md *Metadata) MarshalJSON() ([]byte, error) { return nil, err } - m["type"] = fmt.Sprintf("%T", md) + m["type"] = toType(md) m["data"] = md.Map.Map() - return json.Marshal(m) + return json.MarshalIndent(m, "", " ") } func (md *Metadata) IsEmptyNode() bool { diff --git a/metadata_test.go b/metadata_test.go index 99a6888..e9eaa60 100644 --- a/metadata_test.go +++ b/metadata_test.go @@ -1,9 +1,11 @@ package hype import ( + "errors" "os" "testing" + "github.com/markbates/syncx" "github.com/stretchr/testify/require" ) @@ -49,4 +51,22 @@ func Test_NewMetadata_Multiple_Erro0r(t *testing.T) { p := NewParser(cab) _, err := p.ParseFile("module.md") r.Error(err) + + r.True(errors.Is(err, ParseError{})) +} + +func Test_Metadata_MarshalJSON(t *testing.T) { + t.Parallel() + r := require.New(t) + + md := &Metadata{ + Element: NewEl("metadata", nil), + Map: syncx.Map[string, string]{}, + } + + err := md.Set("title", "Hello, World!") + r.NoError(err) + + testJSON(t, "metadata", md) + } diff --git a/now.go b/now.go index eb61ab7..a7cf7d1 100644 --- a/now.go +++ b/now.go @@ -3,7 +3,6 @@ package hype import ( "context" "encoding/json" - "fmt" "time" ) @@ -23,9 +22,9 @@ func (now *Now) MarshalJSON() ([]byte, error) { return nil, err } - m["type"] = fmt.Sprintf("%T", now) + m["type"] = toType(now) - return json.Marshal(m) + return json.MarshalIndent(m, "", " ") } func (now *Now) Execute(ctx context.Context, doc *Document) error { diff --git a/now_test.go b/now_test.go index 254f81d..febab7a 100644 --- a/now_test.go +++ b/now_test.go @@ -43,3 +43,14 @@ func Test_NowNodes(t *testing.T) { r.Equal(exp, act) } + +func Test_Now_MarshalJSON(t *testing.T) { + t.Parallel() + + n := &Now{ + Element: NewEl("now", nil), + } + n.Nodes = append(n.Nodes, Text("1/2/2006")) + + testJSON(t, "now", n) +} diff --git a/ol.go b/ol.go index bcc1a2f..c3756f6 100644 --- a/ol.go +++ b/ol.go @@ -2,7 +2,6 @@ package hype import ( "encoding/json" - "fmt" ) type OL struct { @@ -22,9 +21,9 @@ func (ol *OL) MarshalJSON() ([]byte, error) { return nil, err } - m["type"] = fmt.Sprintf("%T", ol) + m["type"] = toType(ol) - return json.Marshal(m) + return json.MarshalIndent(m, "", " ") } func (ol *OL) MD() string { diff --git a/ol_test.go b/ol_test.go new file mode 100644 index 0000000..78e1769 --- /dev/null +++ b/ol_test.go @@ -0,0 +1,15 @@ +package hype + +import "testing" + +func Test_OL_MarshalJSON(t *testing.T) { + t.Parallel() + + ol := &OL{ + Element: NewEl("ol", nil), + } + + ol.Nodes = append(ol.Nodes, Text("This is an ordered list")) + + testJSON(t, "ol", ol) +} diff --git a/page.go b/page.go index dbad0e4..371c737 100644 --- a/page.go +++ b/page.go @@ -20,13 +20,13 @@ func (page *Page) MarshalJSON() ([]byte, error) { return nil, err } - m["type"] = fmt.Sprintf("%T", page) + m["type"] = toType(page) if len(page.Title) > 0 { m["title"] = page.Title } - return json.Marshal(m) + return json.MarshalIndent(m, "", " ") } func (page *Page) Body() (*Body, error) { diff --git a/page_test.go b/page_test.go index 1029230..c873983 100644 --- a/page_test.go +++ b/page_test.go @@ -68,3 +68,15 @@ adfadf` r.Len(pages, 5) } + +func Test_Pages_MarshalJSON(t *testing.T) { + t.Parallel() + + p := &Page{ + Title: "Page 1", + Element: NewEl("page", nil), + } + p.Nodes = append(p.Nodes, Text("more text")) + + testJSON(t, "page", p) +} diff --git a/paragraph.go b/paragraph.go index 3da84ce..a222eb7 100644 --- a/paragraph.go +++ b/paragraph.go @@ -2,7 +2,6 @@ package hype import ( "encoding/json" - "fmt" ) type Paragraph struct { @@ -19,9 +18,9 @@ func (p *Paragraph) MarshalJSON() ([]byte, error) { return nil, err } - m["type"] = fmt.Sprintf("%T", p) + m["type"] = toType(p) - return json.Marshal(m) + return json.MarshalIndent(m, "", " ") } func (p *Paragraph) IsEmptyNode() bool { diff --git a/paragraph_test.go b/paragraph_test.go new file mode 100644 index 0000000..b1ccc1c --- /dev/null +++ b/paragraph_test.go @@ -0,0 +1,15 @@ +package hype + +import "testing" + +func Test_Paragraph_MarshalJSON(t *testing.T) { + t.Parallel() + + p := &Paragraph{ + Element: NewEl("p", nil), + } + + p.Nodes = append(p.Nodes, Text("This is a paragraph")) + + testJSON(t, "p", p) +} diff --git a/parse_error.go b/parse_error.go new file mode 100644 index 0000000..97468f6 --- /dev/null +++ b/parse_error.go @@ -0,0 +1,55 @@ +package hype + +import ( + "encoding/json" + "errors" +) + +type ParseError struct { + Err error + Filename string + Root string + Contents []byte +} + +func (pe ParseError) MarshalJSON() ([]byte, error) { + mm := map[string]any{ + "contents": string(pe.Contents), + "error": errForJSON(pe.Err), + "filename": pe.Filename, + "root": pe.Root, + "type": toType(pe), + } + + return json.MarshalIndent(mm, "", " ") +} + +func (pe ParseError) Error() string { + return toError(pe) +} + +func (pe ParseError) Unwrap() error { + if _, ok := pe.Err.(unwrapper); ok { + return errors.Unwrap(pe.Err) + } + + return pe.Err +} + +func (pe ParseError) As(target any) bool { + ex, ok := target.(*ParseError) + if !ok { + return errors.As(pe.Err, target) + } + + (*ex) = pe + return true +} + +func (pe ParseError) Is(target error) bool { + if _, ok := target.(ParseError); ok { + return true + } + + return errors.Is(pe.Err, target) +} diff --git a/parse_error_test.go b/parse_error_test.go new file mode 100644 index 0000000..b31ac8f --- /dev/null +++ b/parse_error_test.go @@ -0,0 +1,47 @@ +package hype + +import ( + "errors" + "fmt" + "io" + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_ParseError(t *testing.T) { + t.Parallel() + r := require.New(t) + + oce := ParseError{ + Err: io.EOF, + } + + wrapped := fmt.Errorf("error: %w", oce) + + r.True(oce.As(&ParseError{}), oce) + r.True(oce.Is(oce), oce) + r.True(oce.Unwrap() == io.EOF, oce) + + var ce ParseError + r.True(errors.As(wrapped, &ce), wrapped) + + ce = ParseError{} + r.True(errors.Is(wrapped, ce), wrapped) + + err := errors.Unwrap(oce) + r.Equal(io.EOF, err) +} + +func Test_ParseError_MarshalJSON(t *testing.T) { + t.Parallel() + + pe := ParseError{ + Contents: []byte("contents"), + Err: io.EOF, + Filename: "test.md", + Root: "root", + } + + testJSON(t, "parse_error", pe) +} diff --git a/parser.go b/parser.go index bdd75a2..9750003 100644 --- a/parser.go +++ b/parser.go @@ -1,6 +1,7 @@ package hype import ( + "bytes" "context" "encoding/json" "errors" @@ -22,20 +23,20 @@ import ( type ParseElementFn func(p *Parser, el *Element) (Nodes, error) type Parser struct { - fs.FS `json:"-"` - - Root string `json:"root,omitempty"` - DisablePages bool `json:"disable_pages,omitempty"` - NodeParsers map[Atom]ParseElementFn `json:"-"` - PreParsers PreParsers `json:"-"` - Snippets Snippets `json:"snippets,omitempty"` - Section int `json:"section,omitempty"` - NowFn func() time.Time `json:"-"` // default: time.Now() - DocIDGen func() (string, error) `json:"-"` // default: uuid.NewV4().String() - Vars syncx.Map[string, any] `json:"vars,omitempty"` - - fileName string - mu sync.RWMutex + fs.FS + + DisablePages bool + DocIDGen func() (string, error) // default: uuid.NewV4().String() + Filename string // only set when Parser.ParseFile() is used + NodeParsers map[Atom]ParseElementFn + NowFn func() time.Time // default: time.Now() + PreParsers PreParsers + Root string + Section int + Vars syncx.Map[string, any] + Contents []byte // a copy of the contents being parsed - set just before parsing + + mu sync.RWMutex } func (p *Parser) MarshalJSON() ([]byte, error) { @@ -46,25 +47,23 @@ func (p *Parser) MarshalJSON() ([]byte, error) { p.mu.RLock() defer p.mu.RUnlock() - // p.Vars.Map() - x := struct { Type string `json:"type,omitempty"` Root string `json:"root,omitempty"` DisablePages bool `json:"disable_pages,omitempty"` Section int `json:"section,omitempty"` - Snippets Snippets `json:"snippets,omitempty"` Vars map[string]any `json:"vars,omitempty"` + Contents string `json:"contents,omitempty"` }{ - Type: fmt.Sprintf("%T", p), + Type: toType(p), Root: p.Root, DisablePages: p.DisablePages, Section: p.Section, - Snippets: p.Snippets, Vars: p.Vars.Map(), + Contents: string(p.Contents), } - return json.Marshal(x) + return json.MarshalIndent(x, "", " ") } @@ -79,54 +78,70 @@ func (p *Parser) Now() time.Time { // ParseFile parses the given file from Parser.FS. // If successful a *Document is returned. The returned // *Document is NOT yet executed. -func (p *Parser) ParseFile(name string) (*Document, error) { +func (p *Parser) ParseFile(name string) (doc *Document, err error) { if p == nil { return nil, ErrIsNil("parser") } + defer func() { + err = p.ensureParseError(err) + }() + p.mu.Lock() - p.fileName = name + p.Filename = name p.mu.Unlock() f, err := p.Open(name) if err != nil { - return nil, p.wrapErr(err) + return nil, err } defer f.Close() - doc, err := p.Parse(f) + doc, err = p.Parse(f) if err != nil { - return nil, p.wrapErr(err) + return nil, err } return doc, nil } -func (p *Parser) Parse(r io.Reader) (*Document, error) { +func (p *Parser) Parse(r io.Reader) (doc *Document, err error) { if p == nil { return nil, ErrIsNil("parser") } + defer func() { + err = p.ensureParseError(err) + }() + + b, err := io.ReadAll(r) + if err != nil { + return nil, err + } + p.Contents = b + + r = bytes.NewReader(b) + // pre parse - r, err := p.PreParsers.PreParse(p, r) + r, err = p.PreParsers.PreParse(p, r) if err != nil { - return nil, p.wrapErr(err) + return nil, err } // parse hdoc, err := html.Parse(r) if err != nil { - return nil, p.wrapErr(err) + return nil, err } node, err := p.ParseHTMLNode(hdoc, nil) if err != nil { - return nil, p.wrapErr(err) + return nil, err } - doc, err := p.newDoc() + doc, err = p.newDoc() if err != nil { - return nil, p.wrapErr(err) + return nil, err } doc.Nodes = Nodes{node} @@ -137,7 +152,7 @@ func (p *Parser) Parse(r io.Reader) (*Document, error) { // post parse err = doc.Nodes.PostParse(p, doc, err) if err != nil { - return nil, p.wrapErr(err) + return nil, err } return doc, nil @@ -150,29 +165,45 @@ func (p *Parser) ParseExecuteFile(ctx context.Context, name string) (*Document, doc, err := p.ParseFile(name) if err != nil { - return nil, p.wrapErr(err) + return nil, err } err = doc.Execute(ctx) if err != nil { - return nil, p.wrapErr(err) + return nil, err } return doc, nil } -func (p *Parser) ParseFolder(name string) (Documents, error) { +func (p *Parser) ParseFolder(name string) (doc Documents, err error) { if p == nil { return nil, ErrIsNil("parser") } + defer func() { + if err == nil { + return + } + + if _, ok := err.(ParseError); ok { + return + } + + err = ParseError{ + Err: err, + Filename: name, + Root: p.Root, + } + }() + var docs Documents var wg errgroup.Group var mu sync.Mutex whole, err := binding.WholeFromPath(p.FS, name, "book", "chapter") if err != nil && !errors.Is(err, binding.ErrPath("")) { - return nil, p.wrapErr(err) + return nil, err } err = fs.WalkDir(p.FS, ".", func(path string, d fs.DirEntry, err error) error { @@ -208,7 +239,7 @@ func (p *Parser) ParseFolder(name string) (Documents, error) { doc, err := p.ParseFile(base) if err != nil { - return fmt.Errorf("error parsing: %q: %w", path, err) + return err } mu.Lock() @@ -223,11 +254,13 @@ func (p *Parser) ParseFolder(name string) (Documents, error) { if err != nil { err = fmt.Errorf("error walking: %q: %w", name, err) - return nil, p.wrapErr(err) + if err != nil && !errors.Is(err, binding.ErrPath("")) { + return nil, err + } } if err := wg.Wait(); err != nil { - return nil, p.wrapErr(err) + return nil, err } sort.Slice(docs, func(i, j int) bool { @@ -244,12 +277,12 @@ func (p *Parser) ParseExecuteFolder(ctx context.Context, name string) (Documents docs, err := p.ParseFolder(name) if err != nil { - return nil, p.wrapErr(err) + return nil, err } err = docs.Execute(ctx) if err != nil { - return nil, p.wrapErr(err) + return nil, err } return docs, nil @@ -262,19 +295,19 @@ func (p *Parser) ParseExecuteFragment(ctx context.Context, r io.Reader) (Nodes, nodes, err := p.ParseFragment(r) if err != nil { - return nil, p.wrapErr(err) + return nil, err } doc, err := p.newDoc() if err != nil { - return nil, p.wrapErr(err) + return nil, err } doc.Nodes = nodes err = doc.Execute(ctx) if err != nil { - return nil, p.wrapErr(err) + return nil, err } return nodes, nil @@ -283,12 +316,12 @@ func (p *Parser) ParseExecuteFragment(ctx context.Context, r io.Reader) (Nodes, func (p *Parser) ParseExecute(ctx context.Context, r io.Reader) (*Document, error) { doc, err := p.Parse(r) if err != nil { - return nil, p.wrapErr(err) + return nil, err } err = doc.Execute(ctx) if err != nil { - return nil, p.wrapErr(err) + return nil, err } return doc, nil @@ -301,7 +334,7 @@ func (p *Parser) ParseFragment(r io.Reader) (Nodes, error) { doc, err := p.Parse(r) if err != nil { - return nil, p.wrapErr(err) + return nil, err } nodes := doc.Nodes @@ -332,21 +365,27 @@ func (p *Parser) ParseHTMLNode(node *html.Node, parent Node) (Node, error) { switch node.Type { case html.CommentNode: return Comment(node.Data), nil - case html.DoctypeNode: - // return p.NewDocType(node) case html.DocumentNode, html.ElementNode: n, err := p.element(node, parent) if err != nil { - return nil, p.wrapErr(err) + return nil, err } return n, nil - case html.ErrorNode: - return nil, p.wrapErr(fmt.Errorf(node.Data)) case html.TextNode: return Text(node.Data), nil + case html.ErrorNode: + return nil, ParseError{ + Err: fmt.Errorf("error node: %+v", node), + Filename: p.Filename, + Root: p.Root, + } } - return nil, p.wrapErr(fmt.Errorf("unknown node type %v", node.Data)) + return nil, ParseError{ + Err: fmt.Errorf("unknown node: %+v", node), + Filename: p.Filename, + Root: p.Root, + } } func (p *Parser) Sub(dir string) (*Parser, error) { @@ -367,7 +406,7 @@ func (p *Parser) Sub(dir string) (*Parser, error) { cab, err := fs.Sub(p.FS, dir) if err != nil { - return nil, p.wrapErr(err) + return nil, err } p2.FS = cab @@ -385,7 +424,7 @@ func (p *Parser) element(node *html.Node, parent Node) (Node, error) { Attributes: ats, HTMLNode: node, Parent: parent, - FileName: p.fileName, + Filename: p.Filename, } var nodes Nodes @@ -444,94 +483,38 @@ func (p *Parser) newDoc() (*Document, error) { id, err := p.DocIDGen() if err != nil { - return nil, p.wrapErr(err) + return nil, err } doc := &Document{ - ID: id, FS: p.FS, + Filename: p.Filename, + ID: id, Parser: p, Root: p.Root, SectionID: p.Section, - Snippets: p.Snippets, - Filename: p.fileName, + Snippets: Snippets{}, } return doc, nil } -func (p *Parser) wrapErr(err error) error { - if err == nil { - return nil - } - - if _, ok := err.(parserErr); ok { - return err - } - +func (p *Parser) ensureParseError(err error) error { if p == nil { - return err - } - - p.mu.RLock() - defer p.mu.RUnlock() - - return parserErr{ - fileName: p.fileName, - root: p.Root, - err: err, - } -} - -type parserErr struct { - err error - fileName string - root string -} - -func (pe parserErr) Error() string { - if pe.err == nil { - return "" - } - - err := pe.err - - if len(pe.fileName) > 0 { - err = fmt.Errorf("file: %q: %s", pe.fileName, err) - } - - if len(pe.root) > 0 { - err = fmt.Errorf("root: %q: %s", pe.root, err) - } - - return err.Error() -} - -func (pe parserErr) Unwrap() error { - type Unwrapper interface { - Unwrap() error + return ErrIsNil("parser") } - if _, ok := pe.err.(Unwrapper); ok { - return errors.Unwrap(pe.err) + if err == nil { + return nil } - return pe.err -} - -func (pe parserErr) As(target any) bool { - ex, ok := target.(*parserErr) - if !ok { - return errors.As(pe.err, target) + if _, ok := err.(ParseError); ok { + return err } - (*ex) = pe - return true -} - -func (pe parserErr) Is(target error) bool { - if _, ok := target.(parserErr); ok { - return true + return ParseError{ + Err: err, + Filename: p.Filename, + Root: p.Root, + Contents: p.Contents, } - - return errors.Is(pe.err, target) } diff --git a/parser_test.go b/parser_test.go index e736656..9084d2c 100644 --- a/parser_test.go +++ b/parser_test.go @@ -2,147 +2,146 @@ package hype import ( "context" + "errors" "fmt" "os" + "strings" "testing" - "testing/fstest" "github.com/stretchr/testify/require" + "golang.org/x/net/html" ) -func Test_Parser(t *testing.T) { +func Test_Parser_ParseFolder(t *testing.T) { t.Parallel() - - // t.Skip() r := require.New(t) - cab := helloCab(t, "second") - - modmd := []byte(`# Page 1 - -This is ` + "`inline`" + ` code. - - - - - -more words`) + root := "testdata/parser/folder" + cab := os.DirFS(root) - second_md := []byte(`# Second Page + p := NewParser(cab) + p.Root = root - + docs, err := p.ParseExecuteFolder(context.Background(), root) + r.NoError(err) - + r.Len(docs, 3) -`) + exp := `var Canceled = errors.New` - cab["module.md"] = &fstest.MapFile{ - Data: modmd, - } + titles := []string{"ONE", "TWO", "THREE"} - cab["second/second.md"] = &fstest.MapFile{ - Data: second_md, + for i, doc := range docs { + r.Equal(titles[i], doc.Title) + act := doc.String() + r.Contains(act, exp) } - p := NewParser(cab) +} - doc, err := p.ParseExecuteFile(context.Background(), "module.md") - r.NoError(err) - r.NotNil(doc) +func Test_Parser_ParseHTMLNode_Error(t *testing.T) { + t.Parallel() - exp := ` -

Page 1

+ tcs := []struct { + name string + node *html.Node + }{ + { + name: "error node", + node: &html.Node{ + Type: html.ErrorNode, + Data: "boom", + }, + }, + { + name: "unknown node", + node: &html.Node{ + Type: 42, + Data: "boom", + }, + }, + } -

This is inline code.

-
- -

Second Page

+ for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + r := require.New(t) - + p := testParser(t, "testdata/auto/parser/hello") + _, err := p.ParseHTMLNode(tc.node, nil) + r.Error(err) -
package main
+			var pe ParseError
+			r.True(errors.As(err, &pe), err)
 
-import "fmt"
+			pe = ParseError{}
+			r.True(errors.Is(err, pe), err)
+		})
+	}
 
-func main() {
-	fmt.Println("Hello second!")
-}
-
+} - -
$ echo hello
+func Test_Parser_ParseFragment_Error(t *testing.T) {
+	t.Parallel()
+	r := require.New(t)
 
-hello
+ p := testParser(t, "testdata/auto/parser/hello") -

more words

-
-` + _, err := p.ParseFragment(strings.NewReader(``)) + r.Error(err) -import "fmt" + var ee ExecuteError + r.True(errors.As(err, &ee), err) -func main() { - fmt.Println("Hello %s!") -}` + ee = ExecuteError{} + r.True(errors.Is(err, ee), err) +} -func Test_Parser_ParseFolder(t *testing.T) { +func Test_Parser_MarshalJSON(t *testing.T) { t.Parallel() - r := require.New(t) - root := "testdata/parser/folder" - cab := os.DirFS(root) + r := require.New(t) - p := NewParser(cab) - p.Root = root + p := testParser(t, "testdata/auto/snippets/simple") + p.DisablePages = true + p.Section = 42 - docs, err := p.ParseExecuteFolder(context.Background(), root) + err := p.Vars.Set("foo", "bar") r.NoError(err) - r.Len(docs, 3) - - exp := `var Canceled = errors.New` - - titles := []string{"ONE", "TWO", "THREE"} - - for i, doc := range docs { - r.Equal(titles[i], doc.Title) - act := doc.String() - r.Contains(act, exp) - } + _, err = p.ParseFile("module.md") + r.NoError(err) + testJSON(t, "parser", p) } diff --git a/post_execute.go b/post_execute.go index 94b0316..e8ebc24 100644 --- a/post_execute.go +++ b/post_execute.go @@ -2,8 +2,6 @@ package hype import ( "context" - "fmt" - "strings" ) type PostExecuter interface { @@ -49,23 +47,3 @@ type PostExecuteFn func(ctx context.Context, d *Document, err error) error func (fn PostExecuteFn) PostExecute(ctx context.Context, d *Document, err error) error { return fn(ctx, d, err) } - -type PostExecuteError struct { - Err error - OrigErr error - PostExecuter PostExecuter -} - -func (e PostExecuteError) Error() string { - var errs []string - - if e.Err != nil { - errs = append(errs, e.Err.Error()) - } - - if e.OrigErr != nil { - errs = append(errs, e.OrigErr.Error()) - } - - return fmt.Sprintf("post execute error: [%T]: %v", e.PostExecuter, strings.Join(errs, "; ")) -} diff --git a/post_execute_error.go b/post_execute_error.go new file mode 100644 index 0000000..869eaba --- /dev/null +++ b/post_execute_error.go @@ -0,0 +1,59 @@ +package hype + +import ( + "encoding/json" + "errors" +) + +type PostExecuteError struct { + Err error + Document *Document + Filename string + OrigErr error + Root string + PostExecuter any +} + +func (pee PostExecuteError) MarshalJSON() ([]byte, error) { + mm := map[string]any{ + "document": pee.Document, + "error": errForJSON(pee.Err), + "filename": pee.Filename, + "origal_error": errForJSON(pee.OrigErr), + "post_executer": toType(pee.PostExecuter), + "root": pee.Root, + "type": toType(pee), + } + + return json.MarshalIndent(mm, "", " ") +} + +func (e PostExecuteError) Error() string { + return toError(e) +} + +func (e PostExecuteError) Unwrap() error { + if _, ok := e.Err.(unwrapper); ok { + return errors.Unwrap(e.Err) + } + + return e.Err +} + +func (e PostExecuteError) As(target any) bool { + ex, ok := target.(*PostExecuteError) + if !ok { + return false + } + + (*ex) = e + return true +} + +func (e PostExecuteError) Is(target error) bool { + if _, ok := target.(PostExecuteError); ok { + return true + } + + return false +} diff --git a/post_execute_error_test.go b/post_execute_error_test.go new file mode 100644 index 0000000..992588d --- /dev/null +++ b/post_execute_error_test.go @@ -0,0 +1,133 @@ +package hype + +import ( + "context" + "errors" + "fmt" + "io" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_PostExecuteError(t *testing.T) { + t.Parallel() + r := require.New(t) + + pee := PostExecuteError{ + Err: io.EOF, + } + + wrapped := fmt.Errorf("error: %w", pee) + + r.True(pee.As(&PostExecuteError{}), pee) + r.True(pee.Is(pee), pee) + r.True(pee.Unwrap() == io.EOF, pee) + + var pe PostExecuteError + r.True(errors.As(wrapped, &pe), wrapped) + + pe = PostExecuteError{} + r.True(errors.Is(wrapped, pe), wrapped) + + err := errors.Unwrap(pee) + r.Equal(io.EOF, err) +} + +func Test_PostExecute_Errors(t *testing.T) { + t.Parallel() + + tp := func() *Parser { + p := testParser(t, "testdata/parser/errors/post_execute") + + p.NodeParsers["foo"] = func(p *Parser, el *Element) (Nodes, error) { + n := newPostExecuteNode(t, func(ctx context.Context, d *Document, err error) error { + return fmt.Errorf("boom") + }) + return Nodes{n}, nil + } + + return p + } + + ctx := context.Background() + + type inFn func() error + + tcs := []struct { + name string + in inFn + }{ + { + name: "ParseExecuteFile", + in: func() error { + p := tp() + _, err := p.ParseExecuteFile(ctx, "module.md") + return err + }, + }, + { + name: "ParseExecuteFolder", + in: func() error { + p := tp() + _, err := p.ParseExecuteFolder(ctx, "testdata/parser/errors/folder") + return err + }, + }, + { + name: "ParseExecuteFragment", + in: func() error { + p := tp() + _, err := p.ParseExecuteFragment(ctx, strings.NewReader("")) + return err + }, + }, + { + name: "ParseExecute", + in: func() error { + p := tp() + _, err := p.ParseExecute(ctx, strings.NewReader("")) + return err + }, + }, + } + + for _, tc := range tcs { + tc := tc + t.Run(tc.name, func(t *testing.T) { + r := require.New(t) + + err := tc.in() + r.Error(err) + + var pe PostExecuteError + r.True(errors.As(err, &pe), err) + + pe = PostExecuteError{} + r.True(errors.Is(err, pe), err) + + var ee ExecuteError + r.True(errors.As(err, &ee), err) + + ee = ExecuteError{} + r.True(errors.Is(err, ee), err) + }) + } +} + +func Test_PostExecuteError_MarshalJSON(t *testing.T) { + t.Parallel() + + pee := PostExecuteError{ + Err: io.EOF, + OrigErr: io.ErrClosedPipe, + Filename: "filename", + Root: "root", + Document: &Document{ + Title: "My Title", + }, + } + + testJSON(t, "post_execute_error", pee) +} diff --git a/post_parse_error.go b/post_parse_error.go new file mode 100644 index 0000000..885f779 --- /dev/null +++ b/post_parse_error.go @@ -0,0 +1,60 @@ +package hype + +import ( + "encoding/json" + "errors" +) + +type PostParseError struct { + Err error + Document *Document + Filename string + OrigErr error + Root string + PostParser any +} + +func (ppe PostParseError) MarshalJSON() ([]byte, error) { + mm := map[string]any{ + "document": ppe.Document, + "error": toError(ppe.Err), + "filename": ppe.Filename, + "origal_error": toError(ppe.OrigErr), + "post_parser": toType(ppe.PostParser), + "root": ppe.Root, + "type": toType(ppe), + } + + return json.MarshalIndent(mm, "", " ") +} + +func (ppe PostParseError) Error() string { + b, _ := ppe.MarshalJSON() + return string(b) +} + +func (ppe PostParseError) Unwrap() error { + if _, ok := ppe.Err.(unwrapper); ok { + return errors.Unwrap(ppe.Err) + } + + return ppe.Err +} + +func (ppe PostParseError) As(target any) bool { + ex, ok := target.(*PostParseError) + if !ok { + return false + } + + (*ex) = ppe + return true +} + +func (ppe PostParseError) Is(target error) bool { + if _, ok := target.(PostParseError); ok { + return true + } + + return false +} diff --git a/post_parse_error_test.go b/post_parse_error_test.go new file mode 100644 index 0000000..454db9f --- /dev/null +++ b/post_parse_error_test.go @@ -0,0 +1,143 @@ +package hype + +import ( + "context" + "errors" + "fmt" + "io" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_PostParseError(t *testing.T) { + t.Parallel() + r := require.New(t) + + pee := PostParseError{ + Err: io.EOF, + } + + wrapped := fmt.Errorf("error: %w", pee) + + r.True(pee.As(&PostParseError{}), pee) + r.True(pee.Is(pee), pee) + r.True(pee.Unwrap() == io.EOF, pee) + + var pe PostParseError + r.True(errors.As(wrapped, &pe), wrapped) + + pe = PostParseError{} + r.True(errors.Is(wrapped, pe), wrapped) + + err := errors.Unwrap(pee) + r.Equal(io.EOF, err) +} + +func Test_PostParser_Errors(t *testing.T) { + t.Parallel() + + tp := func() *Parser { + p := testParser(t, "testdata/parser/errors/post_parse") + + p.NodeParsers["foo"] = func(p *Parser, el *Element) (Nodes, error) { + n := newPostParserNode(t, func(p *Parser, d *Document, err error) error { + return fmt.Errorf("boom") + }) + + return Nodes{n}, nil + } + + return p + } + + type inFn func() error + + ctx := context.Background() + + tcs := []struct { + name string + in inFn + }{ + { + name: "ParseFile", + in: func() error { + p := tp() + _, err := p.ParseFile("module.md") + return err + }, + }, + { + name: "ParseExecuteFile", + in: func() error { + p := tp() + _, err := p.ParseExecuteFile(ctx, "module.md") + return err + }, + }, + { + name: "ParseFragment", + in: func() error { + p := tp() + _, err := p.ParseFragment(strings.NewReader(``)) + return err + }, + }, + { + name: "Parse", + in: func() error { + p := tp() + _, err := p.Parse(strings.NewReader("")) + return err + }, + }, + { + name: "ParseExecute", + in: func() error { + p := tp() + _, err := p.ParseExecute(ctx, strings.NewReader("")) + return err + }, + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + r := require.New(t) + + err := tc.in() + r.Error(err) + + var ppe PostParseError + r.True(errors.As(err, &ppe), err) + r.NotNil(ppe.Document, err) + + ppe = PostParseError{} + r.True(errors.Is(err, ppe), err) + + var pe ParseError + r.True(errors.As(err, &pe), err) + + pe = ParseError{} + r.True(errors.Is(err, pe), err) + + }) + } +} + +func Test_PostParseError_MarshalJSON(t *testing.T) { + t.Parallel() + + ppe := PostParseError{ + Err: io.EOF, + Filename: "foo.md", + OrigErr: io.ErrClosedPipe, + Root: "root", + Document: &Document{ + Title: "My Title", + }, + } + + testJSON(t, "post_parse_error", ppe) +} diff --git a/post_parser.go b/post_parser.go index ccf44e7..2491b50 100644 --- a/post_parser.go +++ b/post_parser.go @@ -1,10 +1,5 @@ package hype -import ( - "fmt" - "strings" -) - type PostParser interface { PostParse(p *Parser, d *Document, err error) error } @@ -23,7 +18,16 @@ func (list Nodes) PostParse(p *Parser, d *Document, err error) error { if nodes, ok := n.(Nodes); ok { err2 = nodes.PostParse(p, d, err) if err2 != nil { - return err2 + if _, ok := err2.(PostParseError); ok { + return err2 + } + return PostParseError{ + Document: d, + Err: err2, + Filename: p.Filename, + OrigErr: err, + Root: p.Root, + } } continue } @@ -34,39 +38,30 @@ func (list Nodes) PostParse(p *Parser, d *Document, err error) error { err2 = pp.PostParse(p, d, err) if err2 != nil { return PostParseError{ - OrigErr: err, + Document: d, Err: err2, + Filename: p.Filename, + OrigErr: err, PostParser: pp, + Root: p.Root, } } } err2 = n.Children().PostParse(p, d, err) if err2 != nil { - // the error should already be wrapped - return err2 + if _, ok := err2.(PostParseError); ok { + return err2 + } + return PostParseError{ + Document: d, + Err: err2, + Filename: p.Filename, + OrigErr: err, + Root: p.Root, + } } } return err } - -type PostParseError struct { - Err error - OrigErr error - PostParser PostParser -} - -func (e PostParseError) Error() string { - var errs []string - - if e.Err != nil { - errs = append(errs, e.Err.Error()) - } - - if e.OrigErr != nil { - errs = append(errs, e.OrigErr.Error()) - } - - return fmt.Sprintf("post parse error: [%T]: %v", e.PostParser, strings.Join(errs, "; ")) -} diff --git a/post_parser_test.go b/post_parser_test.go index 2e2d49e..d3cce63 100644 --- a/post_parser_test.go +++ b/post_parser_test.go @@ -76,11 +76,18 @@ func Test_PostParsers_PostParse_Errors(t *testing.T) { } d := &Document{} - err := nodes.PostParse(nil, d, fmt.Errorf("original")) + p := testParser(t, "testdata/whole/simple") + p.Filename = "module.md" + + err := nodes.PostParse(p, d, fmt.Errorf("original")) r.Error(err) + + var pperr PostParseError + + r.ErrorAs(err, &pperr) + r.Equal("Hello", d.Title) - r.Contains(err.Error(), tc.exp) }) } } diff --git a/pre_execute.go b/pre_execute.go index 4862076..5c57606 100644 --- a/pre_execute.go +++ b/pre_execute.go @@ -2,7 +2,6 @@ package hype import ( "context" - "fmt" ) type PreExecuter interface { @@ -10,6 +9,10 @@ type PreExecuter interface { } func (list Nodes) PreExecute(ctx context.Context, d *Document) error { + if d == nil { + return ErrIsNil("document") + } + var err error for _, n := range list { @@ -27,14 +30,21 @@ func (list Nodes) PreExecute(ctx context.Context, d *Document) error { if err != nil { return PreExecuteError{ Err: err, + Filename: d.Filename, PreExecuter: pe, + Root: d.Root, } } } err = n.Children().PreExecute(ctx, d) if err != nil { - return err + return PreExecuteError{ + Err: err, + Filename: d.Filename, + PreExecuter: pe, + Root: d.Root, + } } } @@ -46,12 +56,3 @@ type PreExecuteFn func(ctx context.Context, d *Document) error func (fn PreExecuteFn) PreExecute(ctx context.Context, d *Document) error { return fn(ctx, d) } - -type PreExecuteError struct { - Err error - PreExecuter PreExecuter -} - -func (e PreExecuteError) Error() string { - return fmt.Sprintf("pre execute error: [%T]: %v", e.PreExecuter, e.Err) -} diff --git a/pre_execute_error.go b/pre_execute_error.go new file mode 100644 index 0000000..9b20ab3 --- /dev/null +++ b/pre_execute_error.go @@ -0,0 +1,57 @@ +package hype + +import ( + "encoding/json" + "errors" +) + +type PreExecuteError struct { + Err error + Document *Document + Filename string + Root string + PreExecuter any +} + +func (pee PreExecuteError) MarshalJSON() ([]byte, error) { + mm := map[string]any{ + "document": pee.Document, + "error": errForJSON(pee.Err), + "filename": pee.Filename, + "pre_executer": toType(pee.PreExecuter), + "root": pee.Root, + "type": toType(pee), + } + + return json.MarshalIndent(mm, "", " ") +} + +func (pee PreExecuteError) Error() string { + return toError(pee) +} + +func (pee PreExecuteError) Unwrap() error { + if _, ok := pee.Err.(unwrapper); ok { + return errors.Unwrap(pee.Err) + } + + return pee.Err +} + +func (pee PreExecuteError) As(target any) bool { + ex, ok := target.(*PreExecuteError) + if !ok { + return false + } + + (*ex) = pee + return true +} + +func (pee PreExecuteError) Is(target error) bool { + if _, ok := target.(PreExecuteError); ok { + return true + } + + return errors.Is(pee.Err, target) +} diff --git a/pre_execute_error_test.go b/pre_execute_error_test.go new file mode 100644 index 0000000..4051e33 --- /dev/null +++ b/pre_execute_error_test.go @@ -0,0 +1,117 @@ +package hype + +import ( + "context" + "errors" + "fmt" + "io" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_PreExecuteError(t *testing.T) { + t.Parallel() + r := require.New(t) + + pee := PreExecuteError{ + Err: io.EOF, + } + + wrapped := fmt.Errorf("error: %w", pee) + + r.True(pee.As(&PreExecuteError{}), pee) + r.True(pee.Is(pee), pee) + r.True(pee.Unwrap() == io.EOF, pee) + + var pe PreExecuteError + r.True(errors.As(wrapped, &pe), wrapped) + + pe = PreExecuteError{} + r.True(errors.Is(wrapped, pe), wrapped) + + err := errors.Unwrap(pee) + r.Equal(io.EOF, err) +} + +func Test_PreExecute_Errors(t *testing.T) { + t.Parallel() + + tp := func() *Parser { + p := testParser(t, "testdata/parser/errors/pre_execute") + + p.NodeParsers["foo"] = func(p *Parser, el *Element) (Nodes, error) { + n := newPreExecuteNode(t, func(ctx context.Context, d *Document) error { + return fmt.Errorf("boom") + }) + + return Nodes{n}, nil + } + + return p + } + + type inFn func() error + + ctx := context.Background() + + tcs := []struct { + name string + in inFn + }{ + { + name: "ParseExecuteFile", + in: func() error { + p := tp() + _, err := p.ParseExecuteFile(ctx, "module.md") + return err + }, + }, + { + name: "ParseExecute", + in: func() error { + p := tp() + _, err := p.ParseExecute(ctx, strings.NewReader("")) + return err + }, + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + r := require.New(t) + + err := tc.in() + r.Error(err) + + var pee PreExecuteError + r.True(errors.As(err, &pee), err) + + pee = PreExecuteError{} + r.True(errors.Is(err, pee), err) + + var ee ExecuteError + r.True(errors.As(err, &ee), err) + + ee = ExecuteError{} + r.True(errors.Is(err, ee), err) + + }) + } +} + +func Test_PreExecuteError_MarshalJSON(t *testing.T) { + t.Parallel() + + pee := PreExecuteError{ + Err: io.EOF, + Filename: "module.md", + Root: "root", + Document: &Document{ + Title: "My Title", + }, + } + + testJSON(t, "pre_execute_error", pee) +} diff --git a/pre_execute_test.go b/pre_execute_test.go index 07805b8..24cfc9a 100644 --- a/pre_execute_test.go +++ b/pre_execute_test.go @@ -76,7 +76,7 @@ func Test_Nodes_PreExecute_Errors(t *testing.T) { nodes := Nodes{tc.node} - err := nodes.PreExecute(context.Background(), nil) + err := nodes.PreExecute(context.Background(), &Document{}) r.Error(err) r.Contains(err.Error(), "boom") diff --git a/pre_parse_error.go b/pre_parse_error.go new file mode 100644 index 0000000..48671dc --- /dev/null +++ b/pre_parse_error.go @@ -0,0 +1,57 @@ +package hype + +import ( + "encoding/json" + "errors" +) + +type PreParseError struct { + Err error + Contents []byte + Filename string + Root string + PreParser any +} + +func (e PreParseError) MarshalJSON() ([]byte, error) { + mm := map[string]any{ + "contents": string(e.Contents), + "error": errForJSON(e.Err), + "filename": e.Filename, + "pre_parser": toType(e.PreParser), + "root": e.Root, + "type": toType(e), + } + + return json.MarshalIndent(mm, "", " ") +} + +func (e PreParseError) Error() string { + return toError(e) +} + +func (e PreParseError) Unwrap() error { + if _, ok := e.Err.(unwrapper); ok { + return errors.Unwrap(e.Err) + } + + return e.Err +} + +func (e PreParseError) As(target any) bool { + ex, ok := target.(*PreParseError) + if !ok { + return false + } + + (*ex) = e + return true +} + +func (e PreParseError) Is(target error) bool { + if _, ok := target.(PreParseError); ok { + return true + } + + return false +} diff --git a/pre_parse_error_test.go b/pre_parse_error_test.go new file mode 100644 index 0000000..304ecaf --- /dev/null +++ b/pre_parse_error_test.go @@ -0,0 +1,131 @@ +package hype + +import ( + "context" + "errors" + "fmt" + "io" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_PreParseError(t *testing.T) { + t.Parallel() + r := require.New(t) + + ppe := PreParseError{ + Err: io.EOF, + } + + r.True(ppe.As(&PreParseError{}), ppe) + r.True(ppe.Is(ppe), ppe) + r.True(ppe.Unwrap() == io.EOF, ppe) + + var pe PreParseError + r.True(ppe.As(&pe), ppe) + + pe = PreParseError{} + r.True(ppe.Is(pe), ppe) + + err := errors.Unwrap(ppe) + r.Equal(io.EOF, err) +} + +func Test_PreParser_Errors(t *testing.T) { + t.Parallel() + + const root = "testdata/parser/errors" + + tp := func() *Parser { + p := testParser(t, filepath.Join(root, "pre_parse")) + + fn := PreParseFn(func(p *Parser, r io.Reader) (io.Reader, error) { + return nil, fmt.Errorf("boom") + }) + + p.PreParsers = append(p.PreParsers, fn) + return p + } + + type inFn func() error + + ctx := context.Background() + + tcs := []struct { + name string + in inFn + }{ + { + name: "ParseFile", + in: func() error { + _, err := tp().ParseFile("module.md") + return err + }, + }, + { + name: "ParseExecuteFile", + in: func() error { + _, err := tp().ParseExecuteFile(ctx, "module.md") + return err + }, + }, + { + name: "Parse", + in: func() error { + _, err := tp().Parse(strings.NewReader("hello")) + return err + }, + }, + { + name: "ParseExecute", + in: func() error { + _, err := tp().ParseExecute(ctx, strings.NewReader("hello")) + return err + }, + }, + { + name: "ParseFragment", + in: func() error { + _, err := tp().ParseFragment(strings.NewReader("hello")) + return err + }, + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + r := require.New(t) + + err := tc.in() + r.Error(err) + + var pe ParseError + r.True(errors.As(err, &pe), err) + + pe = ParseError{} + r.True(errors.Is(err, pe), err) + + var ppe PreParseError + r.True(errors.As(err, &ppe), err) + + ppe = PreParseError{} + r.True(errors.Is(err, ppe), err) + }) + } + +} + +func Test_PreParseError_MarshalJSON(t *testing.T) { + t.Parallel() + + ppe := PreParseError{ + Err: io.EOF, + Filename: "module.md", + Root: "root", + } + + testJSON(t, "pre_parse_error", ppe) +} diff --git a/pre_parser.go b/pre_parser.go index 3ecbd62..3bad70c 100644 --- a/pre_parser.go +++ b/pre_parser.go @@ -1,7 +1,7 @@ package hype import ( - "fmt" + "bytes" "io" ) @@ -12,14 +12,26 @@ type PreParser interface { type PreParsers []PreParser func (list PreParsers) PreParse(p *Parser, r io.Reader) (io.Reader, error) { - var err error + if p == nil { + return nil, ErrIsNil("parser") + } + + contents, err := io.ReadAll(r) + if err != nil { + return nil, err + } + + r = bytes.NewReader(contents) for _, pp := range list { r, err = pp.PreParse(p, r) if err != nil { return nil, PreParseError{ + Contents: contents, Err: err, + Filename: p.Filename, PreParser: pp, + Root: p.Root, } } } @@ -32,12 +44,3 @@ type PreParseFn func(p *Parser, r io.Reader) (io.Reader, error) func (fn PreParseFn) PreParse(p *Parser, r io.Reader) (io.Reader, error) { return fn(p, r) } - -type PreParseError struct { - Err error - PreParser PreParser -} - -func (e PreParseError) Error() string { - return fmt.Sprintf("pre parse error: [%T]: %v", e.PreParser, e.Err) -} diff --git a/pre_parser_test.go b/pre_parser_test.go index 770b1df..daa9cfa 100644 --- a/pre_parser_test.go +++ b/pre_parser_test.go @@ -2,8 +2,6 @@ package hype import ( "bytes" - "errors" - "fmt" "io" "strings" "testing" @@ -11,7 +9,7 @@ import ( "github.com/stretchr/testify/require" ) -func Test_PreParsers_PreParse(t *testing.T) { +func Test_PreParsers(t *testing.T) { t.Parallel() r := require.New(t) @@ -36,7 +34,7 @@ func Test_PreParsers_PreParse(t *testing.T) { }), } - in, err := pp.PreParse(nil, in) + in, err := pp.PreParse(testParser(t, ""), in) r.NoError(err) @@ -48,24 +46,3 @@ func Test_PreParsers_PreParse(t *testing.T) { r.Equal(exp, act) } - -func Test_PreParsers_PreParse_Error(t *testing.T) { - t.Parallel() - r := require.New(t) - - pp := PreParsers{ - PreParseFn(func(p *Parser, r io.Reader) (io.Reader, error) { - return nil, fmt.Errorf("boom") - }), - } - - _, err := pp.PreParse(nil, strings.NewReader("")) - r.Error(err) - - var e2 PreParseError - - r.True(errors.As(err, &e2)) - - r.Contains(e2.Error(), "boom") - r.NotNil(e2.PreParser) -} diff --git a/ref.go b/ref.go index 84da777..5a200c9 100644 --- a/ref.go +++ b/ref.go @@ -27,9 +27,9 @@ func (r *Ref) MarshalJSON() ([]byte, error) { return nil, err } - m["type"] = fmt.Sprintf("%T", r) + m["type"] = toType(r) - return json.Marshal(m) + return json.MarshalIndent(m, "", " ") } func (r *Ref) MD() string { diff --git a/ref_test.go b/ref_test.go new file mode 100644 index 0000000..9332041 --- /dev/null +++ b/ref_test.go @@ -0,0 +1,24 @@ +package hype + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_Ref_MarshalJSON(t *testing.T) { + t.Parallel() + + r := require.New(t) + + ref := &Ref{ + Element: NewEl("ref", nil), + } + ref.Nodes = append(ref.Nodes, Text("foo")) + + err := ref.Set("id", "foo") + r.NoError(err) + + testJSON(t, "ref", ref) + +} diff --git a/snippet.go b/snippet.go index c6d7f3d..07328b7 100644 --- a/snippet.go +++ b/snippet.go @@ -23,7 +23,7 @@ type Snippet struct { End int `json:"end,omitempty"` // the end line of the snippet } -func (snip Snippet) MarshalJSON() ([]byte, error) { +func (snip *Snippet) MarshalJSON() ([]byte, error) { m := struct { Content string `json:"content,omitempty"` File string `json:"file,omitempty"` @@ -42,10 +42,10 @@ func (snip Snippet) MarshalJSON() ([]byte, error) { } if snip.Content != "" { - m.Type = fmt.Sprintf("%T", snip) + m.Type = toType(snip) } - return json.Marshal(m) + return json.MarshalIndent(m, "", " ") } func (snip Snippet) String() string { @@ -84,13 +84,10 @@ func (sm *Snippets) MarshalJSON() ([]byte, error) { }{ Rules: sm.rules, Snippets: sm.snippets, + Type: toType(sm), } - if len(sm.rules) > 0 || len(sm.snippets) > 0 { - m.Type = fmt.Sprintf("%T", sm) - } - - return json.Marshal(m) + return json.MarshalIndent(m, "", " ") } func (sm *Snippets) UnmarshalJSON(data []byte) error { @@ -130,8 +127,10 @@ func (sm *Snippets) init() { sm.rules = map[string]string{ ".go": "// %s", ".html": "", + ".js": "// %s", ".md": "", ".rb": "# %s", + ".ts": "// %s", } } }) diff --git a/source_code.go b/source_code.go index 6bda5e0..0bc913b 100644 --- a/source_code.go +++ b/source_code.go @@ -32,29 +32,13 @@ func (code *SourceCode) MarshalJSON() ([]byte, error) { return nil, err } - m["type"] = fmt.Sprintf("%T", code) + m["type"] = toType(code) if len(code.Lang) > 0 { m["lang"] = code.Lang } - return json.Marshal(m) -} - -func (code *SourceCode) StartTag() string { - if code == nil || code.Element == nil { - return "" - } - - return code.Element.StartTag() -} - -func (code *SourceCode) EndTag() string { - if code == nil || code.Element == nil { - return "" - } - - return "" + return json.MarshalIndent(m, "", " ") } func (code *SourceCode) String() string { diff --git a/table.go b/table.go index 89df482..0a5ce78 100644 --- a/table.go +++ b/table.go @@ -26,9 +26,9 @@ func (tab *Table) MarshalJSON() ([]byte, error) { return nil, err } - m["type"] = fmt.Sprintf("%T", tab) + m["type"] = toType(tab) - return json.Marshal(m) + return json.MarshalIndent(m, "", " ") } func (tab *Table) IsEmptyNode() bool { diff --git a/table_test.go b/table_test.go index 9114820..a930a9b 100644 --- a/table_test.go +++ b/table_test.go @@ -1,7 +1,6 @@ package hype import ( - "os" "testing" "github.com/markbates/table" @@ -13,10 +12,7 @@ func Test_Table_Data(t *testing.T) { r := require.New(t) root := "testdata/table/data" - cab := os.DirFS(root) - - p := NewParser(cab) - p.Root = root + p := testParser(t, root) doc, err := p.ParseFile("module.md") r.NoError(err) @@ -74,3 +70,21 @@ func Test_Table_No_THEAD(t *testing.T) { testModule(t, root) } + +func Test_Table_MarshalJSON(t *testing.T) { + t.Parallel() + r := require.New(t) + + p := testParser(t, "testdata/table/data") + + doc, err := p.ParseFile("module.md") + r.NoError(err) + + tables := ByType[*Table](doc.Children()) + r.Len(tables, 1) + + tab := tables[0] + r.NotNil(tab) + + testJSON(t, "table", tab) +} diff --git a/td.go b/td.go index b158578..9ecafc7 100644 --- a/td.go +++ b/td.go @@ -3,7 +3,6 @@ package hype import ( "encoding/json" "errors" - "fmt" "strings" ) @@ -24,9 +23,9 @@ func (td *TD) MarshalJSON() ([]byte, error) { return nil, err } - m["type"] = fmt.Sprintf("%T", td) + m["type"] = toType(td) - return json.Marshal(m) + return json.MarshalIndent(m, "", " ") } func (td *TD) IsEmptyNode() bool { diff --git a/td_test.go b/td_test.go new file mode 100644 index 0000000..2f932bb --- /dev/null +++ b/td_test.go @@ -0,0 +1,14 @@ +package hype + +import "testing" + +func Test_TD_MarshalJSON(t *testing.T) { + t.Parallel() + + td := &TD{ + Element: NewEl("td", nil), + } + td.Nodes = append(td.Nodes, Text("foo")) + + testJSON(t, "td", td) +} diff --git a/testdata/auto/commands/greet/module.gold b/testdata/auto/commands/greet/module.gold index dc1a359..be02ebb 100644 --- a/testdata/auto/commands/greet/module.gold +++ b/testdata/auto/commands/greet/module.gold @@ -3,7 +3,7 @@
module demo
 
-go 1.18
+go 1.22
 

func main() {
 	// snippet: example
 	fmt.Println("Hello, World!")
diff --git a/testdata/auto/commands/greet/src/go.mod b/testdata/auto/commands/greet/src/go.mod
index 21cc482..2287625 100644
--- a/testdata/auto/commands/greet/src/go.mod
+++ b/testdata/auto/commands/greet/src/go.mod
@@ -1,3 +1,3 @@
 module demo
 
-go 1.18
+go 1.22
diff --git a/testdata/auto/commands/results/truncate/src/go.mod b/testdata/auto/commands/results/truncate/src/go.mod
index 21cc482..2287625 100644
--- a/testdata/auto/commands/results/truncate/src/go.mod
+++ b/testdata/auto/commands/results/truncate/src/go.mod
@@ -1,3 +1,3 @@
 module demo
 
-go 1.18
+go 1.22
diff --git a/testdata/auto/commands/side-by-side/values/src/string-keys/go.mod b/testdata/auto/commands/side-by-side/values/src/string-keys/go.mod
index 21cc482..2287625 100644
--- a/testdata/auto/commands/side-by-side/values/src/string-keys/go.mod
+++ b/testdata/auto/commands/side-by-side/values/src/string-keys/go.mod
@@ -1,3 +1,3 @@
 module demo
 
-go 1.18
+go 1.22
diff --git a/testdata/auto/commands/timeout/go.mod b/testdata/auto/commands/timeout/go.mod
index 21cc482..2287625 100644
--- a/testdata/auto/commands/timeout/go.mod
+++ b/testdata/auto/commands/timeout/go.mod
@@ -1,3 +1,3 @@
 module demo
 
-go 1.18
+go 1.22
diff --git a/testdata/auto/parser/hello/module.gold b/testdata/auto/parser/hello/module.gold
new file mode 100644
index 0000000..0cf4903
--- /dev/null
+++ b/testdata/auto/parser/hello/module.gold
@@ -0,0 +1,26 @@
+
+

Page 1

+ +

This is inline code.

+
+ +

Second Page

+ +
package main
+
+import "fmt"
+
+func main() {
+	fmt.Println("Hello second!")
+}
+
+
+ + +
$ echo hello
+
+hello
+ +

more words

+
+ \ No newline at end of file diff --git a/testdata/auto/parser/hello/module.md b/testdata/auto/parser/hello/module.md new file mode 100644 index 0000000..a18c4c5 --- /dev/null +++ b/testdata/auto/parser/hello/module.md @@ -0,0 +1,9 @@ +# Page 1 + +This is `inline` code. + + + + + +more words diff --git a/testdata/auto/parser/hello/second/second.md b/testdata/auto/parser/hello/second/second.md new file mode 100644 index 0000000..9e6d585 --- /dev/null +++ b/testdata/auto/parser/hello/second/second.md @@ -0,0 +1,3 @@ +# Second Page + + diff --git a/testdata/auto/parser/hello/second/src/go.mod b/testdata/auto/parser/hello/second/src/go.mod new file mode 100644 index 0000000..80e3182 --- /dev/null +++ b/testdata/auto/parser/hello/second/src/go.mod @@ -0,0 +1 @@ +module demo \ No newline at end of file diff --git a/testdata/auto/parser/hello/second/src/main.go b/testdata/auto/parser/hello/second/src/main.go new file mode 100644 index 0000000..741ac62 --- /dev/null +++ b/testdata/auto/parser/hello/second/src/main.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("Hello second!") +} diff --git a/testdata/auto/refs/includes/simple/src/greet/go.mod b/testdata/auto/refs/includes/simple/src/greet/go.mod index 21cc482..2287625 100644 --- a/testdata/auto/refs/includes/simple/src/greet/go.mod +++ b/testdata/auto/refs/includes/simple/src/greet/go.mod @@ -1,3 +1,3 @@ module demo -go 1.18 +go 1.22 diff --git a/testdata/auto/refs/includes/src/greet/go.mod b/testdata/auto/refs/includes/src/greet/go.mod index 21cc482..2287625 100644 --- a/testdata/auto/refs/includes/src/greet/go.mod +++ b/testdata/auto/refs/includes/src/greet/go.mod @@ -1,3 +1,3 @@ module demo -go 1.18 +go 1.22 diff --git a/testdata/auto/refs/simple/src/greet/go.mod b/testdata/auto/refs/simple/src/greet/go.mod index 21cc482..2287625 100644 --- a/testdata/auto/refs/simple/src/greet/go.mod +++ b/testdata/auto/refs/simple/src/greet/go.mod @@ -1,3 +1,3 @@ module demo -go 1.18 +go 1.22 diff --git a/testdata/auto/snippets/range/src/go.mod b/testdata/auto/snippets/range/src/go.mod index 21cc482..2287625 100644 --- a/testdata/auto/snippets/range/src/go.mod +++ b/testdata/auto/snippets/range/src/go.mod @@ -1,3 +1,3 @@ module demo -go 1.18 +go 1.22 diff --git a/testdata/auto/snippets/simple/module.gold b/testdata/auto/snippets/simple/module.gold new file mode 100644 index 0000000..33632c7 --- /dev/null +++ b/testdata/auto/snippets/simple/module.gold @@ -0,0 +1,8 @@ + +

Lone Ranger

+ +
func main() {
+	fmt.Println("Hello, world!")
+}
+
+ \ No newline at end of file diff --git a/testdata/auto/snippets/simple/module.md b/testdata/auto/snippets/simple/module.md new file mode 100644 index 0000000..7faa460 --- /dev/null +++ b/testdata/auto/snippets/simple/module.md @@ -0,0 +1,3 @@ +# Lone Ranger + + diff --git a/testdata/auto/snippets/simple/src/go.mod b/testdata/auto/snippets/simple/src/go.mod new file mode 100644 index 0000000..2287625 --- /dev/null +++ b/testdata/auto/snippets/simple/src/go.mod @@ -0,0 +1,3 @@ +module demo + +go 1.22 diff --git a/testdata/auto/snippets/simple/src/main.go b/testdata/auto/snippets/simple/src/main.go new file mode 100644 index 0000000..d371272 --- /dev/null +++ b/testdata/auto/snippets/simple/src/main.go @@ -0,0 +1,10 @@ +package main + +import "fmt" + +// snippet: foo +func main() { + fmt.Println("Hello, world!") +} + +// snippet: foo diff --git a/testdata/commands/bad-exit/go.mod b/testdata/commands/bad-exit/go.mod index 21cc482..2287625 100644 --- a/testdata/commands/bad-exit/go.mod +++ b/testdata/commands/bad-exit/go.mod @@ -1,3 +1,3 @@ module demo -go 1.18 +go 1.22 diff --git a/testdata/doc/execution/failure/module.md b/testdata/doc/execution/failure/module.md new file mode 100644 index 0000000..1625c01 --- /dev/null +++ b/testdata/doc/execution/failure/module.md @@ -0,0 +1,3 @@ +# Command Error + + diff --git a/testdata/doc/execution/nested_failure/module.md b/testdata/doc/execution/nested_failure/module.md new file mode 100644 index 0000000..a18c4c5 --- /dev/null +++ b/testdata/doc/execution/nested_failure/module.md @@ -0,0 +1,9 @@ +# Page 1 + +This is `inline` code. + + + + + +more words diff --git a/testdata/doc/execution/nested_failure/second/second.md b/testdata/doc/execution/nested_failure/second/second.md new file mode 100644 index 0000000..9e6d585 --- /dev/null +++ b/testdata/doc/execution/nested_failure/second/second.md @@ -0,0 +1,3 @@ +# Second Page + + diff --git a/testdata/doc/execution/nested_failure/second/src/go.mod b/testdata/doc/execution/nested_failure/second/src/go.mod new file mode 100644 index 0000000..80e3182 --- /dev/null +++ b/testdata/doc/execution/nested_failure/second/src/go.mod @@ -0,0 +1 @@ +module demo \ No newline at end of file diff --git a/testdata/doc/execution/nested_failure/second/src/main.go b/testdata/doc/execution/nested_failure/second/src/main.go new file mode 100644 index 0000000..ae56778 --- /dev/null +++ b/testdata/doc/execution/nested_failure/second/src/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "os" +) + +func main() { + os.Exit(-1) +} diff --git a/testdata/doc/execution/success/module.md b/testdata/doc/execution/success/module.md new file mode 100644 index 0000000..70a503e --- /dev/null +++ b/testdata/doc/execution/success/module.md @@ -0,0 +1,3 @@ +# Command + + diff --git a/testdata/doc/pages/module.md b/testdata/doc/pages/module.md new file mode 100644 index 0000000..a18c4c5 --- /dev/null +++ b/testdata/doc/pages/module.md @@ -0,0 +1,9 @@ +# Page 1 + +This is `inline` code. + + + + + +more words diff --git a/testdata/doc/pages/second/second.md b/testdata/doc/pages/second/second.md new file mode 100644 index 0000000..9e6d585 --- /dev/null +++ b/testdata/doc/pages/second/second.md @@ -0,0 +1,3 @@ +# Second Page + + diff --git a/testdata/doc/pages/second/src/go.mod b/testdata/doc/pages/second/src/go.mod new file mode 100644 index 0000000..80e3182 --- /dev/null +++ b/testdata/doc/pages/second/src/go.mod @@ -0,0 +1 @@ +module demo \ No newline at end of file diff --git a/testdata/doc/pages/second/src/main.go b/testdata/doc/pages/second/src/main.go new file mode 100644 index 0000000..741ac62 --- /dev/null +++ b/testdata/doc/pages/second/src/main.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("Hello second!") +} diff --git a/testdata/doc/simple/module.md b/testdata/doc/simple/module.md new file mode 100644 index 0000000..84fe573 --- /dev/null +++ b/testdata/doc/simple/module.md @@ -0,0 +1,3 @@ +# Hello + +Hi there! diff --git a/testdata/doc/snippets/module.md b/testdata/doc/snippets/module.md new file mode 100644 index 0000000..d4987a4 --- /dev/null +++ b/testdata/doc/snippets/module.md @@ -0,0 +1,5 @@ +# Hello + + + + diff --git a/testdata/doc/snippets/src/main.ts b/testdata/doc/snippets/src/main.ts new file mode 100644 index 0000000..060faa1 --- /dev/null +++ b/testdata/doc/snippets/src/main.ts @@ -0,0 +1,8 @@ +// snippet: class +export class Hype { + // snippet: constructor + constructor(parameters) { + } + // snippet: constructor +} +// snippet: class \ No newline at end of file diff --git a/testdata/doc/to_md/module.json b/testdata/doc/to_md/module.json deleted file mode 100644 index caeef0c..0000000 --- a/testdata/doc/to_md/module.json +++ /dev/null @@ -1,19866 +0,0 @@ -{ - "nodes": [ - { - "file": "module.md", - "nodes": [ - { - "atom": "html", - "file": "module.md", - "nodes": [ - { - "atom": "head", - "file": "module.md", - "nodes": [ - { - "atom": "meta", - "attributes": { - "class": "center, middle, inverse" - }, - "file": "module.md", - "type": "*hype.Element" - }, - { - "atom": "meta", - "attributes": { - "duration": "45m" - }, - "file": "module.md", - "type": "*hype.Element" - }, - { - "atom": "meta", - "attributes": { - "exercises": "2 Shared Lab, 1 Student Labs" - }, - "file": "module.md", - "type": "*hype.Element" - }, - { - "atom": "meta", - "attributes": { - "level": "Intermediate" - }, - "file": "module.md", - "type": "*hype.Element" - }, - { - "atom": "meta", - "attributes": { - "name": "gRPC Middleware" - }, - "file": "module.md", - "type": "*hype.Element" - }, - { - "atom": "meta", - "attributes": { - "topic": "Distributed Computing" - }, - "file": "module.md", - "type": "*hype.Element" - } - ], - "type": "*hype.Element" - }, - [ - { - "atom": "body", - "file": "module.md", - "nodes": [ - [ - { - "atom": "page", - "file": "module.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "h1", - "file": "module.md", - "level": 1, - "nodes": [ - { - "text": "Context", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "module.md", - "nodes": [ - [ - { - "atom": "metadata", - "data": { - "class": "center, middle, inverse", - "duration": "45m", - "exercises": "2 Shared Lab, 1 Student Labs", - "level": "Intermediate", - "name": "gRPC Middleware", - "topic": "Distributed Computing" - }, - "file": "module.md", - "type": "*hype.Metadata" - } - ] - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "module.md", - "nodes": [ - { - "text": "Introduced in Go 1.7, the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context", - "href": "https://pkg.go.dev/context", - "target": "_blank" - }, - "file": "module.md", - "nodes": [ - { - "atom": "code", - "file": "module.md", - "nodes": [ - { - "text": "context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context" - } - ], - { - "text": " package was introduced to provide a cleaner way, than the use of channels, of managing cancellation and timeouts across goroutines.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "module.md", - "nodes": [ - { - "text": "While the scope, and API footprint of the package is pretty small, it was a welcome addition to the language when introduced.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "module.md", - "nodes": [ - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context", - "href": "https://pkg.go.dev/context", - "target": "_blank" - }, - "file": "module.md", - "nodes": [ - { - "atom": "code", - "file": "module.md", - "nodes": [ - { - "text": "context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context" - } - ], - { - "text": " package, ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-1" - }, - "file": "module.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-1" - }, - "nodes": [ - { - "text": "Listing 1.1", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-1" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ", defines the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "module.md", - "nodes": [ - { - "atom": "code", - "file": "module.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " type, which carries deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "module.md", - "nodes": [ - { - "text": "Context is, mostly, used for controlling concurrent subsystems in your application. This week we will cover the different kinds of behavior with contexts including canceling, timeouts, and values. We'll also see how we can clean up a lot of code involving channels by using contexts.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - null, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-1", - "type": "listing" - }, - "file": "module.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "args": [ - "go", - "doc", - "-short", - "context" - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "doc": "-short context", - "exec": "go doc -short context" - }, - "expected_exit": 0, - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc -short context\n\nvar Canceled = errors.New(\u0026#34;context canceled\u0026#34;)\nvar DeadlineExceeded error = deadlineExceededError{}\nfunc Cause(c Context) error\nfunc WithCancel(parent Context) (ctx Context, cancel CancelFunc)\nfunc WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc)\nfunc WithDeadline(parent Context, d time.Time) (Context, CancelFunc)\nfunc WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)\ntype CancelCauseFunc func(cause error)\ntype CancelFunc func()\ntype Context interface{ ... }\n func Background() Context\n func TODO() Context\n func WithValue(parent Context, key, val any) Context", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "-short", - "context" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "dmFyIENhbmNlbGVkID0gZXJyb3JzLk5ldygiY29udGV4dCBjYW5jZWxlZCIpCnZhciBEZWFkbGluZUV4Y2VlZGVkIGVycm9yID0gZGVhZGxpbmVFeGNlZWRlZEVycm9ye30KZnVuYyBDYXVzZShjIENvbnRleHQpIGVycm9yCmZ1bmMgV2l0aENhbmNlbChwYXJlbnQgQ29udGV4dCkgKGN0eCBDb250ZXh0LCBjYW5jZWwgQ2FuY2VsRnVuYykKZnVuYyBXaXRoQ2FuY2VsQ2F1c2UocGFyZW50IENvbnRleHQpIChjdHggQ29udGV4dCwgY2FuY2VsIENhbmNlbENhdXNlRnVuYykKZnVuYyBXaXRoRGVhZGxpbmUocGFyZW50IENvbnRleHQsIGQgdGltZS5UaW1lKSAoQ29udGV4dCwgQ2FuY2VsRnVuYykKZnVuYyBXaXRoVGltZW91dChwYXJlbnQgQ29udGV4dCwgdGltZW91dCB0aW1lLkR1cmF0aW9uKSAoQ29udGV4dCwgQ2FuY2VsRnVuYykKdHlwZSBDYW5jZWxDYXVzZUZ1bmMgZnVuYyhjYXVzZSBlcnJvcikKdHlwZSBDYW5jZWxGdW5jIGZ1bmMoKQp0eXBlIENvbnRleHQgaW50ZXJmYWNleyAuLi4gfQogICAgZnVuYyBCYWNrZ3JvdW5kKCkgQ29udGV4dAogICAgZnVuYyBUT0RPKCkgQ29udGV4dAogICAgZnVuYyBXaXRoVmFsdWUocGFyZW50IENvbnRleHQsIGtleSwgdmFsIGFueSkgQ29udGV4dA==", - "duration": 96368458 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc -short context\n\nvar Canceled = errors.New(\u0026#34;context canceled\u0026#34;)\nvar DeadlineExceeded error = deadlineExceededError{}\nfunc Cause(c Context) error\nfunc WithCancel(parent Context) (ctx Context, cancel CancelFunc)\nfunc WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc)\nfunc WithDeadline(parent Context, d time.Time) (Context, CancelFunc)\nfunc WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)\ntype CancelCauseFunc func(cause error)\ntype CancelFunc func()\ntype Context interface{ ... }\n func Background() Context\n func TODO() Context\n func WithValue(parent Context, key, val any) Context", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "-short", - "context" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "dmFyIENhbmNlbGVkID0gZXJyb3JzLk5ldygiY29udGV4dCBjYW5jZWxlZCIpCnZhciBEZWFkbGluZUV4Y2VlZGVkIGVycm9yID0gZGVhZGxpbmVFeGNlZWRlZEVycm9ye30KZnVuYyBDYXVzZShjIENvbnRleHQpIGVycm9yCmZ1bmMgV2l0aENhbmNlbChwYXJlbnQgQ29udGV4dCkgKGN0eCBDb250ZXh0LCBjYW5jZWwgQ2FuY2VsRnVuYykKZnVuYyBXaXRoQ2FuY2VsQ2F1c2UocGFyZW50IENvbnRleHQpIChjdHggQ29udGV4dCwgY2FuY2VsIENhbmNlbENhdXNlRnVuYykKZnVuYyBXaXRoRGVhZGxpbmUocGFyZW50IENvbnRleHQsIGQgdGltZS5UaW1lKSAoQ29udGV4dCwgQ2FuY2VsRnVuYykKZnVuYyBXaXRoVGltZW91dChwYXJlbnQgQ29udGV4dCwgdGltZW91dCB0aW1lLkR1cmF0aW9uKSAoQ29udGV4dCwgQ2FuY2VsRnVuYykKdHlwZSBDYW5jZWxDYXVzZUZ1bmMgZnVuYyhjYXVzZSBlcnJvcikKdHlwZSBDYW5jZWxGdW5jIGZ1bmMoKQp0eXBlIENvbnRleHQgaW50ZXJmYWNleyAuLi4gfQogICAgZnVuYyBCYWNrZ3JvdW5kKCkgQ29udGV4dAogICAgZnVuYyBUT0RPKCkgQ29udGV4dAogICAgZnVuYyBXaXRoVmFsdWUocGFyZW50IENvbnRleHQsIGtleSwgdmFsIGFueSkgQ29udGV4dA==", - "duration": 96368458 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.1:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context", - "href": "https://pkg.go.dev/context", - "target": "_blank" - }, - "nodes": [ - [ - { - "atom": "code", - "nodes": [ - { - "text": "context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ] - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context" - } - ], - { - "text": " package.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 1, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "title": "Context", - "type": "*hype.Page" - } - ], - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "include", - "attributes": { - "src": "basics/basics.md" - }, - "dir": "basics", - "file": "module.md", - "nodes": [ - [ - { - "atom": "page", - "file": "basics.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "h1", - "file": "basics.md", - "level": 1, - "nodes": [ - { - "text": "The Context Interface", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "basics.md", - "nodes": [ - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "basics.md", - "nodes": [ - { - "atom": "code", - "file": "basics.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " interface, ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-2" - }, - "file": "basics.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-2" - }, - "nodes": [ - { - "text": "Listing 1.2", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-2" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ", consists of four methods. These methods provide us the ability to listen for cancellation and timeout events, retrieve values from the context hierarchy, and finally, a way to check what ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "basics.md", - "nodes": [ - { - "text": "error", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": ", if any, caused the context to be canceled.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-2", - "type": "listing" - }, - "file": "basics.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - { - "atom": "pre", - "file": "basics", - "nodes": [ - [ - { - "atom": "code", - "attributes": { - "class": "language-godoc", - "language": "godoc" - }, - "file": "basics", - "lang": "godoc", - "nodes": [ - { - "text": "type Context interface {\n Deadline() (deadline time.Time, ok bool)\n Done() \u003c-chan struct{}\n Err() error\n Value(key interface{}) interface{}\n}\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ] - ], - "type": "*hype.Element" - }, - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "basics", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.2:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "basics", - "nodes": [ - [ - { - "atom": "code", - "file": "basics", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ] - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " interface.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 2, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "basics.md", - "nodes": [ - { - "text": "We can see, in ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-2" - }, - "file": "basics.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-2" - }, - "nodes": [ - { - "text": "Listing 1.2", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-2" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ", that the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "basics.md", - "nodes": [ - { - "atom": "code", - "file": "basics.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " interface implements several of the channel patterns we have already seen, such as have a ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "basics.md", - "nodes": [ - { - "text": "Done", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " channel that can be listened to for cancellation.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "basics.md", - "nodes": [ - { - "text": "We will cover each of these methods in more detail later. For now, let's briefly look at each one of them.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "h2", - "file": "basics.md", - "level": 2, - "nodes": [ - { - "text": "Context#Deadline", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "basics.md", - "nodes": [ - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context.Deadline", - "href": "https://pkg.go.dev/context#Context.Deadline", - "target": "_blank" - }, - "file": "basics.md", - "nodes": [ - { - "atom": "code", - "file": "basics.md", - "nodes": [ - { - "text": "context.Context.Deadline", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context.Deadline" - } - ], - { - "text": " method, ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-3" - }, - "file": "basics.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-3" - }, - "nodes": [ - { - "text": "Listing 1.3", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-3" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ", can be used to check if a context has a cancellation deadline set, and if so, what that deadline is.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-3", - "type": "listing" - }, - "file": "basics.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "args": [ - "go", - "doc", - "context.Context.Deadline" - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "doc": "context.Context.Deadline", - "exec": "go doc context.Context.Deadline" - }, - "expected_exit": 0, - "file": "basics", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc context.Context.Deadline\n\npackage context // import \u0026#34;context\u0026#34;\n\ntype Context interface {\n\t// Deadline returns the time when work done on behalf of this context\n\t// should be canceled. Deadline returns ok==false when no deadline is\n\t// set. Successive calls to Deadline return the same results.\n\tDeadline() (deadline time.Time, ok bool)\n}", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "context.Context.Deadline" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "cGFja2FnZSBjb250ZXh0IC8vIGltcG9ydCAiY29udGV4dCIKCnR5cGUgQ29udGV4dCBpbnRlcmZhY2UgewoJLy8gRGVhZGxpbmUgcmV0dXJucyB0aGUgdGltZSB3aGVuIHdvcmsgZG9uZSBvbiBiZWhhbGYgb2YgdGhpcyBjb250ZXh0CgkvLyBzaG91bGQgYmUgY2FuY2VsZWQuIERlYWRsaW5lIHJldHVybnMgb2s9PWZhbHNlIHdoZW4gbm8gZGVhZGxpbmUgaXMKCS8vIHNldC4gU3VjY2Vzc2l2ZSBjYWxscyB0byBEZWFkbGluZSByZXR1cm4gdGhlIHNhbWUgcmVzdWx0cy4KCURlYWRsaW5lKCkgKGRlYWRsaW5lIHRpbWUuVGltZSwgb2sgYm9vbCkKfQ==", - "duration": 190236875 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc context.Context.Deadline\n\npackage context // import \u0026#34;context\u0026#34;\n\ntype Context interface {\n\t// Deadline returns the time when work done on behalf of this context\n\t// should be canceled. Deadline returns ok==false when no deadline is\n\t// set. Successive calls to Deadline return the same results.\n\tDeadline() (deadline time.Time, ok bool)\n}", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "context.Context.Deadline" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "cGFja2FnZSBjb250ZXh0IC8vIGltcG9ydCAiY29udGV4dCIKCnR5cGUgQ29udGV4dCBpbnRlcmZhY2UgewoJLy8gRGVhZGxpbmUgcmV0dXJucyB0aGUgdGltZSB3aGVuIHdvcmsgZG9uZSBvbiBiZWhhbGYgb2YgdGhpcyBjb250ZXh0CgkvLyBzaG91bGQgYmUgY2FuY2VsZWQuIERlYWRsaW5lIHJldHVybnMgb2s9PWZhbHNlIHdoZW4gbm8gZGVhZGxpbmUgaXMKCS8vIHNldC4gU3VjY2Vzc2l2ZSBjYWxscyB0byBEZWFkbGluZSByZXR1cm4gdGhlIHNhbWUgcmVzdWx0cy4KCURlYWRsaW5lKCkgKGRlYWRsaW5lIHRpbWUuVGltZSwgb2sgYm9vbCkKfQ==", - "duration": 190236875 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "basics", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.3:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context.Deadline", - "href": "https://pkg.go.dev/context#Context.Deadline", - "target": "_blank" - }, - "file": "basics", - "nodes": [ - [ - { - "atom": "code", - "file": "basics", - "nodes": [ - { - "text": "context.Context.Deadline", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ] - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context.Deadline" - } - ], - { - "text": " method.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 3, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "h2", - "file": "basics.md", - "level": 2, - "nodes": [ - { - "text": "Context#Done", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "basics.md", - "nodes": [ - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context.Done", - "href": "https://pkg.go.dev/context#Context.Done", - "target": "_blank" - }, - "file": "basics.md", - "nodes": [ - { - "atom": "code", - "file": "basics.md", - "nodes": [ - { - "text": "context.Context.Done", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context.Done" - } - ], - { - "text": " method, ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-4" - }, - "file": "basics.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-4" - }, - "nodes": [ - { - "text": "Listing 1.4", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-4" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ", can be used to listen for cancellation events. This is similar to how we can listen for a channel being closed, but it is more flexible.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-4", - "type": "listing" - }, - "file": "basics.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "args": [ - "go", - "doc", - "context.Context.Done" - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "doc": "context.Context.Done", - "exec": "go doc context.Context.Done" - }, - "expected_exit": 0, - "file": "basics", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc context.Context.Done\n\npackage context // import \u0026#34;context\u0026#34;\n\ntype Context interface {\n\n\t// Done returns a channel that\u0026#39;s closed when work done on behalf of this\n\t// context should be canceled. Done may return nil if this context can\n\t// never be canceled. Successive calls to Done return the same value.\n\t// The close of the Done channel may happen asynchronously,\n\t// after the cancel function returns.\n\t//\n\t// WithCancel arranges for Done to be closed when cancel is called;\n\t// WithDeadline arranges for Done to be closed when the deadline\n\t// expires; WithTimeout arranges for Done to be closed when the timeout\n\t// elapses.\n\t//\n\t// Done is provided for use in select statements:\n\t//\n\t// // Stream generates values with DoSomething and sends them to out\n\t// // until DoSomething returns an error or ctx.Done is closed.\n\t// func Stream(ctx context.Context, out chan\u0026lt;- Value) error {\n\t// \tfor {\n\t// \t\tv, err := DoSomething(ctx)\n\t// \t\tif err != nil {\n\t// \t\t\treturn err\n\t// \t\t}\n\t// \t\tselect {\n\t// \t\tcase \u0026lt;-ctx.Done():\n\t// \t\t\treturn ctx.Err()\n\t// \t\tcase out \u0026lt;- v:\n\t// \t\t}\n\t// \t}\n\t// }\n\t//\n\t// See https://blog.golang.org/pipelines for more examples of how to use\n\t// a Done channel for cancellation.\n\tDone() \u0026lt;-chan struct{}\n}", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "context.Context.Done" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "cGFja2FnZSBjb250ZXh0IC8vIGltcG9ydCAiY29udGV4dCIKCnR5cGUgQ29udGV4dCBpbnRlcmZhY2UgewoKCS8vIERvbmUgcmV0dXJucyBhIGNoYW5uZWwgdGhhdCdzIGNsb3NlZCB3aGVuIHdvcmsgZG9uZSBvbiBiZWhhbGYgb2YgdGhpcwoJLy8gY29udGV4dCBzaG91bGQgYmUgY2FuY2VsZWQuIERvbmUgbWF5IHJldHVybiBuaWwgaWYgdGhpcyBjb250ZXh0IGNhbgoJLy8gbmV2ZXIgYmUgY2FuY2VsZWQuIFN1Y2Nlc3NpdmUgY2FsbHMgdG8gRG9uZSByZXR1cm4gdGhlIHNhbWUgdmFsdWUuCgkvLyBUaGUgY2xvc2Ugb2YgdGhlIERvbmUgY2hhbm5lbCBtYXkgaGFwcGVuIGFzeW5jaHJvbm91c2x5LAoJLy8gYWZ0ZXIgdGhlIGNhbmNlbCBmdW5jdGlvbiByZXR1cm5zLgoJLy8KCS8vIFdpdGhDYW5jZWwgYXJyYW5nZXMgZm9yIERvbmUgdG8gYmUgY2xvc2VkIHdoZW4gY2FuY2VsIGlzIGNhbGxlZDsKCS8vIFdpdGhEZWFkbGluZSBhcnJhbmdlcyBmb3IgRG9uZSB0byBiZSBjbG9zZWQgd2hlbiB0aGUgZGVhZGxpbmUKCS8vIGV4cGlyZXM7IFdpdGhUaW1lb3V0IGFycmFuZ2VzIGZvciBEb25lIHRvIGJlIGNsb3NlZCB3aGVuIHRoZSB0aW1lb3V0CgkvLyBlbGFwc2VzLgoJLy8KCS8vIERvbmUgaXMgcHJvdmlkZWQgZm9yIHVzZSBpbiBzZWxlY3Qgc3RhdGVtZW50czoKCS8vCgkvLyAgLy8gU3RyZWFtIGdlbmVyYXRlcyB2YWx1ZXMgd2l0aCBEb1NvbWV0aGluZyBhbmQgc2VuZHMgdGhlbSB0byBvdXQKCS8vICAvLyB1bnRpbCBEb1NvbWV0aGluZyByZXR1cm5zIGFuIGVycm9yIG9yIGN0eC5Eb25lIGlzIGNsb3NlZC4KCS8vICBmdW5jIFN0cmVhbShjdHggY29udGV4dC5Db250ZXh0LCBvdXQgY2hhbjwtIFZhbHVlKSBlcnJvciB7CgkvLyAgCWZvciB7CgkvLyAgCQl2LCBlcnIgOj0gRG9Tb21ldGhpbmcoY3R4KQoJLy8gIAkJaWYgZXJyICE9IG5pbCB7CgkvLyAgCQkJcmV0dXJuIGVycgoJLy8gIAkJfQoJLy8gIAkJc2VsZWN0IHsKCS8vICAJCWNhc2UgPC1jdHguRG9uZSgpOgoJLy8gIAkJCXJldHVybiBjdHguRXJyKCkKCS8vICAJCWNhc2Ugb3V0IDwtIHY6CgkvLyAgCQl9CgkvLyAgCX0KCS8vICB9CgkvLwoJLy8gU2VlIGh0dHBzOi8vYmxvZy5nb2xhbmcub3JnL3BpcGVsaW5lcyBmb3IgbW9yZSBleGFtcGxlcyBvZiBob3cgdG8gdXNlCgkvLyBhIERvbmUgY2hhbm5lbCBmb3IgY2FuY2VsbGF0aW9uLgoJRG9uZSgpIDwtY2hhbiBzdHJ1Y3R7fQp9", - "duration": 202997333 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc context.Context.Done\n\npackage context // import \u0026#34;context\u0026#34;\n\ntype Context interface {\n\n\t// Done returns a channel that\u0026#39;s closed when work done on behalf of this\n\t// context should be canceled. Done may return nil if this context can\n\t// never be canceled. Successive calls to Done return the same value.\n\t// The close of the Done channel may happen asynchronously,\n\t// after the cancel function returns.\n\t//\n\t// WithCancel arranges for Done to be closed when cancel is called;\n\t// WithDeadline arranges for Done to be closed when the deadline\n\t// expires; WithTimeout arranges for Done to be closed when the timeout\n\t// elapses.\n\t//\n\t// Done is provided for use in select statements:\n\t//\n\t// // Stream generates values with DoSomething and sends them to out\n\t// // until DoSomething returns an error or ctx.Done is closed.\n\t// func Stream(ctx context.Context, out chan\u0026lt;- Value) error {\n\t// \tfor {\n\t// \t\tv, err := DoSomething(ctx)\n\t// \t\tif err != nil {\n\t// \t\t\treturn err\n\t// \t\t}\n\t// \t\tselect {\n\t// \t\tcase \u0026lt;-ctx.Done():\n\t// \t\t\treturn ctx.Err()\n\t// \t\tcase out \u0026lt;- v:\n\t// \t\t}\n\t// \t}\n\t// }\n\t//\n\t// See https://blog.golang.org/pipelines for more examples of how to use\n\t// a Done channel for cancellation.\n\tDone() \u0026lt;-chan struct{}\n}", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "context.Context.Done" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "cGFja2FnZSBjb250ZXh0IC8vIGltcG9ydCAiY29udGV4dCIKCnR5cGUgQ29udGV4dCBpbnRlcmZhY2UgewoKCS8vIERvbmUgcmV0dXJucyBhIGNoYW5uZWwgdGhhdCdzIGNsb3NlZCB3aGVuIHdvcmsgZG9uZSBvbiBiZWhhbGYgb2YgdGhpcwoJLy8gY29udGV4dCBzaG91bGQgYmUgY2FuY2VsZWQuIERvbmUgbWF5IHJldHVybiBuaWwgaWYgdGhpcyBjb250ZXh0IGNhbgoJLy8gbmV2ZXIgYmUgY2FuY2VsZWQuIFN1Y2Nlc3NpdmUgY2FsbHMgdG8gRG9uZSByZXR1cm4gdGhlIHNhbWUgdmFsdWUuCgkvLyBUaGUgY2xvc2Ugb2YgdGhlIERvbmUgY2hhbm5lbCBtYXkgaGFwcGVuIGFzeW5jaHJvbm91c2x5LAoJLy8gYWZ0ZXIgdGhlIGNhbmNlbCBmdW5jdGlvbiByZXR1cm5zLgoJLy8KCS8vIFdpdGhDYW5jZWwgYXJyYW5nZXMgZm9yIERvbmUgdG8gYmUgY2xvc2VkIHdoZW4gY2FuY2VsIGlzIGNhbGxlZDsKCS8vIFdpdGhEZWFkbGluZSBhcnJhbmdlcyBmb3IgRG9uZSB0byBiZSBjbG9zZWQgd2hlbiB0aGUgZGVhZGxpbmUKCS8vIGV4cGlyZXM7IFdpdGhUaW1lb3V0IGFycmFuZ2VzIGZvciBEb25lIHRvIGJlIGNsb3NlZCB3aGVuIHRoZSB0aW1lb3V0CgkvLyBlbGFwc2VzLgoJLy8KCS8vIERvbmUgaXMgcHJvdmlkZWQgZm9yIHVzZSBpbiBzZWxlY3Qgc3RhdGVtZW50czoKCS8vCgkvLyAgLy8gU3RyZWFtIGdlbmVyYXRlcyB2YWx1ZXMgd2l0aCBEb1NvbWV0aGluZyBhbmQgc2VuZHMgdGhlbSB0byBvdXQKCS8vICAvLyB1bnRpbCBEb1NvbWV0aGluZyByZXR1cm5zIGFuIGVycm9yIG9yIGN0eC5Eb25lIGlzIGNsb3NlZC4KCS8vICBmdW5jIFN0cmVhbShjdHggY29udGV4dC5Db250ZXh0LCBvdXQgY2hhbjwtIFZhbHVlKSBlcnJvciB7CgkvLyAgCWZvciB7CgkvLyAgCQl2LCBlcnIgOj0gRG9Tb21ldGhpbmcoY3R4KQoJLy8gIAkJaWYgZXJyICE9IG5pbCB7CgkvLyAgCQkJcmV0dXJuIGVycgoJLy8gIAkJfQoJLy8gIAkJc2VsZWN0IHsKCS8vICAJCWNhc2UgPC1jdHguRG9uZSgpOgoJLy8gIAkJCXJldHVybiBjdHguRXJyKCkKCS8vICAJCWNhc2Ugb3V0IDwtIHY6CgkvLyAgCQl9CgkvLyAgCX0KCS8vICB9CgkvLwoJLy8gU2VlIGh0dHBzOi8vYmxvZy5nb2xhbmcub3JnL3BpcGVsaW5lcyBmb3IgbW9yZSBleGFtcGxlcyBvZiBob3cgdG8gdXNlCgkvLyBhIERvbmUgY2hhbm5lbCBmb3IgY2FuY2VsbGF0aW9uLgoJRG9uZSgpIDwtY2hhbiBzdHJ1Y3R7fQp9", - "duration": 202997333 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "basics", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.4:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context.Done", - "href": "https://pkg.go.dev/context#Context.Done", - "target": "_blank" - }, - "file": "basics", - "nodes": [ - [ - { - "atom": "code", - "file": "basics", - "nodes": [ - { - "text": "context.Context.Done", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ] - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context.Done" - } - ], - { - "text": " method.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 4, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "h2", - "file": "basics.md", - "level": 2, - "nodes": [ - { - "text": "Context#Err", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "basics.md", - "nodes": [ - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context.Err", - "href": "https://pkg.go.dev/context#Context.Err", - "target": "_blank" - }, - "file": "basics.md", - "nodes": [ - { - "atom": "code", - "file": "basics.md", - "nodes": [ - { - "text": "context.Context.Err", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context.Err" - } - ], - { - "text": " method, ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-5" - }, - "file": "basics.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-5" - }, - "nodes": [ - { - "text": "Listing 1.5", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-5" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ", can be used to check if a context has been canceled.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-5", - "type": "listing" - }, - "file": "basics.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "args": [ - "go", - "doc", - "context.Context.Err" - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "doc": "context.Context.Err", - "exec": "go doc context.Context.Err" - }, - "expected_exit": 0, - "file": "basics", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc context.Context.Err\n\npackage context // import \u0026#34;context\u0026#34;\n\ntype Context interface {\n\n\t// If Done is not yet closed, Err returns nil.\n\t// If Done is closed, Err returns a non-nil error explaining why:\n\t// Canceled if the context was canceled\n\t// or DeadlineExceeded if the context\u0026#39;s deadline passed.\n\t// After Err returns a non-nil error, successive calls to Err return the same error.\n\tErr() error\n}", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "context.Context.Err" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "cGFja2FnZSBjb250ZXh0IC8vIGltcG9ydCAiY29udGV4dCIKCnR5cGUgQ29udGV4dCBpbnRlcmZhY2UgewoKCS8vIElmIERvbmUgaXMgbm90IHlldCBjbG9zZWQsIEVyciByZXR1cm5zIG5pbC4KCS8vIElmIERvbmUgaXMgY2xvc2VkLCBFcnIgcmV0dXJucyBhIG5vbi1uaWwgZXJyb3IgZXhwbGFpbmluZyB3aHk6CgkvLyBDYW5jZWxlZCBpZiB0aGUgY29udGV4dCB3YXMgY2FuY2VsZWQKCS8vIG9yIERlYWRsaW5lRXhjZWVkZWQgaWYgdGhlIGNvbnRleHQncyBkZWFkbGluZSBwYXNzZWQuCgkvLyBBZnRlciBFcnIgcmV0dXJucyBhIG5vbi1uaWwgZXJyb3IsIHN1Y2Nlc3NpdmUgY2FsbHMgdG8gRXJyIHJldHVybiB0aGUgc2FtZSBlcnJvci4KCUVycigpIGVycm9yCn0=", - "duration": 369628375 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc context.Context.Err\n\npackage context // import \u0026#34;context\u0026#34;\n\ntype Context interface {\n\n\t// If Done is not yet closed, Err returns nil.\n\t// If Done is closed, Err returns a non-nil error explaining why:\n\t// Canceled if the context was canceled\n\t// or DeadlineExceeded if the context\u0026#39;s deadline passed.\n\t// After Err returns a non-nil error, successive calls to Err return the same error.\n\tErr() error\n}", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "context.Context.Err" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "cGFja2FnZSBjb250ZXh0IC8vIGltcG9ydCAiY29udGV4dCIKCnR5cGUgQ29udGV4dCBpbnRlcmZhY2UgewoKCS8vIElmIERvbmUgaXMgbm90IHlldCBjbG9zZWQsIEVyciByZXR1cm5zIG5pbC4KCS8vIElmIERvbmUgaXMgY2xvc2VkLCBFcnIgcmV0dXJucyBhIG5vbi1uaWwgZXJyb3IgZXhwbGFpbmluZyB3aHk6CgkvLyBDYW5jZWxlZCBpZiB0aGUgY29udGV4dCB3YXMgY2FuY2VsZWQKCS8vIG9yIERlYWRsaW5lRXhjZWVkZWQgaWYgdGhlIGNvbnRleHQncyBkZWFkbGluZSBwYXNzZWQuCgkvLyBBZnRlciBFcnIgcmV0dXJucyBhIG5vbi1uaWwgZXJyb3IsIHN1Y2Nlc3NpdmUgY2FsbHMgdG8gRXJyIHJldHVybiB0aGUgc2FtZSBlcnJvci4KCUVycigpIGVycm9yCn0=", - "duration": 369628375 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "basics", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.5:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context.Err", - "href": "https://pkg.go.dev/context#Context.Err", - "target": "_blank" - }, - "file": "basics", - "nodes": [ - [ - { - "atom": "code", - "file": "basics", - "nodes": [ - { - "text": "context.Context.Err", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ] - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context.Err" - } - ], - { - "text": " method.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 5, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "h2", - "file": "basics.md", - "level": 2, - "nodes": [ - { - "text": "Context#Value", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "basics.md", - "nodes": [ - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context.Value", - "href": "https://pkg.go.dev/context#Context.Value", - "target": "_blank" - }, - "file": "basics.md", - "nodes": [ - { - "atom": "code", - "file": "basics.md", - "nodes": [ - { - "text": "context.Context.Value", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context.Value" - } - ], - { - "text": " method, ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-6" - }, - "file": "basics.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-6" - }, - "nodes": [ - { - "text": "Listing 1.6", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-6" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ", can be used to retrieve values from the context hierarchy.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-6", - "type": "listing" - }, - "file": "basics.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "args": [ - "go", - "doc", - "context.Context.Value" - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "doc": "context.Context.Value", - "exec": "go doc context.Context.Value" - }, - "expected_exit": 0, - "file": "basics", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc context.Context.Value\n\npackage context // import \u0026#34;context\u0026#34;\n\ntype Context interface {\n\n\t// Value returns the value associated with this context for key, or nil\n\t// if no value is associated with key. Successive calls to Value with\n\t// the same key returns the same result.\n\t//\n\t// Use context values only for request-scoped data that transits\n\t// processes and API boundaries, not for passing optional parameters to\n\t// functions.\n\t//\n\t// A key identifies a specific value in a Context. Functions that wish\n\t// to store values in Context typically allocate a key in a global\n\t// variable then use that key as the argument to context.WithValue and\n\t// Context.Value. A key can be any type that supports equality;\n\t// packages should define keys as an unexported type to avoid\n\t// collisions.\n\t//\n\t// Packages that define a Context key should provide type-safe accessors\n\t// for the values stored using that key:\n\t//\n\t// \t// Package user defines a User type that\u0026#39;s stored in Contexts.\n\t// \tpackage user\n\t//\n\t// \timport \u0026#34;context\u0026#34;\n\t//\n\t// \t// User is the type of value stored in the Contexts.\n\t// \ttype User struct {...}\n\t//\n\t// \t// key is an unexported type for keys defined in this package.\n\t// \t// This prevents collisions with keys defined in other packages.\n\t// \ttype key int\n\t//\n\t// \t// userKey is the key for user.User values in Contexts. It is\n\t// \t// unexported; clients use user.NewContext and user.FromContext\n\t// \t// instead of using this key directly.\n\t// \tvar userKey key\n\t//\n\t// \t// NewContext returns a new Context that carries value u.\n\t// \tfunc NewContext(ctx context.Context, u *User) context.Context {\n\t// \t\treturn context.WithValue(ctx, userKey, u)\n\t// \t}\n\t//\n\t// \t// FromContext returns the User value stored in ctx, if any.\n\t// \tfunc FromContext(ctx context.Context) (*User, bool) {\n\t// \t\tu, ok := ctx.Value(userKey).(*User)\n\t// \t\treturn u, ok\n\t// \t}\n\tValue(key any) any\n}", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "context.Context.Value" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "cGFja2FnZSBjb250ZXh0IC8vIGltcG9ydCAiY29udGV4dCIKCnR5cGUgQ29udGV4dCBpbnRlcmZhY2UgewoKCS8vIFZhbHVlIHJldHVybnMgdGhlIHZhbHVlIGFzc29jaWF0ZWQgd2l0aCB0aGlzIGNvbnRleHQgZm9yIGtleSwgb3IgbmlsCgkvLyBpZiBubyB2YWx1ZSBpcyBhc3NvY2lhdGVkIHdpdGgga2V5LiBTdWNjZXNzaXZlIGNhbGxzIHRvIFZhbHVlIHdpdGgKCS8vIHRoZSBzYW1lIGtleSByZXR1cm5zIHRoZSBzYW1lIHJlc3VsdC4KCS8vCgkvLyBVc2UgY29udGV4dCB2YWx1ZXMgb25seSBmb3IgcmVxdWVzdC1zY29wZWQgZGF0YSB0aGF0IHRyYW5zaXRzCgkvLyBwcm9jZXNzZXMgYW5kIEFQSSBib3VuZGFyaWVzLCBub3QgZm9yIHBhc3Npbmcgb3B0aW9uYWwgcGFyYW1ldGVycyB0bwoJLy8gZnVuY3Rpb25zLgoJLy8KCS8vIEEga2V5IGlkZW50aWZpZXMgYSBzcGVjaWZpYyB2YWx1ZSBpbiBhIENvbnRleHQuIEZ1bmN0aW9ucyB0aGF0IHdpc2gKCS8vIHRvIHN0b3JlIHZhbHVlcyBpbiBDb250ZXh0IHR5cGljYWxseSBhbGxvY2F0ZSBhIGtleSBpbiBhIGdsb2JhbAoJLy8gdmFyaWFibGUgdGhlbiB1c2UgdGhhdCBrZXkgYXMgdGhlIGFyZ3VtZW50IHRvIGNvbnRleHQuV2l0aFZhbHVlIGFuZAoJLy8gQ29udGV4dC5WYWx1ZS4gQSBrZXkgY2FuIGJlIGFueSB0eXBlIHRoYXQgc3VwcG9ydHMgZXF1YWxpdHk7CgkvLyBwYWNrYWdlcyBzaG91bGQgZGVmaW5lIGtleXMgYXMgYW4gdW5leHBvcnRlZCB0eXBlIHRvIGF2b2lkCgkvLyBjb2xsaXNpb25zLgoJLy8KCS8vIFBhY2thZ2VzIHRoYXQgZGVmaW5lIGEgQ29udGV4dCBrZXkgc2hvdWxkIHByb3ZpZGUgdHlwZS1zYWZlIGFjY2Vzc29ycwoJLy8gZm9yIHRoZSB2YWx1ZXMgc3RvcmVkIHVzaW5nIHRoYXQga2V5OgoJLy8KCS8vIAkvLyBQYWNrYWdlIHVzZXIgZGVmaW5lcyBhIFVzZXIgdHlwZSB0aGF0J3Mgc3RvcmVkIGluIENvbnRleHRzLgoJLy8gCXBhY2thZ2UgdXNlcgoJLy8KCS8vIAlpbXBvcnQgImNvbnRleHQiCgkvLwoJLy8gCS8vIFVzZXIgaXMgdGhlIHR5cGUgb2YgdmFsdWUgc3RvcmVkIGluIHRoZSBDb250ZXh0cy4KCS8vIAl0eXBlIFVzZXIgc3RydWN0IHsuLi59CgkvLwoJLy8gCS8vIGtleSBpcyBhbiB1bmV4cG9ydGVkIHR5cGUgZm9yIGtleXMgZGVmaW5lZCBpbiB0aGlzIHBhY2thZ2UuCgkvLyAJLy8gVGhpcyBwcmV2ZW50cyBjb2xsaXNpb25zIHdpdGgga2V5cyBkZWZpbmVkIGluIG90aGVyIHBhY2thZ2VzLgoJLy8gCXR5cGUga2V5IGludAoJLy8KCS8vIAkvLyB1c2VyS2V5IGlzIHRoZSBrZXkgZm9yIHVzZXIuVXNlciB2YWx1ZXMgaW4gQ29udGV4dHMuIEl0IGlzCgkvLyAJLy8gdW5leHBvcnRlZDsgY2xpZW50cyB1c2UgdXNlci5OZXdDb250ZXh0IGFuZCB1c2VyLkZyb21Db250ZXh0CgkvLyAJLy8gaW5zdGVhZCBvZiB1c2luZyB0aGlzIGtleSBkaXJlY3RseS4KCS8vIAl2YXIgdXNlcktleSBrZXkKCS8vCgkvLyAJLy8gTmV3Q29udGV4dCByZXR1cm5zIGEgbmV3IENvbnRleHQgdGhhdCBjYXJyaWVzIHZhbHVlIHUuCgkvLyAJZnVuYyBOZXdDb250ZXh0KGN0eCBjb250ZXh0LkNvbnRleHQsIHUgKlVzZXIpIGNvbnRleHQuQ29udGV4dCB7CgkvLyAJCXJldHVybiBjb250ZXh0LldpdGhWYWx1ZShjdHgsIHVzZXJLZXksIHUpCgkvLyAJfQoJLy8KCS8vIAkvLyBGcm9tQ29udGV4dCByZXR1cm5zIHRoZSBVc2VyIHZhbHVlIHN0b3JlZCBpbiBjdHgsIGlmIGFueS4KCS8vIAlmdW5jIEZyb21Db250ZXh0KGN0eCBjb250ZXh0LkNvbnRleHQpICgqVXNlciwgYm9vbCkgewoJLy8gCQl1LCBvayA6PSBjdHguVmFsdWUodXNlcktleSkuKCpVc2VyKQoJLy8gCQlyZXR1cm4gdSwgb2sKCS8vIAl9CglWYWx1ZShrZXkgYW55KSBhbnkKfQ==", - "duration": 446880625 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc context.Context.Value\n\npackage context // import \u0026#34;context\u0026#34;\n\ntype Context interface {\n\n\t// Value returns the value associated with this context for key, or nil\n\t// if no value is associated with key. Successive calls to Value with\n\t// the same key returns the same result.\n\t//\n\t// Use context values only for request-scoped data that transits\n\t// processes and API boundaries, not for passing optional parameters to\n\t// functions.\n\t//\n\t// A key identifies a specific value in a Context. Functions that wish\n\t// to store values in Context typically allocate a key in a global\n\t// variable then use that key as the argument to context.WithValue and\n\t// Context.Value. A key can be any type that supports equality;\n\t// packages should define keys as an unexported type to avoid\n\t// collisions.\n\t//\n\t// Packages that define a Context key should provide type-safe accessors\n\t// for the values stored using that key:\n\t//\n\t// \t// Package user defines a User type that\u0026#39;s stored in Contexts.\n\t// \tpackage user\n\t//\n\t// \timport \u0026#34;context\u0026#34;\n\t//\n\t// \t// User is the type of value stored in the Contexts.\n\t// \ttype User struct {...}\n\t//\n\t// \t// key is an unexported type for keys defined in this package.\n\t// \t// This prevents collisions with keys defined in other packages.\n\t// \ttype key int\n\t//\n\t// \t// userKey is the key for user.User values in Contexts. It is\n\t// \t// unexported; clients use user.NewContext and user.FromContext\n\t// \t// instead of using this key directly.\n\t// \tvar userKey key\n\t//\n\t// \t// NewContext returns a new Context that carries value u.\n\t// \tfunc NewContext(ctx context.Context, u *User) context.Context {\n\t// \t\treturn context.WithValue(ctx, userKey, u)\n\t// \t}\n\t//\n\t// \t// FromContext returns the User value stored in ctx, if any.\n\t// \tfunc FromContext(ctx context.Context) (*User, bool) {\n\t// \t\tu, ok := ctx.Value(userKey).(*User)\n\t// \t\treturn u, ok\n\t// \t}\n\tValue(key any) any\n}", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "context.Context.Value" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "cGFja2FnZSBjb250ZXh0IC8vIGltcG9ydCAiY29udGV4dCIKCnR5cGUgQ29udGV4dCBpbnRlcmZhY2UgewoKCS8vIFZhbHVlIHJldHVybnMgdGhlIHZhbHVlIGFzc29jaWF0ZWQgd2l0aCB0aGlzIGNvbnRleHQgZm9yIGtleSwgb3IgbmlsCgkvLyBpZiBubyB2YWx1ZSBpcyBhc3NvY2lhdGVkIHdpdGgga2V5LiBTdWNjZXNzaXZlIGNhbGxzIHRvIFZhbHVlIHdpdGgKCS8vIHRoZSBzYW1lIGtleSByZXR1cm5zIHRoZSBzYW1lIHJlc3VsdC4KCS8vCgkvLyBVc2UgY29udGV4dCB2YWx1ZXMgb25seSBmb3IgcmVxdWVzdC1zY29wZWQgZGF0YSB0aGF0IHRyYW5zaXRzCgkvLyBwcm9jZXNzZXMgYW5kIEFQSSBib3VuZGFyaWVzLCBub3QgZm9yIHBhc3Npbmcgb3B0aW9uYWwgcGFyYW1ldGVycyB0bwoJLy8gZnVuY3Rpb25zLgoJLy8KCS8vIEEga2V5IGlkZW50aWZpZXMgYSBzcGVjaWZpYyB2YWx1ZSBpbiBhIENvbnRleHQuIEZ1bmN0aW9ucyB0aGF0IHdpc2gKCS8vIHRvIHN0b3JlIHZhbHVlcyBpbiBDb250ZXh0IHR5cGljYWxseSBhbGxvY2F0ZSBhIGtleSBpbiBhIGdsb2JhbAoJLy8gdmFyaWFibGUgdGhlbiB1c2UgdGhhdCBrZXkgYXMgdGhlIGFyZ3VtZW50IHRvIGNvbnRleHQuV2l0aFZhbHVlIGFuZAoJLy8gQ29udGV4dC5WYWx1ZS4gQSBrZXkgY2FuIGJlIGFueSB0eXBlIHRoYXQgc3VwcG9ydHMgZXF1YWxpdHk7CgkvLyBwYWNrYWdlcyBzaG91bGQgZGVmaW5lIGtleXMgYXMgYW4gdW5leHBvcnRlZCB0eXBlIHRvIGF2b2lkCgkvLyBjb2xsaXNpb25zLgoJLy8KCS8vIFBhY2thZ2VzIHRoYXQgZGVmaW5lIGEgQ29udGV4dCBrZXkgc2hvdWxkIHByb3ZpZGUgdHlwZS1zYWZlIGFjY2Vzc29ycwoJLy8gZm9yIHRoZSB2YWx1ZXMgc3RvcmVkIHVzaW5nIHRoYXQga2V5OgoJLy8KCS8vIAkvLyBQYWNrYWdlIHVzZXIgZGVmaW5lcyBhIFVzZXIgdHlwZSB0aGF0J3Mgc3RvcmVkIGluIENvbnRleHRzLgoJLy8gCXBhY2thZ2UgdXNlcgoJLy8KCS8vIAlpbXBvcnQgImNvbnRleHQiCgkvLwoJLy8gCS8vIFVzZXIgaXMgdGhlIHR5cGUgb2YgdmFsdWUgc3RvcmVkIGluIHRoZSBDb250ZXh0cy4KCS8vIAl0eXBlIFVzZXIgc3RydWN0IHsuLi59CgkvLwoJLy8gCS8vIGtleSBpcyBhbiB1bmV4cG9ydGVkIHR5cGUgZm9yIGtleXMgZGVmaW5lZCBpbiB0aGlzIHBhY2thZ2UuCgkvLyAJLy8gVGhpcyBwcmV2ZW50cyBjb2xsaXNpb25zIHdpdGgga2V5cyBkZWZpbmVkIGluIG90aGVyIHBhY2thZ2VzLgoJLy8gCXR5cGUga2V5IGludAoJLy8KCS8vIAkvLyB1c2VyS2V5IGlzIHRoZSBrZXkgZm9yIHVzZXIuVXNlciB2YWx1ZXMgaW4gQ29udGV4dHMuIEl0IGlzCgkvLyAJLy8gdW5leHBvcnRlZDsgY2xpZW50cyB1c2UgdXNlci5OZXdDb250ZXh0IGFuZCB1c2VyLkZyb21Db250ZXh0CgkvLyAJLy8gaW5zdGVhZCBvZiB1c2luZyB0aGlzIGtleSBkaXJlY3RseS4KCS8vIAl2YXIgdXNlcktleSBrZXkKCS8vCgkvLyAJLy8gTmV3Q29udGV4dCByZXR1cm5zIGEgbmV3IENvbnRleHQgdGhhdCBjYXJyaWVzIHZhbHVlIHUuCgkvLyAJZnVuYyBOZXdDb250ZXh0KGN0eCBjb250ZXh0LkNvbnRleHQsIHUgKlVzZXIpIGNvbnRleHQuQ29udGV4dCB7CgkvLyAJCXJldHVybiBjb250ZXh0LldpdGhWYWx1ZShjdHgsIHVzZXJLZXksIHUpCgkvLyAJfQoJLy8KCS8vIAkvLyBGcm9tQ29udGV4dCByZXR1cm5zIHRoZSBVc2VyIHZhbHVlIHN0b3JlZCBpbiBjdHgsIGlmIGFueS4KCS8vIAlmdW5jIEZyb21Db250ZXh0KGN0eCBjb250ZXh0LkNvbnRleHQpICgqVXNlciwgYm9vbCkgewoJLy8gCQl1LCBvayA6PSBjdHguVmFsdWUodXNlcktleSkuKCpVc2VyKQoJLy8gCQlyZXR1cm4gdSwgb2sKCS8vIAl9CglWYWx1ZShrZXkgYW55KSBhbnkKfQ==", - "duration": 446880625 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "basics", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.6:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context.Value", - "href": "https://pkg.go.dev/context#Context.Value", - "target": "_blank" - }, - "file": "basics", - "nodes": [ - [ - { - "atom": "code", - "file": "basics", - "nodes": [ - { - "text": "context.Context.Value", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ] - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context.Value" - } - ], - { - "text": " method.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 6, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "h2", - "file": "basics.md", - "level": 2, - "nodes": [ - { - "text": "Helper Functions", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "basics.md", - "nodes": [ - { - "text": "As we will see the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context", - "href": "https://pkg.go.dev/context", - "target": "_blank" - }, - "file": "basics.md", - "nodes": [ - { - "atom": "code", - "file": "basics.md", - "nodes": [ - { - "text": "context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context" - } - ], - { - "text": " package provides a number of useful helper functions for wrapping a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "basics.md", - "nodes": [ - { - "atom": "code", - "file": "basics.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " making the need for custom implementations of the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "basics.md", - "nodes": [ - { - "atom": "code", - "file": "basics.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " interface less common.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-7", - "type": "listing" - }, - "file": "basics.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "args": [ - "go", - "doc", - "-short", - "context" - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "doc": "-short context", - "exec": "go doc -short context" - }, - "expected_exit": 0, - "file": "basics", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc -short context\n\nvar Canceled = errors.New(\u0026#34;context canceled\u0026#34;)\nvar DeadlineExceeded error = deadlineExceededError{}\nfunc Cause(c Context) error\nfunc WithCancel(parent Context) (ctx Context, cancel CancelFunc)\nfunc WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc)\nfunc WithDeadline(parent Context, d time.Time) (Context, CancelFunc)\nfunc WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)\ntype CancelCauseFunc func(cause error)\ntype CancelFunc func()\ntype Context interface{ ... }\n func Background() Context\n func TODO() Context\n func WithValue(parent Context, key, val any) Context", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "-short", - "context" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "dmFyIENhbmNlbGVkID0gZXJyb3JzLk5ldygiY29udGV4dCBjYW5jZWxlZCIpCnZhciBEZWFkbGluZUV4Y2VlZGVkIGVycm9yID0gZGVhZGxpbmVFeGNlZWRlZEVycm9ye30KZnVuYyBDYXVzZShjIENvbnRleHQpIGVycm9yCmZ1bmMgV2l0aENhbmNlbChwYXJlbnQgQ29udGV4dCkgKGN0eCBDb250ZXh0LCBjYW5jZWwgQ2FuY2VsRnVuYykKZnVuYyBXaXRoQ2FuY2VsQ2F1c2UocGFyZW50IENvbnRleHQpIChjdHggQ29udGV4dCwgY2FuY2VsIENhbmNlbENhdXNlRnVuYykKZnVuYyBXaXRoRGVhZGxpbmUocGFyZW50IENvbnRleHQsIGQgdGltZS5UaW1lKSAoQ29udGV4dCwgQ2FuY2VsRnVuYykKZnVuYyBXaXRoVGltZW91dChwYXJlbnQgQ29udGV4dCwgdGltZW91dCB0aW1lLkR1cmF0aW9uKSAoQ29udGV4dCwgQ2FuY2VsRnVuYykKdHlwZSBDYW5jZWxDYXVzZUZ1bmMgZnVuYyhjYXVzZSBlcnJvcikKdHlwZSBDYW5jZWxGdW5jIGZ1bmMoKQp0eXBlIENvbnRleHQgaW50ZXJmYWNleyAuLi4gfQogICAgZnVuYyBCYWNrZ3JvdW5kKCkgQ29udGV4dAogICAgZnVuYyBUT0RPKCkgQ29udGV4dAogICAgZnVuYyBXaXRoVmFsdWUocGFyZW50IENvbnRleHQsIGtleSwgdmFsIGFueSkgQ29udGV4dA==", - "duration": 289414458 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc -short context\n\nvar Canceled = errors.New(\u0026#34;context canceled\u0026#34;)\nvar DeadlineExceeded error = deadlineExceededError{}\nfunc Cause(c Context) error\nfunc WithCancel(parent Context) (ctx Context, cancel CancelFunc)\nfunc WithCancelCause(parent Context) (ctx Context, cancel CancelCauseFunc)\nfunc WithDeadline(parent Context, d time.Time) (Context, CancelFunc)\nfunc WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)\ntype CancelCauseFunc func(cause error)\ntype CancelFunc func()\ntype Context interface{ ... }\n func Background() Context\n func TODO() Context\n func WithValue(parent Context, key, val any) Context", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "-short", - "context" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "dmFyIENhbmNlbGVkID0gZXJyb3JzLk5ldygiY29udGV4dCBjYW5jZWxlZCIpCnZhciBEZWFkbGluZUV4Y2VlZGVkIGVycm9yID0gZGVhZGxpbmVFeGNlZWRlZEVycm9ye30KZnVuYyBDYXVzZShjIENvbnRleHQpIGVycm9yCmZ1bmMgV2l0aENhbmNlbChwYXJlbnQgQ29udGV4dCkgKGN0eCBDb250ZXh0LCBjYW5jZWwgQ2FuY2VsRnVuYykKZnVuYyBXaXRoQ2FuY2VsQ2F1c2UocGFyZW50IENvbnRleHQpIChjdHggQ29udGV4dCwgY2FuY2VsIENhbmNlbENhdXNlRnVuYykKZnVuYyBXaXRoRGVhZGxpbmUocGFyZW50IENvbnRleHQsIGQgdGltZS5UaW1lKSAoQ29udGV4dCwgQ2FuY2VsRnVuYykKZnVuYyBXaXRoVGltZW91dChwYXJlbnQgQ29udGV4dCwgdGltZW91dCB0aW1lLkR1cmF0aW9uKSAoQ29udGV4dCwgQ2FuY2VsRnVuYykKdHlwZSBDYW5jZWxDYXVzZUZ1bmMgZnVuYyhjYXVzZSBlcnJvcikKdHlwZSBDYW5jZWxGdW5jIGZ1bmMoKQp0eXBlIENvbnRleHQgaW50ZXJmYWNleyAuLi4gfQogICAgZnVuYyBCYWNrZ3JvdW5kKCkgQ29udGV4dAogICAgZnVuYyBUT0RPKCkgQ29udGV4dAogICAgZnVuYyBXaXRoVmFsdWUocGFyZW50IENvbnRleHQsIGtleSwgdmFsIGFueSkgQ29udGV4dA==", - "duration": 289414458 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "basics", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.7:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context", - "href": "https://pkg.go.dev/context", - "target": "_blank" - }, - "file": "basics", - "nodes": [ - [ - { - "atom": "code", - "file": "basics", - "nodes": [ - { - "text": "context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ] - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context" - } - ], - { - "text": " package.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 7, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "h2", - "file": "basics.md", - "level": 2, - "nodes": [ - { - "text": "The Background Context", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "basics.md", - "nodes": [ - { - "text": "While often we might be given a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "basics.md", - "nodes": [ - { - "atom": "code", - "file": "basics.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": ", we might also be the one start a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "basics.md", - "nodes": [ - { - "atom": "code", - "file": "basics.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": ". The most common way to provide a quick and easy way to start a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "basics.md", - "nodes": [ - { - "atom": "code", - "file": "basics.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " is to use the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Background", - "href": "https://pkg.go.dev/context#Background", - "target": "_blank" - }, - "file": "basics.md", - "nodes": [ - { - "atom": "code", - "file": "basics.md", - "nodes": [ - { - "text": "context.Background", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Background" - } - ], - { - "text": " function, ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-8" - }, - "file": "basics.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-8" - }, - "nodes": [ - { - "text": "Listing 1.8", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-8" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ".", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-8", - "type": "listing" - }, - "file": "basics.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "args": [ - "go", - "doc", - "context.Background" - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "doc": "context.Background", - "exec": "go doc context.Background" - }, - "expected_exit": 0, - "file": "basics", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc context.Background\n\npackage context // import \u0026#34;context\u0026#34;\n\nfunc Background() Context\n Background returns a non-nil, empty Context. It is never canceled, has no\n values, and has no deadline. It is typically used by the main function,\n initialization, and tests, and as the top-level Context for incoming\n requests.", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "context.Background" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "cGFja2FnZSBjb250ZXh0IC8vIGltcG9ydCAiY29udGV4dCIKCmZ1bmMgQmFja2dyb3VuZCgpIENvbnRleHQKICAgIEJhY2tncm91bmQgcmV0dXJucyBhIG5vbi1uaWwsIGVtcHR5IENvbnRleHQuIEl0IGlzIG5ldmVyIGNhbmNlbGVkLCBoYXMgbm8KICAgIHZhbHVlcywgYW5kIGhhcyBubyBkZWFkbGluZS4gSXQgaXMgdHlwaWNhbGx5IHVzZWQgYnkgdGhlIG1haW4gZnVuY3Rpb24sCiAgICBpbml0aWFsaXphdGlvbiwgYW5kIHRlc3RzLCBhbmQgYXMgdGhlIHRvcC1sZXZlbCBDb250ZXh0IGZvciBpbmNvbWluZwogICAgcmVxdWVzdHMu", - "duration": 218176375 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc context.Background\n\npackage context // import \u0026#34;context\u0026#34;\n\nfunc Background() Context\n Background returns a non-nil, empty Context. It is never canceled, has no\n values, and has no deadline. It is typically used by the main function,\n initialization, and tests, and as the top-level Context for incoming\n requests.", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "context.Background" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "cGFja2FnZSBjb250ZXh0IC8vIGltcG9ydCAiY29udGV4dCIKCmZ1bmMgQmFja2dyb3VuZCgpIENvbnRleHQKICAgIEJhY2tncm91bmQgcmV0dXJucyBhIG5vbi1uaWwsIGVtcHR5IENvbnRleHQuIEl0IGlzIG5ldmVyIGNhbmNlbGVkLCBoYXMgbm8KICAgIHZhbHVlcywgYW5kIGhhcyBubyBkZWFkbGluZS4gSXQgaXMgdHlwaWNhbGx5IHVzZWQgYnkgdGhlIG1haW4gZnVuY3Rpb24sCiAgICBpbml0aWFsaXphdGlvbiwgYW5kIHRlc3RzLCBhbmQgYXMgdGhlIHRvcC1sZXZlbCBDb250ZXh0IGZvciBpbmNvbWluZwogICAgcmVxdWVzdHMu", - "duration": 218176375 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "basics", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.8:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Background", - "href": "https://pkg.go.dev/context#Background", - "target": "_blank" - }, - "file": "basics", - "nodes": [ - [ - { - "atom": "code", - "file": "basics", - "nodes": [ - { - "text": "context.Background", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ] - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Background" - } - ], - { - "text": " function.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 8, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "basics.md", - "nodes": [ - { - "text": "In, ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-9" - }, - "file": "basics.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-9" - }, - "nodes": [ - { - "text": "Listing 1.9", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-9" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ", we print the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "basics.md", - "nodes": [ - { - "atom": "code", - "file": "basics.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " returned by ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Background", - "href": "https://pkg.go.dev/context#Background", - "target": "_blank" - }, - "file": "basics.md", - "nodes": [ - { - "atom": "code", - "file": "basics.md", - "nodes": [ - { - "text": "context.Background", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Background" - } - ], - { - "text": ". As we can see from the output, the context is empty.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-9", - "type": "listing" - }, - "file": "basics.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - { - "atom": "pre", - "file": "basics", - "nodes": [ - [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "src": "basics/src/background/empty/main.go#example" - }, - "lang": "go", - "nodes": [ - { - "content": "func main() {\n\tctx := context.Background()\n\n\t// print the current value\n\t// of the context\n\tfmt.Printf(\"%v\\n\", ctx)\n\n\t// print Go-syntax representation of the value\n\tfmt.Printf(\"\\t%#v\\n\", ctx)\n}", - "file": "basics/src/background/empty/main.go", - "lang": "go", - "name": "example", - "start": 8, - "end": 20 - } - ], - "type": "*hype.SourceCode" - } - ] - ], - "type": "*hype.Element" - }, - { - "atom": "hr", - "file": "basics", - "type": "*hype.Element" - }, - [ - { - "args": [ - "go", - "run", - "main.go" - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "exec": "go run main.go", - "run": "main.go", - "src": "basics/src/background/empty" - }, - "expected_exit": 0, - "file": "basics", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go run main.go\n\ncontext.Background\n\t(*context.emptyCtx)(0x14000112000)", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "run", - "main.go" - ], - "dir": "testdata/doc/to_md/basics/src/background/empty", - "stdout": "Y29udGV4dC5CYWNrZ3JvdW5kCgkoKmNvbnRleHQuZW1wdHlDdHgpKDB4MTQwMDAxMTIwMDAp", - "duration": 1150437333 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go run main.go\n\ncontext.Background\n\t(*context.emptyCtx)(0x14000112000)", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "run", - "main.go" - ], - "dir": "testdata/doc/to_md/basics/src/background/empty", - "stdout": "Y29udGV4dC5CYWNrZ3JvdW5kCgkoKmNvbnRleHQuZW1wdHlDdHgpKDB4MTQwMDAxMTIwMDAp", - "duration": 1150437333 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "basics", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.9:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Background", - "href": "https://pkg.go.dev/context#Background", - "target": "_blank" - }, - "file": "basics", - "nodes": [ - [ - { - "atom": "code", - "file": "basics", - "nodes": [ - { - "text": "context.Background", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ] - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Background" - } - ], - { - "text": " function.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 9, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "h2", - "file": "basics.md", - "level": 2, - "nodes": [ - { - "text": "Default Implementations", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "basics.md", - "nodes": [ - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Background", - "href": "https://pkg.go.dev/context#Background", - "target": "_blank" - }, - "file": "basics.md", - "nodes": [ - { - "atom": "code", - "file": "basics.md", - "nodes": [ - { - "text": "context.Background", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Background" - } - ], - { - "text": " interface, while empty, does provide default implementations of the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "basics.md", - "nodes": [ - { - "atom": "code", - "file": "basics.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " interface. Because of this the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Background", - "href": "https://pkg.go.dev/context#Background", - "target": "_blank" - }, - "file": "basics.md", - "nodes": [ - { - "atom": "code", - "file": "basics.md", - "nodes": [ - { - "text": "context.Background", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Background" - } - ], - { - "text": " context is almost always used as the base of a new ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "basics.md", - "nodes": [ - { - "atom": "code", - "file": "basics.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " hierarchy.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-10", - "type": "listing" - }, - "file": "basics.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - { - "atom": "pre", - "file": "basics", - "nodes": [ - [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "src": "basics/src/background/implementation/main.go#example" - }, - "lang": "go", - "nodes": [ - { - "content": "func main() {\n\tctx := context.Background()\n\n\t// print the current value\n\t// of the context\n\tfmt.Printf(\"%v\\n\", ctx)\n\n\t// print Go-syntax representation of the value\n\tfmt.Printf(\"\\t%#v\\n\", ctx)\n\n\t// print the value of the Done channel\n\t// does not block because we are not\n\t// trying to read/write to the channel\n\tfmt.Printf(\"\\tDone:\\t%#v\\n\", ctx.Done())\n\n\t// print the value of the Err\n\tfmt.Printf(\"\\tErr:\\t%#v\\n\", ctx.Err())\n\n\t// print the value of \"KEY\"\n\tfmt.Printf(\"\\tValue:\\t%#v\\n\", ctx.Value(\"KEY\"))\n\n\t// print the deadline time\n\t// and true/false if there is no deadline\n\tdeadline, ok := ctx.Deadline()\n\tfmt.Printf(\"\\tDeadline:\\t%s (%t)\\n\", deadline, ok)\n}", - "file": "basics/src/background/implementation/main.go", - "lang": "go", - "name": "example", - "start": 8, - "end": 36 - } - ], - "type": "*hype.SourceCode" - } - ] - ], - "type": "*hype.Element" - }, - { - "atom": "hr", - "file": "basics", - "type": "*hype.Element" - }, - [ - { - "args": [ - "go", - "run", - "main.go" - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "exec": "go run main.go", - "run": "main.go", - "src": "basics/src/background/implementation" - }, - "expected_exit": 0, - "file": "basics", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go run main.go\n\ncontext.Background\n\t(*context.emptyCtx)(0x14000112000)\n\tDone:\t(\u0026lt;-chan struct {})(nil)\n\tErr:\t\u0026lt;nil\u0026gt;\n\tValue:\t\u0026lt;nil\u0026gt;\n\tDeadline:\t0001-01-01 00:00:00 +0000 UTC (false)", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "run", - "main.go" - ], - "dir": "testdata/doc/to_md/basics/src/background/implementation", - "stdout": "Y29udGV4dC5CYWNrZ3JvdW5kCgkoKmNvbnRleHQuZW1wdHlDdHgpKDB4MTQwMDAxMTIwMDApCglEb25lOgkoPC1jaGFuIHN0cnVjdCB7fSkobmlsKQoJRXJyOgk8bmlsPgoJVmFsdWU6CTxuaWw+CglEZWFkbGluZToJMDAwMS0wMS0wMSAwMDowMDowMCArMDAwMCBVVEMgKGZhbHNlKQ==", - "duration": 1040509959 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go run main.go\n\ncontext.Background\n\t(*context.emptyCtx)(0x14000112000)\n\tDone:\t(\u0026lt;-chan struct {})(nil)\n\tErr:\t\u0026lt;nil\u0026gt;\n\tValue:\t\u0026lt;nil\u0026gt;\n\tDeadline:\t0001-01-01 00:00:00 +0000 UTC (false)", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "run", - "main.go" - ], - "dir": "testdata/doc/to_md/basics/src/background/implementation", - "stdout": "Y29udGV4dC5CYWNrZ3JvdW5kCgkoKmNvbnRleHQuZW1wdHlDdHgpKDB4MTQwMDAxMTIwMDApCglEb25lOgkoPC1jaGFuIHN0cnVjdCB7fSkobmlsKQoJRXJyOgk8bmlsPgoJVmFsdWU6CTxuaWw+CglEZWFkbGluZToJMDAwMS0wMS0wMSAwMDowMDowMCArMDAwMCBVVEMgKGZhbHNlKQ==", - "duration": 1040509959 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "basics", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.10:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Background", - "href": "https://pkg.go.dev/context#Background", - "target": "_blank" - }, - "file": "basics", - "nodes": [ - [ - { - "atom": "code", - "file": "basics", - "nodes": [ - { - "text": "context.Background", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ] - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Background" - } - ], - { - "text": " function provides default implementation of the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "basics", - "nodes": [ - [ - { - "atom": "code", - "file": "basics", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ] - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " interface.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 10, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "title": "The Context Interface", - "type": "*hype.Page" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "type": "*hype.Include" - } - ], - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "include", - "attributes": { - "src": "rules/rules.md" - }, - "dir": "rules", - "file": "module.md", - "nodes": [ - [ - { - "atom": "page", - "file": "rules.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "h1", - "file": "rules.md", - "level": 1, - "nodes": [ - { - "text": "Context Rules", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "rules.md", - "nodes": [ - { - "text": "According the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context", - "href": "https://pkg.go.dev/context", - "target": "_blank" - }, - "file": "rules.md", - "nodes": [ - { - "atom": "code", - "file": "rules.md", - "nodes": [ - { - "text": "context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context" - } - ], - { - "text": " documentation there are rules that must be followed when using the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context", - "href": "https://pkg.go.dev/context", - "target": "_blank" - }, - "file": "rules.md", - "nodes": [ - { - "atom": "code", - "file": "rules.md", - "nodes": [ - { - "text": "context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context" - } - ], - { - "text": " package, ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-11" - }, - "file": "rules.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-11" - }, - "nodes": [ - { - "text": "Listing 1.11", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-11" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ".", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-11", - "type": "listing" - }, - "file": "rules.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "ul", - "file": "rules", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "li", - "file": "rules", - "list-type": "ul", - "nodes": [ - { - "text": "Programs that use Contexts should follow these rules to keep interfaces consistent across packages and enable static analysis tools to check context propagation.", - "type": "hype.Text" - } - ], - "type": "*hype.LI" - } - ], - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "li", - "file": "rules", - "list-type": "ul", - "nodes": [ - { - "text": "Do not store Contexts inside a struct type; instead, pass a Context explicitly to each function that needs it. The Context should be the first parameter, typically named ctx.", - "type": "hype.Text" - } - ], - "type": "*hype.LI" - } - ], - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "li", - "file": "rules", - "list-type": "ul", - "nodes": [ - { - "text": "Do not pass a ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "rules", - "nodes": [ - { - "text": "nil", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " Context, even if a function permits it. Pass ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#TODO", - "href": "https://pkg.go.dev/context#TODO", - "target": "_blank" - }, - "file": "rules", - "nodes": [ - [ - { - "atom": "code", - "file": "rules", - "nodes": [ - { - "text": "context.TODO", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ] - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#TODO" - } - ], - { - "text": " if you are unsure about which Context to use.", - "type": "hype.Text" - } - ], - "type": "*hype.LI" - } - ], - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "li", - "file": "rules", - "list-type": "ul", - "nodes": [ - { - "text": "Use context Values only for request-scoped data that transits processes and APIs, not for passing optional parameters to functions.", - "type": "hype.Text" - } - ], - "type": "*hype.LI" - } - ], - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "li", - "file": "rules", - "list-type": "ul", - "nodes": [ - { - "text": "The same Context may be passed to functions running in different goroutines; Contexts are safe for simultaneous use by multiple goroutines.", - "type": "hype.Text" - } - ], - "type": "*hype.LI" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "type": "*hype.UL" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "rules", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.11:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "Rules for using contexts.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 11, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "title": "Context Rules", - "type": "*hype.Page" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "type": "*hype.Include" - } - ], - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "include", - "attributes": { - "src": "nodes/nodes.md" - }, - "dir": "nodes", - "file": "module.md", - "nodes": [ - [ - { - "atom": "page", - "file": "nodes.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "h1", - "file": "nodes.md", - "level": 1, - "nodes": [ - { - "text": "Context Nodal Hierarchy", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "nodes.md", - "nodes": [ - { - "text": "As the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context", - "href": "https://pkg.go.dev/context", - "target": "_blank" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context" - } - ], - { - "text": " documentation states, a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " is not meant to be stored and held onto, but should be passed at \"runtime\".", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "nodes.md", - "nodes": [ - { - "text": "Consider an HTTP request. An HTTP request is a ", - "type": "hype.Text" - }, - { - "atom": "strong", - "file": "nodes.md", - "nodes": [ - { - "text": "runtime", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " value that gets passed along through the application until eventually the response is returned. We would not want to store, or hold on to, the request for future use as it would be of no benefit once the response is returned.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "nodes.md", - "nodes": [ - { - "text": "Using ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " in our code behaves like the HTTP request. We pass a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " through the application where they can be listened to for cancellation, or for other purposes, at \"runtime\".", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "nodes.md", - "nodes": [ - { - "text": "As a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " is passed through the application, a receiving method may wrap the context with their cancellation functionality or with ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#WithValue", - "href": "https://pkg.go.dev/context#WithValue", - "target": "_blank" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "context.WithValue", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#WithValue" - } - ], - { - "text": " to add a value, such as a \"request id\", before passing the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " along to any functions, or methods, that it may call.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "nodes.md", - "nodes": [ - { - "text": "The result is a nodal hierarchy of ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " values that starts at the beginning of the request, or the start of the application, and spiders out throughout the application.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "h2", - "file": "nodes.md", - "level": 2, - "nodes": [ - { - "text": "Understanding the Nodal Hierarchy", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "nodes.md", - "nodes": [ - { - "text": "Consider ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-12" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-12" - }, - "nodes": [ - { - "text": "Listing 1.12", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-12" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ". We start with a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Background", - "href": "https://pkg.go.dev/context#Background", - "target": "_blank" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "context.Background", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Background" - } - ], - { - "text": " context and pass it the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "A", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " and ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "B", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " functions. Each function wraps the given ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": ", prints that new ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " with a new one before either passing it along to the next function or returning.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-12", - "type": "listing" - }, - "file": "nodes.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - { - "atom": "pre", - "file": "nodes", - "nodes": [ - [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "src": "nodes/src/node-tree/main.go#main" - }, - "lang": "go", - "nodes": [ - { - "content": "func main() {\n\t// create a background context\n\tbg := context.Background()\n\n\t// pass the background context to the A function\n\tA(bg)\n\n\t// pass the background context to the B function\n\tB(bg)\n}", - "file": "nodes/src/node-tree/main.go", - "lang": "go", - "name": "main", - "start": 16, - "end": 28 - } - ], - "type": "*hype.SourceCode" - } - ] - ], - "type": "*hype.Element" - }, - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "nodes", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.12:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "Wrapping contexts creates nodal hierarchies.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 12, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "h2", - "file": "nodes.md", - "level": 2, - "nodes": [ - { - "text": "Wrapping with Context Values", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "nodes.md", - "nodes": [ - { - "text": "To wrap the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " with a new one, we will use ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#WithValue", - "href": "https://pkg.go.dev/context#WithValue", - "target": "_blank" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "context.WithValue", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#WithValue" - } - ], - { - "text": ". The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#WithValue", - "href": "https://pkg.go.dev/context#WithValue", - "target": "_blank" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "context.WithValue", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#WithValue" - } - ], - { - "text": " function takes a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " and a key and value, and returns a new ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " with the given key and value that wraps the original ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": ". We will learn more about ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#WithValue", - "href": "https://pkg.go.dev/context#WithValue", - "target": "_blank" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "context.WithValue", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#WithValue" - } - ], - { - "text": " later.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "h2", - "file": "nodes.md", - "level": 2, - "nodes": [ - { - "text": "Following the Context Nodes", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "nodes.md", - "nodes": [ - { - "text": "In ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-13" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-13" - }, - "nodes": [ - { - "text": "Listing 1.13", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-13" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ", we define the functions used in ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-12" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-12" - }, - "nodes": [ - { - "text": "Listing 1.12", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-12" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ". Each of these functions takes a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " as an argument. They then wrap the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " with a new one, print the new ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " with a new one, and pass it along to the next function.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-13", - "type": "listing" - }, - "file": "nodes.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - { - "atom": "pre", - "file": "nodes", - "nodes": [ - [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "src": "nodes/src/node-tree/main.go#example" - }, - "lang": "go", - "nodes": [ - { - "content": "func A(ctx context.Context) {\n\t// wrap ctx with a new context\n\t// with the ID set to \"A\"\n\tA := context.WithValue(ctx, ID, \"A\")\n\tprint(\"A\", A)\n\n\t// pass the A context to the A1 function\n\tA1(A)\n}\n\nfunc A1(ctx context.Context) {\n\tA1 := context.WithValue(ctx, ID, \"A1\")\n\tprint(\"A1\", A1)\n}\n\nfunc B(ctx context.Context) {\n\t// wrap ctx with a new context\n\t// with the ID set to \"B\"\n\tB := context.WithValue(ctx, ID, \"B\")\n\tprint(\"B\", B)\n\n\t// pass the B context to the B1 function\n\tB1(B)\n}\n\nfunc B1(ctx context.Context) {\n\t// wrap ctx with a new context\n\t// with the ID set to \"B1\"\n\tB1 := context.WithValue(ctx, ID, \"B1\")\n\tprint(\"B1\", B1)\n\n\t// pass the B1 context to the B1a function\n\tB1a(B1)\n}\n\nfunc B1a(ctx context.Context) {\n\t// wrap ctx with a new context\n\t// with the ID set to \"B1a\"\n\tB1a := context.WithValue(ctx, ID, \"B1a\")\n\tprint(\"B1a\", B1a)\n}", - "file": "nodes/src/node-tree/main.go", - "lang": "go", - "name": "example", - "start": 30, - "end": 73 - } - ], - "type": "*hype.SourceCode" - } - ] - ], - "type": "*hype.Element" - }, - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "nodes", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.13:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "The example application.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 13, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "nodes.md", - "nodes": [ - { - "text": "When we look at the output of the program, ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-14" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-14" - }, - "nodes": [ - { - "text": "Listing 1.14", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-14" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ", we can see that when we print out any given ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " we see that is at the bottom of the node tree and the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Background", - "href": "https://pkg.go.dev/context#Background", - "target": "_blank" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "context.Background", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Background" - } - ], - { - "text": " context is at the top of the node tree hierarchy.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-14", - "type": "listing" - }, - "file": "nodes.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "args": [ - "go", - "run", - "main.go" - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "exec": "go run main.go", - "run": "main.go", - "src": "nodes/src/node-tree" - }, - "expected_exit": 0, - "file": "nodes", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go run main.go\n\nA.WithValue(key: ctx_id, value: A)\n\t--\u0026gt; Background\n\nA1.WithValue(key: ctx_id, value: A1)\n\t--\u0026gt; WithValue(key: ctx_id, value: A)\n\t\t--\u0026gt; Background\n\nB.WithValue(key: ctx_id, value: B)\n\t--\u0026gt; Background\n\nB1.WithValue(key: ctx_id, value: B1)\n\t--\u0026gt; WithValue(key: ctx_id, value: B)\n\t\t--\u0026gt; Background\n\nB1a.WithValue(key: ctx_id, value: B1a)\n\t--\u0026gt; WithValue(key: ctx_id, value: B1)\n\t\t--\u0026gt; WithValue(key: ctx_id, value: B)\n\t\t\t--\u0026gt; Background", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "run", - "main.go" - ], - "dir": "testdata/doc/to_md/nodes/src/node-tree", - "stdout": "QS5XaXRoVmFsdWUoa2V5OiBjdHhfaWQsIHZhbHVlOiBBKQoJLS0+IEJhY2tncm91bmQKCkExLldpdGhWYWx1ZShrZXk6IGN0eF9pZCwgdmFsdWU6IEExKQoJLS0+IFdpdGhWYWx1ZShrZXk6IGN0eF9pZCwgdmFsdWU6IEEpCgkJLS0+IEJhY2tncm91bmQKCkIuV2l0aFZhbHVlKGtleTogY3R4X2lkLCB2YWx1ZTogQikKCS0tPiBCYWNrZ3JvdW5kCgpCMS5XaXRoVmFsdWUoa2V5OiBjdHhfaWQsIHZhbHVlOiBCMSkKCS0tPiBXaXRoVmFsdWUoa2V5OiBjdHhfaWQsIHZhbHVlOiBCKQoJCS0tPiBCYWNrZ3JvdW5kCgpCMWEuV2l0aFZhbHVlKGtleTogY3R4X2lkLCB2YWx1ZTogQjFhKQoJLS0+IFdpdGhWYWx1ZShrZXk6IGN0eF9pZCwgdmFsdWU6IEIxKQoJCS0tPiBXaXRoVmFsdWUoa2V5OiBjdHhfaWQsIHZhbHVlOiBCKQoJCQktLT4gQmFja2dyb3VuZA==", - "duration": 1251270167 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go run main.go\n\nA.WithValue(key: ctx_id, value: A)\n\t--\u0026gt; Background\n\nA1.WithValue(key: ctx_id, value: A1)\n\t--\u0026gt; WithValue(key: ctx_id, value: A)\n\t\t--\u0026gt; Background\n\nB.WithValue(key: ctx_id, value: B)\n\t--\u0026gt; Background\n\nB1.WithValue(key: ctx_id, value: B1)\n\t--\u0026gt; WithValue(key: ctx_id, value: B)\n\t\t--\u0026gt; Background\n\nB1a.WithValue(key: ctx_id, value: B1a)\n\t--\u0026gt; WithValue(key: ctx_id, value: B1)\n\t\t--\u0026gt; WithValue(key: ctx_id, value: B)\n\t\t\t--\u0026gt; Background", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "run", - "main.go" - ], - "dir": "testdata/doc/to_md/nodes/src/node-tree", - "stdout": "QS5XaXRoVmFsdWUoa2V5OiBjdHhfaWQsIHZhbHVlOiBBKQoJLS0+IEJhY2tncm91bmQKCkExLldpdGhWYWx1ZShrZXk6IGN0eF9pZCwgdmFsdWU6IEExKQoJLS0+IFdpdGhWYWx1ZShrZXk6IGN0eF9pZCwgdmFsdWU6IEEpCgkJLS0+IEJhY2tncm91bmQKCkIuV2l0aFZhbHVlKGtleTogY3R4X2lkLCB2YWx1ZTogQikKCS0tPiBCYWNrZ3JvdW5kCgpCMS5XaXRoVmFsdWUoa2V5OiBjdHhfaWQsIHZhbHVlOiBCMSkKCS0tPiBXaXRoVmFsdWUoa2V5OiBjdHhfaWQsIHZhbHVlOiBCKQoJCS0tPiBCYWNrZ3JvdW5kCgpCMWEuV2l0aFZhbHVlKGtleTogY3R4X2lkLCB2YWx1ZTogQjFhKQoJLS0+IFdpdGhWYWx1ZShrZXk6IGN0eF9pZCwgdmFsdWU6IEIxKQoJCS0tPiBXaXRoVmFsdWUoa2V5OiBjdHhfaWQsIHZhbHVlOiBCKQoJCQktLT4gQmFja2dyb3VuZA==", - "duration": 1251270167 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "nodes", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.14:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "Printing the node tree.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 14, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "nodes.md", - "nodes": [ - { - "text": "In ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-15" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-15" - }, - "nodes": [ - { - "text": "Listing 1.15", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-15" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ", we see that the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "B1a", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " is a child of the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "B1", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " and the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "B1", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " is a child of the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "B", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " and the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "B", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " is a child of the original background ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "nodes.md", - "nodes": [ - { - "atom": "code", - "file": "nodes.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": ".", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-15", - "type": "listing" - }, - "file": "nodes.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "img", - "attributes": { - "src": "nodes/assets/nodes.svg" - }, - "file": "nodes", - "type": "hype.Image" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "nodes", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.15:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "Visualizing the node tree.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 15, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "title": "Context Nodal Hierarchy", - "type": "*hype.Page" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "type": "*hype.Include" - } - ], - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "include", - "attributes": { - "src": "values/values.md" - }, - "dir": "values", - "file": "module.md", - "nodes": [ - [ - { - "atom": "page", - "file": "values.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "h1", - "file": "values.md", - "level": 1, - "nodes": [ - { - "text": "Context Values", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "values.md", - "nodes": [ - { - "text": "As we have seen one feature of the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context", - "href": "https://pkg.go.dev/context", - "target": "_blank" - }, - "file": "values.md", - "nodes": [ - { - "atom": "code", - "file": "values.md", - "nodes": [ - { - "text": "context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context" - } - ], - { - "text": " package is that it allows you to pass request specific values to the next function in the chain.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "values.md", - "nodes": [ - { - "text": "This provide a lot of useful benefits, such as passing request or session specific values, such as the request id, user id of the requestor, etc. to the next function in the chain.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "values.md", - "nodes": [ - { - "text": "Using values, however, has its disadvantages, as we will see shortly.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "h2", - "file": "values.md", - "level": 2, - "nodes": [ - { - "text": "Understanding Context Values", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "values.md", - "nodes": [ - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#WithValue", - "href": "https://pkg.go.dev/context#WithValue", - "target": "_blank" - }, - "file": "values.md", - "nodes": [ - { - "atom": "code", - "file": "values.md", - "nodes": [ - { - "text": "context.WithValue", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#WithValue" - } - ], - { - "text": " function can be used to wrap a given ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "values.md", - "nodes": [ - { - "atom": "code", - "file": "values.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " with a new ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "values.md", - "nodes": [ - { - "atom": "code", - "file": "values.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " that contains the given key/value pair.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "args": [ - "go", - "doc", - "context.WithValue" - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "doc": "context.WithValue", - "exec": "go doc context.WithValue" - }, - "expected_exit": 0, - "file": "values.md", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc context.WithValue\n\npackage context // import \u0026#34;context\u0026#34;\n\nfunc WithValue(parent Context, key, val any) Context\n WithValue returns a copy of parent in which the value associated with key is\n val.\n\n Use context Values only for request-scoped data that transits processes and\n APIs, not for passing optional parameters to functions.\n\n The provided key must be comparable and should not be of type string or any\n other built-in type to avoid collisions between packages using context.\n Users of WithValue should define their own types for keys. To avoid\n allocating when assigning to an interface{}, context keys often have\n concrete type struct{}. Alternatively, exported context key variables\u0026#39;\n static type should be a pointer or interface.", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "context.WithValue" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "cGFja2FnZSBjb250ZXh0IC8vIGltcG9ydCAiY29udGV4dCIKCmZ1bmMgV2l0aFZhbHVlKHBhcmVudCBDb250ZXh0LCBrZXksIHZhbCBhbnkpIENvbnRleHQKICAgIFdpdGhWYWx1ZSByZXR1cm5zIGEgY29weSBvZiBwYXJlbnQgaW4gd2hpY2ggdGhlIHZhbHVlIGFzc29jaWF0ZWQgd2l0aCBrZXkgaXMKICAgIHZhbC4KCiAgICBVc2UgY29udGV4dCBWYWx1ZXMgb25seSBmb3IgcmVxdWVzdC1zY29wZWQgZGF0YSB0aGF0IHRyYW5zaXRzIHByb2Nlc3NlcyBhbmQKICAgIEFQSXMsIG5vdCBmb3IgcGFzc2luZyBvcHRpb25hbCBwYXJhbWV0ZXJzIHRvIGZ1bmN0aW9ucy4KCiAgICBUaGUgcHJvdmlkZWQga2V5IG11c3QgYmUgY29tcGFyYWJsZSBhbmQgc2hvdWxkIG5vdCBiZSBvZiB0eXBlIHN0cmluZyBvciBhbnkKICAgIG90aGVyIGJ1aWx0LWluIHR5cGUgdG8gYXZvaWQgY29sbGlzaW9ucyBiZXR3ZWVuIHBhY2thZ2VzIHVzaW5nIGNvbnRleHQuCiAgICBVc2VycyBvZiBXaXRoVmFsdWUgc2hvdWxkIGRlZmluZSB0aGVpciBvd24gdHlwZXMgZm9yIGtleXMuIFRvIGF2b2lkCiAgICBhbGxvY2F0aW5nIHdoZW4gYXNzaWduaW5nIHRvIGFuIGludGVyZmFjZXt9LCBjb250ZXh0IGtleXMgb2Z0ZW4gaGF2ZQogICAgY29uY3JldGUgdHlwZSBzdHJ1Y3R7fS4gQWx0ZXJuYXRpdmVseSwgZXhwb3J0ZWQgY29udGV4dCBrZXkgdmFyaWFibGVzJwogICAgc3RhdGljIHR5cGUgc2hvdWxkIGJlIGEgcG9pbnRlciBvciBpbnRlcmZhY2Uu", - "duration": 523256417 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc context.WithValue\n\npackage context // import \u0026#34;context\u0026#34;\n\nfunc WithValue(parent Context, key, val any) Context\n WithValue returns a copy of parent in which the value associated with key is\n val.\n\n Use context Values only for request-scoped data that transits processes and\n APIs, not for passing optional parameters to functions.\n\n The provided key must be comparable and should not be of type string or any\n other built-in type to avoid collisions between packages using context.\n Users of WithValue should define their own types for keys. To avoid\n allocating when assigning to an interface{}, context keys often have\n concrete type struct{}. Alternatively, exported context key variables\u0026#39;\n static type should be a pointer or interface.", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "context.WithValue" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "cGFja2FnZSBjb250ZXh0IC8vIGltcG9ydCAiY29udGV4dCIKCmZ1bmMgV2l0aFZhbHVlKHBhcmVudCBDb250ZXh0LCBrZXksIHZhbCBhbnkpIENvbnRleHQKICAgIFdpdGhWYWx1ZSByZXR1cm5zIGEgY29weSBvZiBwYXJlbnQgaW4gd2hpY2ggdGhlIHZhbHVlIGFzc29jaWF0ZWQgd2l0aCBrZXkgaXMKICAgIHZhbC4KCiAgICBVc2UgY29udGV4dCBWYWx1ZXMgb25seSBmb3IgcmVxdWVzdC1zY29wZWQgZGF0YSB0aGF0IHRyYW5zaXRzIHByb2Nlc3NlcyBhbmQKICAgIEFQSXMsIG5vdCBmb3IgcGFzc2luZyBvcHRpb25hbCBwYXJhbWV0ZXJzIHRvIGZ1bmN0aW9ucy4KCiAgICBUaGUgcHJvdmlkZWQga2V5IG11c3QgYmUgY29tcGFyYWJsZSBhbmQgc2hvdWxkIG5vdCBiZSBvZiB0eXBlIHN0cmluZyBvciBhbnkKICAgIG90aGVyIGJ1aWx0LWluIHR5cGUgdG8gYXZvaWQgY29sbGlzaW9ucyBiZXR3ZWVuIHBhY2thZ2VzIHVzaW5nIGNvbnRleHQuCiAgICBVc2VycyBvZiBXaXRoVmFsdWUgc2hvdWxkIGRlZmluZSB0aGVpciBvd24gdHlwZXMgZm9yIGtleXMuIFRvIGF2b2lkCiAgICBhbGxvY2F0aW5nIHdoZW4gYXNzaWduaW5nIHRvIGFuIGludGVyZmFjZXt9LCBjb250ZXh0IGtleXMgb2Z0ZW4gaGF2ZQogICAgY29uY3JldGUgdHlwZSBzdHJ1Y3R7fS4gQWx0ZXJuYXRpdmVseSwgZXhwb3J0ZWQgY29udGV4dCBrZXkgdmFyaWFibGVzJwogICAgc3RhdGljIHR5cGUgc2hvdWxkIGJlIGEgcG9pbnRlciBvciBpbnRlcmZhY2Uu", - "duration": 523256417 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "values.md", - "nodes": [ - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#WithValue", - "href": "https://pkg.go.dev/context#WithValue", - "target": "_blank" - }, - "file": "values.md", - "nodes": [ - { - "atom": "code", - "file": "values.md", - "nodes": [ - { - "text": "context.WithValue", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#WithValue" - } - ], - { - "text": " function takes a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "values.md", - "nodes": [ - { - "atom": "code", - "file": "values.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " as its first argument, and a key and a value as its second and third arguments.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "values.md", - "nodes": [ - { - "text": "Both the key and value are ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "values.md", - "nodes": [ - { - "text": "any", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " values. While this may seem like you can use any type for the key, this is not the case. Like maps, keys must be comparable, so complex types like maps or functions are not allowed.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "pre", - "file": "values.md", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "src": "values/src/keys/main.go#example" - }, - "file": "values.md", - "lang": "go", - "nodes": [ - { - "content": "ctx := context.Background()\n\n// strings shouldn't be used as keys\n// because they can easily collide\n// with other functions, libraries, etc.\n// that set that same key.\n// instead strings should wrapped in their\n// own type.\nctx = context.WithValue(ctx, \"key\", \"value\")\n\n// keys must be comparable.\n// maps, and other complex types,\n// are not comparable and can't be used\n// used as keys.\nctx = context.WithValue(ctx, map[string]int{}, \"another value\")", - "file": "values/src/keys/main.go", - "lang": "go", - "name": "example", - "start": 9, - "end": 25 - } - ], - "type": "*hype.SourceCode" - } - ], - "type": "*hype.Element" - }, - { - "atom": "hr", - "file": "values", - "type": "*hype.Element" - }, - { - "args": [ - "go", - "run", - "main.go" - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "exec": "go run main.go", - "exit": "1", - "run": "main.go", - "src": "values/src/keys" - }, - "expected_exit": 1, - "file": "values.md", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go run main.go\n\npanic: key is not comparable\n\ngoroutine 1 [running]:\ncontext.WithValue({0x1048f4390, 0x1400010a060}, {0x1048eaf80?, 0x1400010a090}, {0x1048e95e0?, 0x1048f41b8})\n\t/usr/local/go/src/context/context.go:591 +0x13c\nmain.main()\n\t./main.go:24 +0x80\nexit status 2", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "run", - "main.go" - ], - "dir": "testdata/doc/to_md/values/src/keys", - "err": { - "Stderr": null - }, - "exit": 1, - "stderr": "cGFuaWM6IGtleSBpcyBub3QgY29tcGFyYWJsZQoKZ29yb3V0aW5lIDEgW3J1bm5pbmddOgpjb250ZXh0LldpdGhWYWx1ZSh7MHgxMDQ4ZjQzOTAsIDB4MTQwMDAxMGEwNjB9LCB7MHgxMDQ4ZWFmODA/LCAweDE0MDAwMTBhMDkwfSwgezB4MTA0OGU5NWUwPywgMHgxMDQ4ZjQxYjh9KQoJL3Vzci9sb2NhbC9nby9zcmMvY29udGV4dC9jb250ZXh0LmdvOjU5MSArMHgxM2MKbWFpbi5tYWluKCkKCS9Vc2Vycy9tYXJrYmF0ZXMvTGlicmFyeS9DbG91ZFN0b3JhZ2UvRHJvcGJveC9kZXYvZ3VpZGVzL2h5cGUvdGVzdGRhdGEvZG9jL3RvX21kL3ZhbHVlcy9zcmMva2V5cy9tYWluLmdvOjI0ICsweDgwCmV4aXQgc3RhdHVzIDI=", - "duration": 955261083 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go run main.go\n\npanic: key is not comparable\n\ngoroutine 1 [running]:\ncontext.WithValue({0x1048f4390, 0x1400010a060}, {0x1048eaf80?, 0x1400010a090}, {0x1048e95e0?, 0x1048f41b8})\n\t/usr/local/go/src/context/context.go:591 +0x13c\nmain.main()\n\t./main.go:24 +0x80\nexit status 2", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "run", - "main.go" - ], - "dir": "testdata/doc/to_md/values/src/keys", - "err": { - "Stderr": null - }, - "exit": 1, - "stderr": "cGFuaWM6IGtleSBpcyBub3QgY29tcGFyYWJsZQoKZ29yb3V0aW5lIDEgW3J1bm5pbmddOgpjb250ZXh0LldpdGhWYWx1ZSh7MHgxMDQ4ZjQzOTAsIDB4MTQwMDAxMGEwNjB9LCB7MHgxMDQ4ZWFmODA/LCAweDE0MDAwMTBhMDkwfSwgezB4MTA0OGU5NWUwPywgMHgxMDQ4ZjQxYjh9KQoJL3Vzci9sb2NhbC9nby9zcmMvY29udGV4dC9jb250ZXh0LmdvOjU5MSArMHgxM2MKbWFpbi5tYWluKCkKCS9Vc2Vycy9tYXJrYmF0ZXMvTGlicmFyeS9DbG91ZFN0b3JhZ2UvRHJvcGJveC9kZXYvZ3VpZGVzL2h5cGUvdGVzdGRhdGEvZG9jL3RvX21kL3ZhbHVlcy9zcmMva2V5cy9tYWluLmdvOjI0ICsweDgwCmV4aXQgc3RhdHVzIDI=", - "duration": 955261083 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "h2", - "file": "values.md", - "level": 2, - "nodes": [ - { - "text": "Key Resolution", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "values.md", - "nodes": [ - { - "text": "When we ask for a key through the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context.Value", - "href": "https://pkg.go.dev/context#Context.Value", - "target": "_blank" - }, - "file": "values.md", - "nodes": [ - { - "atom": "code", - "file": "values.md", - "nodes": [ - { - "text": "context.Context.Value", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context.Value" - } - ], - { - "text": " function, the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "values.md", - "nodes": [ - { - "atom": "code", - "file": "values.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " will first check if the key is present in the current ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "values.md", - "nodes": [ - { - "atom": "code", - "file": "values.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": ". If the key is present, the value is returned. If the key is not present, the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "values.md", - "nodes": [ - { - "atom": "code", - "file": "values.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " will then check if the key is present in the parent ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "values.md", - "nodes": [ - { - "atom": "code", - "file": "values.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": ". If the key is present, the value is returned. If the key is not present, the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "values.md", - "nodes": [ - { - "atom": "code", - "file": "values.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " will then check if the key is present in the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "values.md", - "nodes": [ - { - "atom": "code", - "file": "values.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": "'s parent's parent, and so on.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "values.md", - "nodes": [ - { - "text": "Consider the following example. We wrap a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "values.md", - "nodes": [ - { - "atom": "code", - "file": "values.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " multiple times with different key/values.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "pre", - "file": "values.md", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "src": "values/src/resolution/main.go#example" - }, - "file": "values.md", - "lang": "go", - "nodes": [ - { - "content": "func main() {\n\n\t// create a new background context\n\tctx := context.Background()\n\n\t// wrap the context with a new context\n\t// that has the key \"A\" and the value \"a\",\n\tctx = context.WithValue(ctx, CtxKey(\"A\"), \"a\")\n\n\t// wrap the context with a new context\n\t// that has the key \"B\" and the value \"b\",\n\tctx = context.WithValue(ctx, CtxKey(\"B\"), \"b\")\n\n\t// wrap the context with a new context\n\t// that has the key \"C\" and the value \"c\",\n\tctx = context.WithValue(ctx, CtxKey(\"C\"), \"c\")\n\n\t// print the final context\n\tprint(\"ctx\", ctx)\n\n\t// retreive and print the value\n\t// for the key \"A\"\n\ta := ctx.Value(CtxKey(\"A\"))\n\tfmt.Println(\"A:\", a)\n\n\t// retreive and print the value\n\t// for the key \"B\"\n\tb := ctx.Value(CtxKey(\"B\"))\n\tfmt.Println(\"B:\", b)\n\n\t// retreive and print the value\n\t// for the key \"C\"\n\tc := ctx.Value(CtxKey(\"C\"))\n\tfmt.Println(\"C:\", c)\n\n}", - "file": "values/src/resolution/main.go", - "lang": "go", - "name": "example", - "start": 15, - "end": 53 - } - ], - "type": "*hype.SourceCode" - } - ], - "type": "*hype.Element" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "values.md", - "nodes": [ - { - "text": "From the output we see that the final ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "values.md", - "nodes": [ - { - "atom": "code", - "file": "values.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " has a parentage that includes all of the values added with ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#WithValue", - "href": "https://pkg.go.dev/context#WithValue", - "target": "_blank" - }, - "file": "values.md", - "nodes": [ - { - "atom": "code", - "file": "values.md", - "nodes": [ - { - "text": "context.WithValue", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#WithValue" - } - ], - { - "text": ". We can also see that we are able to find all of the keys, including the very first one that we set.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "args": [ - "go", - "run", - "main.go" - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "exec": "go run main.go", - "run": "main.go", - "src": "values/src/resolution" - }, - "expected_exit": 0, - "file": "values.md", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go run main.go\n\nctx.WithValue(key: C, value: c)\n\t--\u0026gt; WithValue(key: B, value: b)\n\t\t--\u0026gt; WithValue(key: A, value: a)\n\t\t\t--\u0026gt; Background\n\nA: a\nB: b\nC: c", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "run", - "main.go" - ], - "dir": "testdata/doc/to_md/values/src/resolution", - "stdout": "Y3R4LldpdGhWYWx1ZShrZXk6IEMsIHZhbHVlOiBjKQoJLS0+IFdpdGhWYWx1ZShrZXk6IEIsIHZhbHVlOiBiKQoJCS0tPiBXaXRoVmFsdWUoa2V5OiBBLCB2YWx1ZTogYSkKCQkJLS0+IEJhY2tncm91bmQKCkE6IGEKQjogYgpDOiBj", - "duration": 1341151958 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go run main.go\n\nctx.WithValue(key: C, value: c)\n\t--\u0026gt; WithValue(key: B, value: b)\n\t\t--\u0026gt; WithValue(key: A, value: a)\n\t\t\t--\u0026gt; Background\n\nA: a\nB: b\nC: c", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "run", - "main.go" - ], - "dir": "testdata/doc/to_md/values/src/resolution", - "stdout": "Y3R4LldpdGhWYWx1ZShrZXk6IEMsIHZhbHVlOiBjKQoJLS0+IFdpdGhWYWx1ZShrZXk6IEIsIHZhbHVlOiBiKQoJCS0tPiBXaXRoVmFsdWUoa2V5OiBBLCB2YWx1ZTogYSkKCQkJLS0+IEJhY2tncm91bmQKCkE6IGEKQjogYgpDOiBj", - "duration": 1341151958 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "title": "Context Values", - "type": "*hype.Page" - } - ], - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "include", - "attributes": { - "src": "values/_strings.md" - }, - "dir": ".", - "file": "values.md", - "nodes": [ - [ - { - "atom": "page", - "file": "values/_strings.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "h1", - "file": "values/_strings.md", - "level": 1, - "nodes": [ - { - "text": "Problems with String Keys", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "values/_strings.md", - "nodes": [ - { - "text": "As is mentioned in the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context", - "href": "https://pkg.go.dev/context", - "target": "_blank" - }, - "file": "values/_strings.md", - "nodes": [ - { - "atom": "code", - "file": "values/_strings.md", - "nodes": [ - { - "text": "context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context" - } - ], - { - "text": " documentation using string keys is not recommended. As we just saw when ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context.Value", - "href": "https://pkg.go.dev/context#Context.Value", - "target": "_blank" - }, - "file": "values/_strings.md", - "nodes": [ - { - "atom": "code", - "file": "values/_strings.md", - "nodes": [ - { - "text": "context.Context.Value", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context.Value" - } - ], - { - "text": " tries to resolve a key it finds the first, if any, ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "values/_strings.md", - "nodes": [ - { - "atom": "code", - "file": "values/_strings.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " that contains the key and returns that value.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-16", - "type": "listing" - }, - "file": "values/_strings.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "args": [ - "go", - "doc", - "context.WithValue" - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "doc": "context.WithValue", - "exec": "go doc context.WithValue" - }, - "expected_exit": 0, - "file": "values", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc context.WithValue\n\npackage context // import \u0026#34;context\u0026#34;\n\nfunc WithValue(parent Context, key, val any) Context\n WithValue returns a copy of parent in which the value associated with key is\n val.\n\n Use context Values only for request-scoped data that transits processes and\n APIs, not for passing optional parameters to functions.\n\n The provided key must be comparable and should not be of type string or any\n other built-in type to avoid collisions between packages using context.\n Users of WithValue should define their own types for keys. To avoid\n allocating when assigning to an interface{}, context keys often have\n concrete type struct{}. Alternatively, exported context key variables\u0026#39;\n static type should be a pointer or interface.", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "context.WithValue" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "cGFja2FnZSBjb250ZXh0IC8vIGltcG9ydCAiY29udGV4dCIKCmZ1bmMgV2l0aFZhbHVlKHBhcmVudCBDb250ZXh0LCBrZXksIHZhbCBhbnkpIENvbnRleHQKICAgIFdpdGhWYWx1ZSByZXR1cm5zIGEgY29weSBvZiBwYXJlbnQgaW4gd2hpY2ggdGhlIHZhbHVlIGFzc29jaWF0ZWQgd2l0aCBrZXkgaXMKICAgIHZhbC4KCiAgICBVc2UgY29udGV4dCBWYWx1ZXMgb25seSBmb3IgcmVxdWVzdC1zY29wZWQgZGF0YSB0aGF0IHRyYW5zaXRzIHByb2Nlc3NlcyBhbmQKICAgIEFQSXMsIG5vdCBmb3IgcGFzc2luZyBvcHRpb25hbCBwYXJhbWV0ZXJzIHRvIGZ1bmN0aW9ucy4KCiAgICBUaGUgcHJvdmlkZWQga2V5IG11c3QgYmUgY29tcGFyYWJsZSBhbmQgc2hvdWxkIG5vdCBiZSBvZiB0eXBlIHN0cmluZyBvciBhbnkKICAgIG90aGVyIGJ1aWx0LWluIHR5cGUgdG8gYXZvaWQgY29sbGlzaW9ucyBiZXR3ZWVuIHBhY2thZ2VzIHVzaW5nIGNvbnRleHQuCiAgICBVc2VycyBvZiBXaXRoVmFsdWUgc2hvdWxkIGRlZmluZSB0aGVpciBvd24gdHlwZXMgZm9yIGtleXMuIFRvIGF2b2lkCiAgICBhbGxvY2F0aW5nIHdoZW4gYXNzaWduaW5nIHRvIGFuIGludGVyZmFjZXt9LCBjb250ZXh0IGtleXMgb2Z0ZW4gaGF2ZQogICAgY29uY3JldGUgdHlwZSBzdHJ1Y3R7fS4gQWx0ZXJuYXRpdmVseSwgZXhwb3J0ZWQgY29udGV4dCBrZXkgdmFyaWFibGVzJwogICAgc3RhdGljIHR5cGUgc2hvdWxkIGJlIGEgcG9pbnRlciBvciBpbnRlcmZhY2Uu", - "duration": 560424125 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc context.WithValue\n\npackage context // import \u0026#34;context\u0026#34;\n\nfunc WithValue(parent Context, key, val any) Context\n WithValue returns a copy of parent in which the value associated with key is\n val.\n\n Use context Values only for request-scoped data that transits processes and\n APIs, not for passing optional parameters to functions.\n\n The provided key must be comparable and should not be of type string or any\n other built-in type to avoid collisions between packages using context.\n Users of WithValue should define their own types for keys. To avoid\n allocating when assigning to an interface{}, context keys often have\n concrete type struct{}. Alternatively, exported context key variables\u0026#39;\n static type should be a pointer or interface.", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "context.WithValue" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "cGFja2FnZSBjb250ZXh0IC8vIGltcG9ydCAiY29udGV4dCIKCmZ1bmMgV2l0aFZhbHVlKHBhcmVudCBDb250ZXh0LCBrZXksIHZhbCBhbnkpIENvbnRleHQKICAgIFdpdGhWYWx1ZSByZXR1cm5zIGEgY29weSBvZiBwYXJlbnQgaW4gd2hpY2ggdGhlIHZhbHVlIGFzc29jaWF0ZWQgd2l0aCBrZXkgaXMKICAgIHZhbC4KCiAgICBVc2UgY29udGV4dCBWYWx1ZXMgb25seSBmb3IgcmVxdWVzdC1zY29wZWQgZGF0YSB0aGF0IHRyYW5zaXRzIHByb2Nlc3NlcyBhbmQKICAgIEFQSXMsIG5vdCBmb3IgcGFzc2luZyBvcHRpb25hbCBwYXJhbWV0ZXJzIHRvIGZ1bmN0aW9ucy4KCiAgICBUaGUgcHJvdmlkZWQga2V5IG11c3QgYmUgY29tcGFyYWJsZSBhbmQgc2hvdWxkIG5vdCBiZSBvZiB0eXBlIHN0cmluZyBvciBhbnkKICAgIG90aGVyIGJ1aWx0LWluIHR5cGUgdG8gYXZvaWQgY29sbGlzaW9ucyBiZXR3ZWVuIHBhY2thZ2VzIHVzaW5nIGNvbnRleHQuCiAgICBVc2VycyBvZiBXaXRoVmFsdWUgc2hvdWxkIGRlZmluZSB0aGVpciBvd24gdHlwZXMgZm9yIGtleXMuIFRvIGF2b2lkCiAgICBhbGxvY2F0aW5nIHdoZW4gYXNzaWduaW5nIHRvIGFuIGludGVyZmFjZXt9LCBjb250ZXh0IGtleXMgb2Z0ZW4gaGF2ZQogICAgY29uY3JldGUgdHlwZSBzdHJ1Y3R7fS4gQWx0ZXJuYXRpdmVseSwgZXhwb3J0ZWQgY29udGV4dCBrZXkgdmFyaWFibGVzJwogICAgc3RhdGljIHR5cGUgc2hvdWxkIGJlIGEgcG9pbnRlciBvciBpbnRlcmZhY2Uu", - "duration": 560424125 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "values", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.16:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "Using strings as context keys is not recommended per the documentation.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 16, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "values/_strings.md", - "nodes": [ - { - "text": "When we use the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context.Value", - "href": "https://pkg.go.dev/context#Context.Value", - "target": "_blank" - }, - "file": "values/_strings.md", - "nodes": [ - { - "atom": "code", - "file": "values/_strings.md", - "nodes": [ - { - "text": "context.Context.Value", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context.Value" - } - ], - { - "text": " function, we get the last value that was set for the given key. Each time we use ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#WithValue", - "href": "https://pkg.go.dev/context#WithValue", - "target": "_blank" - }, - "file": "values/_strings.md", - "nodes": [ - { - "atom": "code", - "file": "values/_strings.md", - "nodes": [ - { - "text": "context.WithValue", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#WithValue" - } - ], - { - "text": " to wrap a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "values/_strings.md", - "nodes": [ - { - "atom": "code", - "file": "values/_strings.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " with a new ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "values/_strings.md", - "nodes": [ - { - "atom": "code", - "file": "values/_strings.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": ", the new ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "values/_strings.md", - "nodes": [ - { - "atom": "code", - "file": "values/_strings.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " will have, essentially, replaced the previous value for the given key.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "img", - "attributes": { - "alt": "string-keys", - "src": "values/assets/string-keys.svg" - }, - "file": "values/_strings.md", - "type": "hype.Image" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "h2", - "file": "values/_strings.md", - "level": 2, - "nodes": [ - { - "text": "Key Collisions", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "values/_strings.md", - "nodes": [ - { - "text": "Consider the following example. We wrap a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "values/_strings.md", - "nodes": [ - { - "atom": "code", - "file": "values/_strings.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " multiple times, each time with a different value, but the same key, ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "values/_strings.md", - "nodes": [ - { - "text": "request_id", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": ", which is of type ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "values/_strings.md", - "nodes": [ - { - "text": "string", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": ".", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "pre", - "file": "values/_strings.md", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "src": "values/src/string-keys/main.go#example" - }, - "file": "_strings.md", - "lang": "go", - "nodes": [ - { - "content": "func main() {\n\t// create a new background context\n\tctx := context.Background()\n\n\t// call the A function\n\t// passing in the background context\n\tA(ctx)\n}\n\nfunc A(ctx context.Context) {\n\t// wrap the context with a request_id\n\t// to represent this specific A request\n\tctx = context.WithValue(ctx, \"request_id\", \"123\")\n\n\t// call the B function\n\t// passing in the wrapped context\n\tB(ctx)\n}\n\nfunc B(ctx context.Context) {\n\t// wrap the context with a request_id\n\t// to represent this specific B request\n\tctx = context.WithValue(ctx, \"request_id\", \"456\")\n\tLogger(ctx)\n}\n\n// Logger logs the webs request_id\n// as well as the request_id from the B\nfunc Logger(ctx context.Context) {\n\ta := ctx.Value(\"request_id\")\n\tfmt.Println(\"A\\t\", \"request_id:\", a)\n\n\tb := ctx.Value(\"request_id\")\n\tfmt.Println(\"B\\t\", \"request_id:\", b)\n}", - "file": "values/src/string-keys/main.go", - "lang": "go", - "name": "example", - "start": 8, - "end": 45 - } - ], - "type": "*hype.SourceCode" - } - ], - "type": "*hype.Element" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "values/_strings.md", - "nodes": [ - { - "text": "When we try to log both the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "values/_strings.md", - "nodes": [ - { - "text": "request_id", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " for both ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "values/_strings.md", - "nodes": [ - { - "text": "A", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " and ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "values/_strings.md", - "nodes": [ - { - "text": "A", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " we see that they are both set to the same value.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "args": [ - "go", - "run", - "main.go" - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "exec": "go run main.go", - "run": "main.go", - "src": "values/src/string-keys" - }, - "expected_exit": 0, - "file": "values/_strings.md", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go run main.go\n\nA\t request_id: 456\nB\t request_id: 456", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "run", - "main.go" - ], - "dir": "testdata/doc/to_md/values/src/string-keys", - "stdout": "QQkgcmVxdWVzdF9pZDogNDU2CkIJIHJlcXVlc3RfaWQ6IDQ1Ng==", - "duration": 1172182417 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go run main.go\n\nA\t request_id: 456\nB\t request_id: 456", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "run", - "main.go" - ], - "dir": "testdata/doc/to_md/values/src/string-keys", - "stdout": "QQkgcmVxdWVzdF9pZDogNDU2CkIJIHJlcXVlc3RfaWQ6IDQ1Ng==", - "duration": 1172182417 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "values/_strings.md", - "nodes": [ - { - "text": "One way to solve this problem would be try and \"namespace\" your ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "values/_strings.md", - "nodes": [ - { - "text": "string", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " keys, ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "values/_strings.md", - "nodes": [ - { - "text": "myapp.request_id", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": ". While you may never get into a collision scenario, the possibility of someone else using the same key is there.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "h2", - "file": "values/_strings.md", - "level": 2, - "nodes": [ - { - "text": "Custom String Key Types", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "values/_strings.md", - "nodes": [ - { - "text": "Because Go is a typed language, we can leverage the type system to solve the problem of key collisions. We can create a new type based on ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "values/_strings.md", - "nodes": [ - { - "text": "string", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " that we can use as the key.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "pre", - "file": "values/_strings.md", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "src": "values/src/custom-keys/main.go#types" - }, - "file": "_strings.md", - "lang": "go", - "nodes": [ - { - "content": "// CtxKeyA is used to wrap keys\n// associated with a A request\n// \tCtxKeyA(\"request_id\")\n// \tCtxKeyA(\"user_id\")\ntype CtxKeyA string\n\n// CtxKeyB is used to wrap keys\n// associated with a B request\n// \tCtxKeyB(\"request_id\")\n// \tCtxKeyB(\"user_id\")\ntype CtxKeyB string", - "file": "values/src/custom-keys/main.go", - "lang": "go", - "name": "types", - "start": 8, - "end": 21 - } - ], - "type": "*hype.SourceCode" - } - ], - "type": "*hype.Element" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "pre", - "file": "values/_strings.md", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "src": "values/src/custom-keys/main.go#example" - }, - "file": "_strings.md", - "lang": "go", - "nodes": [ - { - "content": "func A(ctx context.Context) {\n\t// wrap the context with a request_id\n\t// to represent this specific A request\n\tkey := CtxKeyA(\"request_id\")\n\tctx = context.WithValue(ctx, key, \"123\")\n\n\t// call B with the wrapped context\n\tB(ctx)\n}\n\nfunc B(ctx context.Context) {\n\t// wrap the context with a request_id\n\t// to represent this specific B request\n\tkey := CtxKeyB(\"request_id\")\n\tctx = context.WithValue(ctx, key, \"456\")\n\n\tLogger(ctx)\n}", - "file": "values/src/custom-keys/main.go", - "lang": "go", - "name": "example", - "start": 31, - "end": 51 - } - ], - "type": "*hype.SourceCode" - } - ], - "type": "*hype.Element" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "pre", - "file": "values/_strings.md", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "src": "values/src/custom-keys/main.go#logger" - }, - "file": "_strings.md", - "lang": "go", - "nodes": [ - { - "content": "// Logger logs the webs request_id\n// as well as the request_id from the B\nfunc Logger(ctx context.Context) {\n\t// retreive the request_id from the A request\n\taKey := CtxKeyA(\"request_id\")\n\taVal := ctx.Value(aKey)\n\n\t// print the request_id from the A request\n\tprint(\"A\", aKey, aVal)\n\n\t// retreive the request_id from the B request\n\tbKey := CtxKeyB(\"request_id\")\n\tbVal := ctx.Value(bKey)\n\n\t// print the request_id from the B request\n\tprint(\"B\", bKey, bVal)\n}", - "file": "values/src/custom-keys/main.go", - "lang": "go", - "name": "logger", - "start": 53, - "end": 72 - } - ], - "type": "*hype.SourceCode" - } - ], - "type": "*hype.Element" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "values/_strings.md", - "nodes": [ - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "values/_strings.md", - "nodes": [ - { - "text": "Logger", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " is now properly able to retrieve the two different ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "values/_strings.md", - "nodes": [ - { - "text": "request_id", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " values because they are no longer of the same type.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "args": [ - "go", - "run", - "main.go" - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "exec": "go run main.go", - "run": "main.go", - "src": "values/src/custom-keys" - }, - "expected_exit": 0, - "file": "values/_strings.md", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go run main.go\n\nA: main.CtxKeyA(request_id): 123\nB: main.CtxKeyB(request_id): 456", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "run", - "main.go" - ], - "dir": "testdata/doc/to_md/values/src/custom-keys", - "stdout": "QTogbWFpbi5DdHhLZXlBKHJlcXVlc3RfaWQpOiAxMjMKQjogbWFpbi5DdHhLZXlCKHJlcXVlc3RfaWQpOiA0NTY=", - "duration": 1271614083 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go run main.go\n\nA: main.CtxKeyA(request_id): 123\nB: main.CtxKeyB(request_id): 456", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "run", - "main.go" - ], - "dir": "testdata/doc/to_md/values/src/custom-keys", - "stdout": "QTogbWFpbi5DdHhLZXlBKHJlcXVlc3RfaWQpOiAxMjMKQjogbWFpbi5DdHhLZXlCKHJlcXVlc3RfaWQpOiA0NTY=", - "duration": 1271614083 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "values/_strings.md", - "nodes": [ - { - "text": "This code can be further cleaned up by using constants for the keys that our package, or application, uses. This allows for cleaner code and makes it easier to document the potential keys that may be in a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "values/_strings.md", - "nodes": [ - { - "atom": "code", - "file": "values/_strings.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": ".", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "pre", - "file": "values/_strings.md", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "src": "values/src/custom-const/main.go#consts" - }, - "file": "_strings.md", - "lang": "go", - "nodes": [ - { - "content": "const (\n\t// A_RequestID can be used to\n\t// retreive the request_id for\n\t// the A request\n\tA_RequestID CtxKeyA = \"request_id\"\n\t// \tA_SESSION_ID CtxKeyA = \"session_id\"\n\t// \tA_SERVER_ID CtxKeyA = \"server_id\"\n\t// \tother keys...\n\n\t// B_RequestID can be used to\n\t// retreive the request_id for\n\t// the B request\n\tB_RequestID CtxKeyB = \"request_id\"\n)", - "file": "values/src/custom-const/main.go", - "lang": "go", - "name": "consts", - "start": 23, - "end": 39 - } - ], - "type": "*hype.SourceCode" - } - ], - "type": "*hype.Element" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "pre", - "file": "values/_strings.md", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "src": "values/src/custom-const/main.go#logger" - }, - "file": "_strings.md", - "lang": "go", - "nodes": [ - { - "content": "// Logger logs the webs request_id\n// as well as the request_id from the B\nfunc Logger(ctx context.Context) {\n\t// retreive the request_id from the A request\n\taKey := A_RequestID\n\taVal := ctx.Value(aKey)\n\n\t// print the request_id from the A request\n\tprint(\"A\", aKey, aVal)\n\n\t// retreive the request_id from the B request\n\tbKey := B_RequestID\n\tbVal := ctx.Value(bKey)\n\n\t// print the request_id from the B request\n\tprint(\"B\", bKey, bVal)\n}", - "file": "values/src/custom-const/main.go", - "lang": "go", - "name": "logger", - "start": 69, - "end": 88 - } - ], - "type": "*hype.SourceCode" - } - ], - "type": "*hype.Element" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "title": "Problems with String Keys", - "type": "*hype.Page" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "type": "*hype.Include" - } - ], - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "include", - "attributes": { - "src": "values/_securing.md" - }, - "dir": ".", - "file": "values.md", - "nodes": [ - [ - { - "atom": "page", - "file": "values/_securing.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "h1", - "file": "values/_securing.md", - "level": 1, - "nodes": [ - { - "text": "Securing Context Keys and Values", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "values/_securing.md", - "nodes": [ - { - "text": "If we export, make public, the types, and names, of the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "values/_securing.md", - "nodes": [ - { - "atom": "code", - "file": "values/_securing.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " keys our package or application uses, we run the risk of a malicious agent stealing, or modifying our values. For example, in a web request we might set a ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "values/_securing.md", - "nodes": [ - { - "text": "request_id", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " at the beginning of the request, but a piece of middleware later in the chain might modify that value to something else.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "pre", - "file": "values/_securing.md", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "snippet": "types", - "src": "values/src/malicious/foo/foo.go" - }, - "file": "_securing.md", - "lang": "go", - "nodes": [ - { - "content": "type CtxKey string\n\nconst (\n\tRequestID CtxKey = \"request_id\"\n)", - "file": "values/src/malicious/foo/foo.go", - "lang": "go", - "name": "types", - "start": 5, - "end": 12 - } - ], - "type": "*hype.SourceCode" - } - ], - "type": "*hype.Element" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "pre", - "file": "values/_securing.md", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "snippet": "example", - "src": "values/src/malicious/bar/bar.go" - }, - "file": "_securing.md", - "lang": "go", - "nodes": [ - { - "content": "func WithBar(ctx context.Context) context.Context {\n\t// wrap the context with a request_id\n\t// to represent this specific bar request\n\tctx = context.WithValue(ctx, RequestID, \"456\")\n\n\t// maliciously replace the request_id\n\t// set by foo\n\tctx = context.WithValue(ctx, foo.RequestID, \"???\")\n\n\t// return the wrapped context\n\treturn ctx\n}", - "file": "values/src/malicious/bar/bar.go", - "lang": "go", - "name": "example", - "start": 17, - "end": 31 - } - ], - "type": "*hype.SourceCode" - } - ], - "type": "*hype.Element" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "values/_securing.md", - "nodes": [ - [ - { - "atom": "pre", - "file": "values/_securing.md", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "snippet": "example", - "src": "values/src/malicious/main.go" - }, - "file": "_securing.md", - "lang": "go", - "nodes": [ - { - "content": "func main() {\n\t// create a background context\n\tctx := context.Background()\n\n\t// wrap the context with foo\n\tctx = foo.WithFoo(ctx)\n\n\t// wrap the context with bar\n\tctx = bar.WithBar(ctx)\n\n\t// retrieve the foo.RequestID\n\t// value from the context\n\tid := ctx.Value(foo.RequestID)\n\n\t// print the value\n\tfmt.Println(\"foo.RequestID: \", id)\n}", - "file": "values/src/malicious/main.go", - "lang": "go", - "name": "example", - "start": 10, - "end": 29 - } - ], - "type": "*hype.SourceCode" - } - ], - "type": "*hype.Element" - } - ], - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "pre", - "file": "values/_securing.md", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "snippet": "example", - "src": "values/src/malicious/foo/foo.go" - }, - "file": "_securing.md", - "lang": "go", - "nodes": [ - { - "content": "func WithFoo(ctx context.Context) context.Context {\n\t// wrap the context with a request_id\n\t// to represent this specific foo request\n\tctx = context.WithValue(ctx, RequestID, \"123\")\n\n\t// return the wrapped context\n\treturn ctx\n}", - "file": "values/src/malicious/foo/foo.go", - "lang": "go", - "name": "example", - "start": 14, - "end": 24 - } - ], - "type": "*hype.SourceCode" - } - ], - "type": "*hype.Element" - } - ] - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "args": [ - "go", - "run", - "main.go" - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "exec": "go run main.go", - "run": "main.go", - "src": "values/src/malicious" - }, - "expected_exit": 0, - "file": "values/_securing.md", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go run main.go\n\nfoo.RequestID: ???", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "run", - "main.go" - ], - "dir": "testdata/doc/to_md/values/src/malicious", - "stdout": "Zm9vLlJlcXVlc3RJRDogID8/Pw==", - "duration": 1034396042 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go run main.go\n\nfoo.RequestID: ???", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "run", - "main.go" - ], - "dir": "testdata/doc/to_md/values/src/malicious", - "stdout": "Zm9vLlJlcXVlc3RJRDogID8/Pw==", - "duration": 1034396042 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "h2", - "file": "values/_securing.md", - "level": 2, - "nodes": [ - { - "text": "Securing by Not Exporting", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "values/_securing.md", - "nodes": [ - { - "text": "The best way to secure your that your key/value pairs aren't maliciously overwritten, or accessed, is by not exporting the types, and any constants, used for keys.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "pre", - "file": "values/_securing.md", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "snippet": "types", - "src": "values/src/secured/foo/foo.go" - }, - "file": "_securing.md", - "lang": "go", - "nodes": [ - { - "content": "type ctxKey string\n\nconst (\n\trequestID ctxKey = \"request_id\"\n)", - "file": "values/src/secured/foo/foo.go", - "lang": "go", - "name": "types", - "start": 8, - "end": 15 - } - ], - "type": "*hype.SourceCode" - } - ], - "type": "*hype.Element" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "values/_securing.md", - "nodes": [ - { - "text": "Now, you are in control of what values from the context you wish to make public. For example, we can add a helper function to allow others to get access to the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "values/_securing.md", - "nodes": [ - { - "text": "request_id", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " value.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "values/_securing.md", - "nodes": [ - { - "text": "Because the return value from ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context.Value", - "href": "https://pkg.go.dev/context#Context.Value", - "target": "_blank" - }, - "file": "values/_securing.md", - "nodes": [ - { - "atom": "code", - "file": "values/_securing.md", - "nodes": [ - { - "text": "context.Context.Value", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context.Value" - } - ], - { - "text": " is an empty interface, ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "values/_securing.md", - "nodes": [ - { - "text": "interface{}", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": ", we can use these helper functions to, not just retrieve access to the value, but also type assert the value to the type we want, or return an error if it doesn't.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "pre", - "file": "values/_securing.md", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "snippet": "example", - "src": "values/src/secured/foo/foo.go" - }, - "file": "_securing.md", - "lang": "go", - "nodes": [ - { - "content": "func RequestIDFrom(ctx context.Context) (string, error) {\n\t// get the request_id from the context\n\ts, ok := ctx.Value(requestID).(string)\n\tif !ok {\n\t\treturn \"\", fmt.Errorf(\"request_id not found in context\")\n\t}\n\treturn s, nil\n}", - "file": "values/src/secured/foo/foo.go", - "lang": "go", - "name": "example", - "start": 26, - "end": 36 - } - ], - "type": "*hype.SourceCode" - } - ], - "type": "*hype.Element" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "values/_securing.md", - "nodes": [ - { - "text": "Our application can be updated to use the new helper function to print the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "values/_securing.md", - "nodes": [ - { - "text": "request_id", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " or exit if there was a problem getting the value.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "pre", - "file": "values/_securing.md", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "snippet": "example", - "src": "values/src/secured/main.go" - }, - "file": "_securing.md", - "lang": "go", - "nodes": [ - { - "content": "func main() {\n\t// create a background context\n\tctx := context.Background()\n\n\t// wrap the context with foo\n\tctx = foo.WithFoo(ctx)\n\n\t// wrap the context with bar\n\tctx = bar.WithBar(ctx)\n\n\t// retrieve the foo.RequestID\n\t// value from the context\n\tid, err := foo.RequestIDFrom(ctx)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// print the value\n\tfmt.Println(\"foo.RequestID: \", id)\n}", - "file": "values/src/secured/main.go", - "lang": "go", - "name": "example", - "start": 11, - "end": 33 - } - ], - "type": "*hype.SourceCode" - } - ], - "type": "*hype.Element" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "values/_securing.md", - "nodes": [ - { - "text": "The malicious ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "values/_securing.md", - "nodes": [ - { - "text": "bar", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " package can no longer set, or retrieve, the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "values/_securing.md", - "nodes": [ - { - "text": "request_id", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " value set by the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "values/_securing.md", - "nodes": [ - { - "text": "foo", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " package. The ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "values/_securing.md", - "nodes": [ - { - "text": "bar", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " package does not have the ability to create a new type of value ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "values/_securing.md", - "nodes": [ - { - "text": "foo.ctxKey", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " because the type is un-exported can be accessed outside of the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "values/_securing.md", - "nodes": [ - { - "text": "foo", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " package.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "pre", - "file": "values/_securing.md", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "snippet": "example", - "src": "values/src/secured/bar/bar.go" - }, - "file": "_securing.md", - "lang": "go", - "nodes": [ - { - "content": "func WithBar(ctx context.Context) context.Context {\n\t// wrap the context with a request_id\n\t// to represent this specific bar request\n\tctx = context.WithValue(ctx, requestID, \"456\")\n\n\t// no longer able to set the foo request id\n\t// it does not have access to the foo.ctxKey type\n\t// as it is not exported, so bar can not create\n\t// a new key of that type.\n\t// ctx = context.WithValue(ctx, foo.ctxKey(\"request_id\"), \"???\")\n\n\t// return the wrapped context\n\treturn ctx\n}", - "file": "values/src/secured/bar/bar.go", - "lang": "go", - "name": "example", - "start": 16, - "end": 32 - } - ], - "type": "*hype.SourceCode" - } - ], - "type": "*hype.Element" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "values/_securing.md", - "nodes": [ - { - "text": "As a result of securing our ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "values/_securing.md", - "nodes": [ - { - "atom": "code", - "file": "values/_securing.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " values, the application now correctly retrieves the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "values/_securing.md", - "nodes": [ - { - "text": "request_id", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " value set by the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "values/_securing.md", - "nodes": [ - { - "text": "foo", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " package.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "args": [ - "go", - "run", - "main.go" - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "exec": "go run main.go", - "run": "main.go", - "src": "values/src/secured" - }, - "expected_exit": 0, - "file": "values/_securing.md", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go run main.go\n\nfoo.RequestID: 123", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "run", - "main.go" - ], - "dir": "testdata/doc/to_md/values/src/secured", - "stdout": "Zm9vLlJlcXVlc3RJRDogIDEyMw==", - "duration": 1587465625 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go run main.go\n\nfoo.RequestID: 123", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "run", - "main.go" - ], - "dir": "testdata/doc/to_md/values/src/secured", - "stdout": "Zm9vLlJlcXVlc3RJRDogIDEyMw==", - "duration": 1587465625 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "title": "Securing Context Keys and Values", - "type": "*hype.Page" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "type": "*hype.Include" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "type": "*hype.Include" - } - ], - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "include", - "attributes": { - "src": "cancellation/cancellation.md" - }, - "dir": "cancellation", - "file": "module.md", - "nodes": [ - [ - { - "atom": "page", - "file": "cancellation.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "h1", - "file": "cancellation.md", - "level": 1, - "nodes": [ - { - "text": "Cancellation Propagation with Contexts", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "cancellation.md", - "nodes": [ - { - "text": "While having the ability to pass contextual information via the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " is useful, the real benefit, and design of the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context", - "href": "https://pkg.go.dev/context", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context" - } - ], - { - "text": " package, is that it can be used to propagate cancellation events to those listening to the context. When a parent ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " is canceled, all its children are also canceled.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "h2", - "file": "cancellation.md", - "level": 2, - "nodes": [ - { - "text": "Creating a Cancellable Context", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "cancellation.md", - "nodes": [ - { - "text": "In order to cancel a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": ", we must have a way of cancelling it. The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#WithCancel", - "href": "https://pkg.go.dev/context#WithCancel", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.WithCancel", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#WithCancel" - } - ], - { - "text": " function, ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-17" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-17" - }, - "nodes": [ - { - "text": "Listing 1.17", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-17" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ", wraps a given ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " with a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " that can be cancelled.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-17", - "type": "listing" - }, - "file": "cancellation.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "args": [ - "go", - "doc", - "context.WithCancel" - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "doc": "context.WithCancel", - "exec": "go doc context.WithCancel" - }, - "expected_exit": 0, - "file": "cancellation", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc context.WithCancel\n\npackage context // import \u0026#34;context\u0026#34;\n\nfunc WithCancel(parent Context) (ctx Context, cancel CancelFunc)\n WithCancel returns a copy of parent with a new Done channel. The returned\n context\u0026#39;s Done channel is closed when the returned cancel function is called\n or when the parent context\u0026#39;s Done channel is closed, whichever happens\n first.\n\n Canceling this context releases resources associated with it, so code should\n call cancel as soon as the operations running in this Context complete.", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "context.WithCancel" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "cGFja2FnZSBjb250ZXh0IC8vIGltcG9ydCAiY29udGV4dCIKCmZ1bmMgV2l0aENhbmNlbChwYXJlbnQgQ29udGV4dCkgKGN0eCBDb250ZXh0LCBjYW5jZWwgQ2FuY2VsRnVuYykKICAgIFdpdGhDYW5jZWwgcmV0dXJucyBhIGNvcHkgb2YgcGFyZW50IHdpdGggYSBuZXcgRG9uZSBjaGFubmVsLiBUaGUgcmV0dXJuZWQKICAgIGNvbnRleHQncyBEb25lIGNoYW5uZWwgaXMgY2xvc2VkIHdoZW4gdGhlIHJldHVybmVkIGNhbmNlbCBmdW5jdGlvbiBpcyBjYWxsZWQKICAgIG9yIHdoZW4gdGhlIHBhcmVudCBjb250ZXh0J3MgRG9uZSBjaGFubmVsIGlzIGNsb3NlZCwgd2hpY2hldmVyIGhhcHBlbnMKICAgIGZpcnN0LgoKICAgIENhbmNlbGluZyB0aGlzIGNvbnRleHQgcmVsZWFzZXMgcmVzb3VyY2VzIGFzc29jaWF0ZWQgd2l0aCBpdCwgc28gY29kZSBzaG91bGQKICAgIGNhbGwgY2FuY2VsIGFzIHNvb24gYXMgdGhlIG9wZXJhdGlvbnMgcnVubmluZyBpbiB0aGlzIENvbnRleHQgY29tcGxldGUu", - "duration": 554287958 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc context.WithCancel\n\npackage context // import \u0026#34;context\u0026#34;\n\nfunc WithCancel(parent Context) (ctx Context, cancel CancelFunc)\n WithCancel returns a copy of parent with a new Done channel. The returned\n context\u0026#39;s Done channel is closed when the returned cancel function is called\n or when the parent context\u0026#39;s Done channel is closed, whichever happens\n first.\n\n Canceling this context releases resources associated with it, so code should\n call cancel as soon as the operations running in this Context complete.", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "context.WithCancel" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "cGFja2FnZSBjb250ZXh0IC8vIGltcG9ydCAiY29udGV4dCIKCmZ1bmMgV2l0aENhbmNlbChwYXJlbnQgQ29udGV4dCkgKGN0eCBDb250ZXh0LCBjYW5jZWwgQ2FuY2VsRnVuYykKICAgIFdpdGhDYW5jZWwgcmV0dXJucyBhIGNvcHkgb2YgcGFyZW50IHdpdGggYSBuZXcgRG9uZSBjaGFubmVsLiBUaGUgcmV0dXJuZWQKICAgIGNvbnRleHQncyBEb25lIGNoYW5uZWwgaXMgY2xvc2VkIHdoZW4gdGhlIHJldHVybmVkIGNhbmNlbCBmdW5jdGlvbiBpcyBjYWxsZWQKICAgIG9yIHdoZW4gdGhlIHBhcmVudCBjb250ZXh0J3MgRG9uZSBjaGFubmVsIGlzIGNsb3NlZCwgd2hpY2hldmVyIGhhcHBlbnMKICAgIGZpcnN0LgoKICAgIENhbmNlbGluZyB0aGlzIGNvbnRleHQgcmVsZWFzZXMgcmVzb3VyY2VzIGFzc29jaWF0ZWQgd2l0aCBpdCwgc28gY29kZSBzaG91bGQKICAgIGNhbGwgY2FuY2VsIGFzIHNvb24gYXMgdGhlIG9wZXJhdGlvbnMgcnVubmluZyBpbiB0aGlzIENvbnRleHQgY29tcGxldGUu", - "duration": 554287958 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "cancellation", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.17:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#WithCancel", - "href": "https://pkg.go.dev/context#WithCancel", - "target": "_blank" - }, - "file": "cancellation", - "nodes": [ - [ - { - "atom": "code", - "file": "cancellation", - "nodes": [ - { - "text": "context.WithCancel", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ] - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#WithCancel" - } - ], - { - "text": " function.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 17, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "cancellation.md", - "nodes": [ - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#WithCancel", - "href": "https://pkg.go.dev/context#WithCancel", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.WithCancel", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#WithCancel" - } - ], - { - "text": " function returns a second argument, that of a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#CancelFunc", - "href": "https://pkg.go.dev/context#CancelFunc", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.CancelFunc", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#CancelFunc" - } - ], - { - "text": " function, which can be used to cancel the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": ".", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "h3", - "file": "cancellation.md", - "level": 3, - "nodes": [ - { - "text": "The Cancel Function", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "cancellation.md", - "nodes": [ - { - "text": "There a few things that need to be noted about the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#CancelFunc", - "href": "https://pkg.go.dev/context#CancelFunc", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.CancelFunc", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#CancelFunc" - } - ], - { - "text": " function, ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-18" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-18" - }, - "nodes": [ - { - "text": "Listing 1.18", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-18" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ". So let's examine each in more detail.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-18", - "type": "listing" - }, - "file": "cancellation.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "args": [ - "go", - "doc", - "context.CancelFunc" - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "doc": "context.CancelFunc", - "exec": "go doc context.CancelFunc" - }, - "expected_exit": 0, - "file": "cancellation", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc context.CancelFunc\n\npackage context // import \u0026#34;context\u0026#34;\n\ntype CancelFunc func()\n A CancelFunc tells an operation to abandon its work. A CancelFunc does not\n wait for the work to stop. A CancelFunc may be called by multiple goroutines\n simultaneously. After the first call, subsequent calls to a CancelFunc do\n nothing.", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "context.CancelFunc" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "cGFja2FnZSBjb250ZXh0IC8vIGltcG9ydCAiY29udGV4dCIKCnR5cGUgQ2FuY2VsRnVuYyBmdW5jKCkKICAgIEEgQ2FuY2VsRnVuYyB0ZWxscyBhbiBvcGVyYXRpb24gdG8gYWJhbmRvbiBpdHMgd29yay4gQSBDYW5jZWxGdW5jIGRvZXMgbm90CiAgICB3YWl0IGZvciB0aGUgd29yayB0byBzdG9wLiBBIENhbmNlbEZ1bmMgbWF5IGJlIGNhbGxlZCBieSBtdWx0aXBsZSBnb3JvdXRpbmVzCiAgICBzaW11bHRhbmVvdXNseS4gQWZ0ZXIgdGhlIGZpcnN0IGNhbGwsIHN1YnNlcXVlbnQgY2FsbHMgdG8gYSBDYW5jZWxGdW5jIGRvCiAgICBub3RoaW5nLg==", - "duration": 596322208 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc context.CancelFunc\n\npackage context // import \u0026#34;context\u0026#34;\n\ntype CancelFunc func()\n A CancelFunc tells an operation to abandon its work. A CancelFunc does not\n wait for the work to stop. A CancelFunc may be called by multiple goroutines\n simultaneously. After the first call, subsequent calls to a CancelFunc do\n nothing.", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "context.CancelFunc" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "cGFja2FnZSBjb250ZXh0IC8vIGltcG9ydCAiY29udGV4dCIKCnR5cGUgQ2FuY2VsRnVuYyBmdW5jKCkKICAgIEEgQ2FuY2VsRnVuYyB0ZWxscyBhbiBvcGVyYXRpb24gdG8gYWJhbmRvbiBpdHMgd29yay4gQSBDYW5jZWxGdW5jIGRvZXMgbm90CiAgICB3YWl0IGZvciB0aGUgd29yayB0byBzdG9wLiBBIENhbmNlbEZ1bmMgbWF5IGJlIGNhbGxlZCBieSBtdWx0aXBsZSBnb3JvdXRpbmVzCiAgICBzaW11bHRhbmVvdXNseS4gQWZ0ZXIgdGhlIGZpcnN0IGNhbGwsIHN1YnNlcXVlbnQgY2FsbHMgdG8gYSBDYW5jZWxGdW5jIGRvCiAgICBub3RoaW5nLg==", - "duration": 596322208 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "cancellation", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.18:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#CancelFunc", - "href": "https://pkg.go.dev/context#CancelFunc", - "target": "_blank" - }, - "file": "cancellation", - "nodes": [ - [ - { - "atom": "code", - "file": "cancellation", - "nodes": [ - { - "text": "context.CancelFunc", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ] - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#CancelFunc" - } - ], - { - "text": " function.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 18, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "h4", - "file": "cancellation.md", - "level": 4, - "nodes": [ - { - "text": "Idempotent Behavior", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - { - "atom": "blockquote", - "file": "cancellation.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "cancellation.md", - "nodes": [ - { - "text": "\"After the first call, subsequent calls to a CancelFunc do nothing\".", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "cancellation.md", - "nodes": [ - { - "text": "According to the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#CancelFunc", - "href": "https://pkg.go.dev/context#CancelFunc", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.CancelFunc", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#CancelFunc" - } - ], - { - "text": " documentation, the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#CancelFunc", - "href": "https://pkg.go.dev/context#CancelFunc", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.CancelFunc", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#CancelFunc" - } - ], - { - "text": " function is ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "href": "https://en.wikipedia.org/wiki/Idempotence", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "text": "idempotent", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "https://en.wikipedia.org/wiki/Idempotence" - } - ], - { - "text": ", ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-19" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-19" - }, - "nodes": [ - { - "text": "Listing 1.19", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-19" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ". That is, calling it multiple times has no effect beyond the first call.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-19", - "type": "listing" - }, - "file": "cancellation.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - { - "atom": "pre", - "file": "cancellation", - "nodes": [ - [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go" - }, - "file": "cancellation", - "lang": "go", - "nodes": [ - { - "text": "ctx, cancel := context.WithCancel(context.Background())\ncancel() // cancels the context\ncancel() // has no effect\ncancel() // has no effect\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ] - ], - "type": "*hype.Element" - }, - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "cancellation", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.19:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "The idempotent behavior of the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#CancelFunc", - "href": "https://pkg.go.dev/context#CancelFunc", - "target": "_blank" - }, - "file": "cancellation", - "nodes": [ - [ - { - "atom": "code", - "file": "cancellation", - "nodes": [ - { - "text": "context.CancelFunc", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ] - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#CancelFunc" - } - ], - { - "text": " function.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 19, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "h4", - "file": "cancellation.md", - "level": 4, - "nodes": [ - { - "text": "Leaking Resources", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - { - "atom": "blockquote", - "file": "cancellation.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "cancellation.md", - "nodes": [ - { - "text": "\"Canceling this context releases resources associated with it, so code should call cancel as soon as the operations running in this Context complete.\"", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "cancellation.md", - "nodes": [ - { - "text": "Often you will want to defer execution of the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#CancelFunc", - "href": "https://pkg.go.dev/context#CancelFunc", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.CancelFunc", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#CancelFunc" - } - ], - { - "text": " function until the function, or application, exits. This ensure proper shutdown of the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " and prevents the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " from leaking resources.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-20", - "type": "listing" - }, - "file": "cancellation.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - { - "atom": "pre", - "file": "cancellation", - "nodes": [ - [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go" - }, - "file": "cancellation", - "lang": "go", - "nodes": [ - { - "text": "ctx, cancel := context.WithCancel(context.Background())\n// ensure the cancel function is called at least once\n// to avoid leaking resources\ndefer cancel()\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ] - ], - "type": "*hype.Element" - }, - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "cancellation", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.20:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "Prevent leaking goroutines by calling the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#CancelFunc", - "href": "https://pkg.go.dev/context#CancelFunc", - "target": "_blank" - }, - "file": "cancellation", - "nodes": [ - [ - { - "atom": "code", - "file": "cancellation", - "nodes": [ - { - "text": "context.CancelFunc", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ] - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#CancelFunc" - } - ] - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 20, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - { - "atom": "blockquote", - "file": "cancellation.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "cancellation.md", - "nodes": [ - { - "atom": "strong", - "file": "cancellation.md", - "nodes": [ - { - "text": "ALWAYS", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " call the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#CancelFunc", - "href": "https://pkg.go.dev/context#CancelFunc", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.CancelFunc", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#CancelFunc" - } - ], - { - "text": " function when you no longer need the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": ". Failure to do so may cause your program to leak resources.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "h2", - "file": "cancellation.md", - "level": 2, - "nodes": [ - { - "text": "Cancelling a Context", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "cancellation.md", - "nodes": [ - { - "text": "Consider ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-21" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-21" - }, - "nodes": [ - { - "text": "Listing 1.21", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-21" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ". The ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "listener", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " function takes a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " as its first argument and an ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "int", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " representing the goroutine id as its second argument.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "cancellation.md", - "nodes": [ - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "listener", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " function will block until the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " is cancelled, which close the channel behind ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context.Done", - "href": "https://pkg.go.dev/context#Context.Done", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.Context.Done", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context.Done" - } - ], - { - "text": " method. This will unblock the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "listener", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " function and allow it to exit.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-21", - "type": "listing" - }, - "file": "cancellation.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - { - "atom": "pre", - "file": "cancellation", - "nodes": [ - [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "src": "cancellation/src/basic/main.go#listener" - }, - "lang": "go", - "nodes": [ - { - "content": "func listener(ctx context.Context, i int) {\n\tfmt.Printf(\"listener %d is waiting\\n\", i)\n\n\t// this will block until the context\n\t// given context is canceled\n\t\u003c-ctx.Done()\n\n\tfmt.Printf(\"listener %d is exiting\\n\", i)\n}", - "file": "cancellation/src/basic/main.go", - "lang": "go", - "name": "listener", - "start": 9, - "end": 20 - } - ], - "type": "*hype.SourceCode" - } - ] - ], - "type": "*hype.Element" - }, - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "cancellation", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.21:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "Blocking on ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context.Done", - "href": "https://pkg.go.dev/context#Context.Done", - "target": "_blank" - }, - "file": "cancellation", - "nodes": [ - [ - { - "atom": "code", - "file": "cancellation", - "nodes": [ - { - "text": "context.Context.Done", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ] - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context.Done" - } - ], - { - "text": ".", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 21, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "cancellation.md", - "nodes": [ - { - "text": "wThe application creates a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Background", - "href": "https://pkg.go.dev/context#Background", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.Background", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Background" - } - ], - { - "text": " context and then wraps it with a cancellable ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": ". The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#CancelFunc", - "href": "https://pkg.go.dev/context#CancelFunc", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.CancelFunc", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#CancelFunc" - } - ], - { - "text": " returned by is immediately deferred to ensure the application doesn't leak any resources.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "cancellation.md", - "nodes": [ - { - "text": "In ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-22" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-22" - }, - "nodes": [ - { - "text": "Listing 1.22", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-22" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": " we create several goroutines that will listen for the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " to be cancelled.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-22", - "type": "listing" - }, - "file": "cancellation.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - { - "atom": "pre", - "file": "cancellation", - "nodes": [ - [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "src": "cancellation/src/basic/main.go#main" - }, - "lang": "go", - "nodes": [ - { - "content": "func main() {\n\n\t// create a background context\n\tctx := context.Background()\n\n\t// wrap the context with the ability\n\t// to cancel it\n\tctx, cancel := context.WithCancel(ctx)\n\n\t// defer cancellation of the context\n\t// to ensure that any resources are\n\t// cleaned up regardless of how the\n\t// function exits\n\tdefer cancel()\n\n\t// create 5 listeners\n\tfor i := 0; i \u003c 5; i++ {\n\n\t\t// launch listener in a goroutine\n\t\tgo listener(ctx, i)\n\n\t}\n\n\t// allow the listeners to start\n\ttime.Sleep(time.Millisecond * 500)\n\n\tfmt.Println(\"canceling the context\")\n\n\t// cancel the context and tell the\n\t// listeners to exit\n\tcancel()\n\n\t// allow the listeners to exit\n\ttime.Sleep(time.Millisecond * 500)\n}", - "file": "cancellation/src/basic/main.go", - "lang": "go", - "name": "main", - "start": 22, - "end": 59 - } - ], - "type": "*hype.SourceCode" - } - ] - ], - "type": "*hype.Element" - }, - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "cancellation", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.22:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "Using context cancellation.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 22, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "cancellation.md", - "nodes": [ - { - "text": "As we can see from the output, ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-23" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-23" - }, - "nodes": [ - { - "text": "Listing 1.23", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-23" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ", the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "listener", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " function unblocks and exits when the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#CancelFunc", - "href": "https://pkg.go.dev/context#CancelFunc", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.CancelFunc", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#CancelFunc" - } - ], - { - "text": " is called, ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "cancel()", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": ".", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-23", - "type": "listing" - }, - "file": "cancellation.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "args": [ - "go", - "run", - "main.go" - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "exec": "go run main.go", - "run": "main.go", - "src": "cancellation/src/basic" - }, - "expected_exit": 0, - "file": "cancellation", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go run main.go\n\nlistener 4 is waiting\nlistener 0 is waiting\nlistener 2 is waiting\nlistener 3 is waiting\nlistener 1 is waiting\ncanceling the context\nlistener 4 is exiting\nlistener 0 is exiting\nlistener 2 is exiting\nlistener 3 is exiting\nlistener 1 is exiting", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "run", - "main.go" - ], - "dir": "testdata/doc/to_md/cancellation/src/basic", - "stdout": "bGlzdGVuZXIgNCBpcyB3YWl0aW5nCmxpc3RlbmVyIDAgaXMgd2FpdGluZwpsaXN0ZW5lciAyIGlzIHdhaXRpbmcKbGlzdGVuZXIgMyBpcyB3YWl0aW5nCmxpc3RlbmVyIDEgaXMgd2FpdGluZwpjYW5jZWxpbmcgdGhlIGNvbnRleHQKbGlzdGVuZXIgNCBpcyBleGl0aW5nCmxpc3RlbmVyIDAgaXMgZXhpdGluZwpsaXN0ZW5lciAyIGlzIGV4aXRpbmcKbGlzdGVuZXIgMyBpcyBleGl0aW5nCmxpc3RlbmVyIDEgaXMgZXhpdGluZw==", - "duration": 2289118625 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go run main.go\n\nlistener 4 is waiting\nlistener 0 is waiting\nlistener 2 is waiting\nlistener 3 is waiting\nlistener 1 is waiting\ncanceling the context\nlistener 4 is exiting\nlistener 0 is exiting\nlistener 2 is exiting\nlistener 3 is exiting\nlistener 1 is exiting", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "run", - "main.go" - ], - "dir": "testdata/doc/to_md/cancellation/src/basic", - "stdout": "bGlzdGVuZXIgNCBpcyB3YWl0aW5nCmxpc3RlbmVyIDAgaXMgd2FpdGluZwpsaXN0ZW5lciAyIGlzIHdhaXRpbmcKbGlzdGVuZXIgMyBpcyB3YWl0aW5nCmxpc3RlbmVyIDEgaXMgd2FpdGluZwpjYW5jZWxpbmcgdGhlIGNvbnRleHQKbGlzdGVuZXIgNCBpcyBleGl0aW5nCmxpc3RlbmVyIDAgaXMgZXhpdGluZwpsaXN0ZW5lciAyIGlzIGV4aXRpbmcKbGlzdGVuZXIgMyBpcyBleGl0aW5nCmxpc3RlbmVyIDEgaXMgZXhpdGluZw==", - "duration": 2289118625 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "cancellation", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.23:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "The output of the application.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 23, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "h3", - "file": "cancellation.md", - "level": 3, - "nodes": [ - { - "text": "Only Child Nodes of the Context are Cancelled", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "cancellation.md", - "nodes": [ - { - "text": "The illustration in ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-24" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-24" - }, - "nodes": [ - { - "text": "Listing 1.24", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-24" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": " shows that by cancelling a node in the hierarchy, all its child nodes are also cancelled. Other nodes, such as parent and sibling nodes, in the hierarchy are unaffected.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-24", - "type": "listing" - }, - "file": "cancellation.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "img", - "attributes": { - "src": "cancellation/assets/cancellation.svg" - }, - "file": "cancellation", - "type": "hype.Image" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "cancellation", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.24:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "Cancellation propagation.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 24, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "h2", - "file": "cancellation.md", - "level": 2, - "nodes": [ - { - "text": "Listening for Cancellation Confirmation", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "cancellation.md", - "nodes": [ - { - "text": "Previously, we have use ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "time#Sleep", - "href": "https://pkg.go.dev/time#Sleep", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "time.Sleep", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/time#Sleep" - } - ], - { - "text": " to block the execution of the program. This is not a good practice, as it can lead to deadlocks and other problems. Instead, the application should receive a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " cancellation confirmation.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "h3", - "file": "cancellation.md", - "level": 3, - "nodes": [ - { - "text": "Starting a Concurrent Monitor", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "cancellation.md", - "nodes": [ - { - "text": "Consider ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-25" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-25" - }, - "nodes": [ - { - "text": "Listing 1.25", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-25" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ". To start a ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "Monitor", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " we must use the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "Start", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " method giving it a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": ". In return, the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "Start", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " method returns a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " that can be listened to by the application to confirm the shutdown of the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "Monitor", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " later on.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-25", - "type": "listing" - }, - "file": "cancellation.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - { - "atom": "pre", - "file": "cancellation", - "nodes": [ - [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "src": "cancellation/src/cancelling/main.go#start" - }, - "lang": "go", - "nodes": [ - { - "content": "type Monitor struct {\n\tcancel context.CancelFunc\n}\n\nfunc (m *Monitor) Start(ctx context.Context) context.Context {\n\n\t// start the monitor with the given context\n\tgo m.listen(ctx)\n\n\t// create a new context that will be canceled\n\t// when the monitor is shut down\n\tctx, cancel := context.WithCancel(context.Background())\n\n\t// hold on to the cancellation function\n\t// when context that started the manager is canceled\n\t// this cancellation function will be called.\n\tm.cancel = cancel\n\n\t// return the new, cancellable, context.\n\t// clients can listen to this context\n\t// for cancellation to ensure the\n\t// monitor is properly shut down.\n\treturn ctx\n}", - "file": "cancellation/src/cancelling/main.go", - "lang": "go", - "name": "start", - "start": 10, - "end": 36 - } - ], - "type": "*hype.SourceCode" - } - ] - ], - "type": "*hype.Element" - }, - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "cancellation", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.25:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "Accepting a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "cancellation", - "nodes": [ - [ - { - "atom": "code", - "file": "cancellation", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ] - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " and returning a new one.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 25, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "cancellation.md", - "nodes": [ - { - "text": "To prevent the application from blocking, we launch a the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "listen", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " method in a goroutine with the given ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": ". Unless this ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " is cancelled, the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "listen", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " method will never stop and will continue to leak resources until the application exits.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "cancellation.md", - "nodes": [ - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#CancelFunc", - "href": "https://pkg.go.dev/context#CancelFunc", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.CancelFunc", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#CancelFunc" - } - ], - { - "text": " is held onto by the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "Manager", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " so when the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "Manager", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " is told to cancel by the client, it will also cancel the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "Monitor", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " context. This will tell the client that the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "Monitor", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " has been shutdown, confirming the cancellation of the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "Monitor", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": ".", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "h3", - "file": "cancellation.md", - "level": 3, - "nodes": [ - { - "text": "Monitor Checking", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "cancellation.md", - "nodes": [ - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "listen", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " method will block until the given ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": ", given by the application, is cancelled, ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-26" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-26" - }, - "nodes": [ - { - "text": "Listing 1.26", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-26" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ". We first make sure to defer the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#CancelFunc", - "href": "https://pkg.go.dev/context#CancelFunc", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.CancelFunc", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#CancelFunc" - } - ], - { - "text": " in the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "Monitor", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " to ensure that if any the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "listen", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " method exits for any reason, clients will be notified that the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "Monitor", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " has been shutdown.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-26", - "type": "listing" - }, - "file": "cancellation.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - { - "atom": "pre", - "file": "cancellation", - "nodes": [ - [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "src": "cancellation/src/cancelling/main.go#listen" - }, - "lang": "go", - "nodes": [ - { - "content": "func (m *Monitor) listen(ctx context.Context) {\n\tdefer m.cancel()\n\n\t// create a new ticker channel to listen to\n\ttick := time.NewTicker(time.Millisecond * 10)\n\tdefer tick.Stop()\n\n\t// use an infinite loop to continue to listen\n\t// to new messages after the select statement\n\t// has been executed\n\tfor {\n\t\tselect {\n\t\tcase \u003c-ctx.Done(): // listen for context cancellation\n\t\t\t// shut down if the context is canceled\n\t\t\tfmt.Println(\"shutting down monitor\")\n\n\t\t\t// if the monitor was told to shut down\n\t\t\t// then it should call its cancel function\n\t\t\t// so the client will know that the monitor\n\t\t\t// has properly shut down.\n\t\t\tm.cancel()\n\n\t\t\t// return from the function\n\t\t\treturn\n\t\tcase \u003c-tick.C: // listen to the ticker channel\n\t\t\t// and print a message every time it ticks\n\t\t\tfmt.Println(\"monitor check\")\n\t\t}\n\t}\n\n}", - "file": "cancellation/src/cancelling/main.go", - "lang": "go", - "name": "listen", - "start": 38, - "end": 71 - } - ], - "type": "*hype.SourceCode" - } - ] - ], - "type": "*hype.Element" - }, - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "cancellation", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.26:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "cancellation", - "nodes": [ - { - "text": "Monitor", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " will call its ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#CancelFunc", - "href": "https://pkg.go.dev/context#CancelFunc", - "target": "_blank" - }, - "file": "cancellation", - "nodes": [ - [ - { - "atom": "code", - "file": "cancellation", - "nodes": [ - { - "text": "context.CancelFunc", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ] - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#CancelFunc" - } - ], - { - "text": " if external ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "cancellation", - "nodes": [ - [ - { - "atom": "code", - "file": "cancellation", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ] - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " is cancelled.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 26, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "h3", - "file": "cancellation.md", - "level": 3, - "nodes": [ - { - "text": "Using the Cancellation Confirmation", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "cancellation.md", - "nodes": [ - { - "text": "In ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-27" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-27" - }, - "nodes": [ - { - "text": "Listing 1.27", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-27" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ", the application starts with a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Background", - "href": "https://pkg.go.dev/context#Background", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.Background", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Background" - } - ], - { - "text": " context and then wraps that with a cancellable ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": ". The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#CancelFunc", - "href": "https://pkg.go.dev/context#CancelFunc", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.CancelFunc", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#CancelFunc" - } - ], - { - "text": " returned by is immediately deferred to ensure the application doesn't leak any resources. After a short while, in a goroutine, the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "cancel", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " function is called, the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " is cancelled.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "cancellation.md", - "nodes": [ - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "Monitor", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " is then started our cancellable ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": ". The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " returned by the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "Start", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " method is listened to by the application. When the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "Monitor", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " is cancelled, the application will be unblocked and can exit. Alternatively, if the application is still running after a couple of seconds the application is forcibly terminated.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-27", - "type": "listing" - }, - "file": "cancellation.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - { - "atom": "pre", - "file": "cancellation", - "nodes": [ - [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "src": "cancellation/src/cancelling/main.go#main" - }, - "lang": "go", - "nodes": [ - { - "content": "func main() {\n\n\t// create a new background context\n\tctx := context.Background()\n\n\t// wrap the background context with a\n\t// cancellable context.\n\t// this context can be listened to any\n\t// children of this context for notification\n\t// of application shutdown/cancellation.\n\tctx, cancel := context.WithCancel(ctx)\n\n\t// ensure the cancel function is called\n\t// to shut down the monitor when the program\n\t// is exits\n\tdefer cancel()\n\n\t// launch a goroutine to cancel the application\n\t// context after a short while.\n\tgo func() {\n\t\ttime.Sleep(time.Millisecond * 50)\n\n\t\t// cancel the application context\n\t\t// this will shut the monitor down\n\t\tcancel()\n\t}()\n\n\t// create a new monitor\n\tmon := Monitor{}\n\n\t// start the monitor with the application context\n\t// this will return a context that can be listened to\n\t// for cancellation signaling the monitor has shut down.\n\tctx = mon.Start(ctx)\n\n\t// block the application until either the context\n\t// is canceled or the application times out\n\tselect {\n\tcase \u003c-ctx.Done(): // listen for context cancellation\n\t\t// success shutdown\n\t\tos.Exit(0)\n\tcase \u003c-time.After(time.Second * 2): // timeout after 2 second\n\t\tfmt.Println(\"timed out while trying to shut down the monitor\")\n\n\t\t// check if there was an error from the\n\t\t// monitor's context\n\t\tif err := ctx.Err(); err != nil {\n\t\t\tfmt.Printf(\"error: %s\\n\", err)\n\t\t}\n\n\t\t// non-successful shutdown\n\t\tos.Exit(1)\n\t}\n}", - "file": "cancellation/src/cancelling/main.go", - "lang": "go", - "name": "main", - "start": 73, - "end": 129 - } - ], - "type": "*hype.SourceCode" - } - ] - ], - "type": "*hype.Element" - }, - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "cancellation", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.27:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "Using the cancellation confirmation.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 27, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "cancellation.md", - "nodes": [ - { - "text": "As we can see from the output, in ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-28" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-28" - }, - "nodes": [ - { - "text": "Listing 1.28", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-28" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ", the application waits for the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "Monitor", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " to properly shutdown before exiting. We were also able to remove the use of ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "time#Sleep", - "href": "https://pkg.go.dev/time#Sleep", - "target": "_blank" - }, - "file": "cancellation.md", - "nodes": [ - { - "atom": "code", - "file": "cancellation.md", - "nodes": [ - { - "text": "time.Sleep", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/time#Sleep" - } - ], - { - "text": " to allow the monitor to finish.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - null, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-28", - "type": "listing" - }, - "file": "cancellation.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "args": [ - "go", - "run", - "main.go" - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "exec": "go run main.go", - "run": "main.go", - "src": "cancellation/src/cancelling" - }, - "expected_exit": 0, - "file": "cancellation", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go run main.go\n\nmonitor check\nmonitor check\nmonitor check\nmonitor check\nmonitor check\nshutting down monitor", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "run", - "main.go" - ], - "dir": "testdata/doc/to_md/cancellation/src/cancelling", - "stdout": "bW9uaXRvciBjaGVjawptb25pdG9yIGNoZWNrCm1vbml0b3IgY2hlY2sKbW9uaXRvciBjaGVjawptb25pdG9yIGNoZWNrCnNodXR0aW5nIGRvd24gbW9uaXRvcg==", - "duration": 1646920208 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go run main.go\n\nmonitor check\nmonitor check\nmonitor check\nmonitor check\nmonitor check\nshutting down monitor", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "run", - "main.go" - ], - "dir": "testdata/doc/to_md/cancellation/src/cancelling", - "stdout": "bW9uaXRvciBjaGVjawptb25pdG9yIGNoZWNrCm1vbml0b3IgY2hlY2sKbW9uaXRvciBjaGVjawptb25pdG9yIGNoZWNrCnNodXR0aW5nIGRvd24gbW9uaXRvcg==", - "duration": 1646920208 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "cancellation", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.28:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "The output of the application.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 28, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "title": "Cancellation Propagation with Contexts", - "type": "*hype.Page" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "type": "*hype.Include" - } - ], - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "include", - "attributes": { - "src": "timeouts/timeouts.md" - }, - "dir": "timeouts", - "file": "module.md", - "nodes": [ - [ - { - "atom": "page", - "file": "timeouts.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "h1", - "file": "timeouts.md", - "level": 1, - "nodes": [ - { - "text": "Timeouts and Deadlines", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "timeouts.md", - "nodes": [ - { - "text": "In addition to allowing us to manually cancel a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "timeouts.md", - "nodes": [ - { - "atom": "code", - "file": "timeouts.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": ", the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context", - "href": "https://pkg.go.dev/context", - "target": "_blank" - }, - "file": "timeouts.md", - "nodes": [ - { - "atom": "code", - "file": "timeouts.md", - "nodes": [ - { - "text": "context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context" - } - ], - { - "text": " package also provides mechanisms for creating a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "timeouts.md", - "nodes": [ - { - "atom": "code", - "file": "timeouts.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " that will self-cancel after, or at, a given time. Using these mechanics allows us to control how long to run some before we give up and assume that the operation has failed.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "h2", - "file": "timeouts.md", - "level": 2, - "nodes": [ - { - "text": "Cancelling at a Specific Time", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "timeouts.md", - "nodes": [ - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context", - "href": "https://pkg.go.dev/context", - "target": "_blank" - }, - "file": "timeouts.md", - "nodes": [ - { - "atom": "code", - "file": "timeouts.md", - "nodes": [ - { - "text": "context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context" - } - ], - { - "text": " package provides two functions for creating time based, self-cancelling, a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "timeouts.md", - "nodes": [ - { - "atom": "code", - "file": "timeouts.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": "; ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#WithTimeout", - "href": "https://pkg.go.dev/context#WithTimeout", - "target": "_blank" - }, - "file": "timeouts.md", - "nodes": [ - { - "atom": "code", - "file": "timeouts.md", - "nodes": [ - { - "text": "context.WithTimeout", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#WithTimeout" - } - ], - { - "text": " and ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#WithDeadline", - "href": "https://pkg.go.dev/context#WithDeadline", - "target": "_blank" - }, - "file": "timeouts.md", - "nodes": [ - { - "atom": "code", - "file": "timeouts.md", - "nodes": [ - { - "text": "context.WithDeadline", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#WithDeadline" - } - ], - { - "text": ".", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-29", - "type": "listing" - }, - "file": "timeouts.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "args": [ - "go", - "doc", - "context.WithDeadline" - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "doc": "context.WithDeadline", - "exec": "go doc context.WithDeadline" - }, - "expected_exit": 0, - "file": "timeouts", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc context.WithDeadline\n\npackage context // import \u0026#34;context\u0026#34;\n\nfunc WithDeadline(parent Context, d time.Time) (Context, CancelFunc)\n WithDeadline returns a copy of the parent context with the deadline\n adjusted to be no later than d. If the parent\u0026#39;s deadline is already earlier\n than d, WithDeadline(parent, d) is semantically equivalent to parent.\n The returned context\u0026#39;s Done channel is closed when the deadline expires,\n when the returned cancel function is called, or when the parent context\u0026#39;s\n Done channel is closed, whichever happens first.\n\n Canceling this context releases resources associated with it, so code should\n call cancel as soon as the operations running in this Context complete.", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "context.WithDeadline" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "cGFja2FnZSBjb250ZXh0IC8vIGltcG9ydCAiY29udGV4dCIKCmZ1bmMgV2l0aERlYWRsaW5lKHBhcmVudCBDb250ZXh0LCBkIHRpbWUuVGltZSkgKENvbnRleHQsIENhbmNlbEZ1bmMpCiAgICBXaXRoRGVhZGxpbmUgcmV0dXJucyBhIGNvcHkgb2YgdGhlIHBhcmVudCBjb250ZXh0IHdpdGggdGhlIGRlYWRsaW5lCiAgICBhZGp1c3RlZCB0byBiZSBubyBsYXRlciB0aGFuIGQuIElmIHRoZSBwYXJlbnQncyBkZWFkbGluZSBpcyBhbHJlYWR5IGVhcmxpZXIKICAgIHRoYW4gZCwgV2l0aERlYWRsaW5lKHBhcmVudCwgZCkgaXMgc2VtYW50aWNhbGx5IGVxdWl2YWxlbnQgdG8gcGFyZW50LgogICAgVGhlIHJldHVybmVkIGNvbnRleHQncyBEb25lIGNoYW5uZWwgaXMgY2xvc2VkIHdoZW4gdGhlIGRlYWRsaW5lIGV4cGlyZXMsCiAgICB3aGVuIHRoZSByZXR1cm5lZCBjYW5jZWwgZnVuY3Rpb24gaXMgY2FsbGVkLCBvciB3aGVuIHRoZSBwYXJlbnQgY29udGV4dCdzCiAgICBEb25lIGNoYW5uZWwgaXMgY2xvc2VkLCB3aGljaGV2ZXIgaGFwcGVucyBmaXJzdC4KCiAgICBDYW5jZWxpbmcgdGhpcyBjb250ZXh0IHJlbGVhc2VzIHJlc291cmNlcyBhc3NvY2lhdGVkIHdpdGggaXQsIHNvIGNvZGUgc2hvdWxkCiAgICBjYWxsIGNhbmNlbCBhcyBzb29uIGFzIHRoZSBvcGVyYXRpb25zIHJ1bm5pbmcgaW4gdGhpcyBDb250ZXh0IGNvbXBsZXRlLg==", - "duration": 624793125 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc context.WithDeadline\n\npackage context // import \u0026#34;context\u0026#34;\n\nfunc WithDeadline(parent Context, d time.Time) (Context, CancelFunc)\n WithDeadline returns a copy of the parent context with the deadline\n adjusted to be no later than d. If the parent\u0026#39;s deadline is already earlier\n than d, WithDeadline(parent, d) is semantically equivalent to parent.\n The returned context\u0026#39;s Done channel is closed when the deadline expires,\n when the returned cancel function is called, or when the parent context\u0026#39;s\n Done channel is closed, whichever happens first.\n\n Canceling this context releases resources associated with it, so code should\n call cancel as soon as the operations running in this Context complete.", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "context.WithDeadline" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "cGFja2FnZSBjb250ZXh0IC8vIGltcG9ydCAiY29udGV4dCIKCmZ1bmMgV2l0aERlYWRsaW5lKHBhcmVudCBDb250ZXh0LCBkIHRpbWUuVGltZSkgKENvbnRleHQsIENhbmNlbEZ1bmMpCiAgICBXaXRoRGVhZGxpbmUgcmV0dXJucyBhIGNvcHkgb2YgdGhlIHBhcmVudCBjb250ZXh0IHdpdGggdGhlIGRlYWRsaW5lCiAgICBhZGp1c3RlZCB0byBiZSBubyBsYXRlciB0aGFuIGQuIElmIHRoZSBwYXJlbnQncyBkZWFkbGluZSBpcyBhbHJlYWR5IGVhcmxpZXIKICAgIHRoYW4gZCwgV2l0aERlYWRsaW5lKHBhcmVudCwgZCkgaXMgc2VtYW50aWNhbGx5IGVxdWl2YWxlbnQgdG8gcGFyZW50LgogICAgVGhlIHJldHVybmVkIGNvbnRleHQncyBEb25lIGNoYW5uZWwgaXMgY2xvc2VkIHdoZW4gdGhlIGRlYWRsaW5lIGV4cGlyZXMsCiAgICB3aGVuIHRoZSByZXR1cm5lZCBjYW5jZWwgZnVuY3Rpb24gaXMgY2FsbGVkLCBvciB3aGVuIHRoZSBwYXJlbnQgY29udGV4dCdzCiAgICBEb25lIGNoYW5uZWwgaXMgY2xvc2VkLCB3aGljaGV2ZXIgaGFwcGVucyBmaXJzdC4KCiAgICBDYW5jZWxpbmcgdGhpcyBjb250ZXh0IHJlbGVhc2VzIHJlc291cmNlcyBhc3NvY2lhdGVkIHdpdGggaXQsIHNvIGNvZGUgc2hvdWxkCiAgICBjYWxsIGNhbmNlbCBhcyBzb29uIGFzIHRoZSBvcGVyYXRpb25zIHJ1bm5pbmcgaW4gdGhpcyBDb250ZXh0IGNvbXBsZXRlLg==", - "duration": 624793125 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "timeouts", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.29:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#WithDeadline", - "href": "https://pkg.go.dev/context#WithDeadline", - "target": "_blank" - }, - "file": "timeouts", - "nodes": [ - [ - { - "atom": "code", - "file": "timeouts", - "nodes": [ - { - "text": "context.WithDeadline", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ] - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#WithDeadline" - } - ], - { - "text": " function.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 29, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "timeouts.md", - "nodes": [ - { - "text": "When using ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#WithDeadline", - "href": "https://pkg.go.dev/context#WithDeadline", - "target": "_blank" - }, - "file": "timeouts.md", - "nodes": [ - { - "atom": "code", - "file": "timeouts.md", - "nodes": [ - { - "text": "context.WithDeadline", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#WithDeadline" - } - ], - { - "text": ", ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-29" - }, - "file": "timeouts.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-29" - }, - "nodes": [ - { - "text": "Listing 1.29", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-29" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ", we need to provide an ", - "type": "hype.Text" - }, - { - "atom": "strong", - "file": "timeouts.md", - "nodes": [ - { - "text": "absolute", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " time at which the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "timeouts.md", - "nodes": [ - { - "atom": "code", - "file": "timeouts.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " should be cancelled. That means we need an exact date/time we want this ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "timeouts.md", - "nodes": [ - { - "atom": "code", - "file": "timeouts.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " to be cancelled, for example ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "timeouts.md", - "nodes": [ - { - "text": "March 14, 2029 3:45pm", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": ".", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "timeouts.md", - "nodes": [ - { - "text": "Consider ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-30" - }, - "file": "timeouts.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-30" - }, - "nodes": [ - { - "text": "Listing 1.30", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-30" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ". In it, we create a new ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "time#Time", - "href": "https://pkg.go.dev/time#Time", - "target": "_blank" - }, - "file": "timeouts.md", - "nodes": [ - { - "atom": "code", - "file": "timeouts.md", - "nodes": [ - { - "text": "time.Time", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/time#Time" - } - ], - { - "text": " for ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "timeouts.md", - "nodes": [ - { - "text": "January 1, 2030 00:00:00", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " and use it to create a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "timeouts.md", - "nodes": [ - { - "atom": "code", - "file": "timeouts.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " that will self-cancel at that date and time.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-30", - "type": "listing" - }, - "file": "timeouts.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - { - "atom": "pre", - "file": "timeouts", - "nodes": [ - [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "src": "timeouts/src/with-deadline/main.go#example" - }, - "lang": "go", - "nodes": [ - { - "content": "func main() {\n\n\t// create a background context\n\tctx := context.Background()\n\n\t// create an absolute date/time (January 1, 2030)\n\tdeadline := time.Date(2030, 1, 1, 0, 0, 0, 0, time.UTC)\n\tfmt.Println(\"deadline:\", deadline.Format(time.RFC3339))\n\n\t// create a new context with a deadline\n\t// that will cancel at January 1, 2030 00:00:00.\n\tctx, cancel := context.WithDeadline(ctx, deadline)\n\tdefer cancel()\n\n\tprint(ctx)\n}", - "file": "timeouts/src/with-deadline/main.go", - "lang": "go", - "name": "example", - "start": 12, - "end": 30 - } - ], - "type": "*hype.SourceCode" - } - ] - ], - "type": "*hype.Element" - }, - { - "atom": "hr", - "file": "timeouts", - "type": "*hype.Element" - }, - [ - { - "args": [ - "go", - "run", - "." - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "exec": "go run .", - "run": ".", - "src": "timeouts/src/with-deadline" - }, - "expected_exit": 0, - "file": "timeouts", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go run .\n\ndeadline: 2030-01-01T00:00:00Z\nWithTimeout(deadline: {wall:0 ext:64029052800 loc:\u0026lt;nil\u0026gt;})\n\t--\u0026gt; Background", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "run", - "." - ], - "dir": "testdata/doc/to_md/timeouts/src/with-deadline", - "stdout": "ZGVhZGxpbmU6IDIwMzAtMDEtMDFUMDA6MDA6MDBaCldpdGhUaW1lb3V0KGRlYWRsaW5lOiB7d2FsbDowIGV4dDo2NDAyOTA1MjgwMCBsb2M6PG5pbD59KQoJLS0+IEJhY2tncm91bmQ=", - "duration": 1410923584 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go run .\n\ndeadline: 2030-01-01T00:00:00Z\nWithTimeout(deadline: {wall:0 ext:64029052800 loc:\u0026lt;nil\u0026gt;})\n\t--\u0026gt; Background", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "run", - "." - ], - "dir": "testdata/doc/to_md/timeouts/src/with-deadline", - "stdout": "ZGVhZGxpbmU6IDIwMzAtMDEtMDFUMDA6MDA6MDBaCldpdGhUaW1lb3V0KGRlYWRsaW5lOiB7d2FsbDowIGV4dDo2NDAyOTA1MjgwMCBsb2M6PG5pbD59KQoJLS0+IEJhY2tncm91bmQ=", - "duration": 1410923584 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "timeouts", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.30:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "Using ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#WithDeadline", - "href": "https://pkg.go.dev/context#WithDeadline", - "target": "_blank" - }, - "file": "timeouts", - "nodes": [ - [ - { - "atom": "code", - "file": "timeouts", - "nodes": [ - { - "text": "context.WithDeadline", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ] - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#WithDeadline" - } - ], - { - "text": ".", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 30, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "h2", - "file": "timeouts.md", - "level": 2, - "nodes": [ - { - "text": "Cancelling After a Duration", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "timeouts.md", - "nodes": [ - { - "text": "While being able to cancel a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "timeouts.md", - "nodes": [ - { - "atom": "code", - "file": "timeouts.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " at a particular time is useful, more often than not we want to cancel a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "timeouts.md", - "nodes": [ - { - "atom": "code", - "file": "timeouts.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " after a certain amount of time has passed.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-31", - "type": "listing" - }, - "file": "timeouts.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "args": [ - "go", - "doc", - "context.WithTimeout" - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "doc": "context.WithTimeout", - "exec": "go doc context.WithTimeout" - }, - "expected_exit": 0, - "file": "timeouts", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc context.WithTimeout\n\npackage context // import \u0026#34;context\u0026#34;\n\nfunc WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)\n WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).\n\n Canceling this context releases resources associated with it, so code should\n call cancel as soon as the operations running in this Context complete:\n\n func slowOperationWithTimeout(ctx context.Context) (Result, error) {\n \tctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)\n \tdefer cancel() // releases resources if slowOperation completes before timeout elapses\n \treturn slowOperation(ctx)\n }", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "context.WithTimeout" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "cGFja2FnZSBjb250ZXh0IC8vIGltcG9ydCAiY29udGV4dCIKCmZ1bmMgV2l0aFRpbWVvdXQocGFyZW50IENvbnRleHQsIHRpbWVvdXQgdGltZS5EdXJhdGlvbikgKENvbnRleHQsIENhbmNlbEZ1bmMpCiAgICBXaXRoVGltZW91dCByZXR1cm5zIFdpdGhEZWFkbGluZShwYXJlbnQsIHRpbWUuTm93KCkuQWRkKHRpbWVvdXQpKS4KCiAgICBDYW5jZWxpbmcgdGhpcyBjb250ZXh0IHJlbGVhc2VzIHJlc291cmNlcyBhc3NvY2lhdGVkIHdpdGggaXQsIHNvIGNvZGUgc2hvdWxkCiAgICBjYWxsIGNhbmNlbCBhcyBzb29uIGFzIHRoZSBvcGVyYXRpb25zIHJ1bm5pbmcgaW4gdGhpcyBDb250ZXh0IGNvbXBsZXRlOgoKICAgICAgICBmdW5jIHNsb3dPcGVyYXRpb25XaXRoVGltZW91dChjdHggY29udGV4dC5Db250ZXh0KSAoUmVzdWx0LCBlcnJvcikgewogICAgICAgIAljdHgsIGNhbmNlbCA6PSBjb250ZXh0LldpdGhUaW1lb3V0KGN0eCwgMTAwKnRpbWUuTWlsbGlzZWNvbmQpCiAgICAgICAgCWRlZmVyIGNhbmNlbCgpICAvLyByZWxlYXNlcyByZXNvdXJjZXMgaWYgc2xvd09wZXJhdGlvbiBjb21wbGV0ZXMgYmVmb3JlIHRpbWVvdXQgZWxhcHNlcwogICAgICAgIAlyZXR1cm4gc2xvd09wZXJhdGlvbihjdHgpCiAgICAgICAgfQ==", - "duration": 554551833 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc context.WithTimeout\n\npackage context // import \u0026#34;context\u0026#34;\n\nfunc WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)\n WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).\n\n Canceling this context releases resources associated with it, so code should\n call cancel as soon as the operations running in this Context complete:\n\n func slowOperationWithTimeout(ctx context.Context) (Result, error) {\n \tctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)\n \tdefer cancel() // releases resources if slowOperation completes before timeout elapses\n \treturn slowOperation(ctx)\n }", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "context.WithTimeout" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "cGFja2FnZSBjb250ZXh0IC8vIGltcG9ydCAiY29udGV4dCIKCmZ1bmMgV2l0aFRpbWVvdXQocGFyZW50IENvbnRleHQsIHRpbWVvdXQgdGltZS5EdXJhdGlvbikgKENvbnRleHQsIENhbmNlbEZ1bmMpCiAgICBXaXRoVGltZW91dCByZXR1cm5zIFdpdGhEZWFkbGluZShwYXJlbnQsIHRpbWUuTm93KCkuQWRkKHRpbWVvdXQpKS4KCiAgICBDYW5jZWxpbmcgdGhpcyBjb250ZXh0IHJlbGVhc2VzIHJlc291cmNlcyBhc3NvY2lhdGVkIHdpdGggaXQsIHNvIGNvZGUgc2hvdWxkCiAgICBjYWxsIGNhbmNlbCBhcyBzb29uIGFzIHRoZSBvcGVyYXRpb25zIHJ1bm5pbmcgaW4gdGhpcyBDb250ZXh0IGNvbXBsZXRlOgoKICAgICAgICBmdW5jIHNsb3dPcGVyYXRpb25XaXRoVGltZW91dChjdHggY29udGV4dC5Db250ZXh0KSAoUmVzdWx0LCBlcnJvcikgewogICAgICAgIAljdHgsIGNhbmNlbCA6PSBjb250ZXh0LldpdGhUaW1lb3V0KGN0eCwgMTAwKnRpbWUuTWlsbGlzZWNvbmQpCiAgICAgICAgCWRlZmVyIGNhbmNlbCgpICAvLyByZWxlYXNlcyByZXNvdXJjZXMgaWYgc2xvd09wZXJhdGlvbiBjb21wbGV0ZXMgYmVmb3JlIHRpbWVvdXQgZWxhcHNlcwogICAgICAgIAlyZXR1cm4gc2xvd09wZXJhdGlvbihjdHgpCiAgICAgICAgfQ==", - "duration": 554551833 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "timeouts", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.31:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#WithTimeout", - "href": "https://pkg.go.dev/context#WithTimeout", - "target": "_blank" - }, - "file": "timeouts", - "nodes": [ - [ - { - "atom": "code", - "file": "timeouts", - "nodes": [ - { - "text": "context.WithTimeout", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ] - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#WithTimeout" - } - ], - { - "text": " function.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 31, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "timeouts.md", - "nodes": [ - { - "text": "When using ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#WithTimeout", - "href": "https://pkg.go.dev/context#WithTimeout", - "target": "_blank" - }, - "file": "timeouts.md", - "nodes": [ - { - "atom": "code", - "file": "timeouts.md", - "nodes": [ - { - "text": "context.WithTimeout", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#WithTimeout" - } - ], - { - "text": ", ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-31" - }, - "file": "timeouts.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-31" - }, - "nodes": [ - { - "text": "Listing 1.31", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-31" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": "a, we need to provide an ", - "type": "hype.Text" - }, - { - "atom": "strong", - "file": "timeouts.md", - "nodes": [ - { - "text": "relative", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "time#Duration", - "href": "https://pkg.go.dev/time#Duration", - "target": "_blank" - }, - "file": "timeouts.md", - "nodes": [ - { - "atom": "code", - "file": "timeouts.md", - "nodes": [ - { - "text": "time.Duration", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/time#Duration" - } - ], - { - "text": " at which the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "timeouts.md", - "nodes": [ - { - "atom": "code", - "file": "timeouts.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " should be cancelled.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "timeouts.md", - "nodes": [ - { - "text": "Consider ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-32" - }, - "file": "timeouts.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-32" - }, - "nodes": [ - { - "text": "Listing 1.32", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-32" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ". In it, we create a new self-cancelling ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "timeouts.md", - "nodes": [ - { - "atom": "code", - "file": "timeouts.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " that will self-cancel after 5 seconds using ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#WithTimeout", - "href": "https://pkg.go.dev/context#WithTimeout", - "target": "_blank" - }, - "file": "timeouts.md", - "nodes": [ - { - "atom": "code", - "file": "timeouts.md", - "nodes": [ - { - "text": "context.WithTimeout", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#WithTimeout" - } - ], - { - "text": ".", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-32", - "type": "listing" - }, - "file": "timeouts.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - { - "atom": "pre", - "file": "timeouts", - "nodes": [ - [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "src": "timeouts/src/with-timeout/main.go#example" - }, - "lang": "go", - "nodes": [ - { - "content": "func main() {\n\n\t// create a background context\n\tctx := context.Background()\n\n\t// create a new context with a timeout\n\t// that will cancel the context after 10ms\n\t// \tequivalent to:\n\t//\t\tcontext.WithDeadline(ctx, time.Now().Add(10 *time.Millisecond))\n\tctx, cancel := context.WithTimeout(ctx, 10*time.Millisecond)\n\tdefer cancel()\n\n\tprint(ctx)\n}", - "file": "timeouts/src/with-timeout/main.go", - "lang": "go", - "name": "example", - "start": 11, - "end": 27 - } - ], - "type": "*hype.SourceCode" - } - ] - ], - "type": "*hype.Element" - }, - { - "atom": "hr", - "file": "timeouts", - "type": "*hype.Element" - }, - [ - { - "args": [ - "go", - "run", - "." - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "exec": "go run .", - "run": ".", - "src": "timeouts/src/with-timeout" - }, - "expected_exit": 0, - "file": "timeouts", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go run .\n\nWithTimeout(deadline: {wall:13909887561208072904 ext:10321709 loc:0x1047306c0})\n\t--\u0026gt; Background", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "run", - "." - ], - "dir": "testdata/doc/to_md/timeouts/src/with-timeout", - "stdout": "V2l0aFRpbWVvdXQoZGVhZGxpbmU6IHt3YWxsOjEzOTA5ODg3NTYxMjA4MDcyOTA0IGV4dDoxMDMyMTcwOSBsb2M6MHgxMDQ3MzA2YzB9KQoJLS0+IEJhY2tncm91bmQ=", - "duration": 1627207625 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go run .\n\nWithTimeout(deadline: {wall:13909887561208072904 ext:10321709 loc:0x1047306c0})\n\t--\u0026gt; Background", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "run", - "." - ], - "dir": "testdata/doc/to_md/timeouts/src/with-timeout", - "stdout": "V2l0aFRpbWVvdXQoZGVhZGxpbmU6IHt3YWxsOjEzOTA5ODg3NTYxMjA4MDcyOTA0IGV4dDoxMDMyMTcwOSBsb2M6MHgxMDQ3MzA2YzB9KQoJLS0+IEJhY2tncm91bmQ=", - "duration": 1627207625 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "timeouts", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.32:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "Using ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#WithTimeout", - "href": "https://pkg.go.dev/context#WithTimeout", - "target": "_blank" - }, - "file": "timeouts", - "nodes": [ - [ - { - "atom": "code", - "file": "timeouts", - "nodes": [ - { - "text": "context.WithTimeout", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ] - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#WithTimeout" - } - ], - { - "text": ".", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 32, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "timeouts.md", - "nodes": [ - { - "text": "Functionally, we could have used ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#WithDeadline", - "href": "https://pkg.go.dev/context#WithDeadline", - "target": "_blank" - }, - "file": "timeouts.md", - "nodes": [ - { - "atom": "code", - "file": "timeouts.md", - "nodes": [ - { - "text": "context.WithDeadline", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#WithDeadline" - } - ], - { - "text": " instead, but ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#WithTimeout", - "href": "https://pkg.go.dev/context#WithTimeout", - "target": "_blank" - }, - "file": "timeouts.md", - "nodes": [ - { - "atom": "code", - "file": "timeouts.md", - "nodes": [ - { - "text": "context.WithTimeout", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#WithTimeout" - } - ], - { - "text": " is more convenient when we want to cancel a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "timeouts.md", - "nodes": [ - { - "atom": "code", - "file": "timeouts.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " after a certain amount of time has passed.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "title": "Timeouts and Deadlines", - "type": "*hype.Page" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "type": "*hype.Include" - } - ], - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "include", - "attributes": { - "src": "errors/errors.md" - }, - "dir": "errors", - "file": "module.md", - "nodes": [ - [ - { - "atom": "page", - "file": "errors.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "h1", - "file": "errors.md", - "level": 1, - "nodes": [ - { - "text": "Context Errors", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "errors.md", - "nodes": [ - { - "text": "In a complex system, or even in a small one, when a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "code", - "file": "errors.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " is cancelled, we need a way to know what caused the cancellation. It is possible that ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "code", - "file": "errors.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " was cancelled by a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#CancelFunc", - "href": "https://pkg.go.dev/context#CancelFunc", - "target": "_blank" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "code", - "file": "errors.md", - "nodes": [ - { - "text": "context.CancelFunc", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#CancelFunc" - } - ], - { - "text": " successfully, if it was cancelled because it timed out, or some other reason.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "errors.md", - "nodes": [ - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context.Err", - "href": "https://pkg.go.dev/context#Context.Err", - "target": "_blank" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "code", - "file": "errors.md", - "nodes": [ - { - "text": "context.Context.Err", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context.Err" - } - ], - { - "text": " method, ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-33" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-33" - }, - "nodes": [ - { - "text": "Listing 1.33", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-33" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": " returns the error that caused the context to be cancelled.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-33", - "type": "listing" - }, - "file": "errors.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "args": [ - "go", - "doc", - "context.Context.Err" - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "doc": "context.Context.Err", - "exec": "go doc context.Context.Err" - }, - "expected_exit": 0, - "file": "errors", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc context.Context.Err\n\npackage context // import \u0026#34;context\u0026#34;\n\ntype Context interface {\n\n\t// If Done is not yet closed, Err returns nil.\n\t// If Done is closed, Err returns a non-nil error explaining why:\n\t// Canceled if the context was canceled\n\t// or DeadlineExceeded if the context\u0026#39;s deadline passed.\n\t// After Err returns a non-nil error, successive calls to Err return the same error.\n\tErr() error\n}", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "context.Context.Err" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "cGFja2FnZSBjb250ZXh0IC8vIGltcG9ydCAiY29udGV4dCIKCnR5cGUgQ29udGV4dCBpbnRlcmZhY2UgewoKCS8vIElmIERvbmUgaXMgbm90IHlldCBjbG9zZWQsIEVyciByZXR1cm5zIG5pbC4KCS8vIElmIERvbmUgaXMgY2xvc2VkLCBFcnIgcmV0dXJucyBhIG5vbi1uaWwgZXJyb3IgZXhwbGFpbmluZyB3aHk6CgkvLyBDYW5jZWxlZCBpZiB0aGUgY29udGV4dCB3YXMgY2FuY2VsZWQKCS8vIG9yIERlYWRsaW5lRXhjZWVkZWQgaWYgdGhlIGNvbnRleHQncyBkZWFkbGluZSBwYXNzZWQuCgkvLyBBZnRlciBFcnIgcmV0dXJucyBhIG5vbi1uaWwgZXJyb3IsIHN1Y2Nlc3NpdmUgY2FsbHMgdG8gRXJyIHJldHVybiB0aGUgc2FtZSBlcnJvci4KCUVycigpIGVycm9yCn0=", - "duration": 614159916 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc context.Context.Err\n\npackage context // import \u0026#34;context\u0026#34;\n\ntype Context interface {\n\n\t// If Done is not yet closed, Err returns nil.\n\t// If Done is closed, Err returns a non-nil error explaining why:\n\t// Canceled if the context was canceled\n\t// or DeadlineExceeded if the context\u0026#39;s deadline passed.\n\t// After Err returns a non-nil error, successive calls to Err return the same error.\n\tErr() error\n}", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "context.Context.Err" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "cGFja2FnZSBjb250ZXh0IC8vIGltcG9ydCAiY29udGV4dCIKCnR5cGUgQ29udGV4dCBpbnRlcmZhY2UgewoKCS8vIElmIERvbmUgaXMgbm90IHlldCBjbG9zZWQsIEVyciByZXR1cm5zIG5pbC4KCS8vIElmIERvbmUgaXMgY2xvc2VkLCBFcnIgcmV0dXJucyBhIG5vbi1uaWwgZXJyb3IgZXhwbGFpbmluZyB3aHk6CgkvLyBDYW5jZWxlZCBpZiB0aGUgY29udGV4dCB3YXMgY2FuY2VsZWQKCS8vIG9yIERlYWRsaW5lRXhjZWVkZWQgaWYgdGhlIGNvbnRleHQncyBkZWFkbGluZSBwYXNzZWQuCgkvLyBBZnRlciBFcnIgcmV0dXJucyBhIG5vbi1uaWwgZXJyb3IsIHN1Y2Nlc3NpdmUgY2FsbHMgdG8gRXJyIHJldHVybiB0aGUgc2FtZSBlcnJvci4KCUVycigpIGVycm9yCn0=", - "duration": 614159916 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "errors", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.33:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context.Err", - "href": "https://pkg.go.dev/context#Context.Err", - "target": "_blank" - }, - "file": "errors", - "nodes": [ - [ - { - "atom": "code", - "file": "errors", - "nodes": [ - { - "text": "context.Context.Err", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ] - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context.Err" - } - ], - { - "text": " method.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 33, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "h2", - "file": "errors.md", - "level": 2, - "nodes": [ - { - "text": "Context Cancelled Error", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "errors.md", - "nodes": [ - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context", - "href": "https://pkg.go.dev/context", - "target": "_blank" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "code", - "file": "errors.md", - "nodes": [ - { - "text": "context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context" - } - ], - { - "text": " package defines two different ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "builtin#error", - "href": "https://pkg.go.dev/builtin#error", - "target": "_blank" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "code", - "file": "errors.md", - "nodes": [ - { - "text": "error", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/builtin#error" - } - ], - { - "text": " variables that can be used to check an ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "builtin#error", - "href": "https://pkg.go.dev/builtin#error", - "target": "_blank" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "code", - "file": "errors.md", - "nodes": [ - { - "text": "error", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/builtin#error" - } - ], - { - "text": " that was returned from ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context.Err", - "href": "https://pkg.go.dev/context#Context.Err", - "target": "_blank" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "code", - "file": "errors.md", - "nodes": [ - { - "text": "context.Context.Err", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context.Err" - } - ], - { - "text": " method.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "errors.md", - "nodes": [ - { - "text": "The first is ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Canceled", - "href": "https://pkg.go.dev/context#Canceled", - "target": "_blank" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "code", - "file": "errors.md", - "nodes": [ - { - "text": "context.Canceled", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Canceled" - } - ], - { - "text": ", ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-34" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-34" - }, - "nodes": [ - { - "text": "Listing 1.34", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-34" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ", which is returned when the context is cancelled through the use of a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#CancelFunc", - "href": "https://pkg.go.dev/context#CancelFunc", - "target": "_blank" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "code", - "file": "errors.md", - "nodes": [ - { - "text": "context.CancelFunc", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#CancelFunc" - } - ], - { - "text": " function. This ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "builtin#error", - "href": "https://pkg.go.dev/builtin#error", - "target": "_blank" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "code", - "file": "errors.md", - "nodes": [ - { - "text": "error", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/builtin#error" - } - ], - { - "text": " is considered to indicate a \"successful\" cancellation.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-34", - "type": "listing" - }, - "file": "errors.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "args": [ - "go", - "doc", - "context.Canceled" - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "doc": "context.Canceled", - "exec": "go doc context.Canceled" - }, - "expected_exit": 0, - "file": "errors", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc context.Canceled\n\npackage context // import \u0026#34;context\u0026#34;\n\nvar Canceled = errors.New(\u0026#34;context canceled\u0026#34;)\n Canceled is the error returned by Context.Err when the context is canceled.", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "context.Canceled" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "cGFja2FnZSBjb250ZXh0IC8vIGltcG9ydCAiY29udGV4dCIKCnZhciBDYW5jZWxlZCA9IGVycm9ycy5OZXcoImNvbnRleHQgY2FuY2VsZWQiKQogICAgQ2FuY2VsZWQgaXMgdGhlIGVycm9yIHJldHVybmVkIGJ5IENvbnRleHQuRXJyIHdoZW4gdGhlIGNvbnRleHQgaXMgY2FuY2VsZWQu", - "duration": 604817459 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc context.Canceled\n\npackage context // import \u0026#34;context\u0026#34;\n\nvar Canceled = errors.New(\u0026#34;context canceled\u0026#34;)\n Canceled is the error returned by Context.Err when the context is canceled.", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "context.Canceled" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "cGFja2FnZSBjb250ZXh0IC8vIGltcG9ydCAiY29udGV4dCIKCnZhciBDYW5jZWxlZCA9IGVycm9ycy5OZXcoImNvbnRleHQgY2FuY2VsZWQiKQogICAgQ2FuY2VsZWQgaXMgdGhlIGVycm9yIHJldHVybmVkIGJ5IENvbnRleHQuRXJyIHdoZW4gdGhlIGNvbnRleHQgaXMgY2FuY2VsZWQu", - "duration": 604817459 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "errors", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.34:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Canceled", - "href": "https://pkg.go.dev/context#Canceled", - "target": "_blank" - }, - "file": "errors", - "nodes": [ - [ - { - "atom": "code", - "file": "errors", - "nodes": [ - { - "text": "context.Canceled", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ] - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Canceled" - } - ], - { - "text": " ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "builtin#error", - "href": "https://pkg.go.dev/builtin#error", - "target": "_blank" - }, - "file": "errors", - "nodes": [ - [ - { - "atom": "code", - "file": "errors", - "nodes": [ - { - "text": "error", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ] - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/builtin#error" - } - ], - { - "text": ".", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 34, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "errors.md", - "nodes": [ - { - "text": "Consider ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-35" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-35" - }, - "nodes": [ - { - "text": "Listing 1.35", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-35" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ". When we first check the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context.Err", - "href": "https://pkg.go.dev/context#Context.Err", - "target": "_blank" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "code", - "file": "errors.md", - "nodes": [ - { - "text": "context.Context.Err", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context.Err" - } - ], - { - "text": " method, it returns ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "errors.md", - "nodes": [ - { - "text": "nil", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": ". After we call the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#CancelFunc", - "href": "https://pkg.go.dev/context#CancelFunc", - "target": "_blank" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "code", - "file": "errors.md", - "nodes": [ - { - "text": "context.CancelFunc", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#CancelFunc" - } - ], - { - "text": " function provided by ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#WithCancel", - "href": "https://pkg.go.dev/context#WithCancel", - "target": "_blank" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "code", - "file": "errors.md", - "nodes": [ - { - "text": "context.WithCancel", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#WithCancel" - } - ], - { - "text": ", the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context.Err", - "href": "https://pkg.go.dev/context#Context.Err", - "target": "_blank" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "code", - "file": "errors.md", - "nodes": [ - { - "text": "context.Context.Err", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context.Err" - } - ], - { - "text": " method returns a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Canceled", - "href": "https://pkg.go.dev/context#Canceled", - "target": "_blank" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "code", - "file": "errors.md", - "nodes": [ - { - "text": "context.Canceled", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Canceled" - } - ], - { - "text": " error.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-35", - "type": "listing" - }, - "file": "errors.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - { - "atom": "pre", - "file": "errors", - "nodes": [ - [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "src": "errors/src/canceled/main.go#example" - }, - "lang": "go", - "nodes": [ - { - "content": "func main() {\n\n\t// create a background context\n\tctx := context.Background()\n\n\t// wrap the context with a\n\t// cancellable context\n\tctx, cancel := context.WithCancel(ctx)\n\n\t// check the error:\n\t//\t\u003cnil\u003e\n\tfmt.Println(\"ctx.Err()\", ctx.Err())\n\n\t// cancel the context\n\tcancel()\n\n\t// check the error:\n\t//\tcontext.Canceled\n\tfmt.Println(\"ctx.Err()\", ctx.Err())\n\n\t// check the error again:\n\t//\tcontext.Canceled\n\tfmt.Println(\"ctx.Err()\", ctx.Err())\n}", - "file": "errors/src/canceled/main.go", - "lang": "go", - "name": "example", - "start": 8, - "end": 34 - } - ], - "type": "*hype.SourceCode" - } - ] - ], - "type": "*hype.Element" - }, - { - "atom": "hr", - "file": "errors", - "type": "*hype.Element" - }, - [ - { - "args": [ - "go", - "run", - "." - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "exec": "go run .", - "run": ".", - "src": "errors/src/canceled" - }, - "expected_exit": 0, - "file": "errors", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go run .\n\nctx.Err() \u0026lt;nil\u0026gt;\nctx.Err() context canceled\nctx.Err() context canceled", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "run", - "." - ], - "dir": "testdata/doc/to_md/errors/src/canceled", - "stdout": "Y3R4LkVycigpIDxuaWw+CmN0eC5FcnIoKSBjb250ZXh0IGNhbmNlbGVkCmN0eC5FcnIoKSBjb250ZXh0IGNhbmNlbGVk", - "duration": 1453701750 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go run .\n\nctx.Err() \u0026lt;nil\u0026gt;\nctx.Err() context canceled\nctx.Err() context canceled", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "run", - "." - ], - "dir": "testdata/doc/to_md/errors/src/canceled", - "stdout": "Y3R4LkVycigpIDxuaWw+CmN0eC5FcnIoKSBjb250ZXh0IGNhbmNlbGVkCmN0eC5FcnIoKSBjb250ZXh0IGNhbmNlbGVk", - "duration": 1453701750 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "errors", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.35:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "Checking for cancellation errors.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 35, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "errors.md", - "nodes": [ - { - "text": "As we can see from the output in ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-35" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-35" - }, - "nodes": [ - { - "text": "Listing 1.35", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-35" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ", repeated calls to the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context.Err", - "href": "https://pkg.go.dev/context#Context.Err", - "target": "_blank" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "code", - "file": "errors.md", - "nodes": [ - { - "text": "context.Context.Err", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context.Err" - } - ], - { - "text": " method return the same ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Canceled", - "href": "https://pkg.go.dev/context#Canceled", - "target": "_blank" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "code", - "file": "errors.md", - "nodes": [ - { - "text": "context.Canceled", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Canceled" - } - ], - { - "text": " error.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "h2", - "file": "errors.md", - "level": 2, - "nodes": [ - { - "text": "Context Deadline Exceeded Error", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "errors.md", - "nodes": [ - { - "text": "When a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "code", - "file": "errors.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " is cancelled due to a deadline, or timeout, being exceeded, the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context.Err", - "href": "https://pkg.go.dev/context#Context.Err", - "target": "_blank" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "code", - "file": "errors.md", - "nodes": [ - { - "text": "context.Context.Err", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context.Err" - } - ], - { - "text": " method returns a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#DeadlineExceeded", - "href": "https://pkg.go.dev/context#DeadlineExceeded", - "target": "_blank" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "code", - "file": "errors.md", - "nodes": [ - { - "text": "context.DeadlineExceeded", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#DeadlineExceeded" - } - ], - { - "text": " error, ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-36" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-36" - }, - "nodes": [ - { - "text": "Listing 1.36", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-36" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ".", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-36", - "type": "listing" - }, - "file": "errors.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "args": [ - "go", - "doc", - "context.DeadlineExceeded" - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "doc": "context.DeadlineExceeded", - "exec": "go doc context.DeadlineExceeded" - }, - "expected_exit": 0, - "file": "errors", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc context.DeadlineExceeded\n\npackage context // import \u0026#34;context\u0026#34;\n\nvar DeadlineExceeded error = deadlineExceededError{}\n DeadlineExceeded is the error returned by Context.Err when the context\u0026#39;s\n deadline passes.", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "context.DeadlineExceeded" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "cGFja2FnZSBjb250ZXh0IC8vIGltcG9ydCAiY29udGV4dCIKCnZhciBEZWFkbGluZUV4Y2VlZGVkIGVycm9yID0gZGVhZGxpbmVFeGNlZWRlZEVycm9ye30KICAgIERlYWRsaW5lRXhjZWVkZWQgaXMgdGhlIGVycm9yIHJldHVybmVkIGJ5IENvbnRleHQuRXJyIHdoZW4gdGhlIGNvbnRleHQncwogICAgZGVhZGxpbmUgcGFzc2VzLg==", - "duration": 600902917 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc context.DeadlineExceeded\n\npackage context // import \u0026#34;context\u0026#34;\n\nvar DeadlineExceeded error = deadlineExceededError{}\n DeadlineExceeded is the error returned by Context.Err when the context\u0026#39;s\n deadline passes.", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "context.DeadlineExceeded" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "cGFja2FnZSBjb250ZXh0IC8vIGltcG9ydCAiY29udGV4dCIKCnZhciBEZWFkbGluZUV4Y2VlZGVkIGVycm9yID0gZGVhZGxpbmVFeGNlZWRlZEVycm9ye30KICAgIERlYWRsaW5lRXhjZWVkZWQgaXMgdGhlIGVycm9yIHJldHVybmVkIGJ5IENvbnRleHQuRXJyIHdoZW4gdGhlIGNvbnRleHQncwogICAgZGVhZGxpbmUgcGFzc2VzLg==", - "duration": 600902917 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "errors", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.36:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#DeadlineExceeded", - "href": "https://pkg.go.dev/context#DeadlineExceeded", - "target": "_blank" - }, - "file": "errors", - "nodes": [ - [ - { - "atom": "code", - "file": "errors", - "nodes": [ - { - "text": "context.DeadlineExceeded", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ] - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#DeadlineExceeded" - } - ], - { - "text": " ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "builtin#error", - "href": "https://pkg.go.dev/builtin#error", - "target": "_blank" - }, - "file": "errors", - "nodes": [ - [ - { - "atom": "code", - "file": "errors", - "nodes": [ - { - "text": "error", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ] - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/builtin#error" - } - ], - { - "text": ".", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 36, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "errors.md", - "nodes": [ - { - "text": "Consider ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-37" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-37" - }, - "nodes": [ - { - "text": "Listing 1.37", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-37" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ". We create a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "code", - "file": "errors.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " that will self cancel after 1 second. When we check ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context.Err", - "href": "https://pkg.go.dev/context#Context.Err", - "target": "_blank" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "code", - "file": "errors.md", - "nodes": [ - { - "text": "context.Context.Err", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context.Err" - } - ], - { - "text": " method, before the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "code", - "file": "errors.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " times out, it returns ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "errors.md", - "nodes": [ - { - "text": "nil", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": ".", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-37", - "type": "listing" - }, - "file": "errors.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - { - "atom": "pre", - "file": "errors", - "nodes": [ - [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "src": "errors/src/deadline/main.go#example" - }, - "lang": "go", - "nodes": [ - { - "content": "func main() {\n\n\t// create a background context\n\tctx := context.Background()\n\n\t// wrap the context that will\n\t// self cancel after 10 milliseconds\n\tctx, cancel := context.WithTimeout(ctx, 10*time.Millisecond)\n\tdefer cancel()\n\n\t// check the error:\n\t//\t\u003cnil\u003e\n\tfmt.Println(\"ctx.Err()\", ctx.Err())\n\n\t// wait for the context to self cancel\n\t\u003c-ctx.Done()\n\n\t// check the error:\n\t//\tcontext.Canceled\n\tfmt.Println(\"ctx.Err()\", ctx.Err())\n\n\t// check the error again:\n\t//\tcontext.DeadlineExceeded\n\tfmt.Println(\"ctx.Err()\", ctx.Err())\n}", - "file": "errors/src/deadline/main.go", - "lang": "go", - "name": "example", - "start": 9, - "end": 36 - } - ], - "type": "*hype.SourceCode" - } - ] - ], - "type": "*hype.Element" - }, - { - "atom": "hr", - "file": "errors", - "type": "*hype.Element" - }, - [ - { - "args": [ - "go", - "run", - "." - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "exec": "go run .", - "run": ".", - "src": "errors/src/deadline" - }, - "expected_exit": 0, - "file": "errors", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go run .\n\nctx.Err() \u0026lt;nil\u0026gt;\nctx.Err() context deadline exceeded\nctx.Err() context deadline exceeded", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "run", - "." - ], - "dir": "testdata/doc/to_md/errors/src/deadline", - "stdout": "Y3R4LkVycigpIDxuaWw+CmN0eC5FcnIoKSBjb250ZXh0IGRlYWRsaW5lIGV4Y2VlZGVkCmN0eC5FcnIoKSBjb250ZXh0IGRlYWRsaW5lIGV4Y2VlZGVk", - "duration": 1344165167 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go run .\n\nctx.Err() \u0026lt;nil\u0026gt;\nctx.Err() context deadline exceeded\nctx.Err() context deadline exceeded", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "run", - "." - ], - "dir": "testdata/doc/to_md/errors/src/deadline", - "stdout": "Y3R4LkVycigpIDxuaWw+CmN0eC5FcnIoKSBjb250ZXh0IGRlYWRsaW5lIGV4Y2VlZGVkCmN0eC5FcnIoKSBjb250ZXh0IGRlYWRsaW5lIGV4Y2VlZGVk", - "duration": 1344165167 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "errors", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.37:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "Checking for deadline exceeded errors.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 37, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "errors.md", - "nodes": [ - { - "text": "As we can see from the output, the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "code", - "file": "errors.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " times out after the specified time, and the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context.Err", - "href": "https://pkg.go.dev/context#Context.Err", - "target": "_blank" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "code", - "file": "errors.md", - "nodes": [ - { - "text": "context.Context.Err", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context.Err" - } - ], - { - "text": " method returns a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#DeadlineExceeded", - "href": "https://pkg.go.dev/context#DeadlineExceeded", - "target": "_blank" - }, - "file": "errors.md", - "nodes": [ - { - "atom": "code", - "file": "errors.md", - "nodes": [ - { - "text": "context.DeadlineExceeded", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#DeadlineExceeded" - } - ], - { - "text": " error.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "title": "Context Errors", - "type": "*hype.Page" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "type": "*hype.Include" - } - ], - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "include", - "attributes": { - "src": "signals/signals.md" - }, - "dir": "signals", - "file": "module.md", - "nodes": [ - [ - { - "atom": "page", - "file": "signals.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "h1", - "file": "signals.md", - "level": 1, - "nodes": [ - { - "text": "Listening for System Signals with Context", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "signals.md", - "nodes": [ - { - "text": "Previously, when discussing channels, we saw how to capture system signals, such as ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "signals.md", - "nodes": [ - { - "text": "ctrl-c", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": ", using ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "os/signal#Notify", - "href": "https://pkg.go.dev/os/signal#Notify", - "target": "_blank" - }, - "file": "signals.md", - "nodes": [ - { - "atom": "code", - "file": "signals.md", - "nodes": [ - { - "text": "signal.Notify", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/os/signal#Notify" - } - ], - { - "text": ". The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "os/signal#NotifyContext", - "href": "https://pkg.go.dev/os/signal#NotifyContext", - "target": "_blank" - }, - "file": "signals.md", - "nodes": [ - { - "atom": "code", - "file": "signals.md", - "nodes": [ - { - "text": "signal.NotifyContext", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/os/signal#NotifyContext" - } - ], - { - "text": " function, ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-38" - }, - "file": "signals.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-38" - }, - "nodes": [ - { - "text": "Listing 1.38", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-38" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ", is a variant of ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "os/signal#Notify", - "href": "https://pkg.go.dev/os/signal#Notify", - "target": "_blank" - }, - "file": "signals.md", - "nodes": [ - { - "atom": "code", - "file": "signals.md", - "nodes": [ - { - "text": "signal.Notify", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/os/signal#Notify" - } - ], - { - "text": " that takes a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "signals.md", - "nodes": [ - { - "atom": "code", - "file": "signals.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " as an argument. In return, we are given a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "signals.md", - "nodes": [ - { - "atom": "code", - "file": "signals.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " that will be canceled when the signal is received.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-38", - "type": "listing" - }, - "file": "signals.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "args": [ - "go", - "doc", - "os/signal.NotifyContext" - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "doc": "os/signal.NotifyContext", - "exec": "go doc os/signal.NotifyContext" - }, - "expected_exit": 0, - "file": "signals", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc os/signal.NotifyContext\n\npackage signal // import \u0026#34;os/signal\u0026#34;\n\nfunc NotifyContext(parent context.Context, signals ...os.Signal) (ctx context.Context, stop context.CancelFunc)\n NotifyContext returns a copy of the parent context that is marked done (its\n Done channel is closed) when one of the listed signals arrives, when the\n returned stop function is called, or when the parent context\u0026#39;s Done channel\n is closed, whichever happens first.\n\n The stop function unregisters the signal behavior, which, like signal.Reset,\n may restore the default behavior for a given signal. For example,\n the default behavior of a Go program receiving os.Interrupt is to exit.\n Calling NotifyContext(parent, os.Interrupt) will change the behavior to\n cancel the returned context. Future interrupts received will not trigger the\n default (exit) behavior until the returned stop function is called.\n\n The stop function releases resources associated with it, so code should call\n stop as soon as the operations running in this Context complete and signals\n no longer need to be diverted to the context.", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "os/signal.NotifyContext" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "cGFja2FnZSBzaWduYWwgLy8gaW1wb3J0ICJvcy9zaWduYWwiCgpmdW5jIE5vdGlmeUNvbnRleHQocGFyZW50IGNvbnRleHQuQ29udGV4dCwgc2lnbmFscyAuLi5vcy5TaWduYWwpIChjdHggY29udGV4dC5Db250ZXh0LCBzdG9wIGNvbnRleHQuQ2FuY2VsRnVuYykKICAgIE5vdGlmeUNvbnRleHQgcmV0dXJucyBhIGNvcHkgb2YgdGhlIHBhcmVudCBjb250ZXh0IHRoYXQgaXMgbWFya2VkIGRvbmUgKGl0cwogICAgRG9uZSBjaGFubmVsIGlzIGNsb3NlZCkgd2hlbiBvbmUgb2YgdGhlIGxpc3RlZCBzaWduYWxzIGFycml2ZXMsIHdoZW4gdGhlCiAgICByZXR1cm5lZCBzdG9wIGZ1bmN0aW9uIGlzIGNhbGxlZCwgb3Igd2hlbiB0aGUgcGFyZW50IGNvbnRleHQncyBEb25lIGNoYW5uZWwKICAgIGlzIGNsb3NlZCwgd2hpY2hldmVyIGhhcHBlbnMgZmlyc3QuCgogICAgVGhlIHN0b3AgZnVuY3Rpb24gdW5yZWdpc3RlcnMgdGhlIHNpZ25hbCBiZWhhdmlvciwgd2hpY2gsIGxpa2Ugc2lnbmFsLlJlc2V0LAogICAgbWF5IHJlc3RvcmUgdGhlIGRlZmF1bHQgYmVoYXZpb3IgZm9yIGEgZ2l2ZW4gc2lnbmFsLiBGb3IgZXhhbXBsZSwKICAgIHRoZSBkZWZhdWx0IGJlaGF2aW9yIG9mIGEgR28gcHJvZ3JhbSByZWNlaXZpbmcgb3MuSW50ZXJydXB0IGlzIHRvIGV4aXQuCiAgICBDYWxsaW5nIE5vdGlmeUNvbnRleHQocGFyZW50LCBvcy5JbnRlcnJ1cHQpIHdpbGwgY2hhbmdlIHRoZSBiZWhhdmlvciB0bwogICAgY2FuY2VsIHRoZSByZXR1cm5lZCBjb250ZXh0LiBGdXR1cmUgaW50ZXJydXB0cyByZWNlaXZlZCB3aWxsIG5vdCB0cmlnZ2VyIHRoZQogICAgZGVmYXVsdCAoZXhpdCkgYmVoYXZpb3IgdW50aWwgdGhlIHJldHVybmVkIHN0b3AgZnVuY3Rpb24gaXMgY2FsbGVkLgoKICAgIFRoZSBzdG9wIGZ1bmN0aW9uIHJlbGVhc2VzIHJlc291cmNlcyBhc3NvY2lhdGVkIHdpdGggaXQsIHNvIGNvZGUgc2hvdWxkIGNhbGwKICAgIHN0b3AgYXMgc29vbiBhcyB0aGUgb3BlcmF0aW9ucyBydW5uaW5nIGluIHRoaXMgQ29udGV4dCBjb21wbGV0ZSBhbmQgc2lnbmFscwogICAgbm8gbG9uZ2VyIG5lZWQgdG8gYmUgZGl2ZXJ0ZWQgdG8gdGhlIGNvbnRleHQu", - "duration": 594552042 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go doc os/signal.NotifyContext\n\npackage signal // import \u0026#34;os/signal\u0026#34;\n\nfunc NotifyContext(parent context.Context, signals ...os.Signal) (ctx context.Context, stop context.CancelFunc)\n NotifyContext returns a copy of the parent context that is marked done (its\n Done channel is closed) when one of the listed signals arrives, when the\n returned stop function is called, or when the parent context\u0026#39;s Done channel\n is closed, whichever happens first.\n\n The stop function unregisters the signal behavior, which, like signal.Reset,\n may restore the default behavior for a given signal. For example,\n the default behavior of a Go program receiving os.Interrupt is to exit.\n Calling NotifyContext(parent, os.Interrupt) will change the behavior to\n cancel the returned context. Future interrupts received will not trigger the\n default (exit) behavior until the returned stop function is called.\n\n The stop function releases resources associated with it, so code should call\n stop as soon as the operations running in this Context complete and signals\n no longer need to be diverted to the context.", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "doc", - "os/signal.NotifyContext" - ], - "dir": "/Users/markbates/Library/CloudStorage/Dropbox/dev/guides/hype", - "stdout": "cGFja2FnZSBzaWduYWwgLy8gaW1wb3J0ICJvcy9zaWduYWwiCgpmdW5jIE5vdGlmeUNvbnRleHQocGFyZW50IGNvbnRleHQuQ29udGV4dCwgc2lnbmFscyAuLi5vcy5TaWduYWwpIChjdHggY29udGV4dC5Db250ZXh0LCBzdG9wIGNvbnRleHQuQ2FuY2VsRnVuYykKICAgIE5vdGlmeUNvbnRleHQgcmV0dXJucyBhIGNvcHkgb2YgdGhlIHBhcmVudCBjb250ZXh0IHRoYXQgaXMgbWFya2VkIGRvbmUgKGl0cwogICAgRG9uZSBjaGFubmVsIGlzIGNsb3NlZCkgd2hlbiBvbmUgb2YgdGhlIGxpc3RlZCBzaWduYWxzIGFycml2ZXMsIHdoZW4gdGhlCiAgICByZXR1cm5lZCBzdG9wIGZ1bmN0aW9uIGlzIGNhbGxlZCwgb3Igd2hlbiB0aGUgcGFyZW50IGNvbnRleHQncyBEb25lIGNoYW5uZWwKICAgIGlzIGNsb3NlZCwgd2hpY2hldmVyIGhhcHBlbnMgZmlyc3QuCgogICAgVGhlIHN0b3AgZnVuY3Rpb24gdW5yZWdpc3RlcnMgdGhlIHNpZ25hbCBiZWhhdmlvciwgd2hpY2gsIGxpa2Ugc2lnbmFsLlJlc2V0LAogICAgbWF5IHJlc3RvcmUgdGhlIGRlZmF1bHQgYmVoYXZpb3IgZm9yIGEgZ2l2ZW4gc2lnbmFsLiBGb3IgZXhhbXBsZSwKICAgIHRoZSBkZWZhdWx0IGJlaGF2aW9yIG9mIGEgR28gcHJvZ3JhbSByZWNlaXZpbmcgb3MuSW50ZXJydXB0IGlzIHRvIGV4aXQuCiAgICBDYWxsaW5nIE5vdGlmeUNvbnRleHQocGFyZW50LCBvcy5JbnRlcnJ1cHQpIHdpbGwgY2hhbmdlIHRoZSBiZWhhdmlvciB0bwogICAgY2FuY2VsIHRoZSByZXR1cm5lZCBjb250ZXh0LiBGdXR1cmUgaW50ZXJydXB0cyByZWNlaXZlZCB3aWxsIG5vdCB0cmlnZ2VyIHRoZQogICAgZGVmYXVsdCAoZXhpdCkgYmVoYXZpb3IgdW50aWwgdGhlIHJldHVybmVkIHN0b3AgZnVuY3Rpb24gaXMgY2FsbGVkLgoKICAgIFRoZSBzdG9wIGZ1bmN0aW9uIHJlbGVhc2VzIHJlc291cmNlcyBhc3NvY2lhdGVkIHdpdGggaXQsIHNvIGNvZGUgc2hvdWxkIGNhbGwKICAgIHN0b3AgYXMgc29vbiBhcyB0aGUgb3BlcmF0aW9ucyBydW5uaW5nIGluIHRoaXMgQ29udGV4dCBjb21wbGV0ZSBhbmQgc2lnbmFscwogICAgbm8gbG9uZ2VyIG5lZWQgdG8gYmUgZGl2ZXJ0ZWQgdG8gdGhlIGNvbnRleHQu", - "duration": 594552042 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "signals", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.38:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "os/signal#NotifyContext", - "href": "https://pkg.go.dev/os/signal#NotifyContext", - "target": "_blank" - }, - "file": "signals", - "nodes": [ - [ - { - "atom": "code", - "file": "signals", - "nodes": [ - { - "text": "signal.NotifyContext", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ] - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/os/signal#NotifyContext" - } - ], - { - "text": " function.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 38, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "signals.md", - "nodes": [ - { - "text": "Consider ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-39" - }, - "file": "signals.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-39" - }, - "nodes": [ - { - "text": "Listing 1.39", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-39" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ". We use ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "os/signal#NotifyContext", - "href": "https://pkg.go.dev/os/signal#NotifyContext", - "target": "_blank" - }, - "file": "signals.md", - "nodes": [ - { - "atom": "code", - "file": "signals.md", - "nodes": [ - { - "text": "signal.NotifyContext", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/os/signal#NotifyContext" - } - ], - { - "text": " to listen for ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "signals.md", - "nodes": [ - { - "text": "ctrl-c", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": ". This function returns a wrapped ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "signals.md", - "nodes": [ - { - "atom": "code", - "file": "signals.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " that will cancel when the signal is received. It also returns a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#CancelFunc", - "href": "https://pkg.go.dev/context#CancelFunc", - "target": "_blank" - }, - "file": "signals.md", - "nodes": [ - { - "atom": "code", - "file": "signals.md", - "nodes": [ - { - "text": "context.CancelFunc", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#CancelFunc" - } - ], - { - "text": " that can be used to cancel ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "signals.md", - "nodes": [ - { - "atom": "code", - "file": "signals.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " when needed.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-39", - "type": "listing" - }, - "file": "signals.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - { - "atom": "pre", - "file": "signals", - "nodes": [ - [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "src": "signals/src/signals/main.go#example" - }, - "lang": "go", - "nodes": [ - { - "content": "func main() {\n\n\t// create a background context\n\tctx := context.Background()\n\n\t// wrap the context with a timeout\n\t// of 50 milliseconds to ensure the application\n\t// will eventually exit\n\tctx, cancel := context.WithTimeout(ctx, 50*time.Millisecond)\n\tdefer cancel()\n\n\t// wrap the context with a context\n\t// that will be cancelled when an\n\t// interrupt signal is received (ctrl-c)\n\tctx, cancel = signal.NotifyContext(ctx, os.Interrupt)\n\tdefer cancel()\n\n\t// lauch a goroutine that will\n\t// trigger an interrupt signal\n\t// after 10 milliseconds (ctrl-c)\n\tgo func() {\n\t\ttime.Sleep(10 * time.Millisecond)\n\n\t\tfmt.Println(\"sending ctrl-c\")\n\n\t\t// send the interrupt signal\n\t\t// to the current process\n\t\tsyscall.Kill(syscall.Getpid(), syscall.SIGINT)\n\t}()\n\n\tfmt.Println(\"waiting for context to finish\")\n\n\t// wait for the context to finish\n\t\u003c-ctx.Done()\n\n\tfmt.Printf(\"context finished: %v\\n\", ctx.Err())\n\n}", - "file": "signals/src/signals/main.go", - "lang": "go", - "name": "example", - "start": 12, - "end": 52 - } - ], - "type": "*hype.SourceCode" - } - ] - ], - "type": "*hype.Element" - }, - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "signals", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.39:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "Listening for system signals.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 39, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "h2", - "file": "signals.md", - "level": 2, - "nodes": [ - { - "text": "Testing Signals", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "signals.md", - "nodes": [ - { - "text": "Testing system signals is tricky and care must be taken not to accidentally exit your running tests. Unfortunately, the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "syscall", - "href": "https://pkg.go.dev/syscall", - "target": "_blank" - }, - "file": "signals.md", - "nodes": [ - { - "atom": "code", - "file": "signals.md", - "nodes": [ - { - "text": "syscall", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/syscall" - } - ], - { - "text": " package does not provide a \"test\" signal, or a way to implement a test signal.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "signals.md", - "nodes": [ - { - "text": "We can use ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "syscall#SIGUSR1", - "href": "https://pkg.go.dev/syscall#SIGUSR1", - "target": "_blank" - }, - "file": "signals.md", - "nodes": [ - { - "atom": "code", - "file": "signals.md", - "nodes": [ - { - "text": "syscall.SIGUSR1", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/syscall#SIGUSR1" - } - ], - { - "text": " or ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "syscall#SIGUSR2", - "href": "https://pkg.go.dev/syscall#SIGUSR2", - "target": "_blank" - }, - "file": "signals.md", - "nodes": [ - { - "atom": "code", - "file": "signals.md", - "nodes": [ - { - "text": "syscall.SIGUSR2", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/syscall#SIGUSR2" - } - ], - { - "text": " in our tests as these are allocated to the developer to use for their own purposes.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "signals.md", - "nodes": [ - { - "text": "When we are testing signals, we are testing a ", - "type": "hype.Text" - }, - { - "atom": "strong", - "file": "signals.md", - "nodes": [ - { - "text": "global", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " signal, that will caught by anyone else who is listening to that signal. Because of this we want to make that when testing signals we aren't running the tests in parallel and that we don't have other tests also listening to the same signal.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "signals.md", - "nodes": [ - { - "text": "Consider ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-40" - }, - "file": "signals.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-40" - }, - "nodes": [ - { - "text": "Listing 1.40", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-40" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ". How do we test that the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "signals.md", - "nodes": [ - { - "text": "Listener", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " function will respond properly to a signal? We don't want to make that the responsibility of the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "signals.md", - "nodes": [ - { - "text": "Listener", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " function, it already has a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "signals.md", - "nodes": [ - { - "atom": "code", - "file": "signals.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " that it can listen to for cancellation. The ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "signals.md", - "nodes": [ - { - "text": "Listener", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " function doesn't care why it was told to stop listening, it just needs to stop listening. This could be because we receive an interrupt signal, because a deadline has passed, or because the application no longer needs the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "signals.md", - "nodes": [ - { - "text": "Lister", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " function to keep running.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-40", - "type": "listing" - }, - "file": "signals.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - { - "atom": "pre", - "file": "signals", - "nodes": [ - [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "src": "signals/src/testing/signals_test.go#func" - }, - "lang": "go", - "nodes": [ - { - "content": "func Listener(ctx context.Context, t testing.TB) {\n\tt.Log(\"waiting for context to finish\")\n\n\t// wait for the context to finish\n\t\u003c-ctx.Done()\n\n}", - "file": "signals/src/testing/signals_test.go", - "lang": "go", - "name": "func", - "start": 13, - "end": 22 - } - ], - "type": "*hype.SourceCode" - } - ] - ], - "type": "*hype.Element" - }, - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "signals", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.40:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "The ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "signals", - "nodes": [ - { - "text": "Listener", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " function.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 40, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "signals.md", - "nodes": [ - { - "text": "In, ", - "type": "hype.Text" - }, - [ - { - "atom": "ref", - "attributes": { - "id": "listing-1-41" - }, - "file": "signals.md", - "nodes": [ - { - "atom": "a", - "attributes": { - "href": "#listing-1-41" - }, - "nodes": [ - { - "text": "Listing 1.41", - "type": "hype.Text" - } - ], - "type": "*hype.Link", - "url": "#listing-1-41" - } - ], - "type": "*hype.Ref" - } - ], - { - "text": ", before we call the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "signals.md", - "nodes": [ - { - "text": "Listener", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " function, we first create a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "signals.md", - "nodes": [ - { - "atom": "code", - "file": "signals.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " that will self-cancel after 5 seconds if nothing else happens. We then wrap that ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "signals.md", - "nodes": [ - { - "atom": "code", - "file": "signals.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " with one received from the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "os/signal#NotifyContext", - "href": "https://pkg.go.dev/os/signal#NotifyContext", - "target": "_blank" - }, - "file": "signals.md", - "nodes": [ - { - "atom": "code", - "file": "signals.md", - "nodes": [ - { - "text": "signal.NotifyContext", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/os/signal#NotifyContext" - } - ], - { - "text": " function, that will self-cancel when the system receives a ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "signals.md", - "nodes": [ - { - "text": "TEST_SIGNAL", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " signal.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "signals.md", - "nodes": [ - { - "text": "Our test blocks with a ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "signals.md", - "nodes": [ - { - "text": "select", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " waiting for either ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "signals.md", - "nodes": [ - { - "atom": "code", - "file": "signals.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " to be cancelled, and then respond accordingly.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-41", - "type": "listing" - }, - "file": "signals.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - { - "atom": "pre", - "file": "signals", - "nodes": [ - [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "src": "signals/src/testing/signals_test.go#example" - }, - "lang": "go", - "nodes": [ - { - "content": "// use syscall.SIGUSR2 to test\nconst TEST_SIGNAL = syscall.SIGUSR2\n\nfunc Test_Signals(t *testing.T) {\n\n\t// create a background context\n\tctx := context.Background()\n\n\t// wrap the context with a context\n\t// that will self cancel after 5 seconds\n\t// if the context is not finished\n\tctx, cancel := context.WithTimeout(ctx, 5*time.Second)\n\tdefer cancel()\n\n\t// wrap the context with a context\n\t// that will self cancel if the system\n\t// receives a TEST_SIGNAL\n\tsigCtx, cancel := signal.NotifyContext(ctx, TEST_SIGNAL)\n\tdefer cancel()\n\n\tprint(t, sigCtx)\n\n\t// launch a goroutine to wait for the context\n\t// to finish\n\tgo Listener(sigCtx, t)\n\n\t// launch a goroutine to send a TEST_SIGNAL\n\t// to the system after 1 second\n\tgo func() {\n\t\ttime.Sleep(time.Second)\n\n\t\tt.Log(\"sending test signal\")\n\n\t\t// send the TEST_SIGNAL to the system\n\t\tsyscall.Kill(syscall.Getpid(), TEST_SIGNAL)\n\t}()\n\n\t// wait for the context to finish\n\tselect {\n\tcase \u003c-ctx.Done():\n\t\tt.Log(\"context finished\")\n\tcase \u003c-sigCtx.Done():\n\t\tt.Log(\"signal received\")\n\t\tt.Log(\"successfully completed\")\n\t\treturn\n\t}\n\n\terr := ctx.Err()\n\tif err == nil {\n\t\treturn\n\t}\n\n\t// if we receive a DeadlineExceeded error then\n\t// the context timed out and the signal was never\n\t// received.\n\tif err == context.DeadlineExceeded {\n\t\tt.Fatal(\"unexpected error\", err)\n\t}\n\n}", - "file": "signals/src/testing/signals_test.go", - "lang": "go", - "name": "example", - "start": 24, - "end": 89 - } - ], - "type": "*hype.SourceCode" - } - ] - ], - "type": "*hype.Element" - }, - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "signals", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.41:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "Testing the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "signals", - "nodes": [ - { - "text": "Listener", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " function.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 41, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "signals.md", - "nodes": [ - { - "text": "Inside the test, in a goroutine, we can trigger the ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "signals.md", - "nodes": [ - { - "text": "TEST_SIGNAL", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " signal by sending it to the current process, ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "syscall#Getpid", - "href": "https://pkg.go.dev/syscall#Getpid", - "target": "_blank" - }, - "file": "signals.md", - "nodes": [ - { - "atom": "code", - "file": "signals.md", - "nodes": [ - { - "text": "syscall.Getpid", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/syscall#Getpid" - } - ], - { - "text": ", with the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "syscall#Kill", - "href": "https://pkg.go.dev/syscall#Kill", - "target": "_blank" - }, - "file": "signals.md", - "nodes": [ - { - "atom": "code", - "file": "signals.md", - "nodes": [ - { - "text": "syscall.Kill", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/syscall#Kill" - } - ], - { - "text": " function.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - null, - [ - { - "atom": "figure", - "attributes": { - "id": "listing-1-42", - "type": "listing" - }, - "file": "signals.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - { - "atom": "pre", - "file": "signals", - "nodes": [ - [ - { - "atom": "code", - "attributes": { - "class": "language-go", - "language": "go", - "src": "signals/src/testing/signals_test.go#kill" - }, - "lang": "go", - "nodes": [ - { - "content": "// launch a goroutine to send a TEST_SIGNAL\n// to the system after 1 second\ngo func() {\n\ttime.Sleep(time.Second)\n\n\tt.Log(\"sending test signal\")\n\n\t// send the TEST_SIGNAL to the system\n\tsyscall.Kill(syscall.Getpid(), TEST_SIGNAL)\n}()", - "file": "signals/src/testing/signals_test.go", - "lang": "go", - "name": "kill", - "start": 52, - "end": 63 - } - ], - "type": "*hype.SourceCode" - } - ] - ], - "type": "*hype.Element" - }, - { - "atom": "hr", - "file": "signals", - "type": "*hype.Element" - }, - [ - { - "args": [ - "go", - "test", - "-v" - ], - "atom": "cmd", - "attributes": { - "data-go-version": "go.test", - "exec": "go test -v", - "src": "signals/src/testing", - "test": "-v" - }, - "expected_exit": 0, - "file": "signals", - "nodes": [ - { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go test -v\n\n=== RUN Test_Signals\n signals_test.go:46: SignalCtx([]os.Signal{31})\n \t--\u0026gt; WithCancel\n \t\t--\u0026gt; WithTimeout(deadline: {wall:13909887566670467024 ext:5000672626 loc:0x1002327c0})\n \t\t\t--\u0026gt; Background\n signals_test.go:15: waiting for context to finish\n signals_test.go:58: sending test signal\n signals_test.go:70: signal received\n signals_test.go:71: successfully completed\n--- PASS: Test_Signals (1.00s)\nPASS\nok \tdemo\t1.725s", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "test", - "-v" - ], - "dir": "testdata/doc/to_md/signals/src/testing", - "stdout": "PT09IFJVTiAgIFRlc3RfU2lnbmFscwogICAgc2lnbmFsc190ZXN0LmdvOjQ2OiBTaWduYWxDdHgoW11vcy5TaWduYWx7MzF9KQogICAgICAgIAktLT4gV2l0aENhbmNlbAogICAgICAgIAkJLS0+IFdpdGhUaW1lb3V0KGRlYWRsaW5lOiB7d2FsbDoxMzkwOTg4NzU2NjY3MDQ2NzAyNCBleHQ6NTAwMDY3MjYyNiBsb2M6MHgxMDAyMzI3YzB9KQogICAgICAgIAkJCS0tPiBCYWNrZ3JvdW5kCiAgICBzaWduYWxzX3Rlc3QuZ286MTU6IHdhaXRpbmcgZm9yIGNvbnRleHQgdG8gZmluaXNoCiAgICBzaWduYWxzX3Rlc3QuZ286NTg6IHNlbmRpbmcgdGVzdCBzaWduYWwKICAgIHNpZ25hbHNfdGVzdC5nbzo3MDogc2lnbmFsIHJlY2VpdmVkCiAgICBzaWduYWxzX3Rlc3QuZ286NzE6IHN1Y2Nlc3NmdWxseSBjb21wbGV0ZWQKLS0tIFBBU1M6IFRlc3RfU2lnbmFscyAoMS4wMHMpClBBU1MKb2sgIAlkZW1vCTEuNzI1cw==", - "duration": 2714225833 - }, - "type": "*hype.CmdResult" - } - ], - "result": { - "atom": "result", - "nodes": [ - { - "atom": "pre", - "nodes": [ - { - "atom": "code", - "attributes": { - "class": "language-shell", - "language": "shell" - }, - "lang": "shell", - "nodes": [ - { - "text": "$ go test -v\n\n=== RUN Test_Signals\n signals_test.go:46: SignalCtx([]os.Signal{31})\n \t--\u0026gt; WithCancel\n \t\t--\u0026gt; WithTimeout(deadline: {wall:13909887566670467024 ext:5000672626 loc:0x1002327c0})\n \t\t\t--\u0026gt; Background\n signals_test.go:15: waiting for context to finish\n signals_test.go:58: sending test signal\n signals_test.go:70: signal received\n signals_test.go:71: successfully completed\n--- PASS: Test_Signals (1.00s)\nPASS\nok \tdemo\t1.725s", - "type": "hype.Text" - }, - { - "text": "\n\n--------------------------------------------------------------------------------\nGo Version: go.test\n", - "type": "hype.Text" - } - ], - "type": "*hype.FencedCode" - } - ], - "type": "*hype.Element" - } - ], - "result": { - "args": [ - "go", - "test", - "-v" - ], - "dir": "testdata/doc/to_md/signals/src/testing", - "stdout": "PT09IFJVTiAgIFRlc3RfU2lnbmFscwogICAgc2lnbmFsc190ZXN0LmdvOjQ2OiBTaWduYWxDdHgoW11vcy5TaWduYWx7MzF9KQogICAgICAgIAktLT4gV2l0aENhbmNlbAogICAgICAgIAkJLS0+IFdpdGhUaW1lb3V0KGRlYWRsaW5lOiB7d2FsbDoxMzkwOTg4NzU2NjY3MDQ2NzAyNCBleHQ6NTAwMDY3MjYyNiBsb2M6MHgxMDAyMzI3YzB9KQogICAgICAgIAkJCS0tPiBCYWNrZ3JvdW5kCiAgICBzaWduYWxzX3Rlc3QuZ286MTU6IHdhaXRpbmcgZm9yIGNvbnRleHQgdG8gZmluaXNoCiAgICBzaWduYWxzX3Rlc3QuZ286NTg6IHNlbmRpbmcgdGVzdCBzaWduYWwKICAgIHNpZ25hbHNfdGVzdC5nbzo3MDogc2lnbmFsIHJlY2VpdmVkCiAgICBzaWduYWxzX3Rlc3QuZ286NzE6IHN1Y2Nlc3NmdWxseSBjb21wbGV0ZWQKLS0tIFBBU1M6IFRlc3RfU2lnbmFscyAoMS4wMHMpClBBU1MKb2sgIAlkZW1vCTEuNzI1cw==", - "duration": 2714225833 - }, - "type": "*hype.CmdResult" - }, - "timeout": "30s", - "type": "*hype.Cmd" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "figcaption", - "file": "signals", - "nodes": [ - { - "atom": "em", - "attributes": { - "class": "figure-name" - }, - "nodes": [ - { - "text": "Listing 1.42:", - "type": "hype.Text" - } - ], - "type": "*hype.Element" - }, - { - "text": " ", - "type": "hype.Text" - }, - { - "text": "Sending a ", - "type": "hype.Text" - }, - [ - { - "atom": "code", - "file": "signals", - "nodes": [ - { - "text": "TEST_SIGNAL", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - { - "text": " signal.", - "type": "hype.Text" - } - ], - "type": "*hype.Figcaption" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "pos": 42, - "section_id": 1, - "style": "listing", - "type": "*hype.Figure" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "title": "Listening for System Signals with Context", - "type": "*hype.Page" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "type": "*hype.Include" - } - ], - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "page", - "file": "module.md", - "nodes": [ - { - "text": "\n", - "type": "hype.Text" - }, - [ - { - "atom": "h1", - "file": "module.md", - "level": 1, - "nodes": [ - { - "text": "Summary", - "type": "hype.Text" - } - ], - "type": "hype.Heading" - } - ], - { - "text": "\n\n", - "type": "hype.Text" - }, - [ - { - "atom": "p", - "file": "module.md", - "nodes": [ - { - "text": "In this chapter we explore the concept of contexts in Go. We learn that contexts are a way to manage cancellation, timeouts, and other request-scoped values across API boundaries and between processes. We also learn how to use contexts to clean up a lot of code involving channels, such as listening for system signals. We discussed the nodal hierarchy of how the ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context", - "href": "https://pkg.go.dev/context", - "target": "_blank" - }, - "file": "module.md", - "nodes": [ - { - "atom": "code", - "file": "module.md", - "nodes": [ - { - "text": "context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context" - } - ], - { - "text": " package wraps a new ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "module.md", - "nodes": [ - { - "atom": "code", - "file": "module.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " around a parent ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "module.md", - "nodes": [ - { - "atom": "code", - "file": "module.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": ". We learned the different was to cancel a ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "module.md", - "nodes": [ - { - "atom": "code", - "file": "module.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": " and how to use multiple ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context#Context", - "href": "https://pkg.go.dev/context#Context", - "target": "_blank" - }, - "file": "module.md", - "nodes": [ - { - "atom": "code", - "file": "module.md", - "nodes": [ - { - "text": "context.Context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context#Context" - } - ], - { - "text": "s to confirm shutdown behavior. The ", - "type": "hype.Text" - }, - [ - { - "atom": "a", - "attributes": { - "for": "context", - "href": "https://pkg.go.dev/context", - "target": "_blank" - }, - "file": "module.md", - "nodes": [ - { - "atom": "code", - "file": "module.md", - "nodes": [ - { - "text": "context", - "type": "hype.Text" - } - ], - "type": "*hype.InlineCode" - } - ], - "type": "*hype.Link", - "url": "https://pkg.go.dev/context" - } - ], - { - "text": " package, while small, is a very powerful tool for managing concurrency in your application.", - "type": "hype.Text" - } - ], - "type": "*hype.Paragraph" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "title": "Summary", - "type": "*hype.Page" - } - ], - { - "text": "\n", - "type": "hype.Text" - } - ], - "type": "*hype.Body" - } - ] - ], - "type": "*hype.Element" - } - ], - "type": "*hype.Element" - } - ], - "parser": { - "root": "testdata/doc/to_md", - "snippets": {}, - "section": 1 - }, - "root": "testdata/doc/to_md", - "section_id": 1, - "snippets": {}, - "title": "Context", - "type": "*hype.Document" -} \ No newline at end of file diff --git a/testdata/golang/sym/cmd/go.mod b/testdata/golang/sym/cmd/go.mod index 21cc482..2287625 100644 --- a/testdata/golang/sym/cmd/go.mod +++ b/testdata/golang/sym/cmd/go.mod @@ -1,3 +1,3 @@ module demo -go 1.18 +go 1.22 diff --git a/testdata/golang/sym/go.mod b/testdata/golang/sym/go.mod index 21cc482..2287625 100644 --- a/testdata/golang/sym/go.mod +++ b/testdata/golang/sym/go.mod @@ -1,3 +1,3 @@ module demo -go 1.18 +go 1.22 diff --git a/testdata/includes/broken/module.md b/testdata/includes/broken/module.md new file mode 100644 index 0000000..7497e6f --- /dev/null +++ b/testdata/includes/broken/module.md @@ -0,0 +1,3 @@ +# Broken + + diff --git a/testdata/includes/sublevel/module.md b/testdata/includes/sublevel/module.md new file mode 100644 index 0000000..f6ffadc --- /dev/null +++ b/testdata/includes/sublevel/module.md @@ -0,0 +1,3 @@ +# Not Boom + + diff --git a/testdata/includes/toplevel/module.md b/testdata/includes/toplevel/module.md new file mode 100644 index 0000000..55c7f25 --- /dev/null +++ b/testdata/includes/toplevel/module.md @@ -0,0 +1,3 @@ +# Boom + + diff --git a/testdata/json/body.json b/testdata/json/body.json new file mode 100644 index 0000000..a4a6013 --- /dev/null +++ b/testdata/json/body.json @@ -0,0 +1,20 @@ +{ + "atom": "body", + "attributes": {}, + "filename": "", + "html_node": { + "data": "body", + "data_atom": "body", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + { + "text": "hello", + "type": "hype.Text" + } + ], + "tag": "\u003cbody\u003e", + "type": "hype.Body" +} \ No newline at end of file diff --git a/testdata/json/cmd.json b/testdata/json/cmd.json new file mode 100644 index 0000000..7e252ff --- /dev/null +++ b/testdata/json/cmd.json @@ -0,0 +1,50 @@ +{ + "args": [ + "echo", + "hello" + ], + "atom": "cmd", + "attributes": {}, + "env": [ + "FOO=bar", + "BAR=baz" + ], + "expected_exit": 1, + "filename": "", + "html_node": { + "data": "cmd", + "data_atom": "", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [], + "result": { + "args": [ + "echo", + "hello" + ], + "atom": "result", + "attributes": {}, + "dir": "testdata/commands", + "duration": "1s", + "err": "this is an error", + "exit": 1, + "filename": "", + "html_node": { + "data": "result", + "data_atom": "", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [], + "stderr": "this is stderr", + "stdout": "this is stdout", + "tag": "\u003cresult\u003e", + "type": "hype.CmdResult" + }, + "tag": "\u003ccmd\u003e", + "timeout": "1s", + "type": "hype.Cmd" +} \ No newline at end of file diff --git a/testdata/json/cmd_error.json b/testdata/json/cmd_error.json new file mode 100644 index 0000000..1ea5642 --- /dev/null +++ b/testdata/json/cmd_error.json @@ -0,0 +1,12 @@ +{ + "args": [ + "echo", + "hello" + ], + "error": "EOF", + "exit": 1, + "filename": "foo.go", + "output": "foo\nbar\nbaz\n", + "root": "/tmp", + "type": "hype.CmdError" +} \ No newline at end of file diff --git a/testdata/json/cmd_result.json b/testdata/json/cmd_result.json new file mode 100644 index 0000000..9bf4d81 --- /dev/null +++ b/testdata/json/cmd_result.json @@ -0,0 +1,25 @@ +{ + "args": [ + "echo", + "hello" + ], + "atom": "cmd", + "attributes": {}, + "dir": "/tmp", + "duration": "1s", + "err": "EOF", + "exit": 1, + "filename": "", + "html_node": { + "data": "cmd", + "data_atom": "", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [], + "stderr": "nothing", + "stdout": "foo\nbar\nbaz\n", + "tag": "\u003ccmd\u003e", + "type": "hype.CmdResult" +} \ No newline at end of file diff --git a/testdata/json/comment.json b/testdata/json/comment.json new file mode 100644 index 0000000..c3e19c5 --- /dev/null +++ b/testdata/json/comment.json @@ -0,0 +1,4 @@ +{ + "text": "hello", + "type": "hype.Comment" +} \ No newline at end of file diff --git a/testdata/json/document.json b/testdata/json/document.json new file mode 100644 index 0000000..f05bec4 --- /dev/null +++ b/testdata/json/document.json @@ -0,0 +1,293 @@ +{ + "filename": "module.md", + "id": "1", + "nodes": [ + { + "atom": "", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "", + "data_atom": "", + "namespace": "", + "node_type": "html.DocumentNode", + "type": "html.Node" + }, + "nodes": [ + { + "atom": "html", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "html", + "data_atom": "html", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + { + "atom": "head", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "head", + "data_atom": "head", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [], + "tag": "\u003chead\u003e", + "type": "hype.Element" + }, + [ + { + "atom": "body", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "body", + "data_atom": "body", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + [ + { + "atom": "page", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "page", + "data_atom": "", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + { + "text": "\n", + "type": "hype.Text" + }, + [ + { + "atom": "h1", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "h1", + "data_atom": "h1", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "level": 1, + "nodes": [ + { + "text": "Hello", + "type": "hype.Text" + } + ], + "tag": "\u003ch1\u003e", + "type": "hype.Heading" + } + ], + { + "text": "\n\n", + "type": "hype.Text" + }, + [ + { + "atom": "pre", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "pre", + "data_atom": "pre", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + { + "atom": "code", + "attributes": { + "class": "language-ts", + "language": "ts", + "snippet": "class", + "src": "src/main.ts" + }, + "filename": "module.md", + "html_node": { + "attributes": [ + { + "Namespace": "", + "Key": "src", + "Val": "src/main.ts" + }, + { + "Namespace": "", + "Key": "snippet", + "Val": "class" + } + ], + "data": "code", + "data_atom": "code", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "lang": "ts", + "nodes": [ + { + "content": "export class Hype {\n constructor(parameters) {\n }\n}", + "file": "src/main.ts", + "lang": "ts", + "name": "class", + "start": 1, + "end": 8 + } + ], + "tag": "\u003ccode class=\"language-ts\" language=\"ts\" snippet=\"class\" src=\"src/main.ts\"\u003e", + "type": "hype.SourceCode" + } + ], + "tag": "\u003cpre\u003e", + "type": "hype.Element" + } + ], + { + "text": "\n\n", + "type": "hype.Text" + }, + [ + { + "atom": "pre", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "pre", + "data_atom": "pre", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + { + "atom": "code", + "attributes": { + "class": "language-ts", + "language": "ts", + "snippet": "constructor", + "src": "src/main.ts" + }, + "filename": "module.md", + "html_node": { + "attributes": [ + { + "Namespace": "", + "Key": "src", + "Val": "src/main.ts" + }, + { + "Namespace": "", + "Key": "snippet", + "Val": "constructor" + } + ], + "data": "code", + "data_atom": "code", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "lang": "ts", + "nodes": [ + { + "content": "constructor(parameters) {\n}", + "file": "src/main.ts", + "lang": "ts", + "name": "constructor", + "start": 3, + "end": 6 + } + ], + "tag": "\u003ccode class=\"language-ts\" language=\"ts\" snippet=\"constructor\" src=\"src/main.ts\"\u003e", + "type": "hype.SourceCode" + } + ], + "tag": "\u003cpre\u003e", + "type": "hype.Element" + } + ], + { + "text": "\n", + "type": "hype.Text" + } + ], + "tag": "\u003cpage\u003e", + "title": "Hello", + "type": "hype.Page" + } + ], + { + "text": "\n", + "type": "hype.Text" + } + ], + "tag": "\u003cbody\u003e", + "type": "hype.Body" + } + ] + ], + "tag": "\u003chtml\u003e", + "type": "hype.Element" + } + ], + "tag": "", + "type": "hype.Element" + } + ], + "parser": { + "type": "hype.Parser", + "root": "testdata/doc/snippets", + "section": 1, + "contents": "# Hello\n\n\u003ccode src=\"src/main.ts\" snippet=\"class\"\u003e\u003c/code\u003e\n\n\u003ccode src=\"src/main.ts\" snippet=\"constructor\"\u003e\u003c/code\u003e\n" + }, + "root": "testdata/doc/snippets", + "section_id": 1, + "snippets": { + "rules": { + ".go": "// %s", + ".html": "\u003c!-- %s --\u003e", + ".js": "// %s", + ".md": "\u003c!-- %s --\u003e", + ".rb": "# %s", + ".ts": "// %s" + }, + "snippets": { + "src/main.ts": { + "class": { + "content": "export class Hype {\n constructor(parameters) {\n }\n}", + "file": "src/main.ts", + "lang": "ts", + "name": "class", + "start": 1, + "end": 8 + }, + "constructor": { + "content": "constructor(parameters) {\n}", + "file": "src/main.ts", + "lang": "ts", + "name": "constructor", + "start": 3, + "end": 6 + } + } + } + }, + "title": "Hello", + "type": "hype.Document" +} \ No newline at end of file diff --git a/testdata/json/element.json b/testdata/json/element.json new file mode 100644 index 0000000..e501f8a --- /dev/null +++ b/testdata/json/element.json @@ -0,0 +1,17 @@ +{ + "atom": "div", + "attributes": { + "class": "foo" + }, + "filename": "", + "html_node": { + "data": "div", + "data_atom": "div", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [], + "tag": "\u003cdiv class=\"foo\"\u003e", + "type": "hype.Element" +} \ No newline at end of file diff --git a/testdata/json/execute_error.json b/testdata/json/execute_error.json new file mode 100644 index 0000000..6023c86 --- /dev/null +++ b/testdata/json/execute_error.json @@ -0,0 +1,12 @@ +{ + "contents": "foo", + "document": { + "snippets": {}, + "title": "My Title", + "type": "hype.Document" + }, + "error": "EOF", + "filename": "module.md", + "root": "testdata/parser/errors/execute", + "type": "hype.ExecuteError" +} \ No newline at end of file diff --git a/testdata/json/fenced_code.json b/testdata/json/fenced_code.json new file mode 100644 index 0000000..746885c --- /dev/null +++ b/testdata/json/fenced_code.json @@ -0,0 +1,23 @@ +{ + "atom": "code", + "attributes": { + "language": "go" + }, + "filename": "", + "html_node": { + "data": "code", + "data_atom": "code", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "lang": "go", + "nodes": [ + { + "text": "var x = 1", + "type": "hype.Text" + } + ], + "tag": "\u003ccode language=\"go\"\u003e", + "type": "hype.FencedCode" +} \ No newline at end of file diff --git a/testdata/json/figcaption.json b/testdata/json/figcaption.json new file mode 100644 index 0000000..a8b468d --- /dev/null +++ b/testdata/json/figcaption.json @@ -0,0 +1,20 @@ +{ + "atom": "figcaption", + "attributes": {}, + "filename": "", + "html_node": { + "data": "figcaption", + "data_atom": "figcaption", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + { + "text": "This is a caption", + "type": "hype.Text" + } + ], + "tag": "\u003cfigcaption\u003e", + "type": "hype.Figcaption" +} \ No newline at end of file diff --git a/testdata/json/figure.json b/testdata/json/figure.json new file mode 100644 index 0000000..bc6cec5 --- /dev/null +++ b/testdata/json/figure.json @@ -0,0 +1,23 @@ +{ + "atom": "figure", + "attributes": {}, + "filename": "", + "html_node": { + "data": "figure", + "data_atom": "figure", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + { + "text": "This is a figure", + "type": "hype.Text" + } + ], + "pos": 1, + "section_id": 2, + "style": "figure", + "tag": "\u003cfigure\u003e", + "type": "hype.Figure" +} \ No newline at end of file diff --git a/testdata/json/heading.json b/testdata/json/heading.json new file mode 100644 index 0000000..a050bbe --- /dev/null +++ b/testdata/json/heading.json @@ -0,0 +1,21 @@ +{ + "atom": "h1", + "attributes": {}, + "filename": "", + "html_node": { + "data": "h1", + "data_atom": "h1", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "level": 1, + "nodes": [ + { + "text": "This is a heading", + "type": "hype.Text" + } + ], + "tag": "\u003ch1\u003e", + "type": "hype.Heading" +} \ No newline at end of file diff --git a/testdata/json/image.json b/testdata/json/image.json new file mode 100644 index 0000000..5a78232 --- /dev/null +++ b/testdata/json/image.json @@ -0,0 +1,17 @@ +{ + "atom": "img", + "attributes": { + "src": "https://example.com/image.jpg" + }, + "filename": "", + "html_node": { + "data": "img", + "data_atom": "img", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [], + "tag": "\u003cimg src=\"https://example.com/image.jpg\"\u003e", + "type": "hype.Image" +} \ No newline at end of file diff --git a/testdata/json/include.json b/testdata/json/include.json new file mode 100644 index 0000000..76636d4 --- /dev/null +++ b/testdata/json/include.json @@ -0,0 +1,18 @@ +{ + "atom": "include", + "attributes": { + "src": "sub.md" + }, + "dir": "testdata/includes", + "filename": "", + "html_node": { + "data": "include", + "data_atom": "", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [], + "tag": "\u003cinclude src=\"sub.md\"\u003e", + "type": "hype.Include" +} \ No newline at end of file diff --git a/testdata/json/inline_code.json b/testdata/json/inline_code.json new file mode 100644 index 0000000..f9a9473 --- /dev/null +++ b/testdata/json/inline_code.json @@ -0,0 +1,20 @@ +{ + "atom": "code", + "attributes": {}, + "filename": "", + "html_node": { + "data": "code", + "data_atom": "code", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + { + "text": "var x = 1", + "type": "hype.Text" + } + ], + "tag": "\u003ccode\u003e", + "type": "hype.InlineCode" +} \ No newline at end of file diff --git a/testdata/json/li.json b/testdata/json/li.json new file mode 100644 index 0000000..56385b1 --- /dev/null +++ b/testdata/json/li.json @@ -0,0 +1,21 @@ +{ + "atom": "li", + "attributes": {}, + "filename": "", + "html_node": { + "data": "li", + "data_atom": "li", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "list-type": "ul", + "nodes": [ + { + "text": "This is a list item", + "type": "hype.Text" + } + ], + "tag": "\u003cli\u003e", + "type": "hype.LI" +} \ No newline at end of file diff --git a/testdata/json/link.json b/testdata/json/link.json new file mode 100644 index 0000000..d1f7281 --- /dev/null +++ b/testdata/json/link.json @@ -0,0 +1,23 @@ +{ + "atom": "a", + "attributes": { + "href": "https://example.com" + }, + "filename": "", + "html_node": { + "data": "a", + "data_atom": "a", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + { + "text": "This is a link", + "type": "hype.Text" + } + ], + "tag": "\u003ca href=\"https://example.com\"\u003e", + "type": "hype.Link", + "url": "https://example.com" +} \ No newline at end of file diff --git a/testdata/json/metadata.json b/testdata/json/metadata.json new file mode 100644 index 0000000..c741450 --- /dev/null +++ b/testdata/json/metadata.json @@ -0,0 +1,18 @@ +{ + "atom": "metadata", + "attributes": {}, + "data": { + "title": "Hello, World!" + }, + "filename": "", + "html_node": { + "data": "metadata", + "data_atom": "", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [], + "tag": "\u003cmetadata\u003e", + "type": "hype.Metadata" +} \ No newline at end of file diff --git a/testdata/json/now.json b/testdata/json/now.json new file mode 100644 index 0000000..9b5f1b4 --- /dev/null +++ b/testdata/json/now.json @@ -0,0 +1,20 @@ +{ + "atom": "now", + "attributes": {}, + "filename": "", + "html_node": { + "data": "now", + "data_atom": "", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + { + "text": "1/2/2006", + "type": "hype.Text" + } + ], + "tag": "\u003cnow\u003e", + "type": "hype.Now" +} \ No newline at end of file diff --git a/testdata/json/ol.json b/testdata/json/ol.json new file mode 100644 index 0000000..e22aaa7 --- /dev/null +++ b/testdata/json/ol.json @@ -0,0 +1,20 @@ +{ + "atom": "ol", + "attributes": {}, + "filename": "", + "html_node": { + "data": "ol", + "data_atom": "ol", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + { + "text": "This is an ordered list", + "type": "hype.Text" + } + ], + "tag": "\u003col\u003e", + "type": "hype.OL" +} \ No newline at end of file diff --git a/testdata/json/p.json b/testdata/json/p.json new file mode 100644 index 0000000..16d7304 --- /dev/null +++ b/testdata/json/p.json @@ -0,0 +1,20 @@ +{ + "atom": "p", + "attributes": {}, + "filename": "", + "html_node": { + "data": "p", + "data_atom": "p", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + { + "text": "This is a paragraph", + "type": "hype.Text" + } + ], + "tag": "\u003cp\u003e", + "type": "hype.Paragraph" +} \ No newline at end of file diff --git a/testdata/json/page.json b/testdata/json/page.json new file mode 100644 index 0000000..c97b51f --- /dev/null +++ b/testdata/json/page.json @@ -0,0 +1,21 @@ +{ + "atom": "page", + "attributes": {}, + "filename": "", + "html_node": { + "data": "page", + "data_atom": "", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + { + "text": "more text", + "type": "hype.Text" + } + ], + "tag": "\u003cpage\u003e", + "title": "Page 1", + "type": "hype.Page" +} \ No newline at end of file diff --git a/testdata/json/parse_error.json b/testdata/json/parse_error.json new file mode 100644 index 0000000..39e71bc --- /dev/null +++ b/testdata/json/parse_error.json @@ -0,0 +1,7 @@ +{ + "contents": "contents", + "error": "EOF", + "filename": "test.md", + "root": "root", + "type": "hype.ParseError" +} \ No newline at end of file diff --git a/testdata/json/parser.json b/testdata/json/parser.json new file mode 100644 index 0000000..e18f50a --- /dev/null +++ b/testdata/json/parser.json @@ -0,0 +1,10 @@ +{ + "type": "hype.Parser", + "root": "testdata/auto/snippets/simple", + "disable_pages": true, + "section": 42, + "vars": { + "foo": "bar" + }, + "contents": "# Lone Ranger\n\n\u003ccode src=\"src/main.go\" snippet=\"foo\"\u003e\u003c/code\u003e\n" +} \ No newline at end of file diff --git a/testdata/json/post_execute_error.json b/testdata/json/post_execute_error.json new file mode 100644 index 0000000..f8a46d1 --- /dev/null +++ b/testdata/json/post_execute_error.json @@ -0,0 +1,13 @@ +{ + "document": { + "snippets": {}, + "title": "My Title", + "type": "hype.Document" + }, + "error": "EOF", + "filename": "filename", + "origal_error": "io: read/write on closed pipe", + "post_executer": "\u003cnil\u003e", + "root": "root", + "type": "hype.PostExecuteError" +} \ No newline at end of file diff --git a/testdata/json/post_parse_error.json b/testdata/json/post_parse_error.json new file mode 100644 index 0000000..15bcba6 --- /dev/null +++ b/testdata/json/post_parse_error.json @@ -0,0 +1,13 @@ +{ + "document": { + "snippets": {}, + "title": "My Title", + "type": "hype.Document" + }, + "error": "EOF", + "filename": "foo.md", + "origal_error": "io: read/write on closed pipe", + "post_parser": "\u003cnil\u003e", + "root": "root", + "type": "hype.PostParseError" +} \ No newline at end of file diff --git a/testdata/json/pre_execute_error.json b/testdata/json/pre_execute_error.json new file mode 100644 index 0000000..2e194fc --- /dev/null +++ b/testdata/json/pre_execute_error.json @@ -0,0 +1,12 @@ +{ + "document": { + "snippets": {}, + "title": "My Title", + "type": "hype.Document" + }, + "error": "EOF", + "filename": "module.md", + "pre_executer": "\u003cnil\u003e", + "root": "root", + "type": "hype.PreExecuteError" +} \ No newline at end of file diff --git a/testdata/json/pre_parse_error.json b/testdata/json/pre_parse_error.json new file mode 100644 index 0000000..36c482c --- /dev/null +++ b/testdata/json/pre_parse_error.json @@ -0,0 +1,8 @@ +{ + "contents": "", + "error": "EOF", + "filename": "module.md", + "pre_parser": "\u003cnil\u003e", + "root": "root", + "type": "hype.PreParseError" +} \ No newline at end of file diff --git a/testdata/json/ref.json b/testdata/json/ref.json new file mode 100644 index 0000000..69e1385 --- /dev/null +++ b/testdata/json/ref.json @@ -0,0 +1,22 @@ +{ + "atom": "ref", + "attributes": { + "id": "foo" + }, + "filename": "", + "html_node": { + "data": "ref", + "data_atom": "", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + { + "text": "foo", + "type": "hype.Text" + } + ], + "tag": "\u003cref id=\"foo\"\u003e", + "type": "hype.Ref" +} \ No newline at end of file diff --git a/testdata/json/table.json b/testdata/json/table.json new file mode 100644 index 0000000..4651443 --- /dev/null +++ b/testdata/json/table.json @@ -0,0 +1,382 @@ +{ + "atom": "table", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "table", + "data_atom": "table", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + { + "text": "\n", + "type": "hype.Text" + }, + [ + { + "atom": "thead", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "thead", + "data_atom": "thead", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + { + "text": "\n", + "type": "hype.Text" + }, + [ + { + "atom": "tr", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "tr", + "data_atom": "tr", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + { + "text": "\n", + "type": "hype.Text" + }, + [ + { + "atom": "th", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "th", + "data_atom": "th", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + { + "text": "Name", + "type": "hype.Text" + } + ], + "tag": "\u003cth\u003e", + "type": "hype.TH" + } + ], + { + "text": "\n", + "type": "hype.Text" + }, + [ + { + "atom": "th", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "th", + "data_atom": "th", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + { + "text": "Age", + "type": "hype.Text" + } + ], + "tag": "\u003cth\u003e", + "type": "hype.TH" + } + ], + { + "text": "\n", + "type": "hype.Text" + } + ], + "tag": "\u003ctr\u003e", + "type": "hype.TR" + } + ], + { + "text": "\n", + "type": "hype.Text" + } + ], + "tag": "\u003cthead\u003e", + "type": "hype.THead" + } + ], + { + "text": "\n\n", + "type": "hype.Text" + }, + { + "atom": "tbody", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "tbody", + "data_atom": "tbody", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + { + "text": "\n", + "type": "hype.Text" + }, + [ + { + "atom": "tr", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "tr", + "data_atom": "tr", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + { + "text": "\n", + "type": "hype.Text" + }, + [ + { + "atom": "td", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "td", + "data_atom": "td", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + { + "text": "Alice", + "type": "hype.Text" + } + ], + "tag": "\u003ctd\u003e", + "type": "hype.TD" + } + ], + { + "text": "\n", + "type": "hype.Text" + }, + [ + { + "atom": "td", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "td", + "data_atom": "td", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + { + "text": "42", + "type": "hype.Text" + } + ], + "tag": "\u003ctd\u003e", + "type": "hype.TD" + } + ], + { + "text": "\n", + "type": "hype.Text" + } + ], + "tag": "\u003ctr\u003e", + "type": "hype.TR" + } + ], + { + "text": "\n\n", + "type": "hype.Text" + }, + [ + { + "atom": "tr", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "tr", + "data_atom": "tr", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + { + "text": "\n", + "type": "hype.Text" + }, + [ + { + "atom": "td", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "td", + "data_atom": "td", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + { + "text": "Bob", + "type": "hype.Text" + } + ], + "tag": "\u003ctd\u003e", + "type": "hype.TD" + } + ], + { + "text": "\n", + "type": "hype.Text" + }, + [ + { + "atom": "td", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "td", + "data_atom": "td", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + { + "text": "13", + "type": "hype.Text" + } + ], + "tag": "\u003ctd\u003e", + "type": "hype.TD" + } + ], + { + "text": "\n", + "type": "hype.Text" + } + ], + "tag": "\u003ctr\u003e", + "type": "hype.TR" + } + ], + { + "text": "\n\n", + "type": "hype.Text" + }, + [ + { + "atom": "tr", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "tr", + "data_atom": "tr", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + { + "text": "\n", + "type": "hype.Text" + }, + [ + { + "atom": "td", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "td", + "data_atom": "td", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + { + "text": "Kurt", + "type": "hype.Text" + } + ], + "tag": "\u003ctd\u003e", + "type": "hype.TD" + } + ], + { + "text": "\n", + "type": "hype.Text" + }, + [ + { + "atom": "td", + "attributes": {}, + "filename": "module.md", + "html_node": { + "data": "td", + "data_atom": "td", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + { + "text": "27", + "type": "hype.Text" + } + ], + "tag": "\u003ctd\u003e", + "type": "hype.TD" + } + ], + { + "text": "\n", + "type": "hype.Text" + } + ], + "tag": "\u003ctr\u003e", + "type": "hype.TR" + } + ], + { + "text": "\n", + "type": "hype.Text" + } + ], + "tag": "\u003ctbody\u003e", + "type": "hype.Element" + }, + { + "text": "\n", + "type": "hype.Text" + } + ], + "tag": "\u003ctable\u003e", + "type": "hype.Table" +} \ No newline at end of file diff --git a/testdata/json/td.json b/testdata/json/td.json new file mode 100644 index 0000000..253c34e --- /dev/null +++ b/testdata/json/td.json @@ -0,0 +1,20 @@ +{ + "atom": "td", + "attributes": {}, + "filename": "", + "html_node": { + "data": "td", + "data_atom": "td", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [ + { + "text": "foo", + "type": "hype.Text" + } + ], + "tag": "\u003ctd\u003e", + "type": "hype.TD" +} \ No newline at end of file diff --git a/testdata/json/th.json b/testdata/json/th.json new file mode 100644 index 0000000..221bde1 --- /dev/null +++ b/testdata/json/th.json @@ -0,0 +1,15 @@ +{ + "atom": "th", + "attributes": {}, + "filename": "", + "html_node": { + "data": "th", + "data_atom": "th", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [], + "tag": "\u003cth\u003e", + "type": "hype.TH" +} \ No newline at end of file diff --git a/testdata/json/thead.json b/testdata/json/thead.json new file mode 100644 index 0000000..7983ec7 --- /dev/null +++ b/testdata/json/thead.json @@ -0,0 +1,15 @@ +{ + "atom": "th", + "attributes": {}, + "filename": "", + "html_node": { + "data": "th", + "data_atom": "th", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [], + "tag": "\u003cth\u003e", + "type": "hype.THead" +} \ No newline at end of file diff --git a/testdata/json/toc.json b/testdata/json/toc.json new file mode 100644 index 0000000..fa55ee4 --- /dev/null +++ b/testdata/json/toc.json @@ -0,0 +1,15 @@ +{ + "atom": "toc", + "attributes": {}, + "filename": "", + "html_node": { + "data": "toc", + "data_atom": "", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [], + "tag": "\u003ctoc\u003e", + "type": "hype.ToC" +} \ No newline at end of file diff --git a/testdata/json/tr.json b/testdata/json/tr.json new file mode 100644 index 0000000..a77aab6 --- /dev/null +++ b/testdata/json/tr.json @@ -0,0 +1,15 @@ +{ + "atom": "tr", + "attributes": {}, + "filename": "", + "html_node": { + "data": "tr", + "data_atom": "tr", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [], + "tag": "\u003ctr\u003e", + "type": "hype.TR" +} \ No newline at end of file diff --git a/testdata/json/ul.json b/testdata/json/ul.json new file mode 100644 index 0000000..8707e1a --- /dev/null +++ b/testdata/json/ul.json @@ -0,0 +1,15 @@ +{ + "atom": "ul", + "attributes": {}, + "filename": "", + "html_node": { + "data": "ul", + "data_atom": "ul", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "nodes": [], + "tag": "\u003cul\u003e", + "type": "hype.UL" +} \ No newline at end of file diff --git a/testdata/json/var.json b/testdata/json/var.json new file mode 100644 index 0000000..7bee71a --- /dev/null +++ b/testdata/json/var.json @@ -0,0 +1,22 @@ +{ + "atom": "var", + "attributes": {}, + "filename": "", + "html_node": { + "data": "var", + "data_atom": "var", + "namespace": "", + "node_type": "html.ElementNode", + "type": "html.Node" + }, + "key": "id", + "nodes": [ + { + "text": "id", + "type": "hype.Text" + } + ], + "tag": "\u003cvar\u003e", + "type": "hype.Var", + "value": 1 +} \ No newline at end of file diff --git a/testdata/parser/errors/execute/module.md b/testdata/parser/errors/execute/module.md new file mode 100644 index 0000000..0f8b9d4 --- /dev/null +++ b/testdata/parser/errors/execute/module.md @@ -0,0 +1,3 @@ +# Hello + + diff --git a/testdata/parser/errors/folder/01-one/assets/foo.png b/testdata/parser/errors/folder/01-one/assets/foo.png new file mode 100644 index 0000000..e69de29 diff --git a/testdata/parser/errors/folder/01-one/module.md b/testdata/parser/errors/folder/01-one/module.md new file mode 100644 index 0000000..13cdf1d --- /dev/null +++ b/testdata/parser/errors/folder/01-one/module.md @@ -0,0 +1,17 @@ +# ONE + +In we see the reference. + +
+ + + + + +
Optional caption
+ +
+ + + +Some more text that references . diff --git a/testdata/parser/errors/folder/01-one/module.tex.gold b/testdata/parser/errors/folder/01-one/module.tex.gold new file mode 100644 index 0000000..63ebee2 --- /dev/null +++ b/testdata/parser/errors/folder/01-one/module.tex.gold @@ -0,0 +1,47 @@ +\hypertarget{simple-references}{% +\section{Simple References}\label{simple-references}} + +In \protect\hyperlink{figure-1-1}{Figure 1.1} we see the reference. + +\begin{figure} +\centering +\includegraphics{assets/foo.png} +\caption{\emph{Figure 1.1:} Optional caption} +\end{figure} + +\hypertarget{include-references}{% +\section{Include References}\label{include-references}} + +This \protect\hyperlink{figure-1-1}{Figure 1.1} is in an included file. + +\begin{Shaded} +\begin{Highlighting}[] +\NormalTok{$ go doc {-}short context.Canceled} + +\NormalTok{var Canceled = errors.New("context canceled")} +\NormalTok{ Canceled is the error returned by Context.Err when the context is canceled.} + +\NormalTok{{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}} +\NormalTok{Go Version: go1.19beta1} +\end{Highlighting} +\end{Shaded} + +More included text that references \protect\hyperlink{figure-1-1}{Figure +1.1}. + +\begin{Shaded} +\begin{Highlighting}[] +\KeywordTok{package}\NormalTok{ main} + +\KeywordTok{import} \StringTok{"fmt"} + +\KeywordTok{func}\NormalTok{ main}\OperatorTok{()} \OperatorTok{\{} +\NormalTok{ fmt}\OperatorTok{.}\NormalTok{Println}\OperatorTok{(}\StringTok{"Howdy!"}\OperatorTok{)} +\OperatorTok{\}} +\end{Highlighting} +\end{Shaded} + +end note + +Some more text that references \protect\hyperlink{figure-1-1}{Figure +1.1}. \ No newline at end of file diff --git a/testdata/parser/errors/folder/01-one/simple/assets/foo.png b/testdata/parser/errors/folder/01-one/simple/assets/foo.png new file mode 100644 index 0000000..e69de29 diff --git a/testdata/parser/errors/folder/01-one/simple/simple.md b/testdata/parser/errors/folder/01-one/simple/simple.md new file mode 100644 index 0000000..57408c7 --- /dev/null +++ b/testdata/parser/errors/folder/01-one/simple/simple.md @@ -0,0 +1,19 @@ +# Include References + +This is in an included file. + + + +More included text that references . + +```go +package main + +import "fmt" + +func main() { + fmt.Println("Howdy!") +} +``` + +end note diff --git a/testdata/parser/errors/folder/01-one/simple/src/greet/go.mod b/testdata/parser/errors/folder/01-one/simple/src/greet/go.mod new file mode 100644 index 0000000..2287625 --- /dev/null +++ b/testdata/parser/errors/folder/01-one/simple/src/greet/go.mod @@ -0,0 +1,3 @@ +module demo + +go 1.22 diff --git a/testdata/parser/errors/folder/01-one/simple/src/greet/main.go b/testdata/parser/errors/folder/01-one/simple/src/greet/main.go new file mode 100644 index 0000000..a4d726f --- /dev/null +++ b/testdata/parser/errors/folder/01-one/simple/src/greet/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "fmt" +) + +func main() { + // snippet: example + fmt.Println("Howdy!") + // snippet: example +} diff --git a/testdata/parser/errors/folder/01-one/src/greet/go.mod b/testdata/parser/errors/folder/01-one/src/greet/go.mod new file mode 100644 index 0000000..2287625 --- /dev/null +++ b/testdata/parser/errors/folder/01-one/src/greet/go.mod @@ -0,0 +1,3 @@ +module demo + +go 1.22 diff --git a/testdata/parser/errors/folder/01-one/src/greet/main.go b/testdata/parser/errors/folder/01-one/src/greet/main.go new file mode 100644 index 0000000..b8d003d --- /dev/null +++ b/testdata/parser/errors/folder/01-one/src/greet/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "fmt" +) + +func main() { + // snippet: example + fmt.Println("Hello, World!") + // snippet: example +} diff --git a/testdata/parser/errors/folder/02-two/assets/foo.png b/testdata/parser/errors/folder/02-two/assets/foo.png new file mode 100644 index 0000000..e69de29 diff --git a/testdata/parser/errors/folder/02-two/module.md b/testdata/parser/errors/folder/02-two/module.md new file mode 100644 index 0000000..288731d --- /dev/null +++ b/testdata/parser/errors/folder/02-two/module.md @@ -0,0 +1,19 @@ +# TWO + +In we see the reference. + +
+ + + + + +
Optional caption
+ +
+ + + +Some more text that references . + + diff --git a/testdata/parser/errors/folder/02-two/module.tex.gold b/testdata/parser/errors/folder/02-two/module.tex.gold new file mode 100644 index 0000000..63ebee2 --- /dev/null +++ b/testdata/parser/errors/folder/02-two/module.tex.gold @@ -0,0 +1,47 @@ +\hypertarget{simple-references}{% +\section{Simple References}\label{simple-references}} + +In \protect\hyperlink{figure-1-1}{Figure 1.1} we see the reference. + +\begin{figure} +\centering +\includegraphics{assets/foo.png} +\caption{\emph{Figure 1.1:} Optional caption} +\end{figure} + +\hypertarget{include-references}{% +\section{Include References}\label{include-references}} + +This \protect\hyperlink{figure-1-1}{Figure 1.1} is in an included file. + +\begin{Shaded} +\begin{Highlighting}[] +\NormalTok{$ go doc {-}short context.Canceled} + +\NormalTok{var Canceled = errors.New("context canceled")} +\NormalTok{ Canceled is the error returned by Context.Err when the context is canceled.} + +\NormalTok{{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}} +\NormalTok{Go Version: go1.19beta1} +\end{Highlighting} +\end{Shaded} + +More included text that references \protect\hyperlink{figure-1-1}{Figure +1.1}. + +\begin{Shaded} +\begin{Highlighting}[] +\KeywordTok{package}\NormalTok{ main} + +\KeywordTok{import} \StringTok{"fmt"} + +\KeywordTok{func}\NormalTok{ main}\OperatorTok{()} \OperatorTok{\{} +\NormalTok{ fmt}\OperatorTok{.}\NormalTok{Println}\OperatorTok{(}\StringTok{"Howdy!"}\OperatorTok{)} +\OperatorTok{\}} +\end{Highlighting} +\end{Shaded} + +end note + +Some more text that references \protect\hyperlink{figure-1-1}{Figure +1.1}. \ No newline at end of file diff --git a/testdata/parser/errors/folder/02-two/simple/assets/foo.png b/testdata/parser/errors/folder/02-two/simple/assets/foo.png new file mode 100644 index 0000000..e69de29 diff --git a/testdata/parser/errors/folder/02-two/simple/simple.md b/testdata/parser/errors/folder/02-two/simple/simple.md new file mode 100644 index 0000000..57408c7 --- /dev/null +++ b/testdata/parser/errors/folder/02-two/simple/simple.md @@ -0,0 +1,19 @@ +# Include References + +This is in an included file. + + + +More included text that references . + +```go +package main + +import "fmt" + +func main() { + fmt.Println("Howdy!") +} +``` + +end note diff --git a/testdata/parser/errors/folder/02-two/simple/src/greet/go.mod b/testdata/parser/errors/folder/02-two/simple/src/greet/go.mod new file mode 100644 index 0000000..2287625 --- /dev/null +++ b/testdata/parser/errors/folder/02-two/simple/src/greet/go.mod @@ -0,0 +1,3 @@ +module demo + +go 1.22 diff --git a/testdata/parser/errors/folder/02-two/simple/src/greet/main.go b/testdata/parser/errors/folder/02-two/simple/src/greet/main.go new file mode 100644 index 0000000..a4d726f --- /dev/null +++ b/testdata/parser/errors/folder/02-two/simple/src/greet/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "fmt" +) + +func main() { + // snippet: example + fmt.Println("Howdy!") + // snippet: example +} diff --git a/testdata/parser/errors/folder/02-two/src/greet/go.mod b/testdata/parser/errors/folder/02-two/src/greet/go.mod new file mode 100644 index 0000000..2287625 --- /dev/null +++ b/testdata/parser/errors/folder/02-two/src/greet/go.mod @@ -0,0 +1,3 @@ +module demo + +go 1.22 diff --git a/testdata/parser/errors/folder/02-two/src/greet/main.go b/testdata/parser/errors/folder/02-two/src/greet/main.go new file mode 100644 index 0000000..b8d003d --- /dev/null +++ b/testdata/parser/errors/folder/02-two/src/greet/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "fmt" +) + +func main() { + // snippet: example + fmt.Println("Hello, World!") + // snippet: example +} diff --git a/testdata/parser/errors/folder/03-three/assets/foo.png b/testdata/parser/errors/folder/03-three/assets/foo.png new file mode 100644 index 0000000..e69de29 diff --git a/testdata/parser/errors/folder/03-three/module.md b/testdata/parser/errors/folder/03-three/module.md new file mode 100644 index 0000000..c8ea586 --- /dev/null +++ b/testdata/parser/errors/folder/03-three/module.md @@ -0,0 +1,17 @@ +# THREE + +In we see the reference. + +
+ + + + + +
Optional caption
+ +
+ + + +Some more text that references . diff --git a/testdata/parser/errors/folder/03-three/module.tex.gold b/testdata/parser/errors/folder/03-three/module.tex.gold new file mode 100644 index 0000000..63ebee2 --- /dev/null +++ b/testdata/parser/errors/folder/03-three/module.tex.gold @@ -0,0 +1,47 @@ +\hypertarget{simple-references}{% +\section{Simple References}\label{simple-references}} + +In \protect\hyperlink{figure-1-1}{Figure 1.1} we see the reference. + +\begin{figure} +\centering +\includegraphics{assets/foo.png} +\caption{\emph{Figure 1.1:} Optional caption} +\end{figure} + +\hypertarget{include-references}{% +\section{Include References}\label{include-references}} + +This \protect\hyperlink{figure-1-1}{Figure 1.1} is in an included file. + +\begin{Shaded} +\begin{Highlighting}[] +\NormalTok{$ go doc {-}short context.Canceled} + +\NormalTok{var Canceled = errors.New("context canceled")} +\NormalTok{ Canceled is the error returned by Context.Err when the context is canceled.} + +\NormalTok{{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}{-}} +\NormalTok{Go Version: go1.19beta1} +\end{Highlighting} +\end{Shaded} + +More included text that references \protect\hyperlink{figure-1-1}{Figure +1.1}. + +\begin{Shaded} +\begin{Highlighting}[] +\KeywordTok{package}\NormalTok{ main} + +\KeywordTok{import} \StringTok{"fmt"} + +\KeywordTok{func}\NormalTok{ main}\OperatorTok{()} \OperatorTok{\{} +\NormalTok{ fmt}\OperatorTok{.}\NormalTok{Println}\OperatorTok{(}\StringTok{"Howdy!"}\OperatorTok{)} +\OperatorTok{\}} +\end{Highlighting} +\end{Shaded} + +end note + +Some more text that references \protect\hyperlink{figure-1-1}{Figure +1.1}. \ No newline at end of file diff --git a/testdata/parser/errors/folder/03-three/simple/assets/foo.png b/testdata/parser/errors/folder/03-three/simple/assets/foo.png new file mode 100644 index 0000000..e69de29 diff --git a/testdata/parser/errors/folder/03-three/simple/simple.md b/testdata/parser/errors/folder/03-three/simple/simple.md new file mode 100644 index 0000000..57408c7 --- /dev/null +++ b/testdata/parser/errors/folder/03-three/simple/simple.md @@ -0,0 +1,19 @@ +# Include References + +This is in an included file. + + + +More included text that references . + +```go +package main + +import "fmt" + +func main() { + fmt.Println("Howdy!") +} +``` + +end note diff --git a/testdata/parser/errors/folder/03-three/simple/src/greet/go.mod b/testdata/parser/errors/folder/03-three/simple/src/greet/go.mod new file mode 100644 index 0000000..2287625 --- /dev/null +++ b/testdata/parser/errors/folder/03-three/simple/src/greet/go.mod @@ -0,0 +1,3 @@ +module demo + +go 1.22 diff --git a/testdata/parser/errors/folder/03-three/simple/src/greet/main.go b/testdata/parser/errors/folder/03-three/simple/src/greet/main.go new file mode 100644 index 0000000..a4d726f --- /dev/null +++ b/testdata/parser/errors/folder/03-three/simple/src/greet/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "fmt" +) + +func main() { + // snippet: example + fmt.Println("Howdy!") + // snippet: example +} diff --git a/testdata/parser/errors/folder/03-three/src/greet/go.mod b/testdata/parser/errors/folder/03-three/src/greet/go.mod new file mode 100644 index 0000000..2287625 --- /dev/null +++ b/testdata/parser/errors/folder/03-three/src/greet/go.mod @@ -0,0 +1,3 @@ +module demo + +go 1.22 diff --git a/testdata/parser/errors/folder/03-three/src/greet/main.go b/testdata/parser/errors/folder/03-three/src/greet/main.go new file mode 100644 index 0000000..b8d003d --- /dev/null +++ b/testdata/parser/errors/folder/03-three/src/greet/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "fmt" +) + +func main() { + // snippet: example + fmt.Println("Hello, World!") + // snippet: example +} diff --git a/testdata/parser/errors/post_execute/module.md b/testdata/parser/errors/post_execute/module.md new file mode 100644 index 0000000..0f8b9d4 --- /dev/null +++ b/testdata/parser/errors/post_execute/module.md @@ -0,0 +1,3 @@ +# Hello + + diff --git a/testdata/parser/errors/post_parse/module.md b/testdata/parser/errors/post_parse/module.md new file mode 100644 index 0000000..0f8b9d4 --- /dev/null +++ b/testdata/parser/errors/post_parse/module.md @@ -0,0 +1,3 @@ +# Hello + + diff --git a/testdata/parser/errors/pre_execute/module.md b/testdata/parser/errors/pre_execute/module.md new file mode 100644 index 0000000..0f8b9d4 --- /dev/null +++ b/testdata/parser/errors/pre_execute/module.md @@ -0,0 +1,3 @@ +# Hello + + diff --git a/testdata/parser/errors/pre_parse/module.md b/testdata/parser/errors/pre_parse/module.md new file mode 100644 index 0000000..0f8b9d4 --- /dev/null +++ b/testdata/parser/errors/pre_parse/module.md @@ -0,0 +1,3 @@ +# Hello + + diff --git a/testdata/parser/folder/01-one/simple/src/greet/go.mod b/testdata/parser/folder/01-one/simple/src/greet/go.mod index 21cc482..2287625 100644 --- a/testdata/parser/folder/01-one/simple/src/greet/go.mod +++ b/testdata/parser/folder/01-one/simple/src/greet/go.mod @@ -1,3 +1,3 @@ module demo -go 1.18 +go 1.22 diff --git a/testdata/parser/folder/01-one/src/greet/go.mod b/testdata/parser/folder/01-one/src/greet/go.mod index 21cc482..2287625 100644 --- a/testdata/parser/folder/01-one/src/greet/go.mod +++ b/testdata/parser/folder/01-one/src/greet/go.mod @@ -1,3 +1,3 @@ module demo -go 1.18 +go 1.22 diff --git a/testdata/parser/folder/02-two/simple/src/greet/go.mod b/testdata/parser/folder/02-two/simple/src/greet/go.mod index 21cc482..2287625 100644 --- a/testdata/parser/folder/02-two/simple/src/greet/go.mod +++ b/testdata/parser/folder/02-two/simple/src/greet/go.mod @@ -1,3 +1,3 @@ module demo -go 1.18 +go 1.22 diff --git a/testdata/parser/folder/02-two/src/greet/go.mod b/testdata/parser/folder/02-two/src/greet/go.mod index 21cc482..2287625 100644 --- a/testdata/parser/folder/02-two/src/greet/go.mod +++ b/testdata/parser/folder/02-two/src/greet/go.mod @@ -1,3 +1,3 @@ module demo -go 1.18 +go 1.22 diff --git a/testdata/parser/folder/03-three/simple/src/greet/go.mod b/testdata/parser/folder/03-three/simple/src/greet/go.mod index 21cc482..2287625 100644 --- a/testdata/parser/folder/03-three/simple/src/greet/go.mod +++ b/testdata/parser/folder/03-three/simple/src/greet/go.mod @@ -1,3 +1,3 @@ module demo -go 1.18 +go 1.22 diff --git a/testdata/parser/folder/03-three/src/greet/go.mod b/testdata/parser/folder/03-three/src/greet/go.mod index 21cc482..2287625 100644 --- a/testdata/parser/folder/03-three/src/greet/go.mod +++ b/testdata/parser/folder/03-three/src/greet/go.mod @@ -1,3 +1,3 @@ module demo -go 1.18 +go 1.22 diff --git a/text.go b/text.go index 8945614..e04473e 100644 --- a/text.go +++ b/text.go @@ -9,10 +9,10 @@ import ( type Text string func (tn Text) MarshalJSON() ([]byte, error) { - return json.Marshal(map[string]any{ - "type": fmt.Sprintf("%T", tn), + return json.MarshalIndent(map[string]any{ + "type": toType(tn), "text": string(tn), - }) + }, "", " ") } func (tn Text) Children() Nodes { diff --git a/th.go b/th.go index 8231252..cf493cf 100644 --- a/th.go +++ b/th.go @@ -3,7 +3,6 @@ package hype import ( "encoding/json" "errors" - "fmt" "strings" ) @@ -24,9 +23,9 @@ func (th *TH) MarshalJSON() ([]byte, error) { return nil, err } - m["type"] = fmt.Sprintf("%T", th) + m["type"] = toType(th) - return json.Marshal(m) + return json.MarshalIndent(m, "", " ") } func (th *TH) IsEmptyNode() bool { diff --git a/th_test.go b/th_test.go new file mode 100644 index 0000000..62e3b91 --- /dev/null +++ b/th_test.go @@ -0,0 +1,13 @@ +package hype + +import "testing" + +func Test_TH_MarshalJSON(t *testing.T) { + t.Parallel() + + th := &TH{ + Element: NewEl("th", nil), + } + + testJSON(t, "th", th) +} diff --git a/thead.go b/thead.go index b6bb8e2..317291b 100644 --- a/thead.go +++ b/thead.go @@ -2,7 +2,6 @@ package hype import ( "encoding/json" - "fmt" ) type THead struct { @@ -19,9 +18,9 @@ func (th *THead) MarshalJSON() ([]byte, error) { return nil, err } - m["type"] = fmt.Sprintf("%T", th) + m["type"] = toType(th) - return json.Marshal(m) + return json.MarshalIndent(m, "", " ") } func (th *THead) String() string { diff --git a/thead_test.go b/thead_test.go new file mode 100644 index 0000000..0a30926 --- /dev/null +++ b/thead_test.go @@ -0,0 +1,14 @@ +package hype + +import "testing" + +func Test_THead_MarshalJSON(t *testing.T) { + t.Parallel() + + th := &THead{ + Element: NewEl("th", nil), + } + + testJSON(t, "thead", th) + +} diff --git a/tmpl_test.go b/tmpl_test.go index b61d3a3..4e4590f 100644 --- a/tmpl_test.go +++ b/tmpl_test.go @@ -2,7 +2,6 @@ package hype import ( "context" - "fmt" "testing" "time" @@ -26,7 +25,7 @@ func Test_GoTemplates(t *testing.T) { act := doc.String() - fmt.Println(act) + // fmt.Println(act) exp := `` diff --git a/toc.go b/toc.go index 0ee80c6..13cc2ff 100644 --- a/toc.go +++ b/toc.go @@ -22,9 +22,9 @@ func (toc *ToC) MarshalJSON() ([]byte, error) { return nil, err } - m["type"] = fmt.Sprintf("%T", toc) + m["type"] = toType(toc) - return json.Marshal(m) + return json.MarshalIndent(m, "", " ") } func (toc *ToC) PostExecute(ctx context.Context, doc *Document, err error) error { diff --git a/toc_test.go b/toc_test.go index 17971ce..a46e182 100644 --- a/toc_test.go +++ b/toc_test.go @@ -41,3 +41,13 @@ func Test_GenerateToC(t *testing.T) { r.Equal(exp, act) } + +func Test_ToC_MarshalJSON(t *testing.T) { + t.Parallel() + + toc := &ToC{ + Element: NewEl("toc", nil), + } + + testJSON(t, "toc", toc) +} diff --git a/tr.go b/tr.go index c14b780..3153ea8 100644 --- a/tr.go +++ b/tr.go @@ -2,7 +2,6 @@ package hype import ( "encoding/json" - "fmt" ) type TR struct { @@ -19,9 +18,9 @@ func (tr *TR) MarshalJSON() ([]byte, error) { return nil, err } - m["type"] = fmt.Sprintf("%T", tr) + m["type"] = toType(tr) - return json.Marshal(m) + return json.MarshalIndent(m, "", " ") } func (tr *TR) IsEmptyNode() bool { diff --git a/tr_test.go b/tr_test.go new file mode 100644 index 0000000..e599378 --- /dev/null +++ b/tr_test.go @@ -0,0 +1,13 @@ +package hype + +import "testing" + +func Test_TR_MarshalJSON(t *testing.T) { + t.Parallel() + + tr := &TR{ + Element: NewEl("tr", nil), + } + + testJSON(t, "tr", tr) +} diff --git a/type.go b/type.go new file mode 100644 index 0000000..2ba47cb --- /dev/null +++ b/type.go @@ -0,0 +1,11 @@ +package hype + +import ( + "fmt" + "strings" +) + +func toType(x any) string { + s := fmt.Sprintf("%T", x) + return strings.TrimPrefix(s, "*") +} diff --git a/ul.go b/ul.go index e6a07bb..ddd0432 100644 --- a/ul.go +++ b/ul.go @@ -2,7 +2,6 @@ package hype import ( "encoding/json" - "fmt" ) type UL struct { @@ -22,9 +21,9 @@ func (ul *UL) MarshalJSON() ([]byte, error) { return nil, err } - m["type"] = fmt.Sprintf("%T", ul) + m["type"] = toType(ul) - return json.Marshal(m) + return json.MarshalIndent(m, "", " ") } func (ol *UL) MD() string { diff --git a/ul_test.go b/ul_test.go new file mode 100644 index 0000000..679aecf --- /dev/null +++ b/ul_test.go @@ -0,0 +1,13 @@ +package hype + +import "testing" + +func Test_UL_MarshalJSON(t *testing.T) { + t.Parallel() + + ul := &UL{ + Element: NewEl("ul", nil), + } + + testJSON(t, "ul", ul) +} diff --git a/unwrap.go b/unwrap.go new file mode 100644 index 0000000..318a6f4 --- /dev/null +++ b/unwrap.go @@ -0,0 +1,5 @@ +package hype + +type unwrapper interface { + Unwrap() error +} diff --git a/var.go b/var.go index 347d024..6d76769 100644 --- a/var.go +++ b/var.go @@ -3,6 +3,7 @@ package hype import ( "bytes" "context" + "encoding/json" "fmt" "io" "strings" @@ -69,16 +70,37 @@ func VarProcessor() PreParseFn { type Var struct { *Element - value any + Key string + Value any } -func (v *Var) String() string { +func (v *Var) MarshalJSON() ([]byte, error) { if v == nil { - return "" + return nil, ErrIsNil("th") + } + + v.RLock() + defer v.RUnlock() + + m, err := v.JSONMap() + if err != nil { + return nil, err + } + + m["type"] = toType(v) + m["key"] = v.Key + m["value"] = v.Value + + return json.MarshalIndent(m, "", " ") +} + +func (v *Var) String() string { + if v == nil || v.Element == nil { + return "" } - if v.value != nil { - return fmt.Sprintf("%v", v.value) + if v.Value != nil { + return fmt.Sprintf("%v", v.Value) } return v.Element.String() @@ -104,7 +126,7 @@ func (v *Var) Execute(ctx context.Context, doc *Document) error { defer v.Unlock() var ok bool - v.value, ok = doc.Parser.Vars.Get(key) + v.Value, ok = doc.Parser.Vars.Get(key) if !ok { return v.WrapErr(fmt.Errorf("unknown var key %q", key)) } @@ -112,20 +134,39 @@ func (v *Var) Execute(ctx context.Context, doc *Document) error { return nil } -func NewVarNode(el *Element) (*Var, error) { +func NewVarNode(p *Parser, el *Element) (*Var, error) { + if p == nil { + return nil, ErrIsNil("parser") + } + if el == nil { return nil, ErrIsNil("element") } + key := el.Nodes.String() + key = strings.TrimSpace(key) + + if len(key) == 0 { + return nil, fmt.Errorf("missing var key") + } + + var ok bool + val, ok := p.Vars.Get(key) + if !ok { + return nil, el.WrapErr(fmt.Errorf("unknown var key %q", key)) + } + v := &Var{ Element: el, + Key: key, + Value: val, } return v, nil } func NewVarNodes(p *Parser, el *Element) (Nodes, error) { - v, err := NewVarNode(el) + v, err := NewVarNode(p, el) if err != nil { return nil, err } diff --git a/var_test.go b/var_test.go index 895a340..14f4122 100644 --- a/var_test.go +++ b/var_test.go @@ -1,77 +1,98 @@ package hype -// func Test_Var(t *testing.T) { -// t.Parallel() -// r := require.New(t) +import ( + "testing" -// data := map[string]any{ -// "id": 1, -// } + "github.com/stretchr/testify/require" +) -// fn, err := NewVarParserFn(data) -// r.NoError(err) -// r.NotNil(fn) +func Test_Var(t *testing.T) { + t.Parallel() + r := require.New(t) -// tcs := []struct { -// name string -// key string -// exp string -// err bool -// }{ -// {name: "valid", key: "id", exp: "1"}, -// {name: "unknown key", key: "404", err: true}, -// {name: "empty key", key: "", err: true}, -// } + data := map[string]any{ + "id": 1, + } -// for _, tc := range tcs { -// tc := tc + p := testParser(t, "testdata") + err := p.Vars.BulkSet(data) + r.NoError(err) -// t.Run(tc.name, func(t *testing.T) { -// r := require.New(t) + tcs := []struct { + name string + key string + exp string + err bool + }{ + {name: "valid", key: "id", exp: "1"}, + {name: "unknown key", key: "404", err: true}, + {name: "empty key", key: "", err: true}, + } -// el := NewEl("var", nil) -// r.NotNil(el) -// el.Nodes = Nodes{Text(tc.key)} + for _, tc := range tcs { + tc := tc -// nodes, err := fn(nil, el) -// if tc.err { -// r.Error(err) -// return -// } + t.Run(tc.name, func(t *testing.T) { + r := require.New(t) -// r.NoError(err) -// r.NotNil(nodes) -// r.NotEmpty(nodes) + el := NewEl("var", nil) + r.NotNil(el) + el.Nodes = Nodes{Text(tc.key)} -// doc := &Document{ -// Nodes: nodes, -// } + nodes, err := NewVarNodes(p, el) + if tc.err { + r.Error(err) + return + } -// err = doc.Execute(context.Background()) -// r.NoError(err) + r.NoError(err) + r.NotNil(nodes) + r.NotEmpty(nodes) -// r.Equal(tc.exp, doc.String()) -// }) -// } + doc := &Document{ + Nodes: nodes, + Parser: p, + } -// } + r.Equal(tc.exp, doc.String()) + }) + } -// func Test_Var_String(t *testing.T) { -// t.Parallel() -// r := require.New(t) +} -// var v *Var -// r.Equal("", v.String()) +func Test_Var_String(t *testing.T) { + t.Parallel() + r := require.New(t) -// v = &Var{} -// r.Equal("", v.String()) + var v *Var + r.Equal("", v.String()) -// v.Element = &Element{} -// r.Equal("", v.String()) + v = &Var{} + r.Equal("", v.String()) -// v.Nodes = append(v.Nodes, Text("id")) -// r.Equal("id", v.String()) + v = &Var{ + Element: NewEl("var", nil), + } -// v.value = 1 -// r.Equal("1", v.String()) -// } + r.Equal("", v.String()) + + v.Nodes = append(v.Nodes, Text("id")) + r.Equal("id", v.String()) + + v.Value = 1 + r.Equal("1", v.String()) +} + +func Test_Var_MarshalJSON(t *testing.T) { + t.Parallel() + + v := &Var{ + Element: NewEl("var", nil), + Key: "id", + Value: 1, + } + v.Nodes = append(v.Nodes, Text("id")) + + testJSON(t, "var", v) + +}