add analytics via segment again
This commit is contained in:
parent
5a8b8dc48f
commit
6b7d6dcc49
|
@ -8,6 +8,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Xe/ln"
|
"github.com/Xe/ln"
|
||||||
|
analytics "gopkg.in/segmentio/analytics-go.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func logTemplateTime(name string, from time.Time) {
|
func logTemplateTime(name string, from time.Time) {
|
||||||
|
@ -70,4 +71,12 @@ func (s *Site) showPost(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
s.renderTemplatePage("blogpost.html", p).ServeHTTP(w, r)
|
s.renderTemplatePage("blogpost.html", p).ServeHTTP(w, r)
|
||||||
|
|
||||||
|
if s.segment != nil {
|
||||||
|
s.segment.Enqueue(&analytics.Track{
|
||||||
|
UserId: Hash(r.RemoteAddr, r.Header.Get("X-Forwarded-For")),
|
||||||
|
Event: "Post Viewed",
|
||||||
|
Properties: analytics.NewProperties().SetURL(r.RequestURI).SetTitle(p.Title),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"github.com/gorilla/feeds"
|
"github.com/gorilla/feeds"
|
||||||
blackfriday "github.com/russross/blackfriday"
|
blackfriday "github.com/russross/blackfriday"
|
||||||
"github.com/tj/front"
|
"github.com/tj/front"
|
||||||
|
"gopkg.in/segmentio/analytics-go.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var port = os.Getenv("PORT")
|
var port = os.Getenv("PORT")
|
||||||
|
@ -47,11 +48,24 @@ type Site struct {
|
||||||
|
|
||||||
templates map[string]*template.Template
|
templates map[string]*template.Template
|
||||||
tlock sync.RWMutex
|
tlock sync.RWMutex
|
||||||
|
|
||||||
|
segment analytics.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Site) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (s *Site) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
ln.Log(r.Context(), ln.F{"action": "Site.ServeHTTP", "user_ip_address": r.RemoteAddr, "path": r.RequestURI})
|
ln.Log(r.Context(), ln.F{"action": "Site.ServeHTTP", "user_ip_address": r.RemoteAddr, "path": r.RequestURI})
|
||||||
|
|
||||||
s.mux.ServeHTTP(w, r)
|
s.mux.ServeHTTP(w, r)
|
||||||
|
|
||||||
|
if s.segment != nil {
|
||||||
|
if !strings.HasPrefix(r.RequestURI, "/blog/") {
|
||||||
|
s.segment.Enqueue(&analytics.Track{
|
||||||
|
UserId: Hash(r.RemoteAddr, r.Header.Get("X-Forwarded-For")),
|
||||||
|
Event: "Page Viewed",
|
||||||
|
Properties: analytics.NewProperties().SetURL(r.RequestURI),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build creates a new Site instance or fails.
|
// Build creates a new Site instance or fails.
|
||||||
|
@ -88,6 +102,10 @@ func Build() (*Site, error) {
|
||||||
templates: map[string]*template.Template{},
|
templates: map[string]*template.Template{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if wk := os.Getenv("SEGMENT_WRITE_KEY"); wk != "" {
|
||||||
|
s.segment = analytics.New(wk)
|
||||||
|
}
|
||||||
|
|
||||||
err := filepath.Walk("./blog/", func(path string, info os.FileInfo, err error) error {
|
err := filepath.Walk("./blog/", func(path string, info os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
26
go.mod
26
go.mod
|
@ -1,22 +1,26 @@
|
||||||
module github.com/Xe/site
|
module github.com/Xe/site
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/GeertJohan/go.rice v0.0.0-20170420135705-c02ca9a983da
|
github.com/GeertJohan/go.rice v0.0.0-20170420135705-c02ca9a983da // indirect
|
||||||
github.com/Xe/gopreload v0.0.0-20170326043426-a00a8beb369c
|
github.com/Xe/gopreload v0.0.0-20170326043426-a00a8beb369c
|
||||||
github.com/Xe/jsonfeed v0.0.0-20170520170432-e21591505612
|
github.com/Xe/jsonfeed v0.0.0-20170520170432-e21591505612
|
||||||
github.com/Xe/ln v0.0.0-20170921000907-466e05b2ef3e
|
github.com/Xe/ln v0.0.0-20170921000907-466e05b2ef3e
|
||||||
github.com/daaku/go.zipexe v0.0.0-20150329023125-a5fe2436ffcb
|
github.com/daaku/go.zipexe v0.0.0-20150329023125-a5fe2436ffcb // indirect
|
||||||
github.com/google/gops v0.3.2
|
github.com/google/gops v0.3.2
|
||||||
github.com/gorilla/feeds v1.0.0
|
github.com/gorilla/feeds v1.0.0
|
||||||
github.com/jtolds/qod v0.0.0-20170925014538-3abb44dfc7ba
|
github.com/jtolds/qod v0.0.0-20170925014538-3abb44dfc7ba // indirect
|
||||||
github.com/kr/pretty v0.1.0
|
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 // indirect
|
||||||
github.com/magefile/mage v0.0.0-20171025021237-ab3ca2f6f855
|
github.com/kr/pretty v0.1.0 // indirect
|
||||||
github.com/pkg/errors v0.8.0
|
github.com/magefile/mage v0.0.0-20171025021237-ab3ca2f6f855 // indirect
|
||||||
|
github.com/pkg/errors v0.8.0 // indirect
|
||||||
github.com/russross/blackfriday v0.0.0-20170806171014-cadec560ec52
|
github.com/russross/blackfriday v0.0.0-20170806171014-cadec560ec52
|
||||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95
|
github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c // indirect
|
||||||
github.com/st3fan/jsonfeed v0.0.0-20170519104842-498b2850d26b
|
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 // indirect
|
||||||
github.com/stretchr/testify v1.2.2
|
github.com/st3fan/jsonfeed v0.0.0-20170519104842-498b2850d26b // indirect
|
||||||
|
github.com/stretchr/testify v1.2.2 // indirect
|
||||||
github.com/tj/front v0.0.0-20170212063142-739be213b0a1
|
github.com/tj/front v0.0.0-20170212063142-739be213b0a1
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127
|
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
|
||||||
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||||
|
gopkg.in/segmentio/analytics-go.v3 v3.0.0-20180814014620-14e03163be03
|
||||||
|
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 // indirect
|
||||||
)
|
)
|
||||||
|
|
7
go.sum
7
go.sum
|
@ -13,6 +13,7 @@ github.com/gorilla/feeds v1.0.0 h1:EbkEvaYf+PXhYNHS20heBG7Rl2X6Zy8l11ZBWAHkWqE=
|
||||||
github.com/gorilla/feeds v1.0.0/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA=
|
github.com/gorilla/feeds v1.0.0/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA=
|
||||||
github.com/jtolds/qod v0.0.0-20170925014538-3abb44dfc7ba/go.mod h1:CC0XsLGDIrizqHTsUEJh89QHzv2u2mix/O9xl4+LqSw=
|
github.com/jtolds/qod v0.0.0-20170925014538-3abb44dfc7ba/go.mod h1:CC0XsLGDIrizqHTsUEJh89QHzv2u2mix/O9xl4+LqSw=
|
||||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 h1:PJPDf8OUfOK1bb/NeTKd4f1QXZItOX389VN3B6qC8ro=
|
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 h1:PJPDf8OUfOK1bb/NeTKd4f1QXZItOX389VN3B6qC8ro=
|
||||||
|
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
@ -24,6 +25,8 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/russross/blackfriday v0.0.0-20170806171014-cadec560ec52 h1:xZ0R4UuR0EDx7m0OmvsqZaomfqCeYJ/PyLm94WLhnIM=
|
github.com/russross/blackfriday v0.0.0-20170806171014-cadec560ec52 h1:xZ0R4UuR0EDx7m0OmvsqZaomfqCeYJ/PyLm94WLhnIM=
|
||||||
github.com/russross/blackfriday v0.0.0-20170806171014-cadec560ec52/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
github.com/russross/blackfriday v0.0.0-20170806171014-cadec560ec52/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||||
|
github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c h1:rsRTAcCR5CeNLkvgBVSjQoDGRRt6kggsE6XYBqCv2KQ=
|
||||||
|
github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c/go.mod h1:kJ9mm9YmoWSkk+oQ+5Cj8DEoRCX2JT6As4kEtIIOp1M=
|
||||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 h1:/vdW8Cb7EXrkqWGufVMES1OH2sU9gKVb2n9/1y5NMBY=
|
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 h1:/vdW8Cb7EXrkqWGufVMES1OH2sU9gKVb2n9/1y5NMBY=
|
||||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/st3fan/jsonfeed v0.0.0-20170519104842-498b2850d26b h1:i9Wk9W2bjrMvUf59rJKX4/lvZXy3L3lcX7g18c8AveU=
|
github.com/st3fan/jsonfeed v0.0.0-20170519104842-498b2850d26b h1:i9Wk9W2bjrMvUf59rJKX4/lvZXy3L3lcX7g18c8AveU=
|
||||||
|
@ -32,7 +35,11 @@ github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/tj/front v0.0.0-20170212063142-739be213b0a1 h1:lA+aPRvltlx2fwv/BnxyYSDQo3pIeqzHgMO5GvK0T9E=
|
github.com/tj/front v0.0.0-20170212063142-739be213b0a1 h1:lA+aPRvltlx2fwv/BnxyYSDQo3pIeqzHgMO5GvK0T9E=
|
||||||
github.com/tj/front v0.0.0-20170212063142-739be213b0a1/go.mod h1:deJrtusCTptAW4EUn5vBLpl3dhNqPqUwEjWJz5UNxpQ=
|
github.com/tj/front v0.0.0-20170212063142-739be213b0a1/go.mod h1:deJrtusCTptAW4EUn5vBLpl3dhNqPqUwEjWJz5UNxpQ=
|
||||||
|
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEABsc5zNT9+b1CvsJx47JzJ8g=
|
||||||
|
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/segmentio/analytics-go.v3 v3.0.0-20180814014620-14e03163be03 h1:VE9ONTFmnLVqBF4kx4jy+IsK8fCQCy7cIff269RITxs=
|
||||||
|
gopkg.in/segmentio/analytics-go.v3 v3.0.0-20180814014620-14e03163be03/go.mod h1:4QqqlTlSSpVlWA9/9nDcPw+FkM2yv1NQoYjUbL9/JAw=
|
||||||
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 h1:POO/ycCATvegFmVuPpQzZFJ+pGZeX22Ufu6fibxDVjU=
|
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 h1:POO/ycCATvegFmVuPpQzZFJ+pGZeX22Ufu6fibxDVjU=
|
||||||
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
|
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/
|
|
||||||
|
|
||||||
package jsonfeed_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/st3fan/jsonfeed"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_ParseSimple(t *testing.T) {
|
|
||||||
r, err := os.Open("testdata/feed.json")
|
|
||||||
assert.NoError(t, err, "Could not open testdata/feed.json")
|
|
||||||
|
|
||||||
feed, err := jsonfeed.Parse(r)
|
|
||||||
assert.NoError(t, err, "Could not parse testdata/feed.json")
|
|
||||||
|
|
||||||
assert.Equal(t, "https://jsonfeed.org/version/1", feed.Version)
|
|
||||||
assert.Equal(t, "JSON Feed", feed.Title)
|
|
||||||
assert.Equal(t, "JSON Feed is a ...", feed.Description)
|
|
||||||
assert.Equal(t, "https://jsonfeed.org/", feed.HomePageURL)
|
|
||||||
assert.Equal(t, "https://jsonfeed.org/feed.json", feed.FeedURL)
|
|
||||||
assert.Equal(t, "This feed allows ...", feed.UserComment)
|
|
||||||
assert.Equal(t, "https://jsonfeed.org/graphics/icon.png", feed.Favicon)
|
|
||||||
assert.Equal(t, "Brent Simmons and Manton Reece", feed.Author.Name)
|
|
||||||
|
|
||||||
assert.Equal(t, 1, len(feed.Items))
|
|
||||||
|
|
||||||
assert.Equal(t, "https://jsonfeed.org/2017/05/17/announcing_json_feed", feed.Items[0].ID)
|
|
||||||
assert.Equal(t, "https://jsonfeed.org/2017/05/17/announcing_json_feed", feed.Items[0].URL)
|
|
||||||
assert.Equal(t, "Announcing JSON Feed", feed.Items[0].Title)
|
|
||||||
assert.Equal(t, "<p>We ...", feed.Items[0].ContentHTML)
|
|
||||||
|
|
||||||
datePublished, err := time.Parse("2006-01-02T15:04:05-07:00", "2017-05-17T08:02:12-07:00")
|
|
||||||
assert.NoError(t, err, "Could not parse timestamp")
|
|
||||||
|
|
||||||
assert.Equal(t, datePublished, feed.Items[0].DatePublished)
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
{
|
|
||||||
"version": "https://jsonfeed.org/version/1",
|
|
||||||
"title": "JSON Feed",
|
|
||||||
"description": "JSON Feed is a ...",
|
|
||||||
"home_page_url": "https://jsonfeed.org/",
|
|
||||||
"feed_url": "https://jsonfeed.org/feed.json",
|
|
||||||
"user_comment": "This feed allows ...",
|
|
||||||
"favicon": "https://jsonfeed.org/graphics/icon.png",
|
|
||||||
"author": {
|
|
||||||
"name": "Brent Simmons and Manton Reece"
|
|
||||||
},
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": "https://jsonfeed.org/2017/05/17/announcing_json_feed",
|
|
||||||
"url": "https://jsonfeed.org/2017/05/17/announcing_json_feed",
|
|
||||||
"title": "Announcing JSON Feed",
|
|
||||||
"content_html": "<p>We ...",
|
|
||||||
"date_published": "2017-05-17T08:02:12-07:00"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,111 +0,0 @@
|
||||||
package ln
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var ctx context.Context
|
|
||||||
|
|
||||||
func setup(t *testing.T) (*bytes.Buffer, func()) {
|
|
||||||
ctx = context.Background()
|
|
||||||
|
|
||||||
out := bytes.Buffer{}
|
|
||||||
oldFilters := DefaultLogger.Filters
|
|
||||||
DefaultLogger.Filters = []Filter{NewWriterFilter(&out, nil)}
|
|
||||||
return &out, func() {
|
|
||||||
DefaultLogger.Filters = oldFilters
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSimpleError(t *testing.T) {
|
|
||||||
out, teardown := setup(t)
|
|
||||||
defer teardown()
|
|
||||||
|
|
||||||
Log(ctx, F{"err": fmt.Errorf("This is an Error!!!")}, F{"msg": "fooey", "bar": "foo"})
|
|
||||||
data := []string{
|
|
||||||
`err="This is an Error!!!"`,
|
|
||||||
`fooey`,
|
|
||||||
`bar=foo`,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, line := range data {
|
|
||||||
if !bytes.Contains(out.Bytes(), []byte(line)) {
|
|
||||||
t.Fatalf("Bytes: %s not in %s", line, out.Bytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTimeConversion(t *testing.T) {
|
|
||||||
out, teardown := setup(t)
|
|
||||||
defer teardown()
|
|
||||||
|
|
||||||
var zeroTime time.Time
|
|
||||||
|
|
||||||
Log(ctx, F{"zero": zeroTime})
|
|
||||||
data := []string{
|
|
||||||
`zero=0001-01-01T00:00:00Z`,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, line := range data {
|
|
||||||
if !bytes.Contains(out.Bytes(), []byte(line)) {
|
|
||||||
t.Fatalf("Bytes: %s not in %s", line, out.Bytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDebug(t *testing.T) {
|
|
||||||
out, teardown := setup(t)
|
|
||||||
defer teardown()
|
|
||||||
|
|
||||||
// set priority to Debug
|
|
||||||
Error(ctx, fmt.Errorf("This is an Error!!!"), F{})
|
|
||||||
|
|
||||||
data := []string{
|
|
||||||
`err="This is an Error!!!"`,
|
|
||||||
`_lineno=`,
|
|
||||||
`_function=ln.TestDebug`,
|
|
||||||
`_filename=github.com/Xe/ln/logger_test.go`,
|
|
||||||
`cause="This is an Error!!!"`,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, line := range data {
|
|
||||||
if !bytes.Contains(out.Bytes(), []byte(line)) {
|
|
||||||
t.Fatalf("Bytes: %s not in %s", line, out.Bytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFer(t *testing.T) {
|
|
||||||
out, teardown := setup(t)
|
|
||||||
defer teardown()
|
|
||||||
|
|
||||||
underTest := foobar{Foo: 1, Bar: "quux"}
|
|
||||||
|
|
||||||
Log(ctx, underTest)
|
|
||||||
data := []string{
|
|
||||||
`foo=1`,
|
|
||||||
`bar=quux`,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, line := range data {
|
|
||||||
if !bytes.Contains(out.Bytes(), []byte(line)) {
|
|
||||||
t.Fatalf("Bytes: %s not in %s", line, out.Bytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type foobar struct {
|
|
||||||
Foo int
|
|
||||||
Bar string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f foobar) F() F {
|
|
||||||
return F{
|
|
||||||
"foo": f.Foo,
|
|
||||||
"bar": f.Bar,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,152 +0,0 @@
|
||||||
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
|
||||||
//
|
|
||||||
// Permission to use, copy, modify, and distribute this software for any
|
|
||||||
// purpose with or without fee is hereby granted, provided that the above
|
|
||||||
// copyright notice and this permission notice appear in all copies.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
|
||||||
// when the code is not running on Google App Engine, compiled by GopherJS, and
|
|
||||||
// "-tags safe" is not added to the go build command line. The "disableunsafe"
|
|
||||||
// tag is deprecated and thus should not be used.
|
|
||||||
// +build !js,!appengine,!safe,!disableunsafe
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// UnsafeDisabled is a build-time constant which specifies whether or
|
|
||||||
// not access to the unsafe package is available.
|
|
||||||
UnsafeDisabled = false
|
|
||||||
|
|
||||||
// ptrSize is the size of a pointer on the current arch.
|
|
||||||
ptrSize = unsafe.Sizeof((*byte)(nil))
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// offsetPtr, offsetScalar, and offsetFlag are the offsets for the
|
|
||||||
// internal reflect.Value fields. These values are valid before golang
|
|
||||||
// commit ecccf07e7f9d which changed the format. The are also valid
|
|
||||||
// after commit 82f48826c6c7 which changed the format again to mirror
|
|
||||||
// the original format. Code in the init function updates these offsets
|
|
||||||
// as necessary.
|
|
||||||
offsetPtr = uintptr(ptrSize)
|
|
||||||
offsetScalar = uintptr(0)
|
|
||||||
offsetFlag = uintptr(ptrSize * 2)
|
|
||||||
|
|
||||||
// flagKindWidth and flagKindShift indicate various bits that the
|
|
||||||
// reflect package uses internally to track kind information.
|
|
||||||
//
|
|
||||||
// flagRO indicates whether or not the value field of a reflect.Value is
|
|
||||||
// read-only.
|
|
||||||
//
|
|
||||||
// flagIndir indicates whether the value field of a reflect.Value is
|
|
||||||
// the actual data or a pointer to the data.
|
|
||||||
//
|
|
||||||
// These values are valid before golang commit 90a7c3c86944 which
|
|
||||||
// changed their positions. Code in the init function updates these
|
|
||||||
// flags as necessary.
|
|
||||||
flagKindWidth = uintptr(5)
|
|
||||||
flagKindShift = uintptr(flagKindWidth - 1)
|
|
||||||
flagRO = uintptr(1 << 0)
|
|
||||||
flagIndir = uintptr(1 << 1)
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// Older versions of reflect.Value stored small integers directly in the
|
|
||||||
// ptr field (which is named val in the older versions). Versions
|
|
||||||
// between commits ecccf07e7f9d and 82f48826c6c7 added a new field named
|
|
||||||
// scalar for this purpose which unfortunately came before the flag
|
|
||||||
// field, so the offset of the flag field is different for those
|
|
||||||
// versions.
|
|
||||||
//
|
|
||||||
// This code constructs a new reflect.Value from a known small integer
|
|
||||||
// and checks if the size of the reflect.Value struct indicates it has
|
|
||||||
// the scalar field. When it does, the offsets are updated accordingly.
|
|
||||||
vv := reflect.ValueOf(0xf00)
|
|
||||||
if unsafe.Sizeof(vv) == (ptrSize * 4) {
|
|
||||||
offsetScalar = ptrSize * 2
|
|
||||||
offsetFlag = ptrSize * 3
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commit 90a7c3c86944 changed the flag positions such that the low
|
|
||||||
// order bits are the kind. This code extracts the kind from the flags
|
|
||||||
// field and ensures it's the correct type. When it's not, the flag
|
|
||||||
// order has been changed to the newer format, so the flags are updated
|
|
||||||
// accordingly.
|
|
||||||
upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag)
|
|
||||||
upfv := *(*uintptr)(upf)
|
|
||||||
flagKindMask := uintptr((1<<flagKindWidth - 1) << flagKindShift)
|
|
||||||
if (upfv&flagKindMask)>>flagKindShift != uintptr(reflect.Int) {
|
|
||||||
flagKindShift = 0
|
|
||||||
flagRO = 1 << 5
|
|
||||||
flagIndir = 1 << 6
|
|
||||||
|
|
||||||
// Commit adf9b30e5594 modified the flags to separate the
|
|
||||||
// flagRO flag into two bits which specifies whether or not the
|
|
||||||
// field is embedded. This causes flagIndir to move over a bit
|
|
||||||
// and means that flagRO is the combination of either of the
|
|
||||||
// original flagRO bit and the new bit.
|
|
||||||
//
|
|
||||||
// This code detects the change by extracting what used to be
|
|
||||||
// the indirect bit to ensure it's set. When it's not, the flag
|
|
||||||
// order has been changed to the newer format, so the flags are
|
|
||||||
// updated accordingly.
|
|
||||||
if upfv&flagIndir == 0 {
|
|
||||||
flagRO = 3 << 5
|
|
||||||
flagIndir = 1 << 7
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
|
|
||||||
// the typical safety restrictions preventing access to unaddressable and
|
|
||||||
// unexported data. It works by digging the raw pointer to the underlying
|
|
||||||
// value out of the protected value and generating a new unprotected (unsafe)
|
|
||||||
// reflect.Value to it.
|
|
||||||
//
|
|
||||||
// This allows us to check for implementations of the Stringer and error
|
|
||||||
// interfaces to be used for pretty printing ordinarily unaddressable and
|
|
||||||
// inaccessible values such as unexported struct fields.
|
|
||||||
func unsafeReflectValue(v reflect.Value) (rv reflect.Value) {
|
|
||||||
indirects := 1
|
|
||||||
vt := v.Type()
|
|
||||||
upv := unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetPtr)
|
|
||||||
rvf := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetFlag))
|
|
||||||
if rvf&flagIndir != 0 {
|
|
||||||
vt = reflect.PtrTo(v.Type())
|
|
||||||
indirects++
|
|
||||||
} else if offsetScalar != 0 {
|
|
||||||
// The value is in the scalar field when it's not one of the
|
|
||||||
// reference types.
|
|
||||||
switch vt.Kind() {
|
|
||||||
case reflect.Uintptr:
|
|
||||||
case reflect.Chan:
|
|
||||||
case reflect.Func:
|
|
||||||
case reflect.Map:
|
|
||||||
case reflect.Ptr:
|
|
||||||
case reflect.UnsafePointer:
|
|
||||||
default:
|
|
||||||
upv = unsafe.Pointer(uintptr(unsafe.Pointer(&v)) +
|
|
||||||
offsetScalar)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pv := reflect.NewAt(vt, upv)
|
|
||||||
rv = pv
|
|
||||||
for i := 0; i < indirects; i++ {
|
|
||||||
rv = rv.Elem()
|
|
||||||
}
|
|
||||||
return rv
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
|
||||||
//
|
|
||||||
// Permission to use, copy, modify, and distribute this software for any
|
|
||||||
// purpose with or without fee is hereby granted, provided that the above
|
|
||||||
// copyright notice and this permission notice appear in all copies.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
|
||||||
// when the code is running on Google App Engine, compiled by GopherJS, or
|
|
||||||
// "-tags safe" is added to the go build command line. The "disableunsafe"
|
|
||||||
// tag is deprecated and thus should not be used.
|
|
||||||
// +build js appengine safe disableunsafe
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import "reflect"
|
|
||||||
|
|
||||||
const (
|
|
||||||
// UnsafeDisabled is a build-time constant which specifies whether or
|
|
||||||
// not access to the unsafe package is available.
|
|
||||||
UnsafeDisabled = true
|
|
||||||
)
|
|
||||||
|
|
||||||
// unsafeReflectValue typically converts the passed reflect.Value into a one
|
|
||||||
// that bypasses the typical safety restrictions preventing access to
|
|
||||||
// unaddressable and unexported data. However, doing this relies on access to
|
|
||||||
// the unsafe package. This is a stub version which simply returns the passed
|
|
||||||
// reflect.Value when the unsafe package is not available.
|
|
||||||
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
|
||||||
return v
|
|
||||||
}
|
|
|
@ -1,341 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"reflect"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Some constants in the form of bytes to avoid string overhead. This mirrors
|
|
||||||
// the technique used in the fmt package.
|
|
||||||
var (
|
|
||||||
panicBytes = []byte("(PANIC=")
|
|
||||||
plusBytes = []byte("+")
|
|
||||||
iBytes = []byte("i")
|
|
||||||
trueBytes = []byte("true")
|
|
||||||
falseBytes = []byte("false")
|
|
||||||
interfaceBytes = []byte("(interface {})")
|
|
||||||
commaNewlineBytes = []byte(",\n")
|
|
||||||
newlineBytes = []byte("\n")
|
|
||||||
openBraceBytes = []byte("{")
|
|
||||||
openBraceNewlineBytes = []byte("{\n")
|
|
||||||
closeBraceBytes = []byte("}")
|
|
||||||
asteriskBytes = []byte("*")
|
|
||||||
colonBytes = []byte(":")
|
|
||||||
colonSpaceBytes = []byte(": ")
|
|
||||||
openParenBytes = []byte("(")
|
|
||||||
closeParenBytes = []byte(")")
|
|
||||||
spaceBytes = []byte(" ")
|
|
||||||
pointerChainBytes = []byte("->")
|
|
||||||
nilAngleBytes = []byte("<nil>")
|
|
||||||
maxNewlineBytes = []byte("<max depth reached>\n")
|
|
||||||
maxShortBytes = []byte("<max>")
|
|
||||||
circularBytes = []byte("<already shown>")
|
|
||||||
circularShortBytes = []byte("<shown>")
|
|
||||||
invalidAngleBytes = []byte("<invalid>")
|
|
||||||
openBracketBytes = []byte("[")
|
|
||||||
closeBracketBytes = []byte("]")
|
|
||||||
percentBytes = []byte("%")
|
|
||||||
precisionBytes = []byte(".")
|
|
||||||
openAngleBytes = []byte("<")
|
|
||||||
closeAngleBytes = []byte(">")
|
|
||||||
openMapBytes = []byte("map[")
|
|
||||||
closeMapBytes = []byte("]")
|
|
||||||
lenEqualsBytes = []byte("len=")
|
|
||||||
capEqualsBytes = []byte("cap=")
|
|
||||||
)
|
|
||||||
|
|
||||||
// hexDigits is used to map a decimal value to a hex digit.
|
|
||||||
var hexDigits = "0123456789abcdef"
|
|
||||||
|
|
||||||
// catchPanic handles any panics that might occur during the handleMethods
|
|
||||||
// calls.
|
|
||||||
func catchPanic(w io.Writer, v reflect.Value) {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
w.Write(panicBytes)
|
|
||||||
fmt.Fprintf(w, "%v", err)
|
|
||||||
w.Write(closeParenBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleMethods attempts to call the Error and String methods on the underlying
|
|
||||||
// type the passed reflect.Value represents and outputes the result to Writer w.
|
|
||||||
//
|
|
||||||
// It handles panics in any called methods by catching and displaying the error
|
|
||||||
// as the formatted value.
|
|
||||||
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
|
|
||||||
// We need an interface to check if the type implements the error or
|
|
||||||
// Stringer interface. However, the reflect package won't give us an
|
|
||||||
// interface on certain things like unexported struct fields in order
|
|
||||||
// to enforce visibility rules. We use unsafe, when it's available,
|
|
||||||
// to bypass these restrictions since this package does not mutate the
|
|
||||||
// values.
|
|
||||||
if !v.CanInterface() {
|
|
||||||
if UnsafeDisabled {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
v = unsafeReflectValue(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Choose whether or not to do error and Stringer interface lookups against
|
|
||||||
// the base type or a pointer to the base type depending on settings.
|
|
||||||
// Technically calling one of these methods with a pointer receiver can
|
|
||||||
// mutate the value, however, types which choose to satisify an error or
|
|
||||||
// Stringer interface with a pointer receiver should not be mutating their
|
|
||||||
// state inside these interface methods.
|
|
||||||
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
|
|
||||||
v = unsafeReflectValue(v)
|
|
||||||
}
|
|
||||||
if v.CanAddr() {
|
|
||||||
v = v.Addr()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is it an error or Stringer?
|
|
||||||
switch iface := v.Interface().(type) {
|
|
||||||
case error:
|
|
||||||
defer catchPanic(w, v)
|
|
||||||
if cs.ContinueOnMethod {
|
|
||||||
w.Write(openParenBytes)
|
|
||||||
w.Write([]byte(iface.Error()))
|
|
||||||
w.Write(closeParenBytes)
|
|
||||||
w.Write(spaceBytes)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write([]byte(iface.Error()))
|
|
||||||
return true
|
|
||||||
|
|
||||||
case fmt.Stringer:
|
|
||||||
defer catchPanic(w, v)
|
|
||||||
if cs.ContinueOnMethod {
|
|
||||||
w.Write(openParenBytes)
|
|
||||||
w.Write([]byte(iface.String()))
|
|
||||||
w.Write(closeParenBytes)
|
|
||||||
w.Write(spaceBytes)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
w.Write([]byte(iface.String()))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// printBool outputs a boolean value as true or false to Writer w.
|
|
||||||
func printBool(w io.Writer, val bool) {
|
|
||||||
if val {
|
|
||||||
w.Write(trueBytes)
|
|
||||||
} else {
|
|
||||||
w.Write(falseBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// printInt outputs a signed integer value to Writer w.
|
|
||||||
func printInt(w io.Writer, val int64, base int) {
|
|
||||||
w.Write([]byte(strconv.FormatInt(val, base)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// printUint outputs an unsigned integer value to Writer w.
|
|
||||||
func printUint(w io.Writer, val uint64, base int) {
|
|
||||||
w.Write([]byte(strconv.FormatUint(val, base)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// printFloat outputs a floating point value using the specified precision,
|
|
||||||
// which is expected to be 32 or 64bit, to Writer w.
|
|
||||||
func printFloat(w io.Writer, val float64, precision int) {
|
|
||||||
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// printComplex outputs a complex value using the specified float precision
|
|
||||||
// for the real and imaginary parts to Writer w.
|
|
||||||
func printComplex(w io.Writer, c complex128, floatPrecision int) {
|
|
||||||
r := real(c)
|
|
||||||
w.Write(openParenBytes)
|
|
||||||
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
|
|
||||||
i := imag(c)
|
|
||||||
if i >= 0 {
|
|
||||||
w.Write(plusBytes)
|
|
||||||
}
|
|
||||||
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
|
|
||||||
w.Write(iBytes)
|
|
||||||
w.Write(closeParenBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// printHexPtr outputs a uintptr formatted as hexidecimal with a leading '0x'
|
|
||||||
// prefix to Writer w.
|
|
||||||
func printHexPtr(w io.Writer, p uintptr) {
|
|
||||||
// Null pointer.
|
|
||||||
num := uint64(p)
|
|
||||||
if num == 0 {
|
|
||||||
w.Write(nilAngleBytes)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
|
|
||||||
buf := make([]byte, 18)
|
|
||||||
|
|
||||||
// It's simpler to construct the hex string right to left.
|
|
||||||
base := uint64(16)
|
|
||||||
i := len(buf) - 1
|
|
||||||
for num >= base {
|
|
||||||
buf[i] = hexDigits[num%base]
|
|
||||||
num /= base
|
|
||||||
i--
|
|
||||||
}
|
|
||||||
buf[i] = hexDigits[num]
|
|
||||||
|
|
||||||
// Add '0x' prefix.
|
|
||||||
i--
|
|
||||||
buf[i] = 'x'
|
|
||||||
i--
|
|
||||||
buf[i] = '0'
|
|
||||||
|
|
||||||
// Strip unused leading bytes.
|
|
||||||
buf = buf[i:]
|
|
||||||
w.Write(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
|
|
||||||
// elements to be sorted.
|
|
||||||
type valuesSorter struct {
|
|
||||||
values []reflect.Value
|
|
||||||
strings []string // either nil or same len and values
|
|
||||||
cs *ConfigState
|
|
||||||
}
|
|
||||||
|
|
||||||
// newValuesSorter initializes a valuesSorter instance, which holds a set of
|
|
||||||
// surrogate keys on which the data should be sorted. It uses flags in
|
|
||||||
// ConfigState to decide if and how to populate those surrogate keys.
|
|
||||||
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
|
|
||||||
vs := &valuesSorter{values: values, cs: cs}
|
|
||||||
if canSortSimply(vs.values[0].Kind()) {
|
|
||||||
return vs
|
|
||||||
}
|
|
||||||
if !cs.DisableMethods {
|
|
||||||
vs.strings = make([]string, len(values))
|
|
||||||
for i := range vs.values {
|
|
||||||
b := bytes.Buffer{}
|
|
||||||
if !handleMethods(cs, &b, vs.values[i]) {
|
|
||||||
vs.strings = nil
|
|
||||||
break
|
|
||||||
}
|
|
||||||
vs.strings[i] = b.String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if vs.strings == nil && cs.SpewKeys {
|
|
||||||
vs.strings = make([]string, len(values))
|
|
||||||
for i := range vs.values {
|
|
||||||
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return vs
|
|
||||||
}
|
|
||||||
|
|
||||||
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
|
|
||||||
// directly, or whether it should be considered for sorting by surrogate keys
|
|
||||||
// (if the ConfigState allows it).
|
|
||||||
func canSortSimply(kind reflect.Kind) bool {
|
|
||||||
// This switch parallels valueSortLess, except for the default case.
|
|
||||||
switch kind {
|
|
||||||
case reflect.Bool:
|
|
||||||
return true
|
|
||||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
|
||||||
return true
|
|
||||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
|
||||||
return true
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
return true
|
|
||||||
case reflect.String:
|
|
||||||
return true
|
|
||||||
case reflect.Uintptr:
|
|
||||||
return true
|
|
||||||
case reflect.Array:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the number of values in the slice. It is part of the
|
|
||||||
// sort.Interface implementation.
|
|
||||||
func (s *valuesSorter) Len() int {
|
|
||||||
return len(s.values)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Swap swaps the values at the passed indices. It is part of the
|
|
||||||
// sort.Interface implementation.
|
|
||||||
func (s *valuesSorter) Swap(i, j int) {
|
|
||||||
s.values[i], s.values[j] = s.values[j], s.values[i]
|
|
||||||
if s.strings != nil {
|
|
||||||
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// valueSortLess returns whether the first value should sort before the second
|
|
||||||
// value. It is used by valueSorter.Less as part of the sort.Interface
|
|
||||||
// implementation.
|
|
||||||
func valueSortLess(a, b reflect.Value) bool {
|
|
||||||
switch a.Kind() {
|
|
||||||
case reflect.Bool:
|
|
||||||
return !a.Bool() && b.Bool()
|
|
||||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
|
||||||
return a.Int() < b.Int()
|
|
||||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
|
||||||
return a.Uint() < b.Uint()
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
return a.Float() < b.Float()
|
|
||||||
case reflect.String:
|
|
||||||
return a.String() < b.String()
|
|
||||||
case reflect.Uintptr:
|
|
||||||
return a.Uint() < b.Uint()
|
|
||||||
case reflect.Array:
|
|
||||||
// Compare the contents of both arrays.
|
|
||||||
l := a.Len()
|
|
||||||
for i := 0; i < l; i++ {
|
|
||||||
av := a.Index(i)
|
|
||||||
bv := b.Index(i)
|
|
||||||
if av.Interface() == bv.Interface() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return valueSortLess(av, bv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return a.String() < b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Less returns whether the value at index i should sort before the
|
|
||||||
// value at index j. It is part of the sort.Interface implementation.
|
|
||||||
func (s *valuesSorter) Less(i, j int) bool {
|
|
||||||
if s.strings == nil {
|
|
||||||
return valueSortLess(s.values[i], s.values[j])
|
|
||||||
}
|
|
||||||
return s.strings[i] < s.strings[j]
|
|
||||||
}
|
|
||||||
|
|
||||||
// sortValues is a sort function that handles both native types and any type that
|
|
||||||
// can be converted to error or Stringer. Other inputs are sorted according to
|
|
||||||
// their Value.String() value to ensure display stability.
|
|
||||||
func sortValues(values []reflect.Value, cs *ConfigState) {
|
|
||||||
if len(values) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sort.Sort(newValuesSorter(values, cs))
|
|
||||||
}
|
|
|
@ -1,298 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package spew_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
)
|
|
||||||
|
|
||||||
// custom type to test Stinger interface on non-pointer receiver.
|
|
||||||
type stringer string
|
|
||||||
|
|
||||||
// String implements the Stringer interface for testing invocation of custom
|
|
||||||
// stringers on types with non-pointer receivers.
|
|
||||||
func (s stringer) String() string {
|
|
||||||
return "stringer " + string(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// custom type to test Stinger interface on pointer receiver.
|
|
||||||
type pstringer string
|
|
||||||
|
|
||||||
// String implements the Stringer interface for testing invocation of custom
|
|
||||||
// stringers on types with only pointer receivers.
|
|
||||||
func (s *pstringer) String() string {
|
|
||||||
return "stringer " + string(*s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// xref1 and xref2 are cross referencing structs for testing circular reference
|
|
||||||
// detection.
|
|
||||||
type xref1 struct {
|
|
||||||
ps2 *xref2
|
|
||||||
}
|
|
||||||
type xref2 struct {
|
|
||||||
ps1 *xref1
|
|
||||||
}
|
|
||||||
|
|
||||||
// indirCir1, indirCir2, and indirCir3 are used to generate an indirect circular
|
|
||||||
// reference for testing detection.
|
|
||||||
type indirCir1 struct {
|
|
||||||
ps2 *indirCir2
|
|
||||||
}
|
|
||||||
type indirCir2 struct {
|
|
||||||
ps3 *indirCir3
|
|
||||||
}
|
|
||||||
type indirCir3 struct {
|
|
||||||
ps1 *indirCir1
|
|
||||||
}
|
|
||||||
|
|
||||||
// embed is used to test embedded structures.
|
|
||||||
type embed struct {
|
|
||||||
a string
|
|
||||||
}
|
|
||||||
|
|
||||||
// embedwrap is used to test embedded structures.
|
|
||||||
type embedwrap struct {
|
|
||||||
*embed
|
|
||||||
e *embed
|
|
||||||
}
|
|
||||||
|
|
||||||
// panicer is used to intentionally cause a panic for testing spew properly
|
|
||||||
// handles them
|
|
||||||
type panicer int
|
|
||||||
|
|
||||||
func (p panicer) String() string {
|
|
||||||
panic("test panic")
|
|
||||||
}
|
|
||||||
|
|
||||||
// customError is used to test custom error interface invocation.
|
|
||||||
type customError int
|
|
||||||
|
|
||||||
func (e customError) Error() string {
|
|
||||||
return fmt.Sprintf("error: %d", int(e))
|
|
||||||
}
|
|
||||||
|
|
||||||
// stringizeWants converts a slice of wanted test output into a format suitable
|
|
||||||
// for a test error message.
|
|
||||||
func stringizeWants(wants []string) string {
|
|
||||||
s := ""
|
|
||||||
for i, want := range wants {
|
|
||||||
if i > 0 {
|
|
||||||
s += fmt.Sprintf("want%d: %s", i+1, want)
|
|
||||||
} else {
|
|
||||||
s += "want: " + want
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// testFailed returns whether or not a test failed by checking if the result
|
|
||||||
// of the test is in the slice of wanted strings.
|
|
||||||
func testFailed(result string, wants []string) bool {
|
|
||||||
for _, want := range wants {
|
|
||||||
if result == want {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type sortableStruct struct {
|
|
||||||
x int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ss sortableStruct) String() string {
|
|
||||||
return fmt.Sprintf("ss.%d", ss.x)
|
|
||||||
}
|
|
||||||
|
|
||||||
type unsortableStruct struct {
|
|
||||||
x int
|
|
||||||
}
|
|
||||||
|
|
||||||
type sortTestCase struct {
|
|
||||||
input []reflect.Value
|
|
||||||
expected []reflect.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
func helpTestSortValues(tests []sortTestCase, cs *spew.ConfigState, t *testing.T) {
|
|
||||||
getInterfaces := func(values []reflect.Value) []interface{} {
|
|
||||||
interfaces := []interface{}{}
|
|
||||||
for _, v := range values {
|
|
||||||
interfaces = append(interfaces, v.Interface())
|
|
||||||
}
|
|
||||||
return interfaces
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
spew.SortValues(test.input, cs)
|
|
||||||
// reflect.DeepEqual cannot really make sense of reflect.Value,
|
|
||||||
// probably because of all the pointer tricks. For instance,
|
|
||||||
// v(2.0) != v(2.0) on a 32-bits system. Turn them into interface{}
|
|
||||||
// instead.
|
|
||||||
input := getInterfaces(test.input)
|
|
||||||
expected := getInterfaces(test.expected)
|
|
||||||
if !reflect.DeepEqual(input, expected) {
|
|
||||||
t.Errorf("Sort mismatch:\n %v != %v", input, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestSortValues ensures the sort functionality for relect.Value based sorting
|
|
||||||
// works as intended.
|
|
||||||
func TestSortValues(t *testing.T) {
|
|
||||||
v := reflect.ValueOf
|
|
||||||
|
|
||||||
a := v("a")
|
|
||||||
b := v("b")
|
|
||||||
c := v("c")
|
|
||||||
embedA := v(embed{"a"})
|
|
||||||
embedB := v(embed{"b"})
|
|
||||||
embedC := v(embed{"c"})
|
|
||||||
tests := []sortTestCase{
|
|
||||||
// No values.
|
|
||||||
{
|
|
||||||
[]reflect.Value{},
|
|
||||||
[]reflect.Value{},
|
|
||||||
},
|
|
||||||
// Bools.
|
|
||||||
{
|
|
||||||
[]reflect.Value{v(false), v(true), v(false)},
|
|
||||||
[]reflect.Value{v(false), v(false), v(true)},
|
|
||||||
},
|
|
||||||
// Ints.
|
|
||||||
{
|
|
||||||
[]reflect.Value{v(2), v(1), v(3)},
|
|
||||||
[]reflect.Value{v(1), v(2), v(3)},
|
|
||||||
},
|
|
||||||
// Uints.
|
|
||||||
{
|
|
||||||
[]reflect.Value{v(uint8(2)), v(uint8(1)), v(uint8(3))},
|
|
||||||
[]reflect.Value{v(uint8(1)), v(uint8(2)), v(uint8(3))},
|
|
||||||
},
|
|
||||||
// Floats.
|
|
||||||
{
|
|
||||||
[]reflect.Value{v(2.0), v(1.0), v(3.0)},
|
|
||||||
[]reflect.Value{v(1.0), v(2.0), v(3.0)},
|
|
||||||
},
|
|
||||||
// Strings.
|
|
||||||
{
|
|
||||||
[]reflect.Value{b, a, c},
|
|
||||||
[]reflect.Value{a, b, c},
|
|
||||||
},
|
|
||||||
// Array
|
|
||||||
{
|
|
||||||
[]reflect.Value{v([3]int{3, 2, 1}), v([3]int{1, 3, 2}), v([3]int{1, 2, 3})},
|
|
||||||
[]reflect.Value{v([3]int{1, 2, 3}), v([3]int{1, 3, 2}), v([3]int{3, 2, 1})},
|
|
||||||
},
|
|
||||||
// Uintptrs.
|
|
||||||
{
|
|
||||||
[]reflect.Value{v(uintptr(2)), v(uintptr(1)), v(uintptr(3))},
|
|
||||||
[]reflect.Value{v(uintptr(1)), v(uintptr(2)), v(uintptr(3))},
|
|
||||||
},
|
|
||||||
// SortableStructs.
|
|
||||||
{
|
|
||||||
// Note: not sorted - DisableMethods is set.
|
|
||||||
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
|
|
||||||
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
|
|
||||||
},
|
|
||||||
// UnsortableStructs.
|
|
||||||
{
|
|
||||||
// Note: not sorted - SpewKeys is false.
|
|
||||||
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
|
||||||
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
|
||||||
},
|
|
||||||
// Invalid.
|
|
||||||
{
|
|
||||||
[]reflect.Value{embedB, embedA, embedC},
|
|
||||||
[]reflect.Value{embedB, embedA, embedC},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cs := spew.ConfigState{DisableMethods: true, SpewKeys: false}
|
|
||||||
helpTestSortValues(tests, &cs, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestSortValuesWithMethods ensures the sort functionality for relect.Value
|
|
||||||
// based sorting works as intended when using string methods.
|
|
||||||
func TestSortValuesWithMethods(t *testing.T) {
|
|
||||||
v := reflect.ValueOf
|
|
||||||
|
|
||||||
a := v("a")
|
|
||||||
b := v("b")
|
|
||||||
c := v("c")
|
|
||||||
tests := []sortTestCase{
|
|
||||||
// Ints.
|
|
||||||
{
|
|
||||||
[]reflect.Value{v(2), v(1), v(3)},
|
|
||||||
[]reflect.Value{v(1), v(2), v(3)},
|
|
||||||
},
|
|
||||||
// Strings.
|
|
||||||
{
|
|
||||||
[]reflect.Value{b, a, c},
|
|
||||||
[]reflect.Value{a, b, c},
|
|
||||||
},
|
|
||||||
// SortableStructs.
|
|
||||||
{
|
|
||||||
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
|
|
||||||
[]reflect.Value{v(sortableStruct{1}), v(sortableStruct{2}), v(sortableStruct{3})},
|
|
||||||
},
|
|
||||||
// UnsortableStructs.
|
|
||||||
{
|
|
||||||
// Note: not sorted - SpewKeys is false.
|
|
||||||
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
|
||||||
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cs := spew.ConfigState{DisableMethods: false, SpewKeys: false}
|
|
||||||
helpTestSortValues(tests, &cs, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestSortValuesWithSpew ensures the sort functionality for relect.Value
|
|
||||||
// based sorting works as intended when using spew to stringify keys.
|
|
||||||
func TestSortValuesWithSpew(t *testing.T) {
|
|
||||||
v := reflect.ValueOf
|
|
||||||
|
|
||||||
a := v("a")
|
|
||||||
b := v("b")
|
|
||||||
c := v("c")
|
|
||||||
tests := []sortTestCase{
|
|
||||||
// Ints.
|
|
||||||
{
|
|
||||||
[]reflect.Value{v(2), v(1), v(3)},
|
|
||||||
[]reflect.Value{v(1), v(2), v(3)},
|
|
||||||
},
|
|
||||||
// Strings.
|
|
||||||
{
|
|
||||||
[]reflect.Value{b, a, c},
|
|
||||||
[]reflect.Value{a, b, c},
|
|
||||||
},
|
|
||||||
// SortableStructs.
|
|
||||||
{
|
|
||||||
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
|
|
||||||
[]reflect.Value{v(sortableStruct{1}), v(sortableStruct{2}), v(sortableStruct{3})},
|
|
||||||
},
|
|
||||||
// UnsortableStructs.
|
|
||||||
{
|
|
||||||
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
|
||||||
[]reflect.Value{v(unsortableStruct{1}), v(unsortableStruct{2}), v(unsortableStruct{3})},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
cs := spew.ConfigState{DisableMethods: true, SpewKeys: true}
|
|
||||||
helpTestSortValues(tests, &cs, t)
|
|
||||||
}
|
|
|
@ -1,306 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ConfigState houses the configuration options used by spew to format and
|
|
||||||
// display values. There is a global instance, Config, that is used to control
|
|
||||||
// all top-level Formatter and Dump functionality. Each ConfigState instance
|
|
||||||
// provides methods equivalent to the top-level functions.
|
|
||||||
//
|
|
||||||
// The zero value for ConfigState provides no indentation. You would typically
|
|
||||||
// want to set it to a space or a tab.
|
|
||||||
//
|
|
||||||
// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
|
|
||||||
// with default settings. See the documentation of NewDefaultConfig for default
|
|
||||||
// values.
|
|
||||||
type ConfigState struct {
|
|
||||||
// Indent specifies the string to use for each indentation level. The
|
|
||||||
// global config instance that all top-level functions use set this to a
|
|
||||||
// single space by default. If you would like more indentation, you might
|
|
||||||
// set this to a tab with "\t" or perhaps two spaces with " ".
|
|
||||||
Indent string
|
|
||||||
|
|
||||||
// MaxDepth controls the maximum number of levels to descend into nested
|
|
||||||
// data structures. The default, 0, means there is no limit.
|
|
||||||
//
|
|
||||||
// NOTE: Circular data structures are properly detected, so it is not
|
|
||||||
// necessary to set this value unless you specifically want to limit deeply
|
|
||||||
// nested data structures.
|
|
||||||
MaxDepth int
|
|
||||||
|
|
||||||
// DisableMethods specifies whether or not error and Stringer interfaces are
|
|
||||||
// invoked for types that implement them.
|
|
||||||
DisableMethods bool
|
|
||||||
|
|
||||||
// DisablePointerMethods specifies whether or not to check for and invoke
|
|
||||||
// error and Stringer interfaces on types which only accept a pointer
|
|
||||||
// receiver when the current type is not a pointer.
|
|
||||||
//
|
|
||||||
// NOTE: This might be an unsafe action since calling one of these methods
|
|
||||||
// with a pointer receiver could technically mutate the value, however,
|
|
||||||
// in practice, types which choose to satisify an error or Stringer
|
|
||||||
// interface with a pointer receiver should not be mutating their state
|
|
||||||
// inside these interface methods. As a result, this option relies on
|
|
||||||
// access to the unsafe package, so it will not have any effect when
|
|
||||||
// running in environments without access to the unsafe package such as
|
|
||||||
// Google App Engine or with the "safe" build tag specified.
|
|
||||||
DisablePointerMethods bool
|
|
||||||
|
|
||||||
// DisablePointerAddresses specifies whether to disable the printing of
|
|
||||||
// pointer addresses. This is useful when diffing data structures in tests.
|
|
||||||
DisablePointerAddresses bool
|
|
||||||
|
|
||||||
// DisableCapacities specifies whether to disable the printing of capacities
|
|
||||||
// for arrays, slices, maps and channels. This is useful when diffing
|
|
||||||
// data structures in tests.
|
|
||||||
DisableCapacities bool
|
|
||||||
|
|
||||||
// ContinueOnMethod specifies whether or not recursion should continue once
|
|
||||||
// a custom error or Stringer interface is invoked. The default, false,
|
|
||||||
// means it will print the results of invoking the custom error or Stringer
|
|
||||||
// interface and return immediately instead of continuing to recurse into
|
|
||||||
// the internals of the data type.
|
|
||||||
//
|
|
||||||
// NOTE: This flag does not have any effect if method invocation is disabled
|
|
||||||
// via the DisableMethods or DisablePointerMethods options.
|
|
||||||
ContinueOnMethod bool
|
|
||||||
|
|
||||||
// SortKeys specifies map keys should be sorted before being printed. Use
|
|
||||||
// this to have a more deterministic, diffable output. Note that only
|
|
||||||
// native types (bool, int, uint, floats, uintptr and string) and types
|
|
||||||
// that support the error or Stringer interfaces (if methods are
|
|
||||||
// enabled) are supported, with other types sorted according to the
|
|
||||||
// reflect.Value.String() output which guarantees display stability.
|
|
||||||
SortKeys bool
|
|
||||||
|
|
||||||
// SpewKeys specifies that, as a last resort attempt, map keys should
|
|
||||||
// be spewed to strings and sorted by those strings. This is only
|
|
||||||
// considered if SortKeys is true.
|
|
||||||
SpewKeys bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config is the active configuration of the top-level functions.
|
|
||||||
// The configuration can be changed by modifying the contents of spew.Config.
|
|
||||||
var Config = ConfigState{Indent: " "}
|
|
||||||
|
|
||||||
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the formatted string as a value that satisfies error. See NewFormatter
|
|
||||||
// for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
|
|
||||||
return fmt.Errorf(format, c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Fprint(w, c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Fprintf(w, format, c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Fprintln(w, c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Print(c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Printf(format, c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Println(c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the resulting string. See NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Sprint(a ...interface{}) string {
|
|
||||||
return fmt.Sprint(c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
|
||||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
|
||||||
// the resulting string. See NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
|
|
||||||
return fmt.Sprintf(format, c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
|
||||||
// were passed with a Formatter interface returned by c.NewFormatter. It
|
|
||||||
// returns the resulting string. See NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
|
|
||||||
func (c *ConfigState) Sprintln(a ...interface{}) string {
|
|
||||||
return fmt.Sprintln(c.convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
|
||||||
interface. As a result, it integrates cleanly with standard fmt package
|
|
||||||
printing functions. The formatter is useful for inline printing of smaller data
|
|
||||||
types similar to the standard %v format specifier.
|
|
||||||
|
|
||||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
|
||||||
addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
|
|
||||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
|
||||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
|
||||||
the width and precision arguments (however they will still work on the format
|
|
||||||
specifiers not handled by the custom formatter).
|
|
||||||
|
|
||||||
Typically this function shouldn't be called directly. It is much easier to make
|
|
||||||
use of the custom formatter by calling one of the convenience functions such as
|
|
||||||
c.Printf, c.Println, or c.Printf.
|
|
||||||
*/
|
|
||||||
func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
|
|
||||||
return newFormatter(c, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
|
||||||
// exactly the same as Dump.
|
|
||||||
func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
|
|
||||||
fdump(c, w, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Dump displays the passed parameters to standard out with newlines, customizable
|
|
||||||
indentation, and additional debug information such as complete types and all
|
|
||||||
pointer addresses used to indirect to the final value. It provides the
|
|
||||||
following features over the built-in printing facilities provided by the fmt
|
|
||||||
package:
|
|
||||||
|
|
||||||
* Pointers are dereferenced and followed
|
|
||||||
* Circular data structures are detected and handled properly
|
|
||||||
* Custom Stringer/error interfaces are optionally invoked, including
|
|
||||||
on unexported types
|
|
||||||
* Custom types which only implement the Stringer/error interfaces via
|
|
||||||
a pointer receiver are optionally invoked when passing non-pointer
|
|
||||||
variables
|
|
||||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
|
||||||
includes offsets, byte values in hex, and ASCII output
|
|
||||||
|
|
||||||
The configuration options are controlled by modifying the public members
|
|
||||||
of c. See ConfigState for options documentation.
|
|
||||||
|
|
||||||
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
|
||||||
get the formatted result as a string.
|
|
||||||
*/
|
|
||||||
func (c *ConfigState) Dump(a ...interface{}) {
|
|
||||||
fdump(c, os.Stdout, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sdump returns a string with the passed arguments formatted exactly the same
|
|
||||||
// as Dump.
|
|
||||||
func (c *ConfigState) Sdump(a ...interface{}) string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
fdump(c, &buf, a...)
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertArgs accepts a slice of arguments and returns a slice of the same
|
|
||||||
// length with each argument converted to a spew Formatter interface using
|
|
||||||
// the ConfigState associated with s.
|
|
||||||
func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
|
|
||||||
formatters = make([]interface{}, len(args))
|
|
||||||
for index, arg := range args {
|
|
||||||
formatters[index] = newFormatter(c, arg)
|
|
||||||
}
|
|
||||||
return formatters
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDefaultConfig returns a ConfigState with the following default settings.
|
|
||||||
//
|
|
||||||
// Indent: " "
|
|
||||||
// MaxDepth: 0
|
|
||||||
// DisableMethods: false
|
|
||||||
// DisablePointerMethods: false
|
|
||||||
// ContinueOnMethod: false
|
|
||||||
// SortKeys: false
|
|
||||||
func NewDefaultConfig() *ConfigState {
|
|
||||||
return &ConfigState{Indent: " "}
|
|
||||||
}
|
|
|
@ -1,211 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
Package spew implements a deep pretty printer for Go data structures to aid in
|
|
||||||
debugging.
|
|
||||||
|
|
||||||
A quick overview of the additional features spew provides over the built-in
|
|
||||||
printing facilities for Go data types are as follows:
|
|
||||||
|
|
||||||
* Pointers are dereferenced and followed
|
|
||||||
* Circular data structures are detected and handled properly
|
|
||||||
* Custom Stringer/error interfaces are optionally invoked, including
|
|
||||||
on unexported types
|
|
||||||
* Custom types which only implement the Stringer/error interfaces via
|
|
||||||
a pointer receiver are optionally invoked when passing non-pointer
|
|
||||||
variables
|
|
||||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
|
||||||
includes offsets, byte values in hex, and ASCII output (only when using
|
|
||||||
Dump style)
|
|
||||||
|
|
||||||
There are two different approaches spew allows for dumping Go data structures:
|
|
||||||
|
|
||||||
* Dump style which prints with newlines, customizable indentation,
|
|
||||||
and additional debug information such as types and all pointer addresses
|
|
||||||
used to indirect to the final value
|
|
||||||
* A custom Formatter interface that integrates cleanly with the standard fmt
|
|
||||||
package and replaces %v, %+v, %#v, and %#+v to provide inline printing
|
|
||||||
similar to the default %v while providing the additional functionality
|
|
||||||
outlined above and passing unsupported format verbs such as %x and %q
|
|
||||||
along to fmt
|
|
||||||
|
|
||||||
Quick Start
|
|
||||||
|
|
||||||
This section demonstrates how to quickly get started with spew. See the
|
|
||||||
sections below for further details on formatting and configuration options.
|
|
||||||
|
|
||||||
To dump a variable with full newlines, indentation, type, and pointer
|
|
||||||
information use Dump, Fdump, or Sdump:
|
|
||||||
spew.Dump(myVar1, myVar2, ...)
|
|
||||||
spew.Fdump(someWriter, myVar1, myVar2, ...)
|
|
||||||
str := spew.Sdump(myVar1, myVar2, ...)
|
|
||||||
|
|
||||||
Alternatively, if you would prefer to use format strings with a compacted inline
|
|
||||||
printing style, use the convenience wrappers Printf, Fprintf, etc with
|
|
||||||
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
|
|
||||||
%#+v (adds types and pointer addresses):
|
|
||||||
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
|
||||||
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
|
||||||
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
|
||||||
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
|
||||||
|
|
||||||
Configuration Options
|
|
||||||
|
|
||||||
Configuration of spew is handled by fields in the ConfigState type. For
|
|
||||||
convenience, all of the top-level functions use a global state available
|
|
||||||
via the spew.Config global.
|
|
||||||
|
|
||||||
It is also possible to create a ConfigState instance that provides methods
|
|
||||||
equivalent to the top-level functions. This allows concurrent configuration
|
|
||||||
options. See the ConfigState documentation for more details.
|
|
||||||
|
|
||||||
The following configuration options are available:
|
|
||||||
* Indent
|
|
||||||
String to use for each indentation level for Dump functions.
|
|
||||||
It is a single space by default. A popular alternative is "\t".
|
|
||||||
|
|
||||||
* MaxDepth
|
|
||||||
Maximum number of levels to descend into nested data structures.
|
|
||||||
There is no limit by default.
|
|
||||||
|
|
||||||
* DisableMethods
|
|
||||||
Disables invocation of error and Stringer interface methods.
|
|
||||||
Method invocation is enabled by default.
|
|
||||||
|
|
||||||
* DisablePointerMethods
|
|
||||||
Disables invocation of error and Stringer interface methods on types
|
|
||||||
which only accept pointer receivers from non-pointer variables.
|
|
||||||
Pointer method invocation is enabled by default.
|
|
||||||
|
|
||||||
* DisablePointerAddresses
|
|
||||||
DisablePointerAddresses specifies whether to disable the printing of
|
|
||||||
pointer addresses. This is useful when diffing data structures in tests.
|
|
||||||
|
|
||||||
* DisableCapacities
|
|
||||||
DisableCapacities specifies whether to disable the printing of
|
|
||||||
capacities for arrays, slices, maps and channels. This is useful when
|
|
||||||
diffing data structures in tests.
|
|
||||||
|
|
||||||
* ContinueOnMethod
|
|
||||||
Enables recursion into types after invoking error and Stringer interface
|
|
||||||
methods. Recursion after method invocation is disabled by default.
|
|
||||||
|
|
||||||
* SortKeys
|
|
||||||
Specifies map keys should be sorted before being printed. Use
|
|
||||||
this to have a more deterministic, diffable output. Note that
|
|
||||||
only native types (bool, int, uint, floats, uintptr and string)
|
|
||||||
and types which implement error or Stringer interfaces are
|
|
||||||
supported with other types sorted according to the
|
|
||||||
reflect.Value.String() output which guarantees display
|
|
||||||
stability. Natural map order is used by default.
|
|
||||||
|
|
||||||
* SpewKeys
|
|
||||||
Specifies that, as a last resort attempt, map keys should be
|
|
||||||
spewed to strings and sorted by those strings. This is only
|
|
||||||
considered if SortKeys is true.
|
|
||||||
|
|
||||||
Dump Usage
|
|
||||||
|
|
||||||
Simply call spew.Dump with a list of variables you want to dump:
|
|
||||||
|
|
||||||
spew.Dump(myVar1, myVar2, ...)
|
|
||||||
|
|
||||||
You may also call spew.Fdump if you would prefer to output to an arbitrary
|
|
||||||
io.Writer. For example, to dump to standard error:
|
|
||||||
|
|
||||||
spew.Fdump(os.Stderr, myVar1, myVar2, ...)
|
|
||||||
|
|
||||||
A third option is to call spew.Sdump to get the formatted output as a string:
|
|
||||||
|
|
||||||
str := spew.Sdump(myVar1, myVar2, ...)
|
|
||||||
|
|
||||||
Sample Dump Output
|
|
||||||
|
|
||||||
See the Dump example for details on the setup of the types and variables being
|
|
||||||
shown here.
|
|
||||||
|
|
||||||
(main.Foo) {
|
|
||||||
unexportedField: (*main.Bar)(0xf84002e210)({
|
|
||||||
flag: (main.Flag) flagTwo,
|
|
||||||
data: (uintptr) <nil>
|
|
||||||
}),
|
|
||||||
ExportedField: (map[interface {}]interface {}) (len=1) {
|
|
||||||
(string) (len=3) "one": (bool) true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
|
|
||||||
command as shown.
|
|
||||||
([]uint8) (len=32 cap=32) {
|
|
||||||
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
|
||||||
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
|
||||||
00000020 31 32 |12|
|
|
||||||
}
|
|
||||||
|
|
||||||
Custom Formatter
|
|
||||||
|
|
||||||
Spew provides a custom formatter that implements the fmt.Formatter interface
|
|
||||||
so that it integrates cleanly with standard fmt package printing functions. The
|
|
||||||
formatter is useful for inline printing of smaller data types similar to the
|
|
||||||
standard %v format specifier.
|
|
||||||
|
|
||||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
|
||||||
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
|
||||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
|
||||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
|
||||||
the width and precision arguments (however they will still work on the format
|
|
||||||
specifiers not handled by the custom formatter).
|
|
||||||
|
|
||||||
Custom Formatter Usage
|
|
||||||
|
|
||||||
The simplest way to make use of the spew custom formatter is to call one of the
|
|
||||||
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
|
|
||||||
functions have syntax you are most likely already familiar with:
|
|
||||||
|
|
||||||
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
|
||||||
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
|
||||||
spew.Println(myVar, myVar2)
|
|
||||||
spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
|
||||||
spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
|
||||||
|
|
||||||
See the Index for the full list convenience functions.
|
|
||||||
|
|
||||||
Sample Formatter Output
|
|
||||||
|
|
||||||
Double pointer to a uint8:
|
|
||||||
%v: <**>5
|
|
||||||
%+v: <**>(0xf8400420d0->0xf8400420c8)5
|
|
||||||
%#v: (**uint8)5
|
|
||||||
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
|
|
||||||
|
|
||||||
Pointer to circular struct with a uint8 field and a pointer to itself:
|
|
||||||
%v: <*>{1 <*><shown>}
|
|
||||||
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
|
|
||||||
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
|
|
||||||
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
|
|
||||||
|
|
||||||
See the Printf example for details on the setup of variables being shown
|
|
||||||
here.
|
|
||||||
|
|
||||||
Errors
|
|
||||||
|
|
||||||
Since it is possible for custom Stringer/error interfaces to panic, spew
|
|
||||||
detects them and handles them internally by printing the panic information
|
|
||||||
inline with the output. Since spew is intended to provide deep pretty printing
|
|
||||||
capabilities on structures, it intentionally does not return any errors.
|
|
||||||
*/
|
|
||||||
package spew
|
|
|
@ -1,509 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// uint8Type is a reflect.Type representing a uint8. It is used to
|
|
||||||
// convert cgo types to uint8 slices for hexdumping.
|
|
||||||
uint8Type = reflect.TypeOf(uint8(0))
|
|
||||||
|
|
||||||
// cCharRE is a regular expression that matches a cgo char.
|
|
||||||
// It is used to detect character arrays to hexdump them.
|
|
||||||
cCharRE = regexp.MustCompile("^.*\\._Ctype_char$")
|
|
||||||
|
|
||||||
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
|
|
||||||
// char. It is used to detect unsigned character arrays to hexdump
|
|
||||||
// them.
|
|
||||||
cUnsignedCharRE = regexp.MustCompile("^.*\\._Ctype_unsignedchar$")
|
|
||||||
|
|
||||||
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
|
|
||||||
// It is used to detect uint8_t arrays to hexdump them.
|
|
||||||
cUint8tCharRE = regexp.MustCompile("^.*\\._Ctype_uint8_t$")
|
|
||||||
)
|
|
||||||
|
|
||||||
// dumpState contains information about the state of a dump operation.
|
|
||||||
type dumpState struct {
|
|
||||||
w io.Writer
|
|
||||||
depth int
|
|
||||||
pointers map[uintptr]int
|
|
||||||
ignoreNextType bool
|
|
||||||
ignoreNextIndent bool
|
|
||||||
cs *ConfigState
|
|
||||||
}
|
|
||||||
|
|
||||||
// indent performs indentation according to the depth level and cs.Indent
|
|
||||||
// option.
|
|
||||||
func (d *dumpState) indent() {
|
|
||||||
if d.ignoreNextIndent {
|
|
||||||
d.ignoreNextIndent = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
|
|
||||||
}
|
|
||||||
|
|
||||||
// unpackValue returns values inside of non-nil interfaces when possible.
|
|
||||||
// This is useful for data types like structs, arrays, slices, and maps which
|
|
||||||
// can contain varying types packed inside an interface.
|
|
||||||
func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
|
|
||||||
if v.Kind() == reflect.Interface && !v.IsNil() {
|
|
||||||
v = v.Elem()
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// dumpPtr handles formatting of pointers by indirecting them as necessary.
|
|
||||||
func (d *dumpState) dumpPtr(v reflect.Value) {
|
|
||||||
// Remove pointers at or below the current depth from map used to detect
|
|
||||||
// circular refs.
|
|
||||||
for k, depth := range d.pointers {
|
|
||||||
if depth >= d.depth {
|
|
||||||
delete(d.pointers, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep list of all dereferenced pointers to show later.
|
|
||||||
pointerChain := make([]uintptr, 0)
|
|
||||||
|
|
||||||
// Figure out how many levels of indirection there are by dereferencing
|
|
||||||
// pointers and unpacking interfaces down the chain while detecting circular
|
|
||||||
// references.
|
|
||||||
nilFound := false
|
|
||||||
cycleFound := false
|
|
||||||
indirects := 0
|
|
||||||
ve := v
|
|
||||||
for ve.Kind() == reflect.Ptr {
|
|
||||||
if ve.IsNil() {
|
|
||||||
nilFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
indirects++
|
|
||||||
addr := ve.Pointer()
|
|
||||||
pointerChain = append(pointerChain, addr)
|
|
||||||
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
|
|
||||||
cycleFound = true
|
|
||||||
indirects--
|
|
||||||
break
|
|
||||||
}
|
|
||||||
d.pointers[addr] = d.depth
|
|
||||||
|
|
||||||
ve = ve.Elem()
|
|
||||||
if ve.Kind() == reflect.Interface {
|
|
||||||
if ve.IsNil() {
|
|
||||||
nilFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
ve = ve.Elem()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display type information.
|
|
||||||
d.w.Write(openParenBytes)
|
|
||||||
d.w.Write(bytes.Repeat(asteriskBytes, indirects))
|
|
||||||
d.w.Write([]byte(ve.Type().String()))
|
|
||||||
d.w.Write(closeParenBytes)
|
|
||||||
|
|
||||||
// Display pointer information.
|
|
||||||
if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 {
|
|
||||||
d.w.Write(openParenBytes)
|
|
||||||
for i, addr := range pointerChain {
|
|
||||||
if i > 0 {
|
|
||||||
d.w.Write(pointerChainBytes)
|
|
||||||
}
|
|
||||||
printHexPtr(d.w, addr)
|
|
||||||
}
|
|
||||||
d.w.Write(closeParenBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display dereferenced value.
|
|
||||||
d.w.Write(openParenBytes)
|
|
||||||
switch {
|
|
||||||
case nilFound == true:
|
|
||||||
d.w.Write(nilAngleBytes)
|
|
||||||
|
|
||||||
case cycleFound == true:
|
|
||||||
d.w.Write(circularBytes)
|
|
||||||
|
|
||||||
default:
|
|
||||||
d.ignoreNextType = true
|
|
||||||
d.dump(ve)
|
|
||||||
}
|
|
||||||
d.w.Write(closeParenBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
|
|
||||||
// reflection) arrays and slices are dumped in hexdump -C fashion.
|
|
||||||
func (d *dumpState) dumpSlice(v reflect.Value) {
|
|
||||||
// Determine whether this type should be hex dumped or not. Also,
|
|
||||||
// for types which should be hexdumped, try to use the underlying data
|
|
||||||
// first, then fall back to trying to convert them to a uint8 slice.
|
|
||||||
var buf []uint8
|
|
||||||
doConvert := false
|
|
||||||
doHexDump := false
|
|
||||||
numEntries := v.Len()
|
|
||||||
if numEntries > 0 {
|
|
||||||
vt := v.Index(0).Type()
|
|
||||||
vts := vt.String()
|
|
||||||
switch {
|
|
||||||
// C types that need to be converted.
|
|
||||||
case cCharRE.MatchString(vts):
|
|
||||||
fallthrough
|
|
||||||
case cUnsignedCharRE.MatchString(vts):
|
|
||||||
fallthrough
|
|
||||||
case cUint8tCharRE.MatchString(vts):
|
|
||||||
doConvert = true
|
|
||||||
|
|
||||||
// Try to use existing uint8 slices and fall back to converting
|
|
||||||
// and copying if that fails.
|
|
||||||
case vt.Kind() == reflect.Uint8:
|
|
||||||
// We need an addressable interface to convert the type
|
|
||||||
// to a byte slice. However, the reflect package won't
|
|
||||||
// give us an interface on certain things like
|
|
||||||
// unexported struct fields in order to enforce
|
|
||||||
// visibility rules. We use unsafe, when available, to
|
|
||||||
// bypass these restrictions since this package does not
|
|
||||||
// mutate the values.
|
|
||||||
vs := v
|
|
||||||
if !vs.CanInterface() || !vs.CanAddr() {
|
|
||||||
vs = unsafeReflectValue(vs)
|
|
||||||
}
|
|
||||||
if !UnsafeDisabled {
|
|
||||||
vs = vs.Slice(0, numEntries)
|
|
||||||
|
|
||||||
// Use the existing uint8 slice if it can be
|
|
||||||
// type asserted.
|
|
||||||
iface := vs.Interface()
|
|
||||||
if slice, ok := iface.([]uint8); ok {
|
|
||||||
buf = slice
|
|
||||||
doHexDump = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The underlying data needs to be converted if it can't
|
|
||||||
// be type asserted to a uint8 slice.
|
|
||||||
doConvert = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy and convert the underlying type if needed.
|
|
||||||
if doConvert && vt.ConvertibleTo(uint8Type) {
|
|
||||||
// Convert and copy each element into a uint8 byte
|
|
||||||
// slice.
|
|
||||||
buf = make([]uint8, numEntries)
|
|
||||||
for i := 0; i < numEntries; i++ {
|
|
||||||
vv := v.Index(i)
|
|
||||||
buf[i] = uint8(vv.Convert(uint8Type).Uint())
|
|
||||||
}
|
|
||||||
doHexDump = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hexdump the entire slice as needed.
|
|
||||||
if doHexDump {
|
|
||||||
indent := strings.Repeat(d.cs.Indent, d.depth)
|
|
||||||
str := indent + hex.Dump(buf)
|
|
||||||
str = strings.Replace(str, "\n", "\n"+indent, -1)
|
|
||||||
str = strings.TrimRight(str, d.cs.Indent)
|
|
||||||
d.w.Write([]byte(str))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recursively call dump for each item.
|
|
||||||
for i := 0; i < numEntries; i++ {
|
|
||||||
d.dump(d.unpackValue(v.Index(i)))
|
|
||||||
if i < (numEntries - 1) {
|
|
||||||
d.w.Write(commaNewlineBytes)
|
|
||||||
} else {
|
|
||||||
d.w.Write(newlineBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// dump is the main workhorse for dumping a value. It uses the passed reflect
|
|
||||||
// value to figure out what kind of object we are dealing with and formats it
|
|
||||||
// appropriately. It is a recursive function, however circular data structures
|
|
||||||
// are detected and handled properly.
|
|
||||||
func (d *dumpState) dump(v reflect.Value) {
|
|
||||||
// Handle invalid reflect values immediately.
|
|
||||||
kind := v.Kind()
|
|
||||||
if kind == reflect.Invalid {
|
|
||||||
d.w.Write(invalidAngleBytes)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle pointers specially.
|
|
||||||
if kind == reflect.Ptr {
|
|
||||||
d.indent()
|
|
||||||
d.dumpPtr(v)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print type information unless already handled elsewhere.
|
|
||||||
if !d.ignoreNextType {
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(openParenBytes)
|
|
||||||
d.w.Write([]byte(v.Type().String()))
|
|
||||||
d.w.Write(closeParenBytes)
|
|
||||||
d.w.Write(spaceBytes)
|
|
||||||
}
|
|
||||||
d.ignoreNextType = false
|
|
||||||
|
|
||||||
// Display length and capacity if the built-in len and cap functions
|
|
||||||
// work with the value's kind and the len/cap itself is non-zero.
|
|
||||||
valueLen, valueCap := 0, 0
|
|
||||||
switch v.Kind() {
|
|
||||||
case reflect.Array, reflect.Slice, reflect.Chan:
|
|
||||||
valueLen, valueCap = v.Len(), v.Cap()
|
|
||||||
case reflect.Map, reflect.String:
|
|
||||||
valueLen = v.Len()
|
|
||||||
}
|
|
||||||
if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 {
|
|
||||||
d.w.Write(openParenBytes)
|
|
||||||
if valueLen != 0 {
|
|
||||||
d.w.Write(lenEqualsBytes)
|
|
||||||
printInt(d.w, int64(valueLen), 10)
|
|
||||||
}
|
|
||||||
if !d.cs.DisableCapacities && valueCap != 0 {
|
|
||||||
if valueLen != 0 {
|
|
||||||
d.w.Write(spaceBytes)
|
|
||||||
}
|
|
||||||
d.w.Write(capEqualsBytes)
|
|
||||||
printInt(d.w, int64(valueCap), 10)
|
|
||||||
}
|
|
||||||
d.w.Write(closeParenBytes)
|
|
||||||
d.w.Write(spaceBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call Stringer/error interfaces if they exist and the handle methods flag
|
|
||||||
// is enabled
|
|
||||||
if !d.cs.DisableMethods {
|
|
||||||
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
|
||||||
if handled := handleMethods(d.cs, d.w, v); handled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch kind {
|
|
||||||
case reflect.Invalid:
|
|
||||||
// Do nothing. We should never get here since invalid has already
|
|
||||||
// been handled above.
|
|
||||||
|
|
||||||
case reflect.Bool:
|
|
||||||
printBool(d.w, v.Bool())
|
|
||||||
|
|
||||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
|
||||||
printInt(d.w, v.Int(), 10)
|
|
||||||
|
|
||||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
|
||||||
printUint(d.w, v.Uint(), 10)
|
|
||||||
|
|
||||||
case reflect.Float32:
|
|
||||||
printFloat(d.w, v.Float(), 32)
|
|
||||||
|
|
||||||
case reflect.Float64:
|
|
||||||
printFloat(d.w, v.Float(), 64)
|
|
||||||
|
|
||||||
case reflect.Complex64:
|
|
||||||
printComplex(d.w, v.Complex(), 32)
|
|
||||||
|
|
||||||
case reflect.Complex128:
|
|
||||||
printComplex(d.w, v.Complex(), 64)
|
|
||||||
|
|
||||||
case reflect.Slice:
|
|
||||||
if v.IsNil() {
|
|
||||||
d.w.Write(nilAngleBytes)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
|
|
||||||
case reflect.Array:
|
|
||||||
d.w.Write(openBraceNewlineBytes)
|
|
||||||
d.depth++
|
|
||||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(maxNewlineBytes)
|
|
||||||
} else {
|
|
||||||
d.dumpSlice(v)
|
|
||||||
}
|
|
||||||
d.depth--
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(closeBraceBytes)
|
|
||||||
|
|
||||||
case reflect.String:
|
|
||||||
d.w.Write([]byte(strconv.Quote(v.String())))
|
|
||||||
|
|
||||||
case reflect.Interface:
|
|
||||||
// The only time we should get here is for nil interfaces due to
|
|
||||||
// unpackValue calls.
|
|
||||||
if v.IsNil() {
|
|
||||||
d.w.Write(nilAngleBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
case reflect.Ptr:
|
|
||||||
// Do nothing. We should never get here since pointers have already
|
|
||||||
// been handled above.
|
|
||||||
|
|
||||||
case reflect.Map:
|
|
||||||
// nil maps should be indicated as different than empty maps
|
|
||||||
if v.IsNil() {
|
|
||||||
d.w.Write(nilAngleBytes)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
d.w.Write(openBraceNewlineBytes)
|
|
||||||
d.depth++
|
|
||||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(maxNewlineBytes)
|
|
||||||
} else {
|
|
||||||
numEntries := v.Len()
|
|
||||||
keys := v.MapKeys()
|
|
||||||
if d.cs.SortKeys {
|
|
||||||
sortValues(keys, d.cs)
|
|
||||||
}
|
|
||||||
for i, key := range keys {
|
|
||||||
d.dump(d.unpackValue(key))
|
|
||||||
d.w.Write(colonSpaceBytes)
|
|
||||||
d.ignoreNextIndent = true
|
|
||||||
d.dump(d.unpackValue(v.MapIndex(key)))
|
|
||||||
if i < (numEntries - 1) {
|
|
||||||
d.w.Write(commaNewlineBytes)
|
|
||||||
} else {
|
|
||||||
d.w.Write(newlineBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d.depth--
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(closeBraceBytes)
|
|
||||||
|
|
||||||
case reflect.Struct:
|
|
||||||
d.w.Write(openBraceNewlineBytes)
|
|
||||||
d.depth++
|
|
||||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(maxNewlineBytes)
|
|
||||||
} else {
|
|
||||||
vt := v.Type()
|
|
||||||
numFields := v.NumField()
|
|
||||||
for i := 0; i < numFields; i++ {
|
|
||||||
d.indent()
|
|
||||||
vtf := vt.Field(i)
|
|
||||||
d.w.Write([]byte(vtf.Name))
|
|
||||||
d.w.Write(colonSpaceBytes)
|
|
||||||
d.ignoreNextIndent = true
|
|
||||||
d.dump(d.unpackValue(v.Field(i)))
|
|
||||||
if i < (numFields - 1) {
|
|
||||||
d.w.Write(commaNewlineBytes)
|
|
||||||
} else {
|
|
||||||
d.w.Write(newlineBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d.depth--
|
|
||||||
d.indent()
|
|
||||||
d.w.Write(closeBraceBytes)
|
|
||||||
|
|
||||||
case reflect.Uintptr:
|
|
||||||
printHexPtr(d.w, uintptr(v.Uint()))
|
|
||||||
|
|
||||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
|
||||||
printHexPtr(d.w, v.Pointer())
|
|
||||||
|
|
||||||
// There were not any other types at the time this code was written, but
|
|
||||||
// fall back to letting the default fmt package handle it in case any new
|
|
||||||
// types are added.
|
|
||||||
default:
|
|
||||||
if v.CanInterface() {
|
|
||||||
fmt.Fprintf(d.w, "%v", v.Interface())
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(d.w, "%v", v.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fdump is a helper function to consolidate the logic from the various public
|
|
||||||
// methods which take varying writers and config states.
|
|
||||||
func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
|
|
||||||
for _, arg := range a {
|
|
||||||
if arg == nil {
|
|
||||||
w.Write(interfaceBytes)
|
|
||||||
w.Write(spaceBytes)
|
|
||||||
w.Write(nilAngleBytes)
|
|
||||||
w.Write(newlineBytes)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
d := dumpState{w: w, cs: cs}
|
|
||||||
d.pointers = make(map[uintptr]int)
|
|
||||||
d.dump(reflect.ValueOf(arg))
|
|
||||||
d.w.Write(newlineBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
|
||||||
// exactly the same as Dump.
|
|
||||||
func Fdump(w io.Writer, a ...interface{}) {
|
|
||||||
fdump(&Config, w, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sdump returns a string with the passed arguments formatted exactly the same
|
|
||||||
// as Dump.
|
|
||||||
func Sdump(a ...interface{}) string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
fdump(&Config, &buf, a...)
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Dump displays the passed parameters to standard out with newlines, customizable
|
|
||||||
indentation, and additional debug information such as complete types and all
|
|
||||||
pointer addresses used to indirect to the final value. It provides the
|
|
||||||
following features over the built-in printing facilities provided by the fmt
|
|
||||||
package:
|
|
||||||
|
|
||||||
* Pointers are dereferenced and followed
|
|
||||||
* Circular data structures are detected and handled properly
|
|
||||||
* Custom Stringer/error interfaces are optionally invoked, including
|
|
||||||
on unexported types
|
|
||||||
* Custom types which only implement the Stringer/error interfaces via
|
|
||||||
a pointer receiver are optionally invoked when passing non-pointer
|
|
||||||
variables
|
|
||||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
|
||||||
includes offsets, byte values in hex, and ASCII output
|
|
||||||
|
|
||||||
The configuration options are controlled by an exported package global,
|
|
||||||
spew.Config. See ConfigState for options documentation.
|
|
||||||
|
|
||||||
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
|
||||||
get the formatted result as a string.
|
|
||||||
*/
|
|
||||||
func Dump(a ...interface{}) {
|
|
||||||
fdump(&Config, os.Stdout, a...)
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,99 +0,0 @@
|
||||||
// Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
//
|
|
||||||
// Permission to use, copy, modify, and distribute this software for any
|
|
||||||
// purpose with or without fee is hereby granted, provided that the above
|
|
||||||
// copyright notice and this permission notice appear in all copies.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
|
||||||
// when both cgo is supported and "-tags testcgo" is added to the go test
|
|
||||||
// command line. This means the cgo tests are only added (and hence run) when
|
|
||||||
// specifially requested. This configuration is used because spew itself
|
|
||||||
// does not require cgo to run even though it does handle certain cgo types
|
|
||||||
// specially. Rather than forcing all clients to require cgo and an external
|
|
||||||
// C compiler just to run the tests, this scheme makes them optional.
|
|
||||||
// +build cgo,testcgo
|
|
||||||
|
|
||||||
package spew_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew/testdata"
|
|
||||||
)
|
|
||||||
|
|
||||||
func addCgoDumpTests() {
|
|
||||||
// C char pointer.
|
|
||||||
v := testdata.GetCgoCharPointer()
|
|
||||||
nv := testdata.GetCgoNullCharPointer()
|
|
||||||
pv := &v
|
|
||||||
vcAddr := fmt.Sprintf("%p", v)
|
|
||||||
vAddr := fmt.Sprintf("%p", pv)
|
|
||||||
pvAddr := fmt.Sprintf("%p", &pv)
|
|
||||||
vt := "*testdata._Ctype_char"
|
|
||||||
vs := "116"
|
|
||||||
addDumpTest(v, "("+vt+")("+vcAddr+")("+vs+")\n")
|
|
||||||
addDumpTest(pv, "(*"+vt+")("+vAddr+"->"+vcAddr+")("+vs+")\n")
|
|
||||||
addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+"->"+vcAddr+")("+vs+")\n")
|
|
||||||
addDumpTest(nv, "("+vt+")(<nil>)\n")
|
|
||||||
|
|
||||||
// C char array.
|
|
||||||
v2, v2l, v2c := testdata.GetCgoCharArray()
|
|
||||||
v2Len := fmt.Sprintf("%d", v2l)
|
|
||||||
v2Cap := fmt.Sprintf("%d", v2c)
|
|
||||||
v2t := "[6]testdata._Ctype_char"
|
|
||||||
v2s := "(len=" + v2Len + " cap=" + v2Cap + ") " +
|
|
||||||
"{\n 00000000 74 65 73 74 32 00 " +
|
|
||||||
" |test2.|\n}"
|
|
||||||
addDumpTest(v2, "("+v2t+") "+v2s+"\n")
|
|
||||||
|
|
||||||
// C unsigned char array.
|
|
||||||
v3, v3l, v3c := testdata.GetCgoUnsignedCharArray()
|
|
||||||
v3Len := fmt.Sprintf("%d", v3l)
|
|
||||||
v3Cap := fmt.Sprintf("%d", v3c)
|
|
||||||
v3t := "[6]testdata._Ctype_unsignedchar"
|
|
||||||
v3t2 := "[6]testdata._Ctype_uchar"
|
|
||||||
v3s := "(len=" + v3Len + " cap=" + v3Cap + ") " +
|
|
||||||
"{\n 00000000 74 65 73 74 33 00 " +
|
|
||||||
" |test3.|\n}"
|
|
||||||
addDumpTest(v3, "("+v3t+") "+v3s+"\n", "("+v3t2+") "+v3s+"\n")
|
|
||||||
|
|
||||||
// C signed char array.
|
|
||||||
v4, v4l, v4c := testdata.GetCgoSignedCharArray()
|
|
||||||
v4Len := fmt.Sprintf("%d", v4l)
|
|
||||||
v4Cap := fmt.Sprintf("%d", v4c)
|
|
||||||
v4t := "[6]testdata._Ctype_schar"
|
|
||||||
v4t2 := "testdata._Ctype_schar"
|
|
||||||
v4s := "(len=" + v4Len + " cap=" + v4Cap + ") " +
|
|
||||||
"{\n (" + v4t2 + ") 116,\n (" + v4t2 + ") 101,\n (" + v4t2 +
|
|
||||||
") 115,\n (" + v4t2 + ") 116,\n (" + v4t2 + ") 52,\n (" + v4t2 +
|
|
||||||
") 0\n}"
|
|
||||||
addDumpTest(v4, "("+v4t+") "+v4s+"\n")
|
|
||||||
|
|
||||||
// C uint8_t array.
|
|
||||||
v5, v5l, v5c := testdata.GetCgoUint8tArray()
|
|
||||||
v5Len := fmt.Sprintf("%d", v5l)
|
|
||||||
v5Cap := fmt.Sprintf("%d", v5c)
|
|
||||||
v5t := "[6]testdata._Ctype_uint8_t"
|
|
||||||
v5s := "(len=" + v5Len + " cap=" + v5Cap + ") " +
|
|
||||||
"{\n 00000000 74 65 73 74 35 00 " +
|
|
||||||
" |test5.|\n}"
|
|
||||||
addDumpTest(v5, "("+v5t+") "+v5s+"\n")
|
|
||||||
|
|
||||||
// C typedefed unsigned char array.
|
|
||||||
v6, v6l, v6c := testdata.GetCgoTypdefedUnsignedCharArray()
|
|
||||||
v6Len := fmt.Sprintf("%d", v6l)
|
|
||||||
v6Cap := fmt.Sprintf("%d", v6c)
|
|
||||||
v6t := "[6]testdata._Ctype_custom_uchar_t"
|
|
||||||
v6s := "(len=" + v6Len + " cap=" + v6Cap + ") " +
|
|
||||||
"{\n 00000000 74 65 73 74 36 00 " +
|
|
||||||
" |test6.|\n}"
|
|
||||||
addDumpTest(v6, "("+v6t+") "+v6s+"\n")
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
// Copyright (c) 2013 Dave Collins <dave@davec.name>
|
|
||||||
//
|
|
||||||
// Permission to use, copy, modify, and distribute this software for any
|
|
||||||
// purpose with or without fee is hereby granted, provided that the above
|
|
||||||
// copyright notice and this permission notice appear in all copies.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
|
||||||
// when either cgo is not supported or "-tags testcgo" is not added to the go
|
|
||||||
// test command line. This file intentionally does not setup any cgo tests in
|
|
||||||
// this scenario.
|
|
||||||
// +build !cgo !testcgo
|
|
||||||
|
|
||||||
package spew_test
|
|
||||||
|
|
||||||
func addCgoDumpTests() {
|
|
||||||
// Don't add any tests for cgo since this file is only compiled when
|
|
||||||
// there should not be any cgo tests.
|
|
||||||
}
|
|
|
@ -1,226 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package spew_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Flag int
|
|
||||||
|
|
||||||
const (
|
|
||||||
flagOne Flag = iota
|
|
||||||
flagTwo
|
|
||||||
)
|
|
||||||
|
|
||||||
var flagStrings = map[Flag]string{
|
|
||||||
flagOne: "flagOne",
|
|
||||||
flagTwo: "flagTwo",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f Flag) String() string {
|
|
||||||
if s, ok := flagStrings[f]; ok {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("Unknown flag (%d)", int(f))
|
|
||||||
}
|
|
||||||
|
|
||||||
type Bar struct {
|
|
||||||
data uintptr
|
|
||||||
}
|
|
||||||
|
|
||||||
type Foo struct {
|
|
||||||
unexportedField Bar
|
|
||||||
ExportedField map[interface{}]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This example demonstrates how to use Dump to dump variables to stdout.
|
|
||||||
func ExampleDump() {
|
|
||||||
// The following package level declarations are assumed for this example:
|
|
||||||
/*
|
|
||||||
type Flag int
|
|
||||||
|
|
||||||
const (
|
|
||||||
flagOne Flag = iota
|
|
||||||
flagTwo
|
|
||||||
)
|
|
||||||
|
|
||||||
var flagStrings = map[Flag]string{
|
|
||||||
flagOne: "flagOne",
|
|
||||||
flagTwo: "flagTwo",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f Flag) String() string {
|
|
||||||
if s, ok := flagStrings[f]; ok {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("Unknown flag (%d)", int(f))
|
|
||||||
}
|
|
||||||
|
|
||||||
type Bar struct {
|
|
||||||
data uintptr
|
|
||||||
}
|
|
||||||
|
|
||||||
type Foo struct {
|
|
||||||
unexportedField Bar
|
|
||||||
ExportedField map[interface{}]interface{}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Setup some sample data structures for the example.
|
|
||||||
bar := Bar{uintptr(0)}
|
|
||||||
s1 := Foo{bar, map[interface{}]interface{}{"one": true}}
|
|
||||||
f := Flag(5)
|
|
||||||
b := []byte{
|
|
||||||
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
|
|
||||||
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
|
|
||||||
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
|
|
||||||
0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
|
|
||||||
0x31, 0x32,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dump!
|
|
||||||
spew.Dump(s1, f, b)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// (spew_test.Foo) {
|
|
||||||
// unexportedField: (spew_test.Bar) {
|
|
||||||
// data: (uintptr) <nil>
|
|
||||||
// },
|
|
||||||
// ExportedField: (map[interface {}]interface {}) (len=1) {
|
|
||||||
// (string) (len=3) "one": (bool) true
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// (spew_test.Flag) Unknown flag (5)
|
|
||||||
// ([]uint8) (len=34 cap=34) {
|
|
||||||
// 00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
|
||||||
// 00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
|
||||||
// 00000020 31 32 |12|
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
// This example demonstrates how to use Printf to display a variable with a
|
|
||||||
// format string and inline formatting.
|
|
||||||
func ExamplePrintf() {
|
|
||||||
// Create a double pointer to a uint 8.
|
|
||||||
ui8 := uint8(5)
|
|
||||||
pui8 := &ui8
|
|
||||||
ppui8 := &pui8
|
|
||||||
|
|
||||||
// Create a circular data type.
|
|
||||||
type circular struct {
|
|
||||||
ui8 uint8
|
|
||||||
c *circular
|
|
||||||
}
|
|
||||||
c := circular{ui8: 1}
|
|
||||||
c.c = &c
|
|
||||||
|
|
||||||
// Print!
|
|
||||||
spew.Printf("ppui8: %v\n", ppui8)
|
|
||||||
spew.Printf("circular: %v\n", c)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// ppui8: <**>5
|
|
||||||
// circular: {1 <*>{1 <*><shown>}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This example demonstrates how to use a ConfigState.
|
|
||||||
func ExampleConfigState() {
|
|
||||||
// Modify the indent level of the ConfigState only. The global
|
|
||||||
// configuration is not modified.
|
|
||||||
scs := spew.ConfigState{Indent: "\t"}
|
|
||||||
|
|
||||||
// Output using the ConfigState instance.
|
|
||||||
v := map[string]int{"one": 1}
|
|
||||||
scs.Printf("v: %v\n", v)
|
|
||||||
scs.Dump(v)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// v: map[one:1]
|
|
||||||
// (map[string]int) (len=1) {
|
|
||||||
// (string) (len=3) "one": (int) 1
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
// This example demonstrates how to use ConfigState.Dump to dump variables to
|
|
||||||
// stdout
|
|
||||||
func ExampleConfigState_Dump() {
|
|
||||||
// See the top-level Dump example for details on the types used in this
|
|
||||||
// example.
|
|
||||||
|
|
||||||
// Create two ConfigState instances with different indentation.
|
|
||||||
scs := spew.ConfigState{Indent: "\t"}
|
|
||||||
scs2 := spew.ConfigState{Indent: " "}
|
|
||||||
|
|
||||||
// Setup some sample data structures for the example.
|
|
||||||
bar := Bar{uintptr(0)}
|
|
||||||
s1 := Foo{bar, map[interface{}]interface{}{"one": true}}
|
|
||||||
|
|
||||||
// Dump using the ConfigState instances.
|
|
||||||
scs.Dump(s1)
|
|
||||||
scs2.Dump(s1)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// (spew_test.Foo) {
|
|
||||||
// unexportedField: (spew_test.Bar) {
|
|
||||||
// data: (uintptr) <nil>
|
|
||||||
// },
|
|
||||||
// ExportedField: (map[interface {}]interface {}) (len=1) {
|
|
||||||
// (string) (len=3) "one": (bool) true
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// (spew_test.Foo) {
|
|
||||||
// unexportedField: (spew_test.Bar) {
|
|
||||||
// data: (uintptr) <nil>
|
|
||||||
// },
|
|
||||||
// ExportedField: (map[interface {}]interface {}) (len=1) {
|
|
||||||
// (string) (len=3) "one": (bool) true
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
// This example demonstrates how to use ConfigState.Printf to display a variable
|
|
||||||
// with a format string and inline formatting.
|
|
||||||
func ExampleConfigState_Printf() {
|
|
||||||
// See the top-level Dump example for details on the types used in this
|
|
||||||
// example.
|
|
||||||
|
|
||||||
// Create two ConfigState instances and modify the method handling of the
|
|
||||||
// first ConfigState only.
|
|
||||||
scs := spew.NewDefaultConfig()
|
|
||||||
scs2 := spew.NewDefaultConfig()
|
|
||||||
scs.DisableMethods = true
|
|
||||||
|
|
||||||
// Alternatively
|
|
||||||
// scs := spew.ConfigState{Indent: " ", DisableMethods: true}
|
|
||||||
// scs2 := spew.ConfigState{Indent: " "}
|
|
||||||
|
|
||||||
// This is of type Flag which implements a Stringer and has raw value 1.
|
|
||||||
f := flagTwo
|
|
||||||
|
|
||||||
// Dump using the ConfigState instances.
|
|
||||||
scs.Printf("f: %v\n", f)
|
|
||||||
scs2.Printf("f: %v\n", f)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// f: 1
|
|
||||||
// f: flagTwo
|
|
||||||
}
|
|
|
@ -1,419 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// supportedFlags is a list of all the character flags supported by fmt package.
|
|
||||||
const supportedFlags = "0-+# "
|
|
||||||
|
|
||||||
// formatState implements the fmt.Formatter interface and contains information
|
|
||||||
// about the state of a formatting operation. The NewFormatter function can
|
|
||||||
// be used to get a new Formatter which can be used directly as arguments
|
|
||||||
// in standard fmt package printing calls.
|
|
||||||
type formatState struct {
|
|
||||||
value interface{}
|
|
||||||
fs fmt.State
|
|
||||||
depth int
|
|
||||||
pointers map[uintptr]int
|
|
||||||
ignoreNextType bool
|
|
||||||
cs *ConfigState
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildDefaultFormat recreates the original format string without precision
|
|
||||||
// and width information to pass in to fmt.Sprintf in the case of an
|
|
||||||
// unrecognized type. Unless new types are added to the language, this
|
|
||||||
// function won't ever be called.
|
|
||||||
func (f *formatState) buildDefaultFormat() (format string) {
|
|
||||||
buf := bytes.NewBuffer(percentBytes)
|
|
||||||
|
|
||||||
for _, flag := range supportedFlags {
|
|
||||||
if f.fs.Flag(int(flag)) {
|
|
||||||
buf.WriteRune(flag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.WriteRune('v')
|
|
||||||
|
|
||||||
format = buf.String()
|
|
||||||
return format
|
|
||||||
}
|
|
||||||
|
|
||||||
// constructOrigFormat recreates the original format string including precision
|
|
||||||
// and width information to pass along to the standard fmt package. This allows
|
|
||||||
// automatic deferral of all format strings this package doesn't support.
|
|
||||||
func (f *formatState) constructOrigFormat(verb rune) (format string) {
|
|
||||||
buf := bytes.NewBuffer(percentBytes)
|
|
||||||
|
|
||||||
for _, flag := range supportedFlags {
|
|
||||||
if f.fs.Flag(int(flag)) {
|
|
||||||
buf.WriteRune(flag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if width, ok := f.fs.Width(); ok {
|
|
||||||
buf.WriteString(strconv.Itoa(width))
|
|
||||||
}
|
|
||||||
|
|
||||||
if precision, ok := f.fs.Precision(); ok {
|
|
||||||
buf.Write(precisionBytes)
|
|
||||||
buf.WriteString(strconv.Itoa(precision))
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.WriteRune(verb)
|
|
||||||
|
|
||||||
format = buf.String()
|
|
||||||
return format
|
|
||||||
}
|
|
||||||
|
|
||||||
// unpackValue returns values inside of non-nil interfaces when possible and
|
|
||||||
// ensures that types for values which have been unpacked from an interface
|
|
||||||
// are displayed when the show types flag is also set.
|
|
||||||
// This is useful for data types like structs, arrays, slices, and maps which
|
|
||||||
// can contain varying types packed inside an interface.
|
|
||||||
func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
|
|
||||||
if v.Kind() == reflect.Interface {
|
|
||||||
f.ignoreNextType = false
|
|
||||||
if !v.IsNil() {
|
|
||||||
v = v.Elem()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// formatPtr handles formatting of pointers by indirecting them as necessary.
|
|
||||||
func (f *formatState) formatPtr(v reflect.Value) {
|
|
||||||
// Display nil if top level pointer is nil.
|
|
||||||
showTypes := f.fs.Flag('#')
|
|
||||||
if v.IsNil() && (!showTypes || f.ignoreNextType) {
|
|
||||||
f.fs.Write(nilAngleBytes)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove pointers at or below the current depth from map used to detect
|
|
||||||
// circular refs.
|
|
||||||
for k, depth := range f.pointers {
|
|
||||||
if depth >= f.depth {
|
|
||||||
delete(f.pointers, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep list of all dereferenced pointers to possibly show later.
|
|
||||||
pointerChain := make([]uintptr, 0)
|
|
||||||
|
|
||||||
// Figure out how many levels of indirection there are by derferencing
|
|
||||||
// pointers and unpacking interfaces down the chain while detecting circular
|
|
||||||
// references.
|
|
||||||
nilFound := false
|
|
||||||
cycleFound := false
|
|
||||||
indirects := 0
|
|
||||||
ve := v
|
|
||||||
for ve.Kind() == reflect.Ptr {
|
|
||||||
if ve.IsNil() {
|
|
||||||
nilFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
indirects++
|
|
||||||
addr := ve.Pointer()
|
|
||||||
pointerChain = append(pointerChain, addr)
|
|
||||||
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
|
|
||||||
cycleFound = true
|
|
||||||
indirects--
|
|
||||||
break
|
|
||||||
}
|
|
||||||
f.pointers[addr] = f.depth
|
|
||||||
|
|
||||||
ve = ve.Elem()
|
|
||||||
if ve.Kind() == reflect.Interface {
|
|
||||||
if ve.IsNil() {
|
|
||||||
nilFound = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
ve = ve.Elem()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display type or indirection level depending on flags.
|
|
||||||
if showTypes && !f.ignoreNextType {
|
|
||||||
f.fs.Write(openParenBytes)
|
|
||||||
f.fs.Write(bytes.Repeat(asteriskBytes, indirects))
|
|
||||||
f.fs.Write([]byte(ve.Type().String()))
|
|
||||||
f.fs.Write(closeParenBytes)
|
|
||||||
} else {
|
|
||||||
if nilFound || cycleFound {
|
|
||||||
indirects += strings.Count(ve.Type().String(), "*")
|
|
||||||
}
|
|
||||||
f.fs.Write(openAngleBytes)
|
|
||||||
f.fs.Write([]byte(strings.Repeat("*", indirects)))
|
|
||||||
f.fs.Write(closeAngleBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display pointer information depending on flags.
|
|
||||||
if f.fs.Flag('+') && (len(pointerChain) > 0) {
|
|
||||||
f.fs.Write(openParenBytes)
|
|
||||||
for i, addr := range pointerChain {
|
|
||||||
if i > 0 {
|
|
||||||
f.fs.Write(pointerChainBytes)
|
|
||||||
}
|
|
||||||
printHexPtr(f.fs, addr)
|
|
||||||
}
|
|
||||||
f.fs.Write(closeParenBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Display dereferenced value.
|
|
||||||
switch {
|
|
||||||
case nilFound == true:
|
|
||||||
f.fs.Write(nilAngleBytes)
|
|
||||||
|
|
||||||
case cycleFound == true:
|
|
||||||
f.fs.Write(circularShortBytes)
|
|
||||||
|
|
||||||
default:
|
|
||||||
f.ignoreNextType = true
|
|
||||||
f.format(ve)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// format is the main workhorse for providing the Formatter interface. It
|
|
||||||
// uses the passed reflect value to figure out what kind of object we are
|
|
||||||
// dealing with and formats it appropriately. It is a recursive function,
|
|
||||||
// however circular data structures are detected and handled properly.
|
|
||||||
func (f *formatState) format(v reflect.Value) {
|
|
||||||
// Handle invalid reflect values immediately.
|
|
||||||
kind := v.Kind()
|
|
||||||
if kind == reflect.Invalid {
|
|
||||||
f.fs.Write(invalidAngleBytes)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle pointers specially.
|
|
||||||
if kind == reflect.Ptr {
|
|
||||||
f.formatPtr(v)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print type information unless already handled elsewhere.
|
|
||||||
if !f.ignoreNextType && f.fs.Flag('#') {
|
|
||||||
f.fs.Write(openParenBytes)
|
|
||||||
f.fs.Write([]byte(v.Type().String()))
|
|
||||||
f.fs.Write(closeParenBytes)
|
|
||||||
}
|
|
||||||
f.ignoreNextType = false
|
|
||||||
|
|
||||||
// Call Stringer/error interfaces if they exist and the handle methods
|
|
||||||
// flag is enabled.
|
|
||||||
if !f.cs.DisableMethods {
|
|
||||||
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
|
||||||
if handled := handleMethods(f.cs, f.fs, v); handled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch kind {
|
|
||||||
case reflect.Invalid:
|
|
||||||
// Do nothing. We should never get here since invalid has already
|
|
||||||
// been handled above.
|
|
||||||
|
|
||||||
case reflect.Bool:
|
|
||||||
printBool(f.fs, v.Bool())
|
|
||||||
|
|
||||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
|
||||||
printInt(f.fs, v.Int(), 10)
|
|
||||||
|
|
||||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
|
||||||
printUint(f.fs, v.Uint(), 10)
|
|
||||||
|
|
||||||
case reflect.Float32:
|
|
||||||
printFloat(f.fs, v.Float(), 32)
|
|
||||||
|
|
||||||
case reflect.Float64:
|
|
||||||
printFloat(f.fs, v.Float(), 64)
|
|
||||||
|
|
||||||
case reflect.Complex64:
|
|
||||||
printComplex(f.fs, v.Complex(), 32)
|
|
||||||
|
|
||||||
case reflect.Complex128:
|
|
||||||
printComplex(f.fs, v.Complex(), 64)
|
|
||||||
|
|
||||||
case reflect.Slice:
|
|
||||||
if v.IsNil() {
|
|
||||||
f.fs.Write(nilAngleBytes)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
|
|
||||||
case reflect.Array:
|
|
||||||
f.fs.Write(openBracketBytes)
|
|
||||||
f.depth++
|
|
||||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
|
||||||
f.fs.Write(maxShortBytes)
|
|
||||||
} else {
|
|
||||||
numEntries := v.Len()
|
|
||||||
for i := 0; i < numEntries; i++ {
|
|
||||||
if i > 0 {
|
|
||||||
f.fs.Write(spaceBytes)
|
|
||||||
}
|
|
||||||
f.ignoreNextType = true
|
|
||||||
f.format(f.unpackValue(v.Index(i)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.depth--
|
|
||||||
f.fs.Write(closeBracketBytes)
|
|
||||||
|
|
||||||
case reflect.String:
|
|
||||||
f.fs.Write([]byte(v.String()))
|
|
||||||
|
|
||||||
case reflect.Interface:
|
|
||||||
// The only time we should get here is for nil interfaces due to
|
|
||||||
// unpackValue calls.
|
|
||||||
if v.IsNil() {
|
|
||||||
f.fs.Write(nilAngleBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
case reflect.Ptr:
|
|
||||||
// Do nothing. We should never get here since pointers have already
|
|
||||||
// been handled above.
|
|
||||||
|
|
||||||
case reflect.Map:
|
|
||||||
// nil maps should be indicated as different than empty maps
|
|
||||||
if v.IsNil() {
|
|
||||||
f.fs.Write(nilAngleBytes)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
f.fs.Write(openMapBytes)
|
|
||||||
f.depth++
|
|
||||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
|
||||||
f.fs.Write(maxShortBytes)
|
|
||||||
} else {
|
|
||||||
keys := v.MapKeys()
|
|
||||||
if f.cs.SortKeys {
|
|
||||||
sortValues(keys, f.cs)
|
|
||||||
}
|
|
||||||
for i, key := range keys {
|
|
||||||
if i > 0 {
|
|
||||||
f.fs.Write(spaceBytes)
|
|
||||||
}
|
|
||||||
f.ignoreNextType = true
|
|
||||||
f.format(f.unpackValue(key))
|
|
||||||
f.fs.Write(colonBytes)
|
|
||||||
f.ignoreNextType = true
|
|
||||||
f.format(f.unpackValue(v.MapIndex(key)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.depth--
|
|
||||||
f.fs.Write(closeMapBytes)
|
|
||||||
|
|
||||||
case reflect.Struct:
|
|
||||||
numFields := v.NumField()
|
|
||||||
f.fs.Write(openBraceBytes)
|
|
||||||
f.depth++
|
|
||||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
|
||||||
f.fs.Write(maxShortBytes)
|
|
||||||
} else {
|
|
||||||
vt := v.Type()
|
|
||||||
for i := 0; i < numFields; i++ {
|
|
||||||
if i > 0 {
|
|
||||||
f.fs.Write(spaceBytes)
|
|
||||||
}
|
|
||||||
vtf := vt.Field(i)
|
|
||||||
if f.fs.Flag('+') || f.fs.Flag('#') {
|
|
||||||
f.fs.Write([]byte(vtf.Name))
|
|
||||||
f.fs.Write(colonBytes)
|
|
||||||
}
|
|
||||||
f.format(f.unpackValue(v.Field(i)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.depth--
|
|
||||||
f.fs.Write(closeBraceBytes)
|
|
||||||
|
|
||||||
case reflect.Uintptr:
|
|
||||||
printHexPtr(f.fs, uintptr(v.Uint()))
|
|
||||||
|
|
||||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
|
||||||
printHexPtr(f.fs, v.Pointer())
|
|
||||||
|
|
||||||
// There were not any other types at the time this code was written, but
|
|
||||||
// fall back to letting the default fmt package handle it if any get added.
|
|
||||||
default:
|
|
||||||
format := f.buildDefaultFormat()
|
|
||||||
if v.CanInterface() {
|
|
||||||
fmt.Fprintf(f.fs, format, v.Interface())
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(f.fs, format, v.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format satisfies the fmt.Formatter interface. See NewFormatter for usage
|
|
||||||
// details.
|
|
||||||
func (f *formatState) Format(fs fmt.State, verb rune) {
|
|
||||||
f.fs = fs
|
|
||||||
|
|
||||||
// Use standard formatting for verbs that are not v.
|
|
||||||
if verb != 'v' {
|
|
||||||
format := f.constructOrigFormat(verb)
|
|
||||||
fmt.Fprintf(fs, format, f.value)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.value == nil {
|
|
||||||
if fs.Flag('#') {
|
|
||||||
fs.Write(interfaceBytes)
|
|
||||||
}
|
|
||||||
fs.Write(nilAngleBytes)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
f.format(reflect.ValueOf(f.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
// newFormatter is a helper function to consolidate the logic from the various
|
|
||||||
// public methods which take varying config states.
|
|
||||||
func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
|
|
||||||
fs := &formatState{value: v, cs: cs}
|
|
||||||
fs.pointers = make(map[uintptr]int)
|
|
||||||
return fs
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
|
||||||
interface. As a result, it integrates cleanly with standard fmt package
|
|
||||||
printing functions. The formatter is useful for inline printing of smaller data
|
|
||||||
types similar to the standard %v format specifier.
|
|
||||||
|
|
||||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
|
||||||
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
|
||||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
|
||||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
|
||||||
the width and precision arguments (however they will still work on the format
|
|
||||||
specifiers not handled by the custom formatter).
|
|
||||||
|
|
||||||
Typically this function shouldn't be called directly. It is much easier to make
|
|
||||||
use of the custom formatter by calling one of the convenience functions such as
|
|
||||||
Printf, Println, or Fprintf.
|
|
||||||
*/
|
|
||||||
func NewFormatter(v interface{}) fmt.Formatter {
|
|
||||||
return newFormatter(&Config, v)
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,87 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
This test file is part of the spew package rather than than the spew_test
|
|
||||||
package because it needs access to internals to properly test certain cases
|
|
||||||
which are not possible via the public interface since they should never happen.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// dummyFmtState implements a fake fmt.State to use for testing invalid
|
|
||||||
// reflect.Value handling. This is necessary because the fmt package catches
|
|
||||||
// invalid values before invoking the formatter on them.
|
|
||||||
type dummyFmtState struct {
|
|
||||||
bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dfs *dummyFmtState) Flag(f int) bool {
|
|
||||||
if f == int('+') {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dfs *dummyFmtState) Precision() (int, bool) {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dfs *dummyFmtState) Width() (int, bool) {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestInvalidReflectValue ensures the dump and formatter code handles an
|
|
||||||
// invalid reflect value properly. This needs access to internal state since it
|
|
||||||
// should never happen in real code and therefore can't be tested via the public
|
|
||||||
// API.
|
|
||||||
func TestInvalidReflectValue(t *testing.T) {
|
|
||||||
i := 1
|
|
||||||
|
|
||||||
// Dump invalid reflect value.
|
|
||||||
v := new(reflect.Value)
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
d := dumpState{w: buf, cs: &Config}
|
|
||||||
d.dump(*v)
|
|
||||||
s := buf.String()
|
|
||||||
want := "<invalid>"
|
|
||||||
if s != want {
|
|
||||||
t.Errorf("InvalidReflectValue #%d\n got: %s want: %s", i, s, want)
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
|
|
||||||
// Formatter invalid reflect value.
|
|
||||||
buf2 := new(dummyFmtState)
|
|
||||||
f := formatState{value: *v, cs: &Config, fs: buf2}
|
|
||||||
f.format(*v)
|
|
||||||
s = buf2.String()
|
|
||||||
want = "<invalid>"
|
|
||||||
if s != want {
|
|
||||||
t.Errorf("InvalidReflectValue #%d got: %s want: %s", i, s, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SortValues makes the internal sortValues function available to the test
|
|
||||||
// package.
|
|
||||||
func SortValues(values []reflect.Value, cs *ConfigState) {
|
|
||||||
sortValues(values, cs)
|
|
||||||
}
|
|
|
@ -1,102 +0,0 @@
|
||||||
// Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
|
|
||||||
// Permission to use, copy, modify, and distribute this software for any
|
|
||||||
// purpose with or without fee is hereby granted, provided that the above
|
|
||||||
// copyright notice and this permission notice appear in all copies.
|
|
||||||
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
|
||||||
// when the code is not running on Google App Engine, compiled by GopherJS, and
|
|
||||||
// "-tags safe" is not added to the go build command line. The "disableunsafe"
|
|
||||||
// tag is deprecated and thus should not be used.
|
|
||||||
// +build !js,!appengine,!safe,!disableunsafe
|
|
||||||
|
|
||||||
/*
|
|
||||||
This test file is part of the spew package rather than than the spew_test
|
|
||||||
package because it needs access to internals to properly test certain cases
|
|
||||||
which are not possible via the public interface since they should never happen.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// changeKind uses unsafe to intentionally change the kind of a reflect.Value to
|
|
||||||
// the maximum kind value which does not exist. This is needed to test the
|
|
||||||
// fallback code which punts to the standard fmt library for new types that
|
|
||||||
// might get added to the language.
|
|
||||||
func changeKind(v *reflect.Value, readOnly bool) {
|
|
||||||
rvf := (*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + offsetFlag))
|
|
||||||
*rvf = *rvf | ((1<<flagKindWidth - 1) << flagKindShift)
|
|
||||||
if readOnly {
|
|
||||||
*rvf |= flagRO
|
|
||||||
} else {
|
|
||||||
*rvf &= ^uintptr(flagRO)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestAddedReflectValue tests functionaly of the dump and formatter code which
|
|
||||||
// falls back to the standard fmt library for new types that might get added to
|
|
||||||
// the language.
|
|
||||||
func TestAddedReflectValue(t *testing.T) {
|
|
||||||
i := 1
|
|
||||||
|
|
||||||
// Dump using a reflect.Value that is exported.
|
|
||||||
v := reflect.ValueOf(int8(5))
|
|
||||||
changeKind(&v, false)
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
d := dumpState{w: buf, cs: &Config}
|
|
||||||
d.dump(v)
|
|
||||||
s := buf.String()
|
|
||||||
want := "(int8) 5"
|
|
||||||
if s != want {
|
|
||||||
t.Errorf("TestAddedReflectValue #%d\n got: %s want: %s", i, s, want)
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
|
|
||||||
// Dump using a reflect.Value that is not exported.
|
|
||||||
changeKind(&v, true)
|
|
||||||
buf.Reset()
|
|
||||||
d.dump(v)
|
|
||||||
s = buf.String()
|
|
||||||
want = "(int8) <int8 Value>"
|
|
||||||
if s != want {
|
|
||||||
t.Errorf("TestAddedReflectValue #%d\n got: %s want: %s", i, s, want)
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
|
|
||||||
// Formatter using a reflect.Value that is exported.
|
|
||||||
changeKind(&v, false)
|
|
||||||
buf2 := new(dummyFmtState)
|
|
||||||
f := formatState{value: v, cs: &Config, fs: buf2}
|
|
||||||
f.format(v)
|
|
||||||
s = buf2.String()
|
|
||||||
want = "5"
|
|
||||||
if s != want {
|
|
||||||
t.Errorf("TestAddedReflectValue #%d got: %s want: %s", i, s, want)
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
|
|
||||||
// Formatter using a reflect.Value that is not exported.
|
|
||||||
changeKind(&v, true)
|
|
||||||
buf2.Reset()
|
|
||||||
f = formatState{value: v, cs: &Config, fs: buf2}
|
|
||||||
f.format(v)
|
|
||||||
s = buf2.String()
|
|
||||||
want = "<int8 Value>"
|
|
||||||
if s != want {
|
|
||||||
t.Errorf("TestAddedReflectValue #%d got: %s want: %s", i, s, want)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,148 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package spew
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the formatted string as a value that satisfies error. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Errorf(format string, a ...interface{}) (err error) {
|
|
||||||
return fmt.Errorf(format, convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Fprint(w, convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Fprintf(w, format, convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Fprintln(w, convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Print(a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Print(convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Printf(format string, a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Printf(format, convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the number of bytes written and any write error encountered. See
|
|
||||||
// NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Println(a ...interface{}) (n int, err error) {
|
|
||||||
return fmt.Println(convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the resulting string. See NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Sprint(a ...interface{}) string {
|
|
||||||
return fmt.Sprint(convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
|
||||||
// passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the resulting string. See NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Sprintf(format string, a ...interface{}) string {
|
|
||||||
return fmt.Sprintf(format, convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
|
||||||
// were passed with a default Formatter interface returned by NewFormatter. It
|
|
||||||
// returns the resulting string. See NewFormatter for formatting details.
|
|
||||||
//
|
|
||||||
// This function is shorthand for the following syntax:
|
|
||||||
//
|
|
||||||
// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b))
|
|
||||||
func Sprintln(a ...interface{}) string {
|
|
||||||
return fmt.Sprintln(convertArgs(a)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertArgs accepts a slice of arguments and returns a slice of the same
|
|
||||||
// length with each argument converted to a default spew Formatter interface.
|
|
||||||
func convertArgs(args []interface{}) (formatters []interface{}) {
|
|
||||||
formatters = make([]interface{}, len(args))
|
|
||||||
for index, arg := range args {
|
|
||||||
formatters[index] = NewFormatter(arg)
|
|
||||||
}
|
|
||||||
return formatters
|
|
||||||
}
|
|
|
@ -1,320 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted, provided that the above
|
|
||||||
* copyright notice and this permission notice appear in all copies.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package spew_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
)
|
|
||||||
|
|
||||||
// spewFunc is used to identify which public function of the spew package or
|
|
||||||
// ConfigState a test applies to.
|
|
||||||
type spewFunc int
|
|
||||||
|
|
||||||
const (
|
|
||||||
fCSFdump spewFunc = iota
|
|
||||||
fCSFprint
|
|
||||||
fCSFprintf
|
|
||||||
fCSFprintln
|
|
||||||
fCSPrint
|
|
||||||
fCSPrintln
|
|
||||||
fCSSdump
|
|
||||||
fCSSprint
|
|
||||||
fCSSprintf
|
|
||||||
fCSSprintln
|
|
||||||
fCSErrorf
|
|
||||||
fCSNewFormatter
|
|
||||||
fErrorf
|
|
||||||
fFprint
|
|
||||||
fFprintln
|
|
||||||
fPrint
|
|
||||||
fPrintln
|
|
||||||
fSdump
|
|
||||||
fSprint
|
|
||||||
fSprintf
|
|
||||||
fSprintln
|
|
||||||
)
|
|
||||||
|
|
||||||
// Map of spewFunc values to names for pretty printing.
|
|
||||||
var spewFuncStrings = map[spewFunc]string{
|
|
||||||
fCSFdump: "ConfigState.Fdump",
|
|
||||||
fCSFprint: "ConfigState.Fprint",
|
|
||||||
fCSFprintf: "ConfigState.Fprintf",
|
|
||||||
fCSFprintln: "ConfigState.Fprintln",
|
|
||||||
fCSSdump: "ConfigState.Sdump",
|
|
||||||
fCSPrint: "ConfigState.Print",
|
|
||||||
fCSPrintln: "ConfigState.Println",
|
|
||||||
fCSSprint: "ConfigState.Sprint",
|
|
||||||
fCSSprintf: "ConfigState.Sprintf",
|
|
||||||
fCSSprintln: "ConfigState.Sprintln",
|
|
||||||
fCSErrorf: "ConfigState.Errorf",
|
|
||||||
fCSNewFormatter: "ConfigState.NewFormatter",
|
|
||||||
fErrorf: "spew.Errorf",
|
|
||||||
fFprint: "spew.Fprint",
|
|
||||||
fFprintln: "spew.Fprintln",
|
|
||||||
fPrint: "spew.Print",
|
|
||||||
fPrintln: "spew.Println",
|
|
||||||
fSdump: "spew.Sdump",
|
|
||||||
fSprint: "spew.Sprint",
|
|
||||||
fSprintf: "spew.Sprintf",
|
|
||||||
fSprintln: "spew.Sprintln",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f spewFunc) String() string {
|
|
||||||
if s, ok := spewFuncStrings[f]; ok {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("Unknown spewFunc (%d)", int(f))
|
|
||||||
}
|
|
||||||
|
|
||||||
// spewTest is used to describe a test to be performed against the public
|
|
||||||
// functions of the spew package or ConfigState.
|
|
||||||
type spewTest struct {
|
|
||||||
cs *spew.ConfigState
|
|
||||||
f spewFunc
|
|
||||||
format string
|
|
||||||
in interface{}
|
|
||||||
want string
|
|
||||||
}
|
|
||||||
|
|
||||||
// spewTests houses the tests to be performed against the public functions of
|
|
||||||
// the spew package and ConfigState.
|
|
||||||
//
|
|
||||||
// These tests are only intended to ensure the public functions are exercised
|
|
||||||
// and are intentionally not exhaustive of types. The exhaustive type
|
|
||||||
// tests are handled in the dump and format tests.
|
|
||||||
var spewTests []spewTest
|
|
||||||
|
|
||||||
// redirStdout is a helper function to return the standard output from f as a
|
|
||||||
// byte slice.
|
|
||||||
func redirStdout(f func()) ([]byte, error) {
|
|
||||||
tempFile, err := ioutil.TempFile("", "ss-test")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
fileName := tempFile.Name()
|
|
||||||
defer os.Remove(fileName) // Ignore error
|
|
||||||
|
|
||||||
origStdout := os.Stdout
|
|
||||||
os.Stdout = tempFile
|
|
||||||
f()
|
|
||||||
os.Stdout = origStdout
|
|
||||||
tempFile.Close()
|
|
||||||
|
|
||||||
return ioutil.ReadFile(fileName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func initSpewTests() {
|
|
||||||
// Config states with various settings.
|
|
||||||
scsDefault := spew.NewDefaultConfig()
|
|
||||||
scsNoMethods := &spew.ConfigState{Indent: " ", DisableMethods: true}
|
|
||||||
scsNoPmethods := &spew.ConfigState{Indent: " ", DisablePointerMethods: true}
|
|
||||||
scsMaxDepth := &spew.ConfigState{Indent: " ", MaxDepth: 1}
|
|
||||||
scsContinue := &spew.ConfigState{Indent: " ", ContinueOnMethod: true}
|
|
||||||
scsNoPtrAddr := &spew.ConfigState{DisablePointerAddresses: true}
|
|
||||||
scsNoCap := &spew.ConfigState{DisableCapacities: true}
|
|
||||||
|
|
||||||
// Variables for tests on types which implement Stringer interface with and
|
|
||||||
// without a pointer receiver.
|
|
||||||
ts := stringer("test")
|
|
||||||
tps := pstringer("test")
|
|
||||||
|
|
||||||
type ptrTester struct {
|
|
||||||
s *struct{}
|
|
||||||
}
|
|
||||||
tptr := &ptrTester{s: &struct{}{}}
|
|
||||||
|
|
||||||
// depthTester is used to test max depth handling for structs, array, slices
|
|
||||||
// and maps.
|
|
||||||
type depthTester struct {
|
|
||||||
ic indirCir1
|
|
||||||
arr [1]string
|
|
||||||
slice []string
|
|
||||||
m map[string]int
|
|
||||||
}
|
|
||||||
dt := depthTester{indirCir1{nil}, [1]string{"arr"}, []string{"slice"},
|
|
||||||
map[string]int{"one": 1}}
|
|
||||||
|
|
||||||
// Variable for tests on types which implement error interface.
|
|
||||||
te := customError(10)
|
|
||||||
|
|
||||||
spewTests = []spewTest{
|
|
||||||
{scsDefault, fCSFdump, "", int8(127), "(int8) 127\n"},
|
|
||||||
{scsDefault, fCSFprint, "", int16(32767), "32767"},
|
|
||||||
{scsDefault, fCSFprintf, "%v", int32(2147483647), "2147483647"},
|
|
||||||
{scsDefault, fCSFprintln, "", int(2147483647), "2147483647\n"},
|
|
||||||
{scsDefault, fCSPrint, "", int64(9223372036854775807), "9223372036854775807"},
|
|
||||||
{scsDefault, fCSPrintln, "", uint8(255), "255\n"},
|
|
||||||
{scsDefault, fCSSdump, "", uint8(64), "(uint8) 64\n"},
|
|
||||||
{scsDefault, fCSSprint, "", complex(1, 2), "(1+2i)"},
|
|
||||||
{scsDefault, fCSSprintf, "%v", complex(float32(3), 4), "(3+4i)"},
|
|
||||||
{scsDefault, fCSSprintln, "", complex(float64(5), 6), "(5+6i)\n"},
|
|
||||||
{scsDefault, fCSErrorf, "%#v", uint16(65535), "(uint16)65535"},
|
|
||||||
{scsDefault, fCSNewFormatter, "%v", uint32(4294967295), "4294967295"},
|
|
||||||
{scsDefault, fErrorf, "%v", uint64(18446744073709551615), "18446744073709551615"},
|
|
||||||
{scsDefault, fFprint, "", float32(3.14), "3.14"},
|
|
||||||
{scsDefault, fFprintln, "", float64(6.28), "6.28\n"},
|
|
||||||
{scsDefault, fPrint, "", true, "true"},
|
|
||||||
{scsDefault, fPrintln, "", false, "false\n"},
|
|
||||||
{scsDefault, fSdump, "", complex(-10, -20), "(complex128) (-10-20i)\n"},
|
|
||||||
{scsDefault, fSprint, "", complex(-1, -2), "(-1-2i)"},
|
|
||||||
{scsDefault, fSprintf, "%v", complex(float32(-3), -4), "(-3-4i)"},
|
|
||||||
{scsDefault, fSprintln, "", complex(float64(-5), -6), "(-5-6i)\n"},
|
|
||||||
{scsNoMethods, fCSFprint, "", ts, "test"},
|
|
||||||
{scsNoMethods, fCSFprint, "", &ts, "<*>test"},
|
|
||||||
{scsNoMethods, fCSFprint, "", tps, "test"},
|
|
||||||
{scsNoMethods, fCSFprint, "", &tps, "<*>test"},
|
|
||||||
{scsNoPmethods, fCSFprint, "", ts, "stringer test"},
|
|
||||||
{scsNoPmethods, fCSFprint, "", &ts, "<*>stringer test"},
|
|
||||||
{scsNoPmethods, fCSFprint, "", tps, "test"},
|
|
||||||
{scsNoPmethods, fCSFprint, "", &tps, "<*>stringer test"},
|
|
||||||
{scsMaxDepth, fCSFprint, "", dt, "{{<max>} [<max>] [<max>] map[<max>]}"},
|
|
||||||
{scsMaxDepth, fCSFdump, "", dt, "(spew_test.depthTester) {\n" +
|
|
||||||
" ic: (spew_test.indirCir1) {\n <max depth reached>\n },\n" +
|
|
||||||
" arr: ([1]string) (len=1 cap=1) {\n <max depth reached>\n },\n" +
|
|
||||||
" slice: ([]string) (len=1 cap=1) {\n <max depth reached>\n },\n" +
|
|
||||||
" m: (map[string]int) (len=1) {\n <max depth reached>\n }\n}\n"},
|
|
||||||
{scsContinue, fCSFprint, "", ts, "(stringer test) test"},
|
|
||||||
{scsContinue, fCSFdump, "", ts, "(spew_test.stringer) " +
|
|
||||||
"(len=4) (stringer test) \"test\"\n"},
|
|
||||||
{scsContinue, fCSFprint, "", te, "(error: 10) 10"},
|
|
||||||
{scsContinue, fCSFdump, "", te, "(spew_test.customError) " +
|
|
||||||
"(error: 10) 10\n"},
|
|
||||||
{scsNoPtrAddr, fCSFprint, "", tptr, "<*>{<*>{}}"},
|
|
||||||
{scsNoPtrAddr, fCSSdump, "", tptr, "(*spew_test.ptrTester)({\ns: (*struct {})({\n})\n})\n"},
|
|
||||||
{scsNoCap, fCSSdump, "", make([]string, 0, 10), "([]string) {\n}\n"},
|
|
||||||
{scsNoCap, fCSSdump, "", make([]string, 1, 10), "([]string) (len=1) {\n(string) \"\"\n}\n"},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestSpew executes all of the tests described by spewTests.
|
|
||||||
func TestSpew(t *testing.T) {
|
|
||||||
initSpewTests()
|
|
||||||
|
|
||||||
t.Logf("Running %d tests", len(spewTests))
|
|
||||||
for i, test := range spewTests {
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
switch test.f {
|
|
||||||
case fCSFdump:
|
|
||||||
test.cs.Fdump(buf, test.in)
|
|
||||||
|
|
||||||
case fCSFprint:
|
|
||||||
test.cs.Fprint(buf, test.in)
|
|
||||||
|
|
||||||
case fCSFprintf:
|
|
||||||
test.cs.Fprintf(buf, test.format, test.in)
|
|
||||||
|
|
||||||
case fCSFprintln:
|
|
||||||
test.cs.Fprintln(buf, test.in)
|
|
||||||
|
|
||||||
case fCSPrint:
|
|
||||||
b, err := redirStdout(func() { test.cs.Print(test.in) })
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%v #%d %v", test.f, i, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
buf.Write(b)
|
|
||||||
|
|
||||||
case fCSPrintln:
|
|
||||||
b, err := redirStdout(func() { test.cs.Println(test.in) })
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%v #%d %v", test.f, i, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
buf.Write(b)
|
|
||||||
|
|
||||||
case fCSSdump:
|
|
||||||
str := test.cs.Sdump(test.in)
|
|
||||||
buf.WriteString(str)
|
|
||||||
|
|
||||||
case fCSSprint:
|
|
||||||
str := test.cs.Sprint(test.in)
|
|
||||||
buf.WriteString(str)
|
|
||||||
|
|
||||||
case fCSSprintf:
|
|
||||||
str := test.cs.Sprintf(test.format, test.in)
|
|
||||||
buf.WriteString(str)
|
|
||||||
|
|
||||||
case fCSSprintln:
|
|
||||||
str := test.cs.Sprintln(test.in)
|
|
||||||
buf.WriteString(str)
|
|
||||||
|
|
||||||
case fCSErrorf:
|
|
||||||
err := test.cs.Errorf(test.format, test.in)
|
|
||||||
buf.WriteString(err.Error())
|
|
||||||
|
|
||||||
case fCSNewFormatter:
|
|
||||||
fmt.Fprintf(buf, test.format, test.cs.NewFormatter(test.in))
|
|
||||||
|
|
||||||
case fErrorf:
|
|
||||||
err := spew.Errorf(test.format, test.in)
|
|
||||||
buf.WriteString(err.Error())
|
|
||||||
|
|
||||||
case fFprint:
|
|
||||||
spew.Fprint(buf, test.in)
|
|
||||||
|
|
||||||
case fFprintln:
|
|
||||||
spew.Fprintln(buf, test.in)
|
|
||||||
|
|
||||||
case fPrint:
|
|
||||||
b, err := redirStdout(func() { spew.Print(test.in) })
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%v #%d %v", test.f, i, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
buf.Write(b)
|
|
||||||
|
|
||||||
case fPrintln:
|
|
||||||
b, err := redirStdout(func() { spew.Println(test.in) })
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%v #%d %v", test.f, i, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
buf.Write(b)
|
|
||||||
|
|
||||||
case fSdump:
|
|
||||||
str := spew.Sdump(test.in)
|
|
||||||
buf.WriteString(str)
|
|
||||||
|
|
||||||
case fSprint:
|
|
||||||
str := spew.Sprint(test.in)
|
|
||||||
buf.WriteString(str)
|
|
||||||
|
|
||||||
case fSprintf:
|
|
||||||
str := spew.Sprintf(test.format, test.in)
|
|
||||||
buf.WriteString(str)
|
|
||||||
|
|
||||||
case fSprintln:
|
|
||||||
str := spew.Sprintln(test.in)
|
|
||||||
buf.WriteString(str)
|
|
||||||
|
|
||||||
default:
|
|
||||||
t.Errorf("%v #%d unrecognized function", test.f, i)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
s := buf.String()
|
|
||||||
if test.want != s {
|
|
||||||
t.Errorf("ConfigState #%d\n got: %s want: %s", i, s, test.want)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,82 +0,0 @@
|
||||||
// Copyright (c) 2013 Dave Collins <dave@davec.name>
|
|
||||||
//
|
|
||||||
// Permission to use, copy, modify, and distribute this software for any
|
|
||||||
// purpose with or without fee is hereby granted, provided that the above
|
|
||||||
// copyright notice and this permission notice appear in all copies.
|
|
||||||
//
|
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
||||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
||||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
||||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
|
|
||||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
|
||||||
// when both cgo is supported and "-tags testcgo" is added to the go test
|
|
||||||
// command line. This code should really only be in the dumpcgo_test.go file,
|
|
||||||
// but unfortunately Go will not allow cgo in test files, so this is a
|
|
||||||
// workaround to allow cgo types to be tested. This configuration is used
|
|
||||||
// because spew itself does not require cgo to run even though it does handle
|
|
||||||
// certain cgo types specially. Rather than forcing all clients to require cgo
|
|
||||||
// and an external C compiler just to run the tests, this scheme makes them
|
|
||||||
// optional.
|
|
||||||
// +build cgo,testcgo
|
|
||||||
|
|
||||||
package testdata
|
|
||||||
|
|
||||||
/*
|
|
||||||
#include <stdint.h>
|
|
||||||
typedef unsigned char custom_uchar_t;
|
|
||||||
|
|
||||||
char *ncp = 0;
|
|
||||||
char *cp = "test";
|
|
||||||
char ca[6] = {'t', 'e', 's', 't', '2', '\0'};
|
|
||||||
unsigned char uca[6] = {'t', 'e', 's', 't', '3', '\0'};
|
|
||||||
signed char sca[6] = {'t', 'e', 's', 't', '4', '\0'};
|
|
||||||
uint8_t ui8ta[6] = {'t', 'e', 's', 't', '5', '\0'};
|
|
||||||
custom_uchar_t tuca[6] = {'t', 'e', 's', 't', '6', '\0'};
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
// GetCgoNullCharPointer returns a null char pointer via cgo. This is only
|
|
||||||
// used for tests.
|
|
||||||
func GetCgoNullCharPointer() interface{} {
|
|
||||||
return C.ncp
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCgoCharPointer returns a char pointer via cgo. This is only used for
|
|
||||||
// tests.
|
|
||||||
func GetCgoCharPointer() interface{} {
|
|
||||||
return C.cp
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCgoCharArray returns a char array via cgo and the array's len and cap.
|
|
||||||
// This is only used for tests.
|
|
||||||
func GetCgoCharArray() (interface{}, int, int) {
|
|
||||||
return C.ca, len(C.ca), cap(C.ca)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCgoUnsignedCharArray returns an unsigned char array via cgo and the
|
|
||||||
// array's len and cap. This is only used for tests.
|
|
||||||
func GetCgoUnsignedCharArray() (interface{}, int, int) {
|
|
||||||
return C.uca, len(C.uca), cap(C.uca)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCgoSignedCharArray returns a signed char array via cgo and the array's len
|
|
||||||
// and cap. This is only used for tests.
|
|
||||||
func GetCgoSignedCharArray() (interface{}, int, int) {
|
|
||||||
return C.sca, len(C.sca), cap(C.sca)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCgoUint8tArray returns a uint8_t array via cgo and the array's len and
|
|
||||||
// cap. This is only used for tests.
|
|
||||||
func GetCgoUint8tArray() (interface{}, int, int) {
|
|
||||||
return C.ui8ta, len(C.ui8ta), cap(C.ui8ta)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCgoTypdefedUnsignedCharArray returns a typedefed unsigned char array via
|
|
||||||
// cgo and the array's len and cap. This is only used for tests.
|
|
||||||
func GetCgoTypdefedUnsignedCharArray() (interface{}, int, int) {
|
|
||||||
return C.tuca, len(C.tuca), cap(C.tuca)
|
|
||||||
}
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2016 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -1,67 +0,0 @@
|
||||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package agent
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestListen(t *testing.T) {
|
|
||||||
err := Listen(nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAgentClose(t *testing.T) {
|
|
||||||
err := Listen(nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
Close()
|
|
||||||
_, err = os.Stat(portfile)
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
t.Fatalf("portfile = %q doesn't exist; err = %v", portfile, err)
|
|
||||||
}
|
|
||||||
if portfile != "" {
|
|
||||||
t.Fatalf("got = %q; want empty portfile", portfile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAgentListenMultipleClose(t *testing.T) {
|
|
||||||
err := Listen(nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
Close()
|
|
||||||
Close()
|
|
||||||
Close()
|
|
||||||
Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFormatBytes(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
val uint64
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{1023, "1023 bytes"},
|
|
||||||
{1024, "1.00KB (1024 bytes)"},
|
|
||||||
{1024*1024 - 100, "1023.90KB (1048476 bytes)"},
|
|
||||||
{1024 * 1024, "1.00MB (1048576 bytes)"},
|
|
||||||
{1024 * 1025, "1.00MB (1049600 bytes)"},
|
|
||||||
{1024 * 1024 * 1024, "1.00GB (1073741824 bytes)"},
|
|
||||||
{1024*1024*1024 + 430*1024*1024, "1.42GB (1524629504 bytes)"},
|
|
||||||
{1024 * 1024 * 1024 * 1024 * 1024, "1.00PB (1125899906842624 bytes)"},
|
|
||||||
{1024 * 1024 * 1024 * 1024 * 1024 * 1024, "1024.00PB (1152921504606846976 bytes)"},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
result := formatBytes(tt.val)
|
|
||||||
if result != tt.want {
|
|
||||||
t.Errorf("formatBytes(%v) = %q; want %q", tt.val, result, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,254 +0,0 @@
|
||||||
package feeds
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var atomOutput = `<?xml version="1.0" encoding="UTF-8"?><feed xmlns="http://www.w3.org/2005/Atom">
|
|
||||||
<title>jmoiron.net blog</title>
|
|
||||||
<id>http://jmoiron.net/blog</id>
|
|
||||||
<updated>2013-01-16T21:52:35-05:00</updated>
|
|
||||||
<rights>This work is copyright © Benjamin Button</rights>
|
|
||||||
<subtitle>discussion about tech, footie, photos</subtitle>
|
|
||||||
<link href="http://jmoiron.net/blog"></link>
|
|
||||||
<author>
|
|
||||||
<name>Jason Moiron</name>
|
|
||||||
<email>jmoiron@jmoiron.net</email>
|
|
||||||
</author>
|
|
||||||
<entry>
|
|
||||||
<title>Limiting Concurrency in Go</title>
|
|
||||||
<updated>2013-01-16T21:52:35-05:00</updated>
|
|
||||||
<id>tag:jmoiron.net,2013-01-16:/blog/limiting-concurrency-in-go/</id>
|
|
||||||
<content type="html">A discussion on controlled parallelism in golang</content>
|
|
||||||
<link href="http://jmoiron.net/blog/limiting-concurrency-in-go/" rel="alternate"></link>
|
|
||||||
<author>
|
|
||||||
<name>Jason Moiron</name>
|
|
||||||
<email>jmoiron@jmoiron.net</email>
|
|
||||||
</author>
|
|
||||||
</entry>
|
|
||||||
<entry>
|
|
||||||
<title>Logic-less Template Redux</title>
|
|
||||||
<updated>2013-01-16T21:52:35-05:00</updated>
|
|
||||||
<id>tag:jmoiron.net,2013-01-16:/blog/logicless-template-redux/</id>
|
|
||||||
<content type="html">More thoughts on logicless templates</content>
|
|
||||||
<link href="http://jmoiron.net/blog/logicless-template-redux/" rel="alternate"></link>
|
|
||||||
</entry>
|
|
||||||
<entry>
|
|
||||||
<title>Idiomatic Code Reuse in Go</title>
|
|
||||||
<updated>2013-01-16T21:52:35-05:00</updated>
|
|
||||||
<id>tag:jmoiron.net,2013-01-16:/blog/idiomatic-code-reuse-in-go/</id>
|
|
||||||
<content type="html">How to use interfaces <em>effectively</em></content>
|
|
||||||
<link href="http://jmoiron.net/blog/idiomatic-code-reuse-in-go/" rel="alternate"></link>
|
|
||||||
<link href="http://example.com/cover.jpg" rel="enclosure" type="image/jpg" length="123456"></link>
|
|
||||||
</entry>
|
|
||||||
<entry>
|
|
||||||
<title>Never Gonna Give You Up Mp3</title>
|
|
||||||
<updated>2013-01-16T21:52:35-05:00</updated>
|
|
||||||
<id>tag:example.com,2013-01-16:/RickRoll.mp3</id>
|
|
||||||
<content type="html">Never gonna give you up - Never gonna let you down.</content>
|
|
||||||
<link href="http://example.com/RickRoll.mp3" rel="alternate"></link>
|
|
||||||
<link href="http://example.com/RickRoll.mp3" rel="enclosure" type="audio/mpeg" length="123456"></link>
|
|
||||||
</entry>
|
|
||||||
<entry>
|
|
||||||
<title>String formatting in Go</title>
|
|
||||||
<updated>2013-01-16T21:52:35-05:00</updated>
|
|
||||||
<id>tag:example.com,2013-01-16:/strings</id>
|
|
||||||
<content type="html">How to use things like %s, %v, %d, etc.</content>
|
|
||||||
<link href="http://example.com/strings" rel="alternate"></link>
|
|
||||||
</entry>
|
|
||||||
</feed>`
|
|
||||||
|
|
||||||
var rssOutput = `<?xml version="1.0" encoding="UTF-8"?><rss version="2.0">
|
|
||||||
<channel>
|
|
||||||
<title>jmoiron.net blog</title>
|
|
||||||
<link>http://jmoiron.net/blog</link>
|
|
||||||
<description>discussion about tech, footie, photos</description>
|
|
||||||
<copyright>This work is copyright © Benjamin Button</copyright>
|
|
||||||
<managingEditor>jmoiron@jmoiron.net (Jason Moiron)</managingEditor>
|
|
||||||
<pubDate>Wed, 16 Jan 2013 21:52:35 -0500</pubDate>
|
|
||||||
<item>
|
|
||||||
<title>Limiting Concurrency in Go</title>
|
|
||||||
<link>http://jmoiron.net/blog/limiting-concurrency-in-go/</link>
|
|
||||||
<description>A discussion on controlled parallelism in golang</description>
|
|
||||||
<author>Jason Moiron</author>
|
|
||||||
<pubDate>Wed, 16 Jan 2013 21:52:35 -0500</pubDate>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<title>Logic-less Template Redux</title>
|
|
||||||
<link>http://jmoiron.net/blog/logicless-template-redux/</link>
|
|
||||||
<description>More thoughts on logicless templates</description>
|
|
||||||
<pubDate>Wed, 16 Jan 2013 21:52:35 -0500</pubDate>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<title>Idiomatic Code Reuse in Go</title>
|
|
||||||
<link>http://jmoiron.net/blog/idiomatic-code-reuse-in-go/</link>
|
|
||||||
<description>How to use interfaces <em>effectively</em></description>
|
|
||||||
<enclosure url="http://example.com/cover.jpg" length="123456" type="image/jpg"></enclosure>
|
|
||||||
<pubDate>Wed, 16 Jan 2013 21:52:35 -0500</pubDate>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<title>Never Gonna Give You Up Mp3</title>
|
|
||||||
<link>http://example.com/RickRoll.mp3</link>
|
|
||||||
<description>Never gonna give you up - Never gonna let you down.</description>
|
|
||||||
<enclosure url="http://example.com/RickRoll.mp3" length="123456" type="audio/mpeg"></enclosure>
|
|
||||||
<pubDate>Wed, 16 Jan 2013 21:52:35 -0500</pubDate>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<title>String formatting in Go</title>
|
|
||||||
<link>http://example.com/strings</link>
|
|
||||||
<description>How to use things like %s, %v, %d, etc.</description>
|
|
||||||
<pubDate>Wed, 16 Jan 2013 21:52:35 -0500</pubDate>
|
|
||||||
</item>
|
|
||||||
</channel>
|
|
||||||
</rss>`
|
|
||||||
|
|
||||||
var jsonOutput = `{
|
|
||||||
"version": "https://jsonfeed.org/version/1",
|
|
||||||
"title": "jmoiron.net blog",
|
|
||||||
"home_page_url": "http://jmoiron.net/blog",
|
|
||||||
"description": "discussion about tech, footie, photos",
|
|
||||||
"author": {
|
|
||||||
"name": "Jason Moiron"
|
|
||||||
},
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"id": "",
|
|
||||||
"url": "http://jmoiron.net/blog/limiting-concurrency-in-go/",
|
|
||||||
"title": "Limiting Concurrency in Go",
|
|
||||||
"summary": "A discussion on controlled parallelism in golang",
|
|
||||||
"date_published": "2013-01-16T21:52:35-05:00",
|
|
||||||
"author": {
|
|
||||||
"name": "Jason Moiron"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "",
|
|
||||||
"url": "http://jmoiron.net/blog/logicless-template-redux/",
|
|
||||||
"title": "Logic-less Template Redux",
|
|
||||||
"summary": "More thoughts on logicless templates",
|
|
||||||
"date_published": "2013-01-16T21:52:35-05:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "",
|
|
||||||
"url": "http://jmoiron.net/blog/idiomatic-code-reuse-in-go/",
|
|
||||||
"title": "Idiomatic Code Reuse in Go",
|
|
||||||
"summary": "How to use interfaces \u003cem\u003eeffectively\u003c/em\u003e",
|
|
||||||
"image": "http://example.com/cover.jpg",
|
|
||||||
"date_published": "2013-01-16T21:52:35-05:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "",
|
|
||||||
"url": "http://example.com/RickRoll.mp3",
|
|
||||||
"title": "Never Gonna Give You Up Mp3",
|
|
||||||
"summary": "Never gonna give you up - Never gonna let you down.",
|
|
||||||
"date_published": "2013-01-16T21:52:35-05:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "",
|
|
||||||
"url": "http://example.com/strings",
|
|
||||||
"title": "String formatting in Go",
|
|
||||||
"summary": "How to use things like %s, %v, %d, etc.",
|
|
||||||
"date_published": "2013-01-16T21:52:35-05:00"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}`
|
|
||||||
|
|
||||||
func TestFeed(t *testing.T) {
|
|
||||||
now, err := time.Parse(time.RFC3339, "2013-01-16T21:52:35-05:00")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
tz := time.FixedZone("EST", -5*60*60)
|
|
||||||
now = now.In(tz)
|
|
||||||
|
|
||||||
feed := &Feed{
|
|
||||||
Title: "jmoiron.net blog",
|
|
||||||
Link: &Link{Href: "http://jmoiron.net/blog"},
|
|
||||||
Description: "discussion about tech, footie, photos",
|
|
||||||
Author: &Author{Name: "Jason Moiron", Email: "jmoiron@jmoiron.net"},
|
|
||||||
Created: now,
|
|
||||||
Copyright: "This work is copyright © Benjamin Button",
|
|
||||||
}
|
|
||||||
|
|
||||||
feed.Items = []*Item{
|
|
||||||
{
|
|
||||||
Title: "Limiting Concurrency in Go",
|
|
||||||
Link: &Link{Href: "http://jmoiron.net/blog/limiting-concurrency-in-go/"},
|
|
||||||
Description: "A discussion on controlled parallelism in golang",
|
|
||||||
Author: &Author{Name: "Jason Moiron", Email: "jmoiron@jmoiron.net"},
|
|
||||||
Created: now,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Title: "Logic-less Template Redux",
|
|
||||||
Link: &Link{Href: "http://jmoiron.net/blog/logicless-template-redux/"},
|
|
||||||
Description: "More thoughts on logicless templates",
|
|
||||||
Created: now,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Title: "Idiomatic Code Reuse in Go",
|
|
||||||
Link: &Link{Href: "http://jmoiron.net/blog/idiomatic-code-reuse-in-go/"},
|
|
||||||
Description: "How to use interfaces <em>effectively</em>",
|
|
||||||
Enclosure: &Enclosure{Url: "http://example.com/cover.jpg", Length: "123456", Type: "image/jpg"},
|
|
||||||
Created: now,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Title: "Never Gonna Give You Up Mp3",
|
|
||||||
Link: &Link{Href: "http://example.com/RickRoll.mp3"},
|
|
||||||
Enclosure: &Enclosure{Url: "http://example.com/RickRoll.mp3", Length: "123456", Type: "audio/mpeg"},
|
|
||||||
Description: "Never gonna give you up - Never gonna let you down.",
|
|
||||||
Created: now,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Title: "String formatting in Go",
|
|
||||||
Link: &Link{Href: "http://example.com/strings"},
|
|
||||||
Description: "How to use things like %s, %v, %d, etc.",
|
|
||||||
Created: now,
|
|
||||||
}}
|
|
||||||
|
|
||||||
atom, err := feed.ToAtom()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error encoding Atom: %v", err)
|
|
||||||
}
|
|
||||||
if atom != atomOutput {
|
|
||||||
t.Errorf("Atom not what was expected. Got:\n%s\n\nExpected:\n%s\n", atom, atomOutput)
|
|
||||||
}
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if err := feed.WriteAtom(&buf); err != nil {
|
|
||||||
t.Errorf("unexpected error writing Atom: %v", err)
|
|
||||||
}
|
|
||||||
if got := buf.String(); got != atomOutput {
|
|
||||||
t.Errorf("Atom not what was expected. Got:\n%s\n\nExpected:\n%s\n", got, atomOutput)
|
|
||||||
}
|
|
||||||
|
|
||||||
rss, err := feed.ToRss()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error encoding RSS: %v", err)
|
|
||||||
}
|
|
||||||
if rss != rssOutput {
|
|
||||||
t.Errorf("Rss not what was expected. Got:\n%s\n\nExpected:\n%s\n", rss, rssOutput)
|
|
||||||
}
|
|
||||||
buf.Reset()
|
|
||||||
if err := feed.WriteRss(&buf); err != nil {
|
|
||||||
t.Errorf("unexpected error writing RSS: %v", err)
|
|
||||||
}
|
|
||||||
if got := buf.String(); got != rssOutput {
|
|
||||||
t.Errorf("Rss not what was expected. Got:\n%s\n\nExpected:\n%s\n", got, rssOutput)
|
|
||||||
}
|
|
||||||
|
|
||||||
json, err := feed.ToJSON()
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error encoding JSON: %v", err)
|
|
||||||
}
|
|
||||||
if json != jsonOutput {
|
|
||||||
t.Errorf("JSON not what was expected. Got:\n%s\n\nExpected:\n%s\n", json, jsonOutput)
|
|
||||||
}
|
|
||||||
buf.Reset()
|
|
||||||
if err := feed.WriteJSON(&buf); err != nil {
|
|
||||||
t.Errorf("unexpected error writing JSON: %v", err)
|
|
||||||
}
|
|
||||||
if got := buf.String(); got != jsonOutput+"\n" { //json.Encode appends a newline after the JSON output: https://github.com/golang/go/commit/6f25f1d4c901417af1da65e41992d71c30f64f8f#diff-50848cbd686f250623a2ef6ddb07e157
|
|
||||||
t.Errorf("JSON not what was expected. Got:\n||%s||\n\nExpected:\n||%s||\n", got, jsonOutput)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
package feeds
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUUID(t *testing.T) {
|
|
||||||
s := NewUUID()
|
|
||||||
s2 := NewUUID()
|
|
||||||
if len(s) != 16 {
|
|
||||||
t.Errorf("Expecting len of 16, got %d\n", len(s))
|
|
||||||
}
|
|
||||||
if len(s.String()) != 36 {
|
|
||||||
t.Errorf("Expecting uuid hex string len of 36, got %d\n", len(s.String()))
|
|
||||||
}
|
|
||||||
if s == s2 {
|
|
||||||
t.Errorf("Expecting different UUIDs to be different, but they are the same.\n")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,203 +0,0 @@
|
||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build darwin linux freebsd netbsd windows openbsd
|
|
||||||
|
|
||||||
package osext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
executableEnvVar = "OSTEST_OUTPUT_EXECUTABLE"
|
|
||||||
|
|
||||||
executableEnvValueMatch = "match"
|
|
||||||
executableEnvValueDelete = "delete"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPrintExecutable(t *testing.T) {
|
|
||||||
ef, err := Executable()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Executable failed: %v", err)
|
|
||||||
}
|
|
||||||
t.Log("Executable:", ef)
|
|
||||||
}
|
|
||||||
func TestPrintExecutableFolder(t *testing.T) {
|
|
||||||
ef, err := ExecutableFolder()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("ExecutableFolder failed: %v", err)
|
|
||||||
}
|
|
||||||
t.Log("Executable Folder:", ef)
|
|
||||||
}
|
|
||||||
func TestExecutableFolder(t *testing.T) {
|
|
||||||
ef, err := ExecutableFolder()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("ExecutableFolder failed: %v", err)
|
|
||||||
}
|
|
||||||
if ef[len(ef)-1] == filepath.Separator {
|
|
||||||
t.Fatal("ExecutableFolder ends with a trailing slash.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func TestExecutableMatch(t *testing.T) {
|
|
||||||
ep, err := Executable()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Executable failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// fullpath to be of the form "dir/prog".
|
|
||||||
dir := filepath.Dir(filepath.Dir(ep))
|
|
||||||
fullpath, err := filepath.Rel(dir, ep)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("filepath.Rel: %v", err)
|
|
||||||
}
|
|
||||||
// Make child start with a relative program path.
|
|
||||||
// Alter argv[0] for child to verify getting real path without argv[0].
|
|
||||||
cmd := &exec.Cmd{
|
|
||||||
Dir: dir,
|
|
||||||
Path: fullpath,
|
|
||||||
Env: []string{fmt.Sprintf("%s=%s", executableEnvVar, executableEnvValueMatch)},
|
|
||||||
}
|
|
||||||
out, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("exec(self) failed: %v", err)
|
|
||||||
}
|
|
||||||
outs := string(out)
|
|
||||||
if !filepath.IsAbs(outs) {
|
|
||||||
t.Fatalf("Child returned %q, want an absolute path", out)
|
|
||||||
}
|
|
||||||
if !sameFile(outs, ep) {
|
|
||||||
t.Fatalf("Child returned %q, not the same file as %q", out, ep)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExecutableDelete(t *testing.T) {
|
|
||||||
if runtime.GOOS != "linux" {
|
|
||||||
t.Skip()
|
|
||||||
}
|
|
||||||
fpath, err := Executable()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Executable failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r, w := io.Pipe()
|
|
||||||
stderrBuff := &bytes.Buffer{}
|
|
||||||
stdoutBuff := &bytes.Buffer{}
|
|
||||||
cmd := &exec.Cmd{
|
|
||||||
Path: fpath,
|
|
||||||
Env: []string{fmt.Sprintf("%s=%s", executableEnvVar, executableEnvValueDelete)},
|
|
||||||
Stdin: r,
|
|
||||||
Stderr: stderrBuff,
|
|
||||||
Stdout: stdoutBuff,
|
|
||||||
}
|
|
||||||
err = cmd.Start()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("exec(self) start failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tempPath := fpath + "_copy"
|
|
||||||
_ = os.Remove(tempPath)
|
|
||||||
|
|
||||||
err = copyFile(tempPath, fpath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("copy file failed: %v", err)
|
|
||||||
}
|
|
||||||
err = os.Remove(fpath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("remove running test file failed: %v", err)
|
|
||||||
}
|
|
||||||
err = os.Rename(tempPath, fpath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("rename copy to previous name failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write([]byte{0})
|
|
||||||
w.Close()
|
|
||||||
|
|
||||||
err = cmd.Wait()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("exec wait failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
childPath := stderrBuff.String()
|
|
||||||
if !filepath.IsAbs(childPath) {
|
|
||||||
t.Fatalf("Child returned %q, want an absolute path", childPath)
|
|
||||||
}
|
|
||||||
if !sameFile(childPath, fpath) {
|
|
||||||
t.Fatalf("Child returned %q, not the same file as %q", childPath, fpath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sameFile(fn1, fn2 string) bool {
|
|
||||||
fi1, err := os.Stat(fn1)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
fi2, err := os.Stat(fn2)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return os.SameFile(fi1, fi2)
|
|
||||||
}
|
|
||||||
func copyFile(dest, src string) error {
|
|
||||||
df, err := os.Create(dest)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer df.Close()
|
|
||||||
|
|
||||||
sf, err := os.Open(src)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer sf.Close()
|
|
||||||
|
|
||||||
_, err = io.Copy(df, sf)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
|
||||||
env := os.Getenv(executableEnvVar)
|
|
||||||
switch env {
|
|
||||||
case "":
|
|
||||||
os.Exit(m.Run())
|
|
||||||
case executableEnvValueMatch:
|
|
||||||
// First chdir to another path.
|
|
||||||
dir := "/"
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
dir = filepath.VolumeName(".")
|
|
||||||
}
|
|
||||||
os.Chdir(dir)
|
|
||||||
if ep, err := Executable(); err != nil {
|
|
||||||
fmt.Fprint(os.Stderr, "ERROR: ", err)
|
|
||||||
} else {
|
|
||||||
fmt.Fprint(os.Stderr, ep)
|
|
||||||
}
|
|
||||||
case executableEnvValueDelete:
|
|
||||||
bb := make([]byte, 1)
|
|
||||||
var err error
|
|
||||||
n, err := os.Stdin.Read(bb)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprint(os.Stderr, "ERROR: ", err)
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
if n != 1 {
|
|
||||||
fmt.Fprint(os.Stderr, "ERROR: n != 1, n == ", n)
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
if ep, err := Executable(); err != nil {
|
|
||||||
fmt.Fprint(os.Stderr, "ERROR: ", err)
|
|
||||||
} else {
|
|
||||||
fmt.Fprint(os.Stderr, ep)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
[568].out
|
|
||||||
_go*
|
|
||||||
_test*
|
|
||||||
_obj
|
|
|
@ -1,9 +0,0 @@
|
||||||
package pretty
|
|
||||||
|
|
||||||
import "github.com/kr/pretty"
|
|
||||||
|
|
||||||
Package pretty provides pretty-printing for Go values.
|
|
||||||
|
|
||||||
Documentation
|
|
||||||
|
|
||||||
http://godoc.org/github.com/kr/pretty
|
|
|
@ -1,265 +0,0 @@
|
||||||
package pretty
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"reflect"
|
|
||||||
)
|
|
||||||
|
|
||||||
type sbuf []string
|
|
||||||
|
|
||||||
func (p *sbuf) Printf(format string, a ...interface{}) {
|
|
||||||
s := fmt.Sprintf(format, a...)
|
|
||||||
*p = append(*p, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Diff returns a slice where each element describes
|
|
||||||
// a difference between a and b.
|
|
||||||
func Diff(a, b interface{}) (desc []string) {
|
|
||||||
Pdiff((*sbuf)(&desc), a, b)
|
|
||||||
return desc
|
|
||||||
}
|
|
||||||
|
|
||||||
// wprintfer calls Fprintf on w for each Printf call
|
|
||||||
// with a trailing newline.
|
|
||||||
type wprintfer struct{ w io.Writer }
|
|
||||||
|
|
||||||
func (p *wprintfer) Printf(format string, a ...interface{}) {
|
|
||||||
fmt.Fprintf(p.w, format+"\n", a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fdiff writes to w a description of the differences between a and b.
|
|
||||||
func Fdiff(w io.Writer, a, b interface{}) {
|
|
||||||
Pdiff(&wprintfer{w}, a, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Printfer interface {
|
|
||||||
Printf(format string, a ...interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pdiff prints to p a description of the differences between a and b.
|
|
||||||
// It calls Printf once for each difference, with no trailing newline.
|
|
||||||
// The standard library log.Logger is a Printfer.
|
|
||||||
func Pdiff(p Printfer, a, b interface{}) {
|
|
||||||
diffPrinter{w: p}.diff(reflect.ValueOf(a), reflect.ValueOf(b))
|
|
||||||
}
|
|
||||||
|
|
||||||
type Logfer interface {
|
|
||||||
Logf(format string, a ...interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// logprintfer calls Fprintf on w for each Printf call
|
|
||||||
// with a trailing newline.
|
|
||||||
type logprintfer struct{ l Logfer }
|
|
||||||
|
|
||||||
func (p *logprintfer) Printf(format string, a ...interface{}) {
|
|
||||||
p.l.Logf(format, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ldiff prints to l a description of the differences between a and b.
|
|
||||||
// It calls Logf once for each difference, with no trailing newline.
|
|
||||||
// The standard library testing.T and testing.B are Logfers.
|
|
||||||
func Ldiff(l Logfer, a, b interface{}) {
|
|
||||||
Pdiff(&logprintfer{l}, a, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
type diffPrinter struct {
|
|
||||||
w Printfer
|
|
||||||
l string // label
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w diffPrinter) printf(f string, a ...interface{}) {
|
|
||||||
var l string
|
|
||||||
if w.l != "" {
|
|
||||||
l = w.l + ": "
|
|
||||||
}
|
|
||||||
w.w.Printf(l+f, a...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w diffPrinter) diff(av, bv reflect.Value) {
|
|
||||||
if !av.IsValid() && bv.IsValid() {
|
|
||||||
w.printf("nil != %# v", formatter{v: bv, quote: true})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if av.IsValid() && !bv.IsValid() {
|
|
||||||
w.printf("%# v != nil", formatter{v: av, quote: true})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !av.IsValid() && !bv.IsValid() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
at := av.Type()
|
|
||||||
bt := bv.Type()
|
|
||||||
if at != bt {
|
|
||||||
w.printf("%v != %v", at, bt)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch kind := at.Kind(); kind {
|
|
||||||
case reflect.Bool:
|
|
||||||
if a, b := av.Bool(), bv.Bool(); a != b {
|
|
||||||
w.printf("%v != %v", a, b)
|
|
||||||
}
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
if a, b := av.Int(), bv.Int(); a != b {
|
|
||||||
w.printf("%d != %d", a, b)
|
|
||||||
}
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
||||||
if a, b := av.Uint(), bv.Uint(); a != b {
|
|
||||||
w.printf("%d != %d", a, b)
|
|
||||||
}
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
if a, b := av.Float(), bv.Float(); a != b {
|
|
||||||
w.printf("%v != %v", a, b)
|
|
||||||
}
|
|
||||||
case reflect.Complex64, reflect.Complex128:
|
|
||||||
if a, b := av.Complex(), bv.Complex(); a != b {
|
|
||||||
w.printf("%v != %v", a, b)
|
|
||||||
}
|
|
||||||
case reflect.Array:
|
|
||||||
n := av.Len()
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
w.relabel(fmt.Sprintf("[%d]", i)).diff(av.Index(i), bv.Index(i))
|
|
||||||
}
|
|
||||||
case reflect.Chan, reflect.Func, reflect.UnsafePointer:
|
|
||||||
if a, b := av.Pointer(), bv.Pointer(); a != b {
|
|
||||||
w.printf("%#x != %#x", a, b)
|
|
||||||
}
|
|
||||||
case reflect.Interface:
|
|
||||||
w.diff(av.Elem(), bv.Elem())
|
|
||||||
case reflect.Map:
|
|
||||||
ak, both, bk := keyDiff(av.MapKeys(), bv.MapKeys())
|
|
||||||
for _, k := range ak {
|
|
||||||
w := w.relabel(fmt.Sprintf("[%#v]", k))
|
|
||||||
w.printf("%q != (missing)", av.MapIndex(k))
|
|
||||||
}
|
|
||||||
for _, k := range both {
|
|
||||||
w := w.relabel(fmt.Sprintf("[%#v]", k))
|
|
||||||
w.diff(av.MapIndex(k), bv.MapIndex(k))
|
|
||||||
}
|
|
||||||
for _, k := range bk {
|
|
||||||
w := w.relabel(fmt.Sprintf("[%#v]", k))
|
|
||||||
w.printf("(missing) != %q", bv.MapIndex(k))
|
|
||||||
}
|
|
||||||
case reflect.Ptr:
|
|
||||||
switch {
|
|
||||||
case av.IsNil() && !bv.IsNil():
|
|
||||||
w.printf("nil != %# v", formatter{v: bv, quote: true})
|
|
||||||
case !av.IsNil() && bv.IsNil():
|
|
||||||
w.printf("%# v != nil", formatter{v: av, quote: true})
|
|
||||||
case !av.IsNil() && !bv.IsNil():
|
|
||||||
w.diff(av.Elem(), bv.Elem())
|
|
||||||
}
|
|
||||||
case reflect.Slice:
|
|
||||||
lenA := av.Len()
|
|
||||||
lenB := bv.Len()
|
|
||||||
if lenA != lenB {
|
|
||||||
w.printf("%s[%d] != %s[%d]", av.Type(), lenA, bv.Type(), lenB)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
for i := 0; i < lenA; i++ {
|
|
||||||
w.relabel(fmt.Sprintf("[%d]", i)).diff(av.Index(i), bv.Index(i))
|
|
||||||
}
|
|
||||||
case reflect.String:
|
|
||||||
if a, b := av.String(), bv.String(); a != b {
|
|
||||||
w.printf("%q != %q", a, b)
|
|
||||||
}
|
|
||||||
case reflect.Struct:
|
|
||||||
for i := 0; i < av.NumField(); i++ {
|
|
||||||
w.relabel(at.Field(i).Name).diff(av.Field(i), bv.Field(i))
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
panic("unknown reflect Kind: " + kind.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d diffPrinter) relabel(name string) (d1 diffPrinter) {
|
|
||||||
d1 = d
|
|
||||||
if d.l != "" && name[0] != '[' {
|
|
||||||
d1.l += "."
|
|
||||||
}
|
|
||||||
d1.l += name
|
|
||||||
return d1
|
|
||||||
}
|
|
||||||
|
|
||||||
// keyEqual compares a and b for equality.
|
|
||||||
// Both a and b must be valid map keys.
|
|
||||||
func keyEqual(av, bv reflect.Value) bool {
|
|
||||||
if !av.IsValid() && !bv.IsValid() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if !av.IsValid() || !bv.IsValid() || av.Type() != bv.Type() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
switch kind := av.Kind(); kind {
|
|
||||||
case reflect.Bool:
|
|
||||||
a, b := av.Bool(), bv.Bool()
|
|
||||||
return a == b
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
a, b := av.Int(), bv.Int()
|
|
||||||
return a == b
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
||||||
a, b := av.Uint(), bv.Uint()
|
|
||||||
return a == b
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
a, b := av.Float(), bv.Float()
|
|
||||||
return a == b
|
|
||||||
case reflect.Complex64, reflect.Complex128:
|
|
||||||
a, b := av.Complex(), bv.Complex()
|
|
||||||
return a == b
|
|
||||||
case reflect.Array:
|
|
||||||
for i := 0; i < av.Len(); i++ {
|
|
||||||
if !keyEqual(av.Index(i), bv.Index(i)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
case reflect.Chan, reflect.UnsafePointer, reflect.Ptr:
|
|
||||||
a, b := av.Pointer(), bv.Pointer()
|
|
||||||
return a == b
|
|
||||||
case reflect.Interface:
|
|
||||||
return keyEqual(av.Elem(), bv.Elem())
|
|
||||||
case reflect.String:
|
|
||||||
a, b := av.String(), bv.String()
|
|
||||||
return a == b
|
|
||||||
case reflect.Struct:
|
|
||||||
for i := 0; i < av.NumField(); i++ {
|
|
||||||
if !keyEqual(av.Field(i), bv.Field(i)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
panic("invalid map key type " + av.Type().String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func keyDiff(a, b []reflect.Value) (ak, both, bk []reflect.Value) {
|
|
||||||
for _, av := range a {
|
|
||||||
inBoth := false
|
|
||||||
for _, bv := range b {
|
|
||||||
if keyEqual(av, bv) {
|
|
||||||
inBoth = true
|
|
||||||
both = append(both, av)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !inBoth {
|
|
||||||
ak = append(ak, av)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, bv := range b {
|
|
||||||
inBoth := false
|
|
||||||
for _, av := range a {
|
|
||||||
if keyEqual(av, bv) {
|
|
||||||
inBoth = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !inBoth {
|
|
||||||
bk = append(bk, bv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -1,213 +0,0 @@
|
||||||
package pretty
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ Logfer = (*testing.T)(nil)
|
|
||||||
_ Logfer = (*testing.B)(nil)
|
|
||||||
_ Printfer = (*log.Logger)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
type difftest struct {
|
|
||||||
a interface{}
|
|
||||||
b interface{}
|
|
||||||
exp []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type S struct {
|
|
||||||
A int
|
|
||||||
S *S
|
|
||||||
I interface{}
|
|
||||||
C []int
|
|
||||||
}
|
|
||||||
|
|
||||||
type (
|
|
||||||
N struct{ N int }
|
|
||||||
E interface{}
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
c0 = make(chan int)
|
|
||||||
c1 = make(chan int)
|
|
||||||
f0 = func() {}
|
|
||||||
f1 = func() {}
|
|
||||||
i0 = 0
|
|
||||||
i1 = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
var diffs = []difftest{
|
|
||||||
{a: nil, b: nil},
|
|
||||||
{a: S{A: 1}, b: S{A: 1}},
|
|
||||||
|
|
||||||
{0, "", []string{`int != string`}},
|
|
||||||
{0, 1, []string{`0 != 1`}},
|
|
||||||
{S{}, new(S), []string{`pretty.S != *pretty.S`}},
|
|
||||||
{"a", "b", []string{`"a" != "b"`}},
|
|
||||||
{S{}, S{A: 1}, []string{`A: 0 != 1`}},
|
|
||||||
{new(S), &S{A: 1}, []string{`A: 0 != 1`}},
|
|
||||||
{S{S: new(S)}, S{S: &S{A: 1}}, []string{`S.A: 0 != 1`}},
|
|
||||||
{S{}, S{I: 0}, []string{`I: nil != int(0)`}},
|
|
||||||
{S{I: 1}, S{I: "x"}, []string{`I: int != string`}},
|
|
||||||
{S{}, S{C: []int{1}}, []string{`C: []int[0] != []int[1]`}},
|
|
||||||
{S{C: []int{}}, S{C: []int{1}}, []string{`C: []int[0] != []int[1]`}},
|
|
||||||
{S{C: []int{1, 2, 3}}, S{C: []int{1, 2, 4}}, []string{`C[2]: 3 != 4`}},
|
|
||||||
{S{}, S{A: 1, S: new(S)}, []string{`A: 0 != 1`, `S: nil != &pretty.S{}`}},
|
|
||||||
|
|
||||||
// unexported fields of every reflect.Kind (both equal and unequal)
|
|
||||||
{struct{ x bool }{false}, struct{ x bool }{false}, nil},
|
|
||||||
{struct{ x bool }{false}, struct{ x bool }{true}, []string{`x: false != true`}},
|
|
||||||
{struct{ x int }{0}, struct{ x int }{0}, nil},
|
|
||||||
{struct{ x int }{0}, struct{ x int }{1}, []string{`x: 0 != 1`}},
|
|
||||||
{struct{ x int8 }{0}, struct{ x int8 }{0}, nil},
|
|
||||||
{struct{ x int8 }{0}, struct{ x int8 }{1}, []string{`x: 0 != 1`}},
|
|
||||||
{struct{ x int16 }{0}, struct{ x int16 }{0}, nil},
|
|
||||||
{struct{ x int16 }{0}, struct{ x int16 }{1}, []string{`x: 0 != 1`}},
|
|
||||||
{struct{ x int32 }{0}, struct{ x int32 }{0}, nil},
|
|
||||||
{struct{ x int32 }{0}, struct{ x int32 }{1}, []string{`x: 0 != 1`}},
|
|
||||||
{struct{ x int64 }{0}, struct{ x int64 }{0}, nil},
|
|
||||||
{struct{ x int64 }{0}, struct{ x int64 }{1}, []string{`x: 0 != 1`}},
|
|
||||||
{struct{ x uint }{0}, struct{ x uint }{0}, nil},
|
|
||||||
{struct{ x uint }{0}, struct{ x uint }{1}, []string{`x: 0 != 1`}},
|
|
||||||
{struct{ x uint8 }{0}, struct{ x uint8 }{0}, nil},
|
|
||||||
{struct{ x uint8 }{0}, struct{ x uint8 }{1}, []string{`x: 0 != 1`}},
|
|
||||||
{struct{ x uint16 }{0}, struct{ x uint16 }{0}, nil},
|
|
||||||
{struct{ x uint16 }{0}, struct{ x uint16 }{1}, []string{`x: 0 != 1`}},
|
|
||||||
{struct{ x uint32 }{0}, struct{ x uint32 }{0}, nil},
|
|
||||||
{struct{ x uint32 }{0}, struct{ x uint32 }{1}, []string{`x: 0 != 1`}},
|
|
||||||
{struct{ x uint64 }{0}, struct{ x uint64 }{0}, nil},
|
|
||||||
{struct{ x uint64 }{0}, struct{ x uint64 }{1}, []string{`x: 0 != 1`}},
|
|
||||||
{struct{ x uintptr }{0}, struct{ x uintptr }{0}, nil},
|
|
||||||
{struct{ x uintptr }{0}, struct{ x uintptr }{1}, []string{`x: 0 != 1`}},
|
|
||||||
{struct{ x float32 }{0}, struct{ x float32 }{0}, nil},
|
|
||||||
{struct{ x float32 }{0}, struct{ x float32 }{1}, []string{`x: 0 != 1`}},
|
|
||||||
{struct{ x float64 }{0}, struct{ x float64 }{0}, nil},
|
|
||||||
{struct{ x float64 }{0}, struct{ x float64 }{1}, []string{`x: 0 != 1`}},
|
|
||||||
{struct{ x complex64 }{0}, struct{ x complex64 }{0}, nil},
|
|
||||||
{struct{ x complex64 }{0}, struct{ x complex64 }{1}, []string{`x: (0+0i) != (1+0i)`}},
|
|
||||||
{struct{ x complex128 }{0}, struct{ x complex128 }{0}, nil},
|
|
||||||
{struct{ x complex128 }{0}, struct{ x complex128 }{1}, []string{`x: (0+0i) != (1+0i)`}},
|
|
||||||
{struct{ x [1]int }{[1]int{0}}, struct{ x [1]int }{[1]int{0}}, nil},
|
|
||||||
{struct{ x [1]int }{[1]int{0}}, struct{ x [1]int }{[1]int{1}}, []string{`x[0]: 0 != 1`}},
|
|
||||||
{struct{ x chan int }{c0}, struct{ x chan int }{c0}, nil},
|
|
||||||
{struct{ x chan int }{c0}, struct{ x chan int }{c1}, []string{fmt.Sprintf("x: %p != %p", c0, c1)}},
|
|
||||||
{struct{ x func() }{f0}, struct{ x func() }{f0}, nil},
|
|
||||||
{struct{ x func() }{f0}, struct{ x func() }{f1}, []string{fmt.Sprintf("x: %p != %p", f0, f1)}},
|
|
||||||
{struct{ x interface{} }{0}, struct{ x interface{} }{0}, nil},
|
|
||||||
{struct{ x interface{} }{0}, struct{ x interface{} }{1}, []string{`x: 0 != 1`}},
|
|
||||||
{struct{ x interface{} }{0}, struct{ x interface{} }{""}, []string{`x: int != string`}},
|
|
||||||
{struct{ x interface{} }{0}, struct{ x interface{} }{nil}, []string{`x: int(0) != nil`}},
|
|
||||||
{struct{ x interface{} }{nil}, struct{ x interface{} }{0}, []string{`x: nil != int(0)`}},
|
|
||||||
{struct{ x map[int]int }{map[int]int{0: 0}}, struct{ x map[int]int }{map[int]int{0: 0}}, nil},
|
|
||||||
{struct{ x map[int]int }{map[int]int{0: 0}}, struct{ x map[int]int }{map[int]int{0: 1}}, []string{`x[0]: 0 != 1`}},
|
|
||||||
{struct{ x *int }{new(int)}, struct{ x *int }{new(int)}, nil},
|
|
||||||
{struct{ x *int }{&i0}, struct{ x *int }{&i1}, []string{`x: 0 != 1`}},
|
|
||||||
{struct{ x *int }{nil}, struct{ x *int }{&i0}, []string{`x: nil != &int(0)`}},
|
|
||||||
{struct{ x *int }{&i0}, struct{ x *int }{nil}, []string{`x: &int(0) != nil`}},
|
|
||||||
{struct{ x []int }{[]int{0}}, struct{ x []int }{[]int{0}}, nil},
|
|
||||||
{struct{ x []int }{[]int{0}}, struct{ x []int }{[]int{1}}, []string{`x[0]: 0 != 1`}},
|
|
||||||
{struct{ x string }{"a"}, struct{ x string }{"a"}, nil},
|
|
||||||
{struct{ x string }{"a"}, struct{ x string }{"b"}, []string{`x: "a" != "b"`}},
|
|
||||||
{struct{ x N }{N{0}}, struct{ x N }{N{0}}, nil},
|
|
||||||
{struct{ x N }{N{0}}, struct{ x N }{N{1}}, []string{`x.N: 0 != 1`}},
|
|
||||||
{
|
|
||||||
struct{ x unsafe.Pointer }{unsafe.Pointer(uintptr(0))},
|
|
||||||
struct{ x unsafe.Pointer }{unsafe.Pointer(uintptr(0))},
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
struct{ x unsafe.Pointer }{unsafe.Pointer(uintptr(0))},
|
|
||||||
struct{ x unsafe.Pointer }{unsafe.Pointer(uintptr(1))},
|
|
||||||
[]string{`x: 0x0 != 0x1`},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDiff(t *testing.T) {
|
|
||||||
for _, tt := range diffs {
|
|
||||||
got := Diff(tt.a, tt.b)
|
|
||||||
eq := len(got) == len(tt.exp)
|
|
||||||
if eq {
|
|
||||||
for i := range got {
|
|
||||||
eq = eq && got[i] == tt.exp[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !eq {
|
|
||||||
t.Errorf("diffing % #v", tt.a)
|
|
||||||
t.Errorf("with % #v", tt.b)
|
|
||||||
diffdiff(t, got, tt.exp)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestKeyEqual(t *testing.T) {
|
|
||||||
var emptyInterfaceZero interface{} = 0
|
|
||||||
|
|
||||||
cases := []interface{}{
|
|
||||||
new(bool),
|
|
||||||
new(int),
|
|
||||||
new(int8),
|
|
||||||
new(int16),
|
|
||||||
new(int32),
|
|
||||||
new(int64),
|
|
||||||
new(uint),
|
|
||||||
new(uint8),
|
|
||||||
new(uint16),
|
|
||||||
new(uint32),
|
|
||||||
new(uint64),
|
|
||||||
new(uintptr),
|
|
||||||
new(float32),
|
|
||||||
new(float64),
|
|
||||||
new(complex64),
|
|
||||||
new(complex128),
|
|
||||||
new([1]int),
|
|
||||||
new(chan int),
|
|
||||||
new(unsafe.Pointer),
|
|
||||||
new(interface{}),
|
|
||||||
&emptyInterfaceZero,
|
|
||||||
new(*int),
|
|
||||||
new(string),
|
|
||||||
new(struct{ int }),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range cases {
|
|
||||||
rv := reflect.ValueOf(test).Elem()
|
|
||||||
if !keyEqual(rv, rv) {
|
|
||||||
t.Errorf("keyEqual(%s, %s) = false want true", rv.Type(), rv.Type())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFdiff(t *testing.T) {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
Fdiff(&buf, 0, 1)
|
|
||||||
want := "0 != 1\n"
|
|
||||||
if got := buf.String(); got != want {
|
|
||||||
t.Errorf("Fdiff(0, 1) = %q want %q", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func diffdiff(t *testing.T, got, exp []string) {
|
|
||||||
minus(t, "unexpected:", got, exp)
|
|
||||||
minus(t, "missing:", exp, got)
|
|
||||||
}
|
|
||||||
|
|
||||||
func minus(t *testing.T, s string, a, b []string) {
|
|
||||||
var i, j int
|
|
||||||
for i = 0; i < len(a); i++ {
|
|
||||||
for j = 0; j < len(b); j++ {
|
|
||||||
if a[i] == b[j] {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if j == len(b) {
|
|
||||||
t.Error(s, a[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
package pretty_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/kr/pretty"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Example() {
|
|
||||||
type myType struct {
|
|
||||||
a, b int
|
|
||||||
}
|
|
||||||
var x = []myType{{1, 2}, {3, 4}, {5, 6}}
|
|
||||||
fmt.Printf("%# v", pretty.Formatter(x))
|
|
||||||
// output:
|
|
||||||
// []pretty_test.myType{
|
|
||||||
// {a:1, b:2},
|
|
||||||
// {a:3, b:4},
|
|
||||||
// {a:5, b:6},
|
|
||||||
// }
|
|
||||||
}
|
|
|
@ -1,328 +0,0 @@
|
||||||
package pretty
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"text/tabwriter"
|
|
||||||
|
|
||||||
"github.com/kr/text"
|
|
||||||
)
|
|
||||||
|
|
||||||
type formatter struct {
|
|
||||||
v reflect.Value
|
|
||||||
force bool
|
|
||||||
quote bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Formatter makes a wrapper, f, that will format x as go source with line
|
|
||||||
// breaks and tabs. Object f responds to the "%v" formatting verb when both the
|
|
||||||
// "#" and " " (space) flags are set, for example:
|
|
||||||
//
|
|
||||||
// fmt.Sprintf("%# v", Formatter(x))
|
|
||||||
//
|
|
||||||
// If one of these two flags is not set, or any other verb is used, f will
|
|
||||||
// format x according to the usual rules of package fmt.
|
|
||||||
// In particular, if x satisfies fmt.Formatter, then x.Format will be called.
|
|
||||||
func Formatter(x interface{}) (f fmt.Formatter) {
|
|
||||||
return formatter{v: reflect.ValueOf(x), quote: true}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fo formatter) String() string {
|
|
||||||
return fmt.Sprint(fo.v.Interface()) // unwrap it
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fo formatter) passThrough(f fmt.State, c rune) {
|
|
||||||
s := "%"
|
|
||||||
for i := 0; i < 128; i++ {
|
|
||||||
if f.Flag(i) {
|
|
||||||
s += string(i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if w, ok := f.Width(); ok {
|
|
||||||
s += fmt.Sprintf("%d", w)
|
|
||||||
}
|
|
||||||
if p, ok := f.Precision(); ok {
|
|
||||||
s += fmt.Sprintf(".%d", p)
|
|
||||||
}
|
|
||||||
s += string(c)
|
|
||||||
fmt.Fprintf(f, s, fo.v.Interface())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fo formatter) Format(f fmt.State, c rune) {
|
|
||||||
if fo.force || c == 'v' && f.Flag('#') && f.Flag(' ') {
|
|
||||||
w := tabwriter.NewWriter(f, 4, 4, 1, ' ', 0)
|
|
||||||
p := &printer{tw: w, Writer: w, visited: make(map[visit]int)}
|
|
||||||
p.printValue(fo.v, true, fo.quote)
|
|
||||||
w.Flush()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fo.passThrough(f, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
type printer struct {
|
|
||||||
io.Writer
|
|
||||||
tw *tabwriter.Writer
|
|
||||||
visited map[visit]int
|
|
||||||
depth int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *printer) indent() *printer {
|
|
||||||
q := *p
|
|
||||||
q.tw = tabwriter.NewWriter(p.Writer, 4, 4, 1, ' ', 0)
|
|
||||||
q.Writer = text.NewIndentWriter(q.tw, []byte{'\t'})
|
|
||||||
return &q
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *printer) printInline(v reflect.Value, x interface{}, showType bool) {
|
|
||||||
if showType {
|
|
||||||
io.WriteString(p, v.Type().String())
|
|
||||||
fmt.Fprintf(p, "(%#v)", x)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(p, "%#v", x)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// printValue must keep track of already-printed pointer values to avoid
|
|
||||||
// infinite recursion.
|
|
||||||
type visit struct {
|
|
||||||
v uintptr
|
|
||||||
typ reflect.Type
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *printer) printValue(v reflect.Value, showType, quote bool) {
|
|
||||||
if p.depth > 10 {
|
|
||||||
io.WriteString(p, "!%v(DEPTH EXCEEDED)")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v.Kind() {
|
|
||||||
case reflect.Bool:
|
|
||||||
p.printInline(v, v.Bool(), showType)
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
p.printInline(v, v.Int(), showType)
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
||||||
p.printInline(v, v.Uint(), showType)
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
p.printInline(v, v.Float(), showType)
|
|
||||||
case reflect.Complex64, reflect.Complex128:
|
|
||||||
fmt.Fprintf(p, "%#v", v.Complex())
|
|
||||||
case reflect.String:
|
|
||||||
p.fmtString(v.String(), quote)
|
|
||||||
case reflect.Map:
|
|
||||||
t := v.Type()
|
|
||||||
if showType {
|
|
||||||
io.WriteString(p, t.String())
|
|
||||||
}
|
|
||||||
writeByte(p, '{')
|
|
||||||
if nonzero(v) {
|
|
||||||
expand := !canInline(v.Type())
|
|
||||||
pp := p
|
|
||||||
if expand {
|
|
||||||
writeByte(p, '\n')
|
|
||||||
pp = p.indent()
|
|
||||||
}
|
|
||||||
keys := v.MapKeys()
|
|
||||||
for i := 0; i < v.Len(); i++ {
|
|
||||||
showTypeInStruct := true
|
|
||||||
k := keys[i]
|
|
||||||
mv := v.MapIndex(k)
|
|
||||||
pp.printValue(k, false, true)
|
|
||||||
writeByte(pp, ':')
|
|
||||||
if expand {
|
|
||||||
writeByte(pp, '\t')
|
|
||||||
}
|
|
||||||
showTypeInStruct = t.Elem().Kind() == reflect.Interface
|
|
||||||
pp.printValue(mv, showTypeInStruct, true)
|
|
||||||
if expand {
|
|
||||||
io.WriteString(pp, ",\n")
|
|
||||||
} else if i < v.Len()-1 {
|
|
||||||
io.WriteString(pp, ", ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if expand {
|
|
||||||
pp.tw.Flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writeByte(p, '}')
|
|
||||||
case reflect.Struct:
|
|
||||||
t := v.Type()
|
|
||||||
if v.CanAddr() {
|
|
||||||
addr := v.UnsafeAddr()
|
|
||||||
vis := visit{addr, t}
|
|
||||||
if vd, ok := p.visited[vis]; ok && vd < p.depth {
|
|
||||||
p.fmtString(t.String()+"{(CYCLIC REFERENCE)}", false)
|
|
||||||
break // don't print v again
|
|
||||||
}
|
|
||||||
p.visited[vis] = p.depth
|
|
||||||
}
|
|
||||||
|
|
||||||
if showType {
|
|
||||||
io.WriteString(p, t.String())
|
|
||||||
}
|
|
||||||
writeByte(p, '{')
|
|
||||||
if nonzero(v) {
|
|
||||||
expand := !canInline(v.Type())
|
|
||||||
pp := p
|
|
||||||
if expand {
|
|
||||||
writeByte(p, '\n')
|
|
||||||
pp = p.indent()
|
|
||||||
}
|
|
||||||
for i := 0; i < v.NumField(); i++ {
|
|
||||||
showTypeInStruct := true
|
|
||||||
if f := t.Field(i); f.Name != "" {
|
|
||||||
io.WriteString(pp, f.Name)
|
|
||||||
writeByte(pp, ':')
|
|
||||||
if expand {
|
|
||||||
writeByte(pp, '\t')
|
|
||||||
}
|
|
||||||
showTypeInStruct = labelType(f.Type)
|
|
||||||
}
|
|
||||||
pp.printValue(getField(v, i), showTypeInStruct, true)
|
|
||||||
if expand {
|
|
||||||
io.WriteString(pp, ",\n")
|
|
||||||
} else if i < v.NumField()-1 {
|
|
||||||
io.WriteString(pp, ", ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if expand {
|
|
||||||
pp.tw.Flush()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writeByte(p, '}')
|
|
||||||
case reflect.Interface:
|
|
||||||
switch e := v.Elem(); {
|
|
||||||
case e.Kind() == reflect.Invalid:
|
|
||||||
io.WriteString(p, "nil")
|
|
||||||
case e.IsValid():
|
|
||||||
pp := *p
|
|
||||||
pp.depth++
|
|
||||||
pp.printValue(e, showType, true)
|
|
||||||
default:
|
|
||||||
io.WriteString(p, v.Type().String())
|
|
||||||
io.WriteString(p, "(nil)")
|
|
||||||
}
|
|
||||||
case reflect.Array, reflect.Slice:
|
|
||||||
t := v.Type()
|
|
||||||
if showType {
|
|
||||||
io.WriteString(p, t.String())
|
|
||||||
}
|
|
||||||
if v.Kind() == reflect.Slice && v.IsNil() && showType {
|
|
||||||
io.WriteString(p, "(nil)")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if v.Kind() == reflect.Slice && v.IsNil() {
|
|
||||||
io.WriteString(p, "nil")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
writeByte(p, '{')
|
|
||||||
expand := !canInline(v.Type())
|
|
||||||
pp := p
|
|
||||||
if expand {
|
|
||||||
writeByte(p, '\n')
|
|
||||||
pp = p.indent()
|
|
||||||
}
|
|
||||||
for i := 0; i < v.Len(); i++ {
|
|
||||||
showTypeInSlice := t.Elem().Kind() == reflect.Interface
|
|
||||||
pp.printValue(v.Index(i), showTypeInSlice, true)
|
|
||||||
if expand {
|
|
||||||
io.WriteString(pp, ",\n")
|
|
||||||
} else if i < v.Len()-1 {
|
|
||||||
io.WriteString(pp, ", ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if expand {
|
|
||||||
pp.tw.Flush()
|
|
||||||
}
|
|
||||||
writeByte(p, '}')
|
|
||||||
case reflect.Ptr:
|
|
||||||
e := v.Elem()
|
|
||||||
if !e.IsValid() {
|
|
||||||
writeByte(p, '(')
|
|
||||||
io.WriteString(p, v.Type().String())
|
|
||||||
io.WriteString(p, ")(nil)")
|
|
||||||
} else {
|
|
||||||
pp := *p
|
|
||||||
pp.depth++
|
|
||||||
writeByte(pp, '&')
|
|
||||||
pp.printValue(e, true, true)
|
|
||||||
}
|
|
||||||
case reflect.Chan:
|
|
||||||
x := v.Pointer()
|
|
||||||
if showType {
|
|
||||||
writeByte(p, '(')
|
|
||||||
io.WriteString(p, v.Type().String())
|
|
||||||
fmt.Fprintf(p, ")(%#v)", x)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(p, "%#v", x)
|
|
||||||
}
|
|
||||||
case reflect.Func:
|
|
||||||
io.WriteString(p, v.Type().String())
|
|
||||||
io.WriteString(p, " {...}")
|
|
||||||
case reflect.UnsafePointer:
|
|
||||||
p.printInline(v, v.Pointer(), showType)
|
|
||||||
case reflect.Invalid:
|
|
||||||
io.WriteString(p, "nil")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func canInline(t reflect.Type) bool {
|
|
||||||
switch t.Kind() {
|
|
||||||
case reflect.Map:
|
|
||||||
return !canExpand(t.Elem())
|
|
||||||
case reflect.Struct:
|
|
||||||
for i := 0; i < t.NumField(); i++ {
|
|
||||||
if canExpand(t.Field(i).Type) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
case reflect.Interface:
|
|
||||||
return false
|
|
||||||
case reflect.Array, reflect.Slice:
|
|
||||||
return !canExpand(t.Elem())
|
|
||||||
case reflect.Ptr:
|
|
||||||
return false
|
|
||||||
case reflect.Chan, reflect.Func, reflect.UnsafePointer:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func canExpand(t reflect.Type) bool {
|
|
||||||
switch t.Kind() {
|
|
||||||
case reflect.Map, reflect.Struct,
|
|
||||||
reflect.Interface, reflect.Array, reflect.Slice,
|
|
||||||
reflect.Ptr:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func labelType(t reflect.Type) bool {
|
|
||||||
switch t.Kind() {
|
|
||||||
case reflect.Interface, reflect.Struct:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *printer) fmtString(s string, quote bool) {
|
|
||||||
if quote {
|
|
||||||
s = strconv.Quote(s)
|
|
||||||
}
|
|
||||||
io.WriteString(p, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeByte(w io.Writer, b byte) {
|
|
||||||
w.Write([]byte{b})
|
|
||||||
}
|
|
||||||
|
|
||||||
func getField(v reflect.Value, i int) reflect.Value {
|
|
||||||
val := v.Field(i)
|
|
||||||
if val.Kind() == reflect.Interface && !val.IsNil() {
|
|
||||||
val = val.Elem()
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
|
@ -1,288 +0,0 @@
|
||||||
package pretty
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
type test struct {
|
|
||||||
v interface{}
|
|
||||||
s string
|
|
||||||
}
|
|
||||||
|
|
||||||
type passtest struct {
|
|
||||||
v interface{}
|
|
||||||
f, s string
|
|
||||||
}
|
|
||||||
|
|
||||||
type LongStructTypeName struct {
|
|
||||||
longFieldName interface{}
|
|
||||||
otherLongFieldName interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type SA struct {
|
|
||||||
t *T
|
|
||||||
v T
|
|
||||||
}
|
|
||||||
|
|
||||||
type T struct {
|
|
||||||
x, y int
|
|
||||||
}
|
|
||||||
|
|
||||||
type F int
|
|
||||||
|
|
||||||
func (f F) Format(s fmt.State, c rune) {
|
|
||||||
fmt.Fprintf(s, "F(%d)", int(f))
|
|
||||||
}
|
|
||||||
|
|
||||||
type Stringer struct { i int }
|
|
||||||
|
|
||||||
func (s *Stringer) String() string { return "foo" }
|
|
||||||
|
|
||||||
var long = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
||||||
|
|
||||||
var passthrough = []passtest{
|
|
||||||
{1, "%d", "1"},
|
|
||||||
{"a", "%s", "a"},
|
|
||||||
{&Stringer{}, "%s", "foo"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPassthrough(t *testing.T) {
|
|
||||||
for _, tt := range passthrough {
|
|
||||||
s := fmt.Sprintf(tt.f, Formatter(tt.v))
|
|
||||||
if tt.s != s {
|
|
||||||
t.Errorf("expected %q", tt.s)
|
|
||||||
t.Errorf("got %q", s)
|
|
||||||
t.Errorf("expraw\n%s", tt.s)
|
|
||||||
t.Errorf("gotraw\n%s", s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var gosyntax = []test{
|
|
||||||
{nil, `nil`},
|
|
||||||
{"", `""`},
|
|
||||||
{"a", `"a"`},
|
|
||||||
{1, "int(1)"},
|
|
||||||
{1.0, "float64(1)"},
|
|
||||||
{[]int(nil), "[]int(nil)"},
|
|
||||||
{[0]int{}, "[0]int{}"},
|
|
||||||
{complex(1, 0), "(1+0i)"},
|
|
||||||
//{make(chan int), "(chan int)(0x1234)"},
|
|
||||||
{unsafe.Pointer(uintptr(unsafe.Pointer(&long))), fmt.Sprintf("unsafe.Pointer(0x%02x)", uintptr(unsafe.Pointer(&long)))},
|
|
||||||
{func(int) {}, "func(int) {...}"},
|
|
||||||
{map[int]int{1: 1}, "map[int]int{1:1}"},
|
|
||||||
{int32(1), "int32(1)"},
|
|
||||||
{io.EOF, `&errors.errorString{s:"EOF"}`},
|
|
||||||
{[]string{"a"}, `[]string{"a"}`},
|
|
||||||
{
|
|
||||||
[]string{long},
|
|
||||||
`[]string{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"}`,
|
|
||||||
},
|
|
||||||
{F(5), "pretty.F(5)"},
|
|
||||||
{
|
|
||||||
SA{&T{1, 2}, T{3, 4}},
|
|
||||||
`pretty.SA{
|
|
||||||
t: &pretty.T{x:1, y:2},
|
|
||||||
v: pretty.T{x:3, y:4},
|
|
||||||
}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[int][]byte{1: {}},
|
|
||||||
`map[int][]uint8{
|
|
||||||
1: {},
|
|
||||||
}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[int]T{1: {}},
|
|
||||||
`map[int]pretty.T{
|
|
||||||
1: {},
|
|
||||||
}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
long,
|
|
||||||
`"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
LongStructTypeName{
|
|
||||||
longFieldName: LongStructTypeName{},
|
|
||||||
otherLongFieldName: long,
|
|
||||||
},
|
|
||||||
`pretty.LongStructTypeName{
|
|
||||||
longFieldName: pretty.LongStructTypeName{},
|
|
||||||
otherLongFieldName: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
|
|
||||||
}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
&LongStructTypeName{
|
|
||||||
longFieldName: &LongStructTypeName{},
|
|
||||||
otherLongFieldName: (*LongStructTypeName)(nil),
|
|
||||||
},
|
|
||||||
`&pretty.LongStructTypeName{
|
|
||||||
longFieldName: &pretty.LongStructTypeName{},
|
|
||||||
otherLongFieldName: (*pretty.LongStructTypeName)(nil),
|
|
||||||
}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]LongStructTypeName{
|
|
||||||
{nil, nil},
|
|
||||||
{3, 3},
|
|
||||||
{long, nil},
|
|
||||||
},
|
|
||||||
`[]pretty.LongStructTypeName{
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
longFieldName: int(3),
|
|
||||||
otherLongFieldName: int(3),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
longFieldName: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
|
|
||||||
otherLongFieldName: nil,
|
|
||||||
},
|
|
||||||
}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]interface{}{
|
|
||||||
LongStructTypeName{nil, nil},
|
|
||||||
[]byte{1, 2, 3},
|
|
||||||
T{3, 4},
|
|
||||||
LongStructTypeName{long, nil},
|
|
||||||
},
|
|
||||||
`[]interface {}{
|
|
||||||
pretty.LongStructTypeName{},
|
|
||||||
[]uint8{0x1, 0x2, 0x3},
|
|
||||||
pretty.T{x:3, y:4},
|
|
||||||
pretty.LongStructTypeName{
|
|
||||||
longFieldName: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
|
|
||||||
otherLongFieldName: nil,
|
|
||||||
},
|
|
||||||
}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGoSyntax(t *testing.T) {
|
|
||||||
for _, tt := range gosyntax {
|
|
||||||
s := fmt.Sprintf("%# v", Formatter(tt.v))
|
|
||||||
if tt.s != s {
|
|
||||||
t.Errorf("expected %q", tt.s)
|
|
||||||
t.Errorf("got %q", s)
|
|
||||||
t.Errorf("expraw\n%s", tt.s)
|
|
||||||
t.Errorf("gotraw\n%s", s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type I struct {
|
|
||||||
i int
|
|
||||||
R interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *I) I() *I { return i.R.(*I) }
|
|
||||||
|
|
||||||
func TestCycle(t *testing.T) {
|
|
||||||
type A struct{ *A }
|
|
||||||
v := &A{}
|
|
||||||
v.A = v
|
|
||||||
|
|
||||||
// panics from stack overflow without cycle detection
|
|
||||||
t.Logf("Example cycle:\n%# v", Formatter(v))
|
|
||||||
|
|
||||||
p := &A{}
|
|
||||||
s := fmt.Sprintf("%# v", Formatter([]*A{p, p}))
|
|
||||||
if strings.Contains(s, "CYCLIC") {
|
|
||||||
t.Errorf("Repeated address detected as cyclic reference:\n%s", s)
|
|
||||||
}
|
|
||||||
|
|
||||||
type R struct {
|
|
||||||
i int
|
|
||||||
*R
|
|
||||||
}
|
|
||||||
r := &R{
|
|
||||||
i: 1,
|
|
||||||
R: &R{
|
|
||||||
i: 2,
|
|
||||||
R: &R{
|
|
||||||
i: 3,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
r.R.R.R = r
|
|
||||||
t.Logf("Example longer cycle:\n%# v", Formatter(r))
|
|
||||||
|
|
||||||
r = &R{
|
|
||||||
i: 1,
|
|
||||||
R: &R{
|
|
||||||
i: 2,
|
|
||||||
R: &R{
|
|
||||||
i: 3,
|
|
||||||
R: &R{
|
|
||||||
i: 4,
|
|
||||||
R: &R{
|
|
||||||
i: 5,
|
|
||||||
R: &R{
|
|
||||||
i: 6,
|
|
||||||
R: &R{
|
|
||||||
i: 7,
|
|
||||||
R: &R{
|
|
||||||
i: 8,
|
|
||||||
R: &R{
|
|
||||||
i: 9,
|
|
||||||
R: &R{
|
|
||||||
i: 10,
|
|
||||||
R: &R{
|
|
||||||
i: 11,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
// here be pirates
|
|
||||||
r.R.R.R.R.R.R.R.R.R.R.R = r
|
|
||||||
t.Logf("Example very long cycle:\n%# v", Formatter(r))
|
|
||||||
|
|
||||||
i := &I{
|
|
||||||
i: 1,
|
|
||||||
R: &I{
|
|
||||||
i: 2,
|
|
||||||
R: &I{
|
|
||||||
i: 3,
|
|
||||||
R: &I{
|
|
||||||
i: 4,
|
|
||||||
R: &I{
|
|
||||||
i: 5,
|
|
||||||
R: &I{
|
|
||||||
i: 6,
|
|
||||||
R: &I{
|
|
||||||
i: 7,
|
|
||||||
R: &I{
|
|
||||||
i: 8,
|
|
||||||
R: &I{
|
|
||||||
i: 9,
|
|
||||||
R: &I{
|
|
||||||
i: 10,
|
|
||||||
R: &I{
|
|
||||||
i: 11,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
iv := i.I().I().I().I().I().I().I().I().I().I()
|
|
||||||
*iv = *i
|
|
||||||
t.Logf("Example long interface cycle:\n%# v", Formatter(i))
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
module "github.com/kr/pretty"
|
|
||||||
|
|
||||||
require "github.com/kr/text" v0.1.0
|
|
|
@ -1,108 +0,0 @@
|
||||||
// Package pretty provides pretty-printing for Go values. This is
|
|
||||||
// useful during debugging, to avoid wrapping long output lines in
|
|
||||||
// the terminal.
|
|
||||||
//
|
|
||||||
// It provides a function, Formatter, that can be used with any
|
|
||||||
// function that accepts a format string. It also provides
|
|
||||||
// convenience wrappers for functions in packages fmt and log.
|
|
||||||
package pretty
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"reflect"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Errorf is a convenience wrapper for fmt.Errorf.
|
|
||||||
//
|
|
||||||
// Calling Errorf(f, x, y) is equivalent to
|
|
||||||
// fmt.Errorf(f, Formatter(x), Formatter(y)).
|
|
||||||
func Errorf(format string, a ...interface{}) error {
|
|
||||||
return fmt.Errorf(format, wrap(a, false)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fprintf is a convenience wrapper for fmt.Fprintf.
|
|
||||||
//
|
|
||||||
// Calling Fprintf(w, f, x, y) is equivalent to
|
|
||||||
// fmt.Fprintf(w, f, Formatter(x), Formatter(y)).
|
|
||||||
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, error error) {
|
|
||||||
return fmt.Fprintf(w, format, wrap(a, false)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log is a convenience wrapper for log.Printf.
|
|
||||||
//
|
|
||||||
// Calling Log(x, y) is equivalent to
|
|
||||||
// log.Print(Formatter(x), Formatter(y)), but each operand is
|
|
||||||
// formatted with "%# v".
|
|
||||||
func Log(a ...interface{}) {
|
|
||||||
log.Print(wrap(a, true)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logf is a convenience wrapper for log.Printf.
|
|
||||||
//
|
|
||||||
// Calling Logf(f, x, y) is equivalent to
|
|
||||||
// log.Printf(f, Formatter(x), Formatter(y)).
|
|
||||||
func Logf(format string, a ...interface{}) {
|
|
||||||
log.Printf(format, wrap(a, false)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logln is a convenience wrapper for log.Printf.
|
|
||||||
//
|
|
||||||
// Calling Logln(x, y) is equivalent to
|
|
||||||
// log.Println(Formatter(x), Formatter(y)), but each operand is
|
|
||||||
// formatted with "%# v".
|
|
||||||
func Logln(a ...interface{}) {
|
|
||||||
log.Println(wrap(a, true)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print pretty-prints its operands and writes to standard output.
|
|
||||||
//
|
|
||||||
// Calling Print(x, y) is equivalent to
|
|
||||||
// fmt.Print(Formatter(x), Formatter(y)), but each operand is
|
|
||||||
// formatted with "%# v".
|
|
||||||
func Print(a ...interface{}) (n int, errno error) {
|
|
||||||
return fmt.Print(wrap(a, true)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Printf is a convenience wrapper for fmt.Printf.
|
|
||||||
//
|
|
||||||
// Calling Printf(f, x, y) is equivalent to
|
|
||||||
// fmt.Printf(f, Formatter(x), Formatter(y)).
|
|
||||||
func Printf(format string, a ...interface{}) (n int, errno error) {
|
|
||||||
return fmt.Printf(format, wrap(a, false)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Println pretty-prints its operands and writes to standard output.
|
|
||||||
//
|
|
||||||
// Calling Print(x, y) is equivalent to
|
|
||||||
// fmt.Println(Formatter(x), Formatter(y)), but each operand is
|
|
||||||
// formatted with "%# v".
|
|
||||||
func Println(a ...interface{}) (n int, errno error) {
|
|
||||||
return fmt.Println(wrap(a, true)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprint is a convenience wrapper for fmt.Sprintf.
|
|
||||||
//
|
|
||||||
// Calling Sprint(x, y) is equivalent to
|
|
||||||
// fmt.Sprint(Formatter(x), Formatter(y)), but each operand is
|
|
||||||
// formatted with "%# v".
|
|
||||||
func Sprint(a ...interface{}) string {
|
|
||||||
return fmt.Sprint(wrap(a, true)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sprintf is a convenience wrapper for fmt.Sprintf.
|
|
||||||
//
|
|
||||||
// Calling Sprintf(f, x, y) is equivalent to
|
|
||||||
// fmt.Sprintf(f, Formatter(x), Formatter(y)).
|
|
||||||
func Sprintf(format string, a ...interface{}) string {
|
|
||||||
return fmt.Sprintf(format, wrap(a, false)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func wrap(a []interface{}, force bool) []interface{} {
|
|
||||||
w := make([]interface{}, len(a))
|
|
||||||
for i, x := range a {
|
|
||||||
w[i] = formatter{v: reflect.ValueOf(x), force: force}
|
|
||||||
}
|
|
||||||
return w
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
package pretty
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
)
|
|
||||||
|
|
||||||
func nonzero(v reflect.Value) bool {
|
|
||||||
switch v.Kind() {
|
|
||||||
case reflect.Bool:
|
|
||||||
return v.Bool()
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
return v.Int() != 0
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
||||||
return v.Uint() != 0
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
return v.Float() != 0
|
|
||||||
case reflect.Complex64, reflect.Complex128:
|
|
||||||
return v.Complex() != complex(0, 0)
|
|
||||||
case reflect.String:
|
|
||||||
return v.String() != ""
|
|
||||||
case reflect.Struct:
|
|
||||||
for i := 0; i < v.NumField(); i++ {
|
|
||||||
if nonzero(getField(v, i)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
case reflect.Array:
|
|
||||||
for i := 0; i < v.Len(); i++ {
|
|
||||||
if nonzero(v.Index(i)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
case reflect.Map, reflect.Interface, reflect.Slice, reflect.Ptr, reflect.Chan, reflect.Func:
|
|
||||||
return !v.IsNil()
|
|
||||||
case reflect.UnsafePointer:
|
|
||||||
return v.Pointer() != 0
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
Copyright 2012 Keith Rarick
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
|
@ -1,3 +0,0 @@
|
||||||
This is a Go package for manipulating paragraphs of text.
|
|
||||||
|
|
||||||
See http://go.pkgdoc.org/github.com/kr/text for full documentation.
|
|
|
@ -1,3 +0,0 @@
|
||||||
// Package text provides rudimentary functions for manipulating text in
|
|
||||||
// paragraphs.
|
|
||||||
package text
|
|
|
@ -1,3 +0,0 @@
|
||||||
module "github.com/kr/text"
|
|
||||||
|
|
||||||
require "github.com/kr/pty" v1.1.1
|
|
|
@ -1,74 +0,0 @@
|
||||||
package text
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Indent inserts prefix at the beginning of each non-empty line of s. The
|
|
||||||
// end-of-line marker is NL.
|
|
||||||
func Indent(s, prefix string) string {
|
|
||||||
return string(IndentBytes([]byte(s), []byte(prefix)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// IndentBytes inserts prefix at the beginning of each non-empty line of b.
|
|
||||||
// The end-of-line marker is NL.
|
|
||||||
func IndentBytes(b, prefix []byte) []byte {
|
|
||||||
var res []byte
|
|
||||||
bol := true
|
|
||||||
for _, c := range b {
|
|
||||||
if bol && c != '\n' {
|
|
||||||
res = append(res, prefix...)
|
|
||||||
}
|
|
||||||
res = append(res, c)
|
|
||||||
bol = c == '\n'
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// Writer indents each line of its input.
|
|
||||||
type indentWriter struct {
|
|
||||||
w io.Writer
|
|
||||||
bol bool
|
|
||||||
pre [][]byte
|
|
||||||
sel int
|
|
||||||
off int
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewIndentWriter makes a new write filter that indents the input
|
|
||||||
// lines. Each line is prefixed in order with the corresponding
|
|
||||||
// element of pre. If there are more lines than elements, the last
|
|
||||||
// element of pre is repeated for each subsequent line.
|
|
||||||
func NewIndentWriter(w io.Writer, pre ...[]byte) io.Writer {
|
|
||||||
return &indentWriter{
|
|
||||||
w: w,
|
|
||||||
pre: pre,
|
|
||||||
bol: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The only errors returned are from the underlying indentWriter.
|
|
||||||
func (w *indentWriter) Write(p []byte) (n int, err error) {
|
|
||||||
for _, c := range p {
|
|
||||||
if w.bol {
|
|
||||||
var i int
|
|
||||||
i, err = w.w.Write(w.pre[w.sel][w.off:])
|
|
||||||
w.off += i
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, err = w.w.Write([]byte{c})
|
|
||||||
if err != nil {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
n++
|
|
||||||
w.bol = c == '\n'
|
|
||||||
if w.bol {
|
|
||||||
w.off = 0
|
|
||||||
if w.sel < len(w.pre)-1 {
|
|
||||||
w.sel++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return n, nil
|
|
||||||
}
|
|
|
@ -1,119 +0,0 @@
|
||||||
package text
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type T struct {
|
|
||||||
inp, exp, pre string
|
|
||||||
}
|
|
||||||
|
|
||||||
var tests = []T{
|
|
||||||
{
|
|
||||||
"The quick brown fox\njumps over the lazy\ndog.\nBut not quickly.\n",
|
|
||||||
"xxxThe quick brown fox\nxxxjumps over the lazy\nxxxdog.\nxxxBut not quickly.\n",
|
|
||||||
"xxx",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"The quick brown fox\njumps over the lazy\ndog.\n\nBut not quickly.",
|
|
||||||
"xxxThe quick brown fox\nxxxjumps over the lazy\nxxxdog.\n\nxxxBut not quickly.",
|
|
||||||
"xxx",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIndent(t *testing.T) {
|
|
||||||
for _, test := range tests {
|
|
||||||
got := Indent(test.inp, test.pre)
|
|
||||||
if got != test.exp {
|
|
||||||
t.Errorf("mismatch %q != %q", got, test.exp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type IndentWriterTest struct {
|
|
||||||
inp, exp string
|
|
||||||
pre []string
|
|
||||||
}
|
|
||||||
|
|
||||||
var ts = []IndentWriterTest{
|
|
||||||
{
|
|
||||||
`
|
|
||||||
The quick brown fox
|
|
||||||
jumps over the lazy
|
|
||||||
dog.
|
|
||||||
But not quickly.
|
|
||||||
`[1:],
|
|
||||||
`
|
|
||||||
xxxThe quick brown fox
|
|
||||||
xxxjumps over the lazy
|
|
||||||
xxxdog.
|
|
||||||
xxxBut not quickly.
|
|
||||||
`[1:],
|
|
||||||
[]string{"xxx"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`
|
|
||||||
The quick brown fox
|
|
||||||
jumps over the lazy
|
|
||||||
dog.
|
|
||||||
But not quickly.
|
|
||||||
`[1:],
|
|
||||||
`
|
|
||||||
xxaThe quick brown fox
|
|
||||||
xxxjumps over the lazy
|
|
||||||
xxxdog.
|
|
||||||
xxxBut not quickly.
|
|
||||||
`[1:],
|
|
||||||
[]string{"xxa", "xxx"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`
|
|
||||||
The quick brown fox
|
|
||||||
jumps over the lazy
|
|
||||||
dog.
|
|
||||||
But not quickly.
|
|
||||||
`[1:],
|
|
||||||
`
|
|
||||||
xxaThe quick brown fox
|
|
||||||
xxbjumps over the lazy
|
|
||||||
xxcdog.
|
|
||||||
xxxBut not quickly.
|
|
||||||
`[1:],
|
|
||||||
[]string{"xxa", "xxb", "xxc", "xxx"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
`
|
|
||||||
The quick brown fox
|
|
||||||
jumps over the lazy
|
|
||||||
dog.
|
|
||||||
|
|
||||||
But not quickly.`[1:],
|
|
||||||
`
|
|
||||||
xxaThe quick brown fox
|
|
||||||
xxxjumps over the lazy
|
|
||||||
xxxdog.
|
|
||||||
xxx
|
|
||||||
xxxBut not quickly.`[1:],
|
|
||||||
[]string{"xxa", "xxx"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIndentWriter(t *testing.T) {
|
|
||||||
for _, test := range ts {
|
|
||||||
b := new(bytes.Buffer)
|
|
||||||
pre := make([][]byte, len(test.pre))
|
|
||||||
for i := range test.pre {
|
|
||||||
pre[i] = []byte(test.pre[i])
|
|
||||||
}
|
|
||||||
w := NewIndentWriter(b, pre...)
|
|
||||||
if _, err := w.Write([]byte(test.inp)); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
if got := b.String(); got != test.exp {
|
|
||||||
t.Errorf("mismatch %q != %q", got, test.exp)
|
|
||||||
t.Log(got)
|
|
||||||
t.Log(test.exp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,86 +0,0 @@
|
||||||
package text
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"math"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
nl = []byte{'\n'}
|
|
||||||
sp = []byte{' '}
|
|
||||||
)
|
|
||||||
|
|
||||||
const defaultPenalty = 1e5
|
|
||||||
|
|
||||||
// Wrap wraps s into a paragraph of lines of length lim, with minimal
|
|
||||||
// raggedness.
|
|
||||||
func Wrap(s string, lim int) string {
|
|
||||||
return string(WrapBytes([]byte(s), lim))
|
|
||||||
}
|
|
||||||
|
|
||||||
// WrapBytes wraps b into a paragraph of lines of length lim, with minimal
|
|
||||||
// raggedness.
|
|
||||||
func WrapBytes(b []byte, lim int) []byte {
|
|
||||||
words := bytes.Split(bytes.Replace(bytes.TrimSpace(b), nl, sp, -1), sp)
|
|
||||||
var lines [][]byte
|
|
||||||
for _, line := range WrapWords(words, 1, lim, defaultPenalty) {
|
|
||||||
lines = append(lines, bytes.Join(line, sp))
|
|
||||||
}
|
|
||||||
return bytes.Join(lines, nl)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WrapWords is the low-level line-breaking algorithm, useful if you need more
|
|
||||||
// control over the details of the text wrapping process. For most uses, either
|
|
||||||
// Wrap or WrapBytes will be sufficient and more convenient.
|
|
||||||
//
|
|
||||||
// WrapWords splits a list of words into lines with minimal "raggedness",
|
|
||||||
// treating each byte as one unit, accounting for spc units between adjacent
|
|
||||||
// words on each line, and attempting to limit lines to lim units. Raggedness
|
|
||||||
// is the total error over all lines, where error is the square of the
|
|
||||||
// difference of the length of the line and lim. Too-long lines (which only
|
|
||||||
// happen when a single word is longer than lim units) have pen penalty units
|
|
||||||
// added to the error.
|
|
||||||
func WrapWords(words [][]byte, spc, lim, pen int) [][][]byte {
|
|
||||||
n := len(words)
|
|
||||||
|
|
||||||
length := make([][]int, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
length[i] = make([]int, n)
|
|
||||||
length[i][i] = len(words[i])
|
|
||||||
for j := i + 1; j < n; j++ {
|
|
||||||
length[i][j] = length[i][j-1] + spc + len(words[j])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nbrk := make([]int, n)
|
|
||||||
cost := make([]int, n)
|
|
||||||
for i := range cost {
|
|
||||||
cost[i] = math.MaxInt32
|
|
||||||
}
|
|
||||||
for i := n - 1; i >= 0; i-- {
|
|
||||||
if length[i][n-1] <= lim || i == n-1 {
|
|
||||||
cost[i] = 0
|
|
||||||
nbrk[i] = n
|
|
||||||
} else {
|
|
||||||
for j := i + 1; j < n; j++ {
|
|
||||||
d := lim - length[i][j-1]
|
|
||||||
c := d*d + cost[j]
|
|
||||||
if length[i][j-1] > lim {
|
|
||||||
c += pen // too-long lines get a worse penalty
|
|
||||||
}
|
|
||||||
if c < cost[i] {
|
|
||||||
cost[i] = c
|
|
||||||
nbrk[i] = j
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var lines [][][]byte
|
|
||||||
i := 0
|
|
||||||
for i < n {
|
|
||||||
lines = append(lines, words[i:nbrk[i]])
|
|
||||||
i = nbrk[i]
|
|
||||||
}
|
|
||||||
return lines
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
package text
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var text = "The quick brown fox jumps over the lazy dog."
|
|
||||||
|
|
||||||
func TestWrap(t *testing.T) {
|
|
||||||
exp := [][]string{
|
|
||||||
{"The", "quick", "brown", "fox"},
|
|
||||||
{"jumps", "over", "the", "lazy", "dog."},
|
|
||||||
}
|
|
||||||
words := bytes.Split([]byte(text), sp)
|
|
||||||
got := WrapWords(words, 1, 24, defaultPenalty)
|
|
||||||
if len(exp) != len(got) {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
for i := range exp {
|
|
||||||
if len(exp[i]) != len(got[i]) {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
for j := range exp[i] {
|
|
||||||
if exp[i][j] != string(got[i][j]) {
|
|
||||||
t.Fatal(i, exp[i][j], got[i][j])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWrapNarrow(t *testing.T) {
|
|
||||||
exp := "The\nquick\nbrown\nfox\njumps\nover\nthe\nlazy\ndog."
|
|
||||||
if Wrap(text, 5) != exp {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWrapOneLine(t *testing.T) {
|
|
||||||
exp := "The quick brown fox jumps over the lazy dog."
|
|
||||||
if Wrap(text, 500) != exp {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWrapBug1(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
limit int
|
|
||||||
text string
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{4, "aaaaa", "aaaaa"},
|
|
||||||
{4, "a aaaaa", "a\naaaaa"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range cases {
|
|
||||||
got := Wrap(test.text, test.limit)
|
|
||||||
if got != test.want {
|
|
||||||
t.Errorf("Wrap(%q, %d) = %q want %q", test.text, test.limit, got, test.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
// +build go1.7
|
|
||||||
|
|
||||||
package errors
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
stderrors "errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
func noErrors(at, depth int) error {
|
|
||||||
if at >= depth {
|
|
||||||
return stderrors.New("no error")
|
|
||||||
}
|
|
||||||
return noErrors(at+1, depth)
|
|
||||||
}
|
|
||||||
func yesErrors(at, depth int) error {
|
|
||||||
if at >= depth {
|
|
||||||
return New("ye error")
|
|
||||||
}
|
|
||||||
return yesErrors(at+1, depth)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkErrors(b *testing.B) {
|
|
||||||
var toperr error
|
|
||||||
type run struct {
|
|
||||||
stack int
|
|
||||||
std bool
|
|
||||||
}
|
|
||||||
runs := []run{
|
|
||||||
{10, false},
|
|
||||||
{10, true},
|
|
||||||
{100, false},
|
|
||||||
{100, true},
|
|
||||||
{1000, false},
|
|
||||||
{1000, true},
|
|
||||||
}
|
|
||||||
for _, r := range runs {
|
|
||||||
part := "pkg/errors"
|
|
||||||
if r.std {
|
|
||||||
part = "errors"
|
|
||||||
}
|
|
||||||
name := fmt.Sprintf("%s-stack-%d", part, r.stack)
|
|
||||||
b.Run(name, func(b *testing.B) {
|
|
||||||
var err error
|
|
||||||
f := yesErrors
|
|
||||||
if r.std {
|
|
||||||
f = noErrors
|
|
||||||
}
|
|
||||||
b.ReportAllocs()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
err = f(0, r.stack)
|
|
||||||
}
|
|
||||||
b.StopTimer()
|
|
||||||
toperr = err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,226 +0,0 @@
|
||||||
package errors
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNew(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
err string
|
|
||||||
want error
|
|
||||||
}{
|
|
||||||
{"", fmt.Errorf("")},
|
|
||||||
{"foo", fmt.Errorf("foo")},
|
|
||||||
{"foo", New("foo")},
|
|
||||||
{"string with format specifiers: %v", errors.New("string with format specifiers: %v")},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
got := New(tt.err)
|
|
||||||
if got.Error() != tt.want.Error() {
|
|
||||||
t.Errorf("New.Error(): got: %q, want %q", got, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWrapNil(t *testing.T) {
|
|
||||||
got := Wrap(nil, "no error")
|
|
||||||
if got != nil {
|
|
||||||
t.Errorf("Wrap(nil, \"no error\"): got %#v, expected nil", got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWrap(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
err error
|
|
||||||
message string
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{io.EOF, "read error", "read error: EOF"},
|
|
||||||
{Wrap(io.EOF, "read error"), "client error", "client error: read error: EOF"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
got := Wrap(tt.err, tt.message).Error()
|
|
||||||
if got != tt.want {
|
|
||||||
t.Errorf("Wrap(%v, %q): got: %v, want %v", tt.err, tt.message, got, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type nilError struct{}
|
|
||||||
|
|
||||||
func (nilError) Error() string { return "nil error" }
|
|
||||||
|
|
||||||
func TestCause(t *testing.T) {
|
|
||||||
x := New("error")
|
|
||||||
tests := []struct {
|
|
||||||
err error
|
|
||||||
want error
|
|
||||||
}{{
|
|
||||||
// nil error is nil
|
|
||||||
err: nil,
|
|
||||||
want: nil,
|
|
||||||
}, {
|
|
||||||
// explicit nil error is nil
|
|
||||||
err: (error)(nil),
|
|
||||||
want: nil,
|
|
||||||
}, {
|
|
||||||
// typed nil is nil
|
|
||||||
err: (*nilError)(nil),
|
|
||||||
want: (*nilError)(nil),
|
|
||||||
}, {
|
|
||||||
// uncaused error is unaffected
|
|
||||||
err: io.EOF,
|
|
||||||
want: io.EOF,
|
|
||||||
}, {
|
|
||||||
// caused error returns cause
|
|
||||||
err: Wrap(io.EOF, "ignored"),
|
|
||||||
want: io.EOF,
|
|
||||||
}, {
|
|
||||||
err: x, // return from errors.New
|
|
||||||
want: x,
|
|
||||||
}, {
|
|
||||||
WithMessage(nil, "whoops"),
|
|
||||||
nil,
|
|
||||||
}, {
|
|
||||||
WithMessage(io.EOF, "whoops"),
|
|
||||||
io.EOF,
|
|
||||||
}, {
|
|
||||||
WithStack(nil),
|
|
||||||
nil,
|
|
||||||
}, {
|
|
||||||
WithStack(io.EOF),
|
|
||||||
io.EOF,
|
|
||||||
}}
|
|
||||||
|
|
||||||
for i, tt := range tests {
|
|
||||||
got := Cause(tt.err)
|
|
||||||
if !reflect.DeepEqual(got, tt.want) {
|
|
||||||
t.Errorf("test %d: got %#v, want %#v", i+1, got, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWrapfNil(t *testing.T) {
|
|
||||||
got := Wrapf(nil, "no error")
|
|
||||||
if got != nil {
|
|
||||||
t.Errorf("Wrapf(nil, \"no error\"): got %#v, expected nil", got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWrapf(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
err error
|
|
||||||
message string
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{io.EOF, "read error", "read error: EOF"},
|
|
||||||
{Wrapf(io.EOF, "read error without format specifiers"), "client error", "client error: read error without format specifiers: EOF"},
|
|
||||||
{Wrapf(io.EOF, "read error with %d format specifier", 1), "client error", "client error: read error with 1 format specifier: EOF"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
got := Wrapf(tt.err, tt.message).Error()
|
|
||||||
if got != tt.want {
|
|
||||||
t.Errorf("Wrapf(%v, %q): got: %v, want %v", tt.err, tt.message, got, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestErrorf(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
err error
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{Errorf("read error without format specifiers"), "read error without format specifiers"},
|
|
||||||
{Errorf("read error with %d format specifier", 1), "read error with 1 format specifier"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
got := tt.err.Error()
|
|
||||||
if got != tt.want {
|
|
||||||
t.Errorf("Errorf(%v): got: %q, want %q", tt.err, got, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithStackNil(t *testing.T) {
|
|
||||||
got := WithStack(nil)
|
|
||||||
if got != nil {
|
|
||||||
t.Errorf("WithStack(nil): got %#v, expected nil", got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithStack(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
err error
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{io.EOF, "EOF"},
|
|
||||||
{WithStack(io.EOF), "EOF"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
got := WithStack(tt.err).Error()
|
|
||||||
if got != tt.want {
|
|
||||||
t.Errorf("WithStack(%v): got: %v, want %v", tt.err, got, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithMessageNil(t *testing.T) {
|
|
||||||
got := WithMessage(nil, "no error")
|
|
||||||
if got != nil {
|
|
||||||
t.Errorf("WithMessage(nil, \"no error\"): got %#v, expected nil", got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithMessage(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
err error
|
|
||||||
message string
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{io.EOF, "read error", "read error: EOF"},
|
|
||||||
{WithMessage(io.EOF, "read error"), "client error", "client error: read error: EOF"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
got := WithMessage(tt.err, tt.message).Error()
|
|
||||||
if got != tt.want {
|
|
||||||
t.Errorf("WithMessage(%v, %q): got: %q, want %q", tt.err, tt.message, got, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// errors.New, etc values are not expected to be compared by value
|
|
||||||
// but the change in errors#27 made them incomparable. Assert that
|
|
||||||
// various kinds of errors have a functional equality operator, even
|
|
||||||
// if the result of that equality is always false.
|
|
||||||
func TestErrorEquality(t *testing.T) {
|
|
||||||
vals := []error{
|
|
||||||
nil,
|
|
||||||
io.EOF,
|
|
||||||
errors.New("EOF"),
|
|
||||||
New("EOF"),
|
|
||||||
Errorf("EOF"),
|
|
||||||
Wrap(io.EOF, "EOF"),
|
|
||||||
Wrapf(io.EOF, "EOF%d", 2),
|
|
||||||
WithMessage(nil, "whoops"),
|
|
||||||
WithMessage(io.EOF, "whoops"),
|
|
||||||
WithStack(io.EOF),
|
|
||||||
WithStack(nil),
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range vals {
|
|
||||||
for j := range vals {
|
|
||||||
_ = vals[i] == vals[j] // mustn't panic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,205 +0,0 @@
|
||||||
package errors_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ExampleNew() {
|
|
||||||
err := errors.New("whoops")
|
|
||||||
fmt.Println(err)
|
|
||||||
|
|
||||||
// Output: whoops
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleNew_printf() {
|
|
||||||
err := errors.New("whoops")
|
|
||||||
fmt.Printf("%+v", err)
|
|
||||||
|
|
||||||
// Example output:
|
|
||||||
// whoops
|
|
||||||
// github.com/pkg/errors_test.ExampleNew_printf
|
|
||||||
// /home/dfc/src/github.com/pkg/errors/example_test.go:17
|
|
||||||
// testing.runExample
|
|
||||||
// /home/dfc/go/src/testing/example.go:114
|
|
||||||
// testing.RunExamples
|
|
||||||
// /home/dfc/go/src/testing/example.go:38
|
|
||||||
// testing.(*M).Run
|
|
||||||
// /home/dfc/go/src/testing/testing.go:744
|
|
||||||
// main.main
|
|
||||||
// /github.com/pkg/errors/_test/_testmain.go:106
|
|
||||||
// runtime.main
|
|
||||||
// /home/dfc/go/src/runtime/proc.go:183
|
|
||||||
// runtime.goexit
|
|
||||||
// /home/dfc/go/src/runtime/asm_amd64.s:2059
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleWithMessage() {
|
|
||||||
cause := errors.New("whoops")
|
|
||||||
err := errors.WithMessage(cause, "oh noes")
|
|
||||||
fmt.Println(err)
|
|
||||||
|
|
||||||
// Output: oh noes: whoops
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleWithStack() {
|
|
||||||
cause := errors.New("whoops")
|
|
||||||
err := errors.WithStack(cause)
|
|
||||||
fmt.Println(err)
|
|
||||||
|
|
||||||
// Output: whoops
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleWithStack_printf() {
|
|
||||||
cause := errors.New("whoops")
|
|
||||||
err := errors.WithStack(cause)
|
|
||||||
fmt.Printf("%+v", err)
|
|
||||||
|
|
||||||
// Example Output:
|
|
||||||
// whoops
|
|
||||||
// github.com/pkg/errors_test.ExampleWithStack_printf
|
|
||||||
// /home/fabstu/go/src/github.com/pkg/errors/example_test.go:55
|
|
||||||
// testing.runExample
|
|
||||||
// /usr/lib/go/src/testing/example.go:114
|
|
||||||
// testing.RunExamples
|
|
||||||
// /usr/lib/go/src/testing/example.go:38
|
|
||||||
// testing.(*M).Run
|
|
||||||
// /usr/lib/go/src/testing/testing.go:744
|
|
||||||
// main.main
|
|
||||||
// github.com/pkg/errors/_test/_testmain.go:106
|
|
||||||
// runtime.main
|
|
||||||
// /usr/lib/go/src/runtime/proc.go:183
|
|
||||||
// runtime.goexit
|
|
||||||
// /usr/lib/go/src/runtime/asm_amd64.s:2086
|
|
||||||
// github.com/pkg/errors_test.ExampleWithStack_printf
|
|
||||||
// /home/fabstu/go/src/github.com/pkg/errors/example_test.go:56
|
|
||||||
// testing.runExample
|
|
||||||
// /usr/lib/go/src/testing/example.go:114
|
|
||||||
// testing.RunExamples
|
|
||||||
// /usr/lib/go/src/testing/example.go:38
|
|
||||||
// testing.(*M).Run
|
|
||||||
// /usr/lib/go/src/testing/testing.go:744
|
|
||||||
// main.main
|
|
||||||
// github.com/pkg/errors/_test/_testmain.go:106
|
|
||||||
// runtime.main
|
|
||||||
// /usr/lib/go/src/runtime/proc.go:183
|
|
||||||
// runtime.goexit
|
|
||||||
// /usr/lib/go/src/runtime/asm_amd64.s:2086
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleWrap() {
|
|
||||||
cause := errors.New("whoops")
|
|
||||||
err := errors.Wrap(cause, "oh noes")
|
|
||||||
fmt.Println(err)
|
|
||||||
|
|
||||||
// Output: oh noes: whoops
|
|
||||||
}
|
|
||||||
|
|
||||||
func fn() error {
|
|
||||||
e1 := errors.New("error")
|
|
||||||
e2 := errors.Wrap(e1, "inner")
|
|
||||||
e3 := errors.Wrap(e2, "middle")
|
|
||||||
return errors.Wrap(e3, "outer")
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleCause() {
|
|
||||||
err := fn()
|
|
||||||
fmt.Println(err)
|
|
||||||
fmt.Println(errors.Cause(err))
|
|
||||||
|
|
||||||
// Output: outer: middle: inner: error
|
|
||||||
// error
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleWrap_extended() {
|
|
||||||
err := fn()
|
|
||||||
fmt.Printf("%+v\n", err)
|
|
||||||
|
|
||||||
// Example output:
|
|
||||||
// error
|
|
||||||
// github.com/pkg/errors_test.fn
|
|
||||||
// /home/dfc/src/github.com/pkg/errors/example_test.go:47
|
|
||||||
// github.com/pkg/errors_test.ExampleCause_printf
|
|
||||||
// /home/dfc/src/github.com/pkg/errors/example_test.go:63
|
|
||||||
// testing.runExample
|
|
||||||
// /home/dfc/go/src/testing/example.go:114
|
|
||||||
// testing.RunExamples
|
|
||||||
// /home/dfc/go/src/testing/example.go:38
|
|
||||||
// testing.(*M).Run
|
|
||||||
// /home/dfc/go/src/testing/testing.go:744
|
|
||||||
// main.main
|
|
||||||
// /github.com/pkg/errors/_test/_testmain.go:104
|
|
||||||
// runtime.main
|
|
||||||
// /home/dfc/go/src/runtime/proc.go:183
|
|
||||||
// runtime.goexit
|
|
||||||
// /home/dfc/go/src/runtime/asm_amd64.s:2059
|
|
||||||
// github.com/pkg/errors_test.fn
|
|
||||||
// /home/dfc/src/github.com/pkg/errors/example_test.go:48: inner
|
|
||||||
// github.com/pkg/errors_test.fn
|
|
||||||
// /home/dfc/src/github.com/pkg/errors/example_test.go:49: middle
|
|
||||||
// github.com/pkg/errors_test.fn
|
|
||||||
// /home/dfc/src/github.com/pkg/errors/example_test.go:50: outer
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleWrapf() {
|
|
||||||
cause := errors.New("whoops")
|
|
||||||
err := errors.Wrapf(cause, "oh noes #%d", 2)
|
|
||||||
fmt.Println(err)
|
|
||||||
|
|
||||||
// Output: oh noes #2: whoops
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleErrorf_extended() {
|
|
||||||
err := errors.Errorf("whoops: %s", "foo")
|
|
||||||
fmt.Printf("%+v", err)
|
|
||||||
|
|
||||||
// Example output:
|
|
||||||
// whoops: foo
|
|
||||||
// github.com/pkg/errors_test.ExampleErrorf
|
|
||||||
// /home/dfc/src/github.com/pkg/errors/example_test.go:101
|
|
||||||
// testing.runExample
|
|
||||||
// /home/dfc/go/src/testing/example.go:114
|
|
||||||
// testing.RunExamples
|
|
||||||
// /home/dfc/go/src/testing/example.go:38
|
|
||||||
// testing.(*M).Run
|
|
||||||
// /home/dfc/go/src/testing/testing.go:744
|
|
||||||
// main.main
|
|
||||||
// /github.com/pkg/errors/_test/_testmain.go:102
|
|
||||||
// runtime.main
|
|
||||||
// /home/dfc/go/src/runtime/proc.go:183
|
|
||||||
// runtime.goexit
|
|
||||||
// /home/dfc/go/src/runtime/asm_amd64.s:2059
|
|
||||||
}
|
|
||||||
|
|
||||||
func Example_stackTrace() {
|
|
||||||
type stackTracer interface {
|
|
||||||
StackTrace() errors.StackTrace
|
|
||||||
}
|
|
||||||
|
|
||||||
err, ok := errors.Cause(fn()).(stackTracer)
|
|
||||||
if !ok {
|
|
||||||
panic("oops, err does not implement stackTracer")
|
|
||||||
}
|
|
||||||
|
|
||||||
st := err.StackTrace()
|
|
||||||
fmt.Printf("%+v", st[0:2]) // top two frames
|
|
||||||
|
|
||||||
// Example output:
|
|
||||||
// github.com/pkg/errors_test.fn
|
|
||||||
// /home/dfc/src/github.com/pkg/errors/example_test.go:47
|
|
||||||
// github.com/pkg/errors_test.Example_stackTrace
|
|
||||||
// /home/dfc/src/github.com/pkg/errors/example_test.go:127
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleCause_printf() {
|
|
||||||
err := errors.Wrap(func() error {
|
|
||||||
return func() error {
|
|
||||||
return errors.Errorf("hello %s", fmt.Sprintf("world"))
|
|
||||||
}()
|
|
||||||
}(), "failed")
|
|
||||||
|
|
||||||
fmt.Printf("%v", err)
|
|
||||||
|
|
||||||
// Output: failed: hello world
|
|
||||||
}
|
|
|
@ -1,535 +0,0 @@
|
||||||
package errors
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFormatNew(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
error
|
|
||||||
format string
|
|
||||||
want string
|
|
||||||
}{{
|
|
||||||
New("error"),
|
|
||||||
"%s",
|
|
||||||
"error",
|
|
||||||
}, {
|
|
||||||
New("error"),
|
|
||||||
"%v",
|
|
||||||
"error",
|
|
||||||
}, {
|
|
||||||
New("error"),
|
|
||||||
"%+v",
|
|
||||||
"error\n" +
|
|
||||||
"github.com/pkg/errors.TestFormatNew\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:26",
|
|
||||||
}, {
|
|
||||||
New("error"),
|
|
||||||
"%q",
|
|
||||||
`"error"`,
|
|
||||||
}}
|
|
||||||
|
|
||||||
for i, tt := range tests {
|
|
||||||
testFormatRegexp(t, i, tt.error, tt.format, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFormatErrorf(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
error
|
|
||||||
format string
|
|
||||||
want string
|
|
||||||
}{{
|
|
||||||
Errorf("%s", "error"),
|
|
||||||
"%s",
|
|
||||||
"error",
|
|
||||||
}, {
|
|
||||||
Errorf("%s", "error"),
|
|
||||||
"%v",
|
|
||||||
"error",
|
|
||||||
}, {
|
|
||||||
Errorf("%s", "error"),
|
|
||||||
"%+v",
|
|
||||||
"error\n" +
|
|
||||||
"github.com/pkg/errors.TestFormatErrorf\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:56",
|
|
||||||
}}
|
|
||||||
|
|
||||||
for i, tt := range tests {
|
|
||||||
testFormatRegexp(t, i, tt.error, tt.format, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFormatWrap(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
error
|
|
||||||
format string
|
|
||||||
want string
|
|
||||||
}{{
|
|
||||||
Wrap(New("error"), "error2"),
|
|
||||||
"%s",
|
|
||||||
"error2: error",
|
|
||||||
}, {
|
|
||||||
Wrap(New("error"), "error2"),
|
|
||||||
"%v",
|
|
||||||
"error2: error",
|
|
||||||
}, {
|
|
||||||
Wrap(New("error"), "error2"),
|
|
||||||
"%+v",
|
|
||||||
"error\n" +
|
|
||||||
"github.com/pkg/errors.TestFormatWrap\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:82",
|
|
||||||
}, {
|
|
||||||
Wrap(io.EOF, "error"),
|
|
||||||
"%s",
|
|
||||||
"error: EOF",
|
|
||||||
}, {
|
|
||||||
Wrap(io.EOF, "error"),
|
|
||||||
"%v",
|
|
||||||
"error: EOF",
|
|
||||||
}, {
|
|
||||||
Wrap(io.EOF, "error"),
|
|
||||||
"%+v",
|
|
||||||
"EOF\n" +
|
|
||||||
"error\n" +
|
|
||||||
"github.com/pkg/errors.TestFormatWrap\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:96",
|
|
||||||
}, {
|
|
||||||
Wrap(Wrap(io.EOF, "error1"), "error2"),
|
|
||||||
"%+v",
|
|
||||||
"EOF\n" +
|
|
||||||
"error1\n" +
|
|
||||||
"github.com/pkg/errors.TestFormatWrap\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:103\n",
|
|
||||||
}, {
|
|
||||||
Wrap(New("error with space"), "context"),
|
|
||||||
"%q",
|
|
||||||
`"context: error with space"`,
|
|
||||||
}}
|
|
||||||
|
|
||||||
for i, tt := range tests {
|
|
||||||
testFormatRegexp(t, i, tt.error, tt.format, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFormatWrapf(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
error
|
|
||||||
format string
|
|
||||||
want string
|
|
||||||
}{{
|
|
||||||
Wrapf(io.EOF, "error%d", 2),
|
|
||||||
"%s",
|
|
||||||
"error2: EOF",
|
|
||||||
}, {
|
|
||||||
Wrapf(io.EOF, "error%d", 2),
|
|
||||||
"%v",
|
|
||||||
"error2: EOF",
|
|
||||||
}, {
|
|
||||||
Wrapf(io.EOF, "error%d", 2),
|
|
||||||
"%+v",
|
|
||||||
"EOF\n" +
|
|
||||||
"error2\n" +
|
|
||||||
"github.com/pkg/errors.TestFormatWrapf\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:134",
|
|
||||||
}, {
|
|
||||||
Wrapf(New("error"), "error%d", 2),
|
|
||||||
"%s",
|
|
||||||
"error2: error",
|
|
||||||
}, {
|
|
||||||
Wrapf(New("error"), "error%d", 2),
|
|
||||||
"%v",
|
|
||||||
"error2: error",
|
|
||||||
}, {
|
|
||||||
Wrapf(New("error"), "error%d", 2),
|
|
||||||
"%+v",
|
|
||||||
"error\n" +
|
|
||||||
"github.com/pkg/errors.TestFormatWrapf\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:149",
|
|
||||||
}}
|
|
||||||
|
|
||||||
for i, tt := range tests {
|
|
||||||
testFormatRegexp(t, i, tt.error, tt.format, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFormatWithStack(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
error
|
|
||||||
format string
|
|
||||||
want []string
|
|
||||||
}{{
|
|
||||||
WithStack(io.EOF),
|
|
||||||
"%s",
|
|
||||||
[]string{"EOF"},
|
|
||||||
}, {
|
|
||||||
WithStack(io.EOF),
|
|
||||||
"%v",
|
|
||||||
[]string{"EOF"},
|
|
||||||
}, {
|
|
||||||
WithStack(io.EOF),
|
|
||||||
"%+v",
|
|
||||||
[]string{"EOF",
|
|
||||||
"github.com/pkg/errors.TestFormatWithStack\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:175"},
|
|
||||||
}, {
|
|
||||||
WithStack(New("error")),
|
|
||||||
"%s",
|
|
||||||
[]string{"error"},
|
|
||||||
}, {
|
|
||||||
WithStack(New("error")),
|
|
||||||
"%v",
|
|
||||||
[]string{"error"},
|
|
||||||
}, {
|
|
||||||
WithStack(New("error")),
|
|
||||||
"%+v",
|
|
||||||
[]string{"error",
|
|
||||||
"github.com/pkg/errors.TestFormatWithStack\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:189",
|
|
||||||
"github.com/pkg/errors.TestFormatWithStack\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:189"},
|
|
||||||
}, {
|
|
||||||
WithStack(WithStack(io.EOF)),
|
|
||||||
"%+v",
|
|
||||||
[]string{"EOF",
|
|
||||||
"github.com/pkg/errors.TestFormatWithStack\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:197",
|
|
||||||
"github.com/pkg/errors.TestFormatWithStack\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:197"},
|
|
||||||
}, {
|
|
||||||
WithStack(WithStack(Wrapf(io.EOF, "message"))),
|
|
||||||
"%+v",
|
|
||||||
[]string{"EOF",
|
|
||||||
"message",
|
|
||||||
"github.com/pkg/errors.TestFormatWithStack\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:205",
|
|
||||||
"github.com/pkg/errors.TestFormatWithStack\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:205",
|
|
||||||
"github.com/pkg/errors.TestFormatWithStack\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:205"},
|
|
||||||
}, {
|
|
||||||
WithStack(Errorf("error%d", 1)),
|
|
||||||
"%+v",
|
|
||||||
[]string{"error1",
|
|
||||||
"github.com/pkg/errors.TestFormatWithStack\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:216",
|
|
||||||
"github.com/pkg/errors.TestFormatWithStack\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:216"},
|
|
||||||
}}
|
|
||||||
|
|
||||||
for i, tt := range tests {
|
|
||||||
testFormatCompleteCompare(t, i, tt.error, tt.format, tt.want, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFormatWithMessage(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
error
|
|
||||||
format string
|
|
||||||
want []string
|
|
||||||
}{{
|
|
||||||
WithMessage(New("error"), "error2"),
|
|
||||||
"%s",
|
|
||||||
[]string{"error2: error"},
|
|
||||||
}, {
|
|
||||||
WithMessage(New("error"), "error2"),
|
|
||||||
"%v",
|
|
||||||
[]string{"error2: error"},
|
|
||||||
}, {
|
|
||||||
WithMessage(New("error"), "error2"),
|
|
||||||
"%+v",
|
|
||||||
[]string{
|
|
||||||
"error",
|
|
||||||
"github.com/pkg/errors.TestFormatWithMessage\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:244",
|
|
||||||
"error2"},
|
|
||||||
}, {
|
|
||||||
WithMessage(io.EOF, "addition1"),
|
|
||||||
"%s",
|
|
||||||
[]string{"addition1: EOF"},
|
|
||||||
}, {
|
|
||||||
WithMessage(io.EOF, "addition1"),
|
|
||||||
"%v",
|
|
||||||
[]string{"addition1: EOF"},
|
|
||||||
}, {
|
|
||||||
WithMessage(io.EOF, "addition1"),
|
|
||||||
"%+v",
|
|
||||||
[]string{"EOF", "addition1"},
|
|
||||||
}, {
|
|
||||||
WithMessage(WithMessage(io.EOF, "addition1"), "addition2"),
|
|
||||||
"%v",
|
|
||||||
[]string{"addition2: addition1: EOF"},
|
|
||||||
}, {
|
|
||||||
WithMessage(WithMessage(io.EOF, "addition1"), "addition2"),
|
|
||||||
"%+v",
|
|
||||||
[]string{"EOF", "addition1", "addition2"},
|
|
||||||
}, {
|
|
||||||
Wrap(WithMessage(io.EOF, "error1"), "error2"),
|
|
||||||
"%+v",
|
|
||||||
[]string{"EOF", "error1", "error2",
|
|
||||||
"github.com/pkg/errors.TestFormatWithMessage\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:272"},
|
|
||||||
}, {
|
|
||||||
WithMessage(Errorf("error%d", 1), "error2"),
|
|
||||||
"%+v",
|
|
||||||
[]string{"error1",
|
|
||||||
"github.com/pkg/errors.TestFormatWithMessage\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:278",
|
|
||||||
"error2"},
|
|
||||||
}, {
|
|
||||||
WithMessage(WithStack(io.EOF), "error"),
|
|
||||||
"%+v",
|
|
||||||
[]string{
|
|
||||||
"EOF",
|
|
||||||
"github.com/pkg/errors.TestFormatWithMessage\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:285",
|
|
||||||
"error"},
|
|
||||||
}, {
|
|
||||||
WithMessage(Wrap(WithStack(io.EOF), "inside-error"), "outside-error"),
|
|
||||||
"%+v",
|
|
||||||
[]string{
|
|
||||||
"EOF",
|
|
||||||
"github.com/pkg/errors.TestFormatWithMessage\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:293",
|
|
||||||
"inside-error",
|
|
||||||
"github.com/pkg/errors.TestFormatWithMessage\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:293",
|
|
||||||
"outside-error"},
|
|
||||||
}}
|
|
||||||
|
|
||||||
for i, tt := range tests {
|
|
||||||
testFormatCompleteCompare(t, i, tt.error, tt.format, tt.want, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFormatGeneric(t *testing.T) {
|
|
||||||
starts := []struct {
|
|
||||||
err error
|
|
||||||
want []string
|
|
||||||
}{
|
|
||||||
{New("new-error"), []string{
|
|
||||||
"new-error",
|
|
||||||
"github.com/pkg/errors.TestFormatGeneric\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:315"},
|
|
||||||
}, {Errorf("errorf-error"), []string{
|
|
||||||
"errorf-error",
|
|
||||||
"github.com/pkg/errors.TestFormatGeneric\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/format_test.go:319"},
|
|
||||||
}, {errors.New("errors-new-error"), []string{
|
|
||||||
"errors-new-error"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
wrappers := []wrapper{
|
|
||||||
{
|
|
||||||
func(err error) error { return WithMessage(err, "with-message") },
|
|
||||||
[]string{"with-message"},
|
|
||||||
}, {
|
|
||||||
func(err error) error { return WithStack(err) },
|
|
||||||
[]string{
|
|
||||||
"github.com/pkg/errors.(func·002|TestFormatGeneric.func2)\n\t" +
|
|
||||||
".+/github.com/pkg/errors/format_test.go:333",
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
func(err error) error { return Wrap(err, "wrap-error") },
|
|
||||||
[]string{
|
|
||||||
"wrap-error",
|
|
||||||
"github.com/pkg/errors.(func·003|TestFormatGeneric.func3)\n\t" +
|
|
||||||
".+/github.com/pkg/errors/format_test.go:339",
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
func(err error) error { return Wrapf(err, "wrapf-error%d", 1) },
|
|
||||||
[]string{
|
|
||||||
"wrapf-error1",
|
|
||||||
"github.com/pkg/errors.(func·004|TestFormatGeneric.func4)\n\t" +
|
|
||||||
".+/github.com/pkg/errors/format_test.go:346",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for s := range starts {
|
|
||||||
err := starts[s].err
|
|
||||||
want := starts[s].want
|
|
||||||
testFormatCompleteCompare(t, s, err, "%+v", want, false)
|
|
||||||
testGenericRecursive(t, err, want, wrappers, 3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testFormatRegexp(t *testing.T, n int, arg interface{}, format, want string) {
|
|
||||||
got := fmt.Sprintf(format, arg)
|
|
||||||
gotLines := strings.SplitN(got, "\n", -1)
|
|
||||||
wantLines := strings.SplitN(want, "\n", -1)
|
|
||||||
|
|
||||||
if len(wantLines) > len(gotLines) {
|
|
||||||
t.Errorf("test %d: wantLines(%d) > gotLines(%d):\n got: %q\nwant: %q", n+1, len(wantLines), len(gotLines), got, want)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, w := range wantLines {
|
|
||||||
match, err := regexp.MatchString(w, gotLines[i])
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if !match {
|
|
||||||
t.Errorf("test %d: line %d: fmt.Sprintf(%q, err):\n got: %q\nwant: %q", n+1, i+1, format, got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var stackLineR = regexp.MustCompile(`\.`)
|
|
||||||
|
|
||||||
// parseBlocks parses input into a slice, where:
|
|
||||||
// - incase entry contains a newline, its a stacktrace
|
|
||||||
// - incase entry contains no newline, its a solo line.
|
|
||||||
//
|
|
||||||
// Detecting stack boundaries only works incase the WithStack-calls are
|
|
||||||
// to be found on the same line, thats why it is optionally here.
|
|
||||||
//
|
|
||||||
// Example use:
|
|
||||||
//
|
|
||||||
// for _, e := range blocks {
|
|
||||||
// if strings.ContainsAny(e, "\n") {
|
|
||||||
// // Match as stack
|
|
||||||
// } else {
|
|
||||||
// // Match as line
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
func parseBlocks(input string, detectStackboundaries bool) ([]string, error) {
|
|
||||||
var blocks []string
|
|
||||||
|
|
||||||
stack := ""
|
|
||||||
wasStack := false
|
|
||||||
lines := map[string]bool{} // already found lines
|
|
||||||
|
|
||||||
for _, l := range strings.Split(input, "\n") {
|
|
||||||
isStackLine := stackLineR.MatchString(l)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case !isStackLine && wasStack:
|
|
||||||
blocks = append(blocks, stack, l)
|
|
||||||
stack = ""
|
|
||||||
lines = map[string]bool{}
|
|
||||||
case isStackLine:
|
|
||||||
if wasStack {
|
|
||||||
// Detecting two stacks after another, possible cause lines match in
|
|
||||||
// our tests due to WithStack(WithStack(io.EOF)) on same line.
|
|
||||||
if detectStackboundaries {
|
|
||||||
if lines[l] {
|
|
||||||
if len(stack) == 0 {
|
|
||||||
return nil, errors.New("len of block must not be zero here")
|
|
||||||
}
|
|
||||||
|
|
||||||
blocks = append(blocks, stack)
|
|
||||||
stack = l
|
|
||||||
lines = map[string]bool{l: true}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stack = stack + "\n" + l
|
|
||||||
} else {
|
|
||||||
stack = l
|
|
||||||
}
|
|
||||||
lines[l] = true
|
|
||||||
case !isStackLine && !wasStack:
|
|
||||||
blocks = append(blocks, l)
|
|
||||||
default:
|
|
||||||
return nil, errors.New("must not happen")
|
|
||||||
}
|
|
||||||
|
|
||||||
wasStack = isStackLine
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use up stack
|
|
||||||
if stack != "" {
|
|
||||||
blocks = append(blocks, stack)
|
|
||||||
}
|
|
||||||
return blocks, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func testFormatCompleteCompare(t *testing.T, n int, arg interface{}, format string, want []string, detectStackBoundaries bool) {
|
|
||||||
gotStr := fmt.Sprintf(format, arg)
|
|
||||||
|
|
||||||
got, err := parseBlocks(gotStr, detectStackBoundaries)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(got) != len(want) {
|
|
||||||
t.Fatalf("test %d: fmt.Sprintf(%s, err) -> wrong number of blocks: got(%d) want(%d)\n got: %s\nwant: %s\ngotStr: %q",
|
|
||||||
n+1, format, len(got), len(want), prettyBlocks(got), prettyBlocks(want), gotStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range got {
|
|
||||||
if strings.ContainsAny(want[i], "\n") {
|
|
||||||
// Match as stack
|
|
||||||
match, err := regexp.MatchString(want[i], got[i])
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if !match {
|
|
||||||
t.Fatalf("test %d: block %d: fmt.Sprintf(%q, err):\ngot:\n%q\nwant:\n%q\nall-got:\n%s\nall-want:\n%s\n",
|
|
||||||
n+1, i+1, format, got[i], want[i], prettyBlocks(got), prettyBlocks(want))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Match as message
|
|
||||||
if got[i] != want[i] {
|
|
||||||
t.Fatalf("test %d: fmt.Sprintf(%s, err) at block %d got != want:\n got: %q\nwant: %q", n+1, format, i+1, got[i], want[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type wrapper struct {
|
|
||||||
wrap func(err error) error
|
|
||||||
want []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func prettyBlocks(blocks []string, prefix ...string) string {
|
|
||||||
var out []string
|
|
||||||
|
|
||||||
for _, b := range blocks {
|
|
||||||
out = append(out, fmt.Sprintf("%v", b))
|
|
||||||
}
|
|
||||||
|
|
||||||
return " " + strings.Join(out, "\n ")
|
|
||||||
}
|
|
||||||
|
|
||||||
func testGenericRecursive(t *testing.T, beforeErr error, beforeWant []string, list []wrapper, maxDepth int) {
|
|
||||||
if len(beforeWant) == 0 {
|
|
||||||
panic("beforeWant must not be empty")
|
|
||||||
}
|
|
||||||
for _, w := range list {
|
|
||||||
if len(w.want) == 0 {
|
|
||||||
panic("want must not be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
err := w.wrap(beforeErr)
|
|
||||||
|
|
||||||
// Copy required cause append(beforeWant, ..) modified beforeWant subtly.
|
|
||||||
beforeCopy := make([]string, len(beforeWant))
|
|
||||||
copy(beforeCopy, beforeWant)
|
|
||||||
|
|
||||||
beforeWant := beforeCopy
|
|
||||||
last := len(beforeWant) - 1
|
|
||||||
var want []string
|
|
||||||
|
|
||||||
// Merge two stacks behind each other.
|
|
||||||
if strings.ContainsAny(beforeWant[last], "\n") && strings.ContainsAny(w.want[0], "\n") {
|
|
||||||
want = append(beforeWant[:last], append([]string{beforeWant[last] + "((?s).*)" + w.want[0]}, w.want[1:]...)...)
|
|
||||||
} else {
|
|
||||||
want = append(beforeWant, w.want...)
|
|
||||||
}
|
|
||||||
|
|
||||||
testFormatCompleteCompare(t, maxDepth, err, "%+v", want, false)
|
|
||||||
if maxDepth > 0 {
|
|
||||||
testGenericRecursive(t, err, want, list, maxDepth-1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,292 +0,0 @@
|
||||||
package errors
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"runtime"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var initpc, _, _, _ = runtime.Caller(0)
|
|
||||||
|
|
||||||
func TestFrameLine(t *testing.T) {
|
|
||||||
var tests = []struct {
|
|
||||||
Frame
|
|
||||||
want int
|
|
||||||
}{{
|
|
||||||
Frame(initpc),
|
|
||||||
9,
|
|
||||||
}, {
|
|
||||||
func() Frame {
|
|
||||||
var pc, _, _, _ = runtime.Caller(0)
|
|
||||||
return Frame(pc)
|
|
||||||
}(),
|
|
||||||
20,
|
|
||||||
}, {
|
|
||||||
func() Frame {
|
|
||||||
var pc, _, _, _ = runtime.Caller(1)
|
|
||||||
return Frame(pc)
|
|
||||||
}(),
|
|
||||||
28,
|
|
||||||
}, {
|
|
||||||
Frame(0), // invalid PC
|
|
||||||
0,
|
|
||||||
}}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
got := tt.Frame.line()
|
|
||||||
want := tt.want
|
|
||||||
if want != got {
|
|
||||||
t.Errorf("Frame(%v): want: %v, got: %v", uintptr(tt.Frame), want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type X struct{}
|
|
||||||
|
|
||||||
func (x X) val() Frame {
|
|
||||||
var pc, _, _, _ = runtime.Caller(0)
|
|
||||||
return Frame(pc)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *X) ptr() Frame {
|
|
||||||
var pc, _, _, _ = runtime.Caller(0)
|
|
||||||
return Frame(pc)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFrameFormat(t *testing.T) {
|
|
||||||
var tests = []struct {
|
|
||||||
Frame
|
|
||||||
format string
|
|
||||||
want string
|
|
||||||
}{{
|
|
||||||
Frame(initpc),
|
|
||||||
"%s",
|
|
||||||
"stack_test.go",
|
|
||||||
}, {
|
|
||||||
Frame(initpc),
|
|
||||||
"%+s",
|
|
||||||
"github.com/pkg/errors.init\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/stack_test.go",
|
|
||||||
}, {
|
|
||||||
Frame(0),
|
|
||||||
"%s",
|
|
||||||
"unknown",
|
|
||||||
}, {
|
|
||||||
Frame(0),
|
|
||||||
"%+s",
|
|
||||||
"unknown",
|
|
||||||
}, {
|
|
||||||
Frame(initpc),
|
|
||||||
"%d",
|
|
||||||
"9",
|
|
||||||
}, {
|
|
||||||
Frame(0),
|
|
||||||
"%d",
|
|
||||||
"0",
|
|
||||||
}, {
|
|
||||||
Frame(initpc),
|
|
||||||
"%n",
|
|
||||||
"init",
|
|
||||||
}, {
|
|
||||||
func() Frame {
|
|
||||||
var x X
|
|
||||||
return x.ptr()
|
|
||||||
}(),
|
|
||||||
"%n",
|
|
||||||
`\(\*X\).ptr`,
|
|
||||||
}, {
|
|
||||||
func() Frame {
|
|
||||||
var x X
|
|
||||||
return x.val()
|
|
||||||
}(),
|
|
||||||
"%n",
|
|
||||||
"X.val",
|
|
||||||
}, {
|
|
||||||
Frame(0),
|
|
||||||
"%n",
|
|
||||||
"",
|
|
||||||
}, {
|
|
||||||
Frame(initpc),
|
|
||||||
"%v",
|
|
||||||
"stack_test.go:9",
|
|
||||||
}, {
|
|
||||||
Frame(initpc),
|
|
||||||
"%+v",
|
|
||||||
"github.com/pkg/errors.init\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/stack_test.go:9",
|
|
||||||
}, {
|
|
||||||
Frame(0),
|
|
||||||
"%v",
|
|
||||||
"unknown:0",
|
|
||||||
}}
|
|
||||||
|
|
||||||
for i, tt := range tests {
|
|
||||||
testFormatRegexp(t, i, tt.Frame, tt.format, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFuncname(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name, want string
|
|
||||||
}{
|
|
||||||
{"", ""},
|
|
||||||
{"runtime.main", "main"},
|
|
||||||
{"github.com/pkg/errors.funcname", "funcname"},
|
|
||||||
{"funcname", "funcname"},
|
|
||||||
{"io.copyBuffer", "copyBuffer"},
|
|
||||||
{"main.(*R).Write", "(*R).Write"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
got := funcname(tt.name)
|
|
||||||
want := tt.want
|
|
||||||
if got != want {
|
|
||||||
t.Errorf("funcname(%q): want: %q, got %q", tt.name, want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTrimGOPATH(t *testing.T) {
|
|
||||||
var tests = []struct {
|
|
||||||
Frame
|
|
||||||
want string
|
|
||||||
}{{
|
|
||||||
Frame(initpc),
|
|
||||||
"github.com/pkg/errors/stack_test.go",
|
|
||||||
}}
|
|
||||||
|
|
||||||
for i, tt := range tests {
|
|
||||||
pc := tt.Frame.pc()
|
|
||||||
fn := runtime.FuncForPC(pc)
|
|
||||||
file, _ := fn.FileLine(pc)
|
|
||||||
got := trimGOPATH(fn.Name(), file)
|
|
||||||
testFormatRegexp(t, i, got, "%s", tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStackTrace(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
err error
|
|
||||||
want []string
|
|
||||||
}{{
|
|
||||||
New("ooh"), []string{
|
|
||||||
"github.com/pkg/errors.TestStackTrace\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/stack_test.go:172",
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
Wrap(New("ooh"), "ahh"), []string{
|
|
||||||
"github.com/pkg/errors.TestStackTrace\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/stack_test.go:177", // this is the stack of Wrap, not New
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
Cause(Wrap(New("ooh"), "ahh")), []string{
|
|
||||||
"github.com/pkg/errors.TestStackTrace\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/stack_test.go:182", // this is the stack of New
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
func() error { return New("ooh") }(), []string{
|
|
||||||
`github.com/pkg/errors.(func·009|TestStackTrace.func1)` +
|
|
||||||
"\n\t.+/github.com/pkg/errors/stack_test.go:187", // this is the stack of New
|
|
||||||
"github.com/pkg/errors.TestStackTrace\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/stack_test.go:187", // this is the stack of New's caller
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
Cause(func() error {
|
|
||||||
return func() error {
|
|
||||||
return Errorf("hello %s", fmt.Sprintf("world"))
|
|
||||||
}()
|
|
||||||
}()), []string{
|
|
||||||
`github.com/pkg/errors.(func·010|TestStackTrace.func2.1)` +
|
|
||||||
"\n\t.+/github.com/pkg/errors/stack_test.go:196", // this is the stack of Errorf
|
|
||||||
`github.com/pkg/errors.(func·011|TestStackTrace.func2)` +
|
|
||||||
"\n\t.+/github.com/pkg/errors/stack_test.go:197", // this is the stack of Errorf's caller
|
|
||||||
"github.com/pkg/errors.TestStackTrace\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/stack_test.go:198", // this is the stack of Errorf's caller's caller
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
for i, tt := range tests {
|
|
||||||
x, ok := tt.err.(interface {
|
|
||||||
StackTrace() StackTrace
|
|
||||||
})
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("expected %#v to implement StackTrace() StackTrace", tt.err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
st := x.StackTrace()
|
|
||||||
for j, want := range tt.want {
|
|
||||||
testFormatRegexp(t, i, st[j], "%+v", want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func stackTrace() StackTrace {
|
|
||||||
const depth = 8
|
|
||||||
var pcs [depth]uintptr
|
|
||||||
n := runtime.Callers(1, pcs[:])
|
|
||||||
var st stack = pcs[0:n]
|
|
||||||
return st.StackTrace()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStackTraceFormat(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
StackTrace
|
|
||||||
format string
|
|
||||||
want string
|
|
||||||
}{{
|
|
||||||
nil,
|
|
||||||
"%s",
|
|
||||||
`\[\]`,
|
|
||||||
}, {
|
|
||||||
nil,
|
|
||||||
"%v",
|
|
||||||
`\[\]`,
|
|
||||||
}, {
|
|
||||||
nil,
|
|
||||||
"%+v",
|
|
||||||
"",
|
|
||||||
}, {
|
|
||||||
nil,
|
|
||||||
"%#v",
|
|
||||||
`\[\]errors.Frame\(nil\)`,
|
|
||||||
}, {
|
|
||||||
make(StackTrace, 0),
|
|
||||||
"%s",
|
|
||||||
`\[\]`,
|
|
||||||
}, {
|
|
||||||
make(StackTrace, 0),
|
|
||||||
"%v",
|
|
||||||
`\[\]`,
|
|
||||||
}, {
|
|
||||||
make(StackTrace, 0),
|
|
||||||
"%+v",
|
|
||||||
"",
|
|
||||||
}, {
|
|
||||||
make(StackTrace, 0),
|
|
||||||
"%#v",
|
|
||||||
`\[\]errors.Frame{}`,
|
|
||||||
}, {
|
|
||||||
stackTrace()[:2],
|
|
||||||
"%s",
|
|
||||||
`\[stack_test.go stack_test.go\]`,
|
|
||||||
}, {
|
|
||||||
stackTrace()[:2],
|
|
||||||
"%v",
|
|
||||||
`\[stack_test.go:225 stack_test.go:272\]`,
|
|
||||||
}, {
|
|
||||||
stackTrace()[:2],
|
|
||||||
"%+v",
|
|
||||||
"\n" +
|
|
||||||
"github.com/pkg/errors.stackTrace\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/stack_test.go:225\n" +
|
|
||||||
"github.com/pkg/errors.TestStackTraceFormat\n" +
|
|
||||||
"\t.+/github.com/pkg/errors/stack_test.go:276",
|
|
||||||
}, {
|
|
||||||
stackTrace()[:2],
|
|
||||||
"%#v",
|
|
||||||
`\[\]errors.Frame{stack_test.go:225, stack_test.go:284}`,
|
|
||||||
}}
|
|
||||||
|
|
||||||
for i, tt := range tests {
|
|
||||||
testFormatRegexp(t, i, tt.StackTrace, tt.format, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,772 +0,0 @@
|
||||||
// Package difflib is a partial port of Python difflib module.
|
|
||||||
//
|
|
||||||
// It provides tools to compare sequences of strings and generate textual diffs.
|
|
||||||
//
|
|
||||||
// The following class and functions have been ported:
|
|
||||||
//
|
|
||||||
// - SequenceMatcher
|
|
||||||
//
|
|
||||||
// - unified_diff
|
|
||||||
//
|
|
||||||
// - context_diff
|
|
||||||
//
|
|
||||||
// Getting unified diffs was the main goal of the port. Keep in mind this code
|
|
||||||
// is mostly suitable to output text differences in a human friendly way, there
|
|
||||||
// are no guarantees generated diffs are consumable by patch(1).
|
|
||||||
package difflib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func min(a, b int) int {
|
|
||||||
if a < b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func max(a, b int) int {
|
|
||||||
if a > b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func calculateRatio(matches, length int) float64 {
|
|
||||||
if length > 0 {
|
|
||||||
return 2.0 * float64(matches) / float64(length)
|
|
||||||
}
|
|
||||||
return 1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
type Match struct {
|
|
||||||
A int
|
|
||||||
B int
|
|
||||||
Size int
|
|
||||||
}
|
|
||||||
|
|
||||||
type OpCode struct {
|
|
||||||
Tag byte
|
|
||||||
I1 int
|
|
||||||
I2 int
|
|
||||||
J1 int
|
|
||||||
J2 int
|
|
||||||
}
|
|
||||||
|
|
||||||
// SequenceMatcher compares sequence of strings. The basic
|
|
||||||
// algorithm predates, and is a little fancier than, an algorithm
|
|
||||||
// published in the late 1980's by Ratcliff and Obershelp under the
|
|
||||||
// hyperbolic name "gestalt pattern matching". The basic idea is to find
|
|
||||||
// the longest contiguous matching subsequence that contains no "junk"
|
|
||||||
// elements (R-O doesn't address junk). The same idea is then applied
|
|
||||||
// recursively to the pieces of the sequences to the left and to the right
|
|
||||||
// of the matching subsequence. This does not yield minimal edit
|
|
||||||
// sequences, but does tend to yield matches that "look right" to people.
|
|
||||||
//
|
|
||||||
// SequenceMatcher tries to compute a "human-friendly diff" between two
|
|
||||||
// sequences. Unlike e.g. UNIX(tm) diff, the fundamental notion is the
|
|
||||||
// longest *contiguous* & junk-free matching subsequence. That's what
|
|
||||||
// catches peoples' eyes. The Windows(tm) windiff has another interesting
|
|
||||||
// notion, pairing up elements that appear uniquely in each sequence.
|
|
||||||
// That, and the method here, appear to yield more intuitive difference
|
|
||||||
// reports than does diff. This method appears to be the least vulnerable
|
|
||||||
// to synching up on blocks of "junk lines", though (like blank lines in
|
|
||||||
// ordinary text files, or maybe "<P>" lines in HTML files). That may be
|
|
||||||
// because this is the only method of the 3 that has a *concept* of
|
|
||||||
// "junk" <wink>.
|
|
||||||
//
|
|
||||||
// Timing: Basic R-O is cubic time worst case and quadratic time expected
|
|
||||||
// case. SequenceMatcher is quadratic time for the worst case and has
|
|
||||||
// expected-case behavior dependent in a complicated way on how many
|
|
||||||
// elements the sequences have in common; best case time is linear.
|
|
||||||
type SequenceMatcher struct {
|
|
||||||
a []string
|
|
||||||
b []string
|
|
||||||
b2j map[string][]int
|
|
||||||
IsJunk func(string) bool
|
|
||||||
autoJunk bool
|
|
||||||
bJunk map[string]struct{}
|
|
||||||
matchingBlocks []Match
|
|
||||||
fullBCount map[string]int
|
|
||||||
bPopular map[string]struct{}
|
|
||||||
opCodes []OpCode
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMatcher(a, b []string) *SequenceMatcher {
|
|
||||||
m := SequenceMatcher{autoJunk: true}
|
|
||||||
m.SetSeqs(a, b)
|
|
||||||
return &m
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMatcherWithJunk(a, b []string, autoJunk bool,
|
|
||||||
isJunk func(string) bool) *SequenceMatcher {
|
|
||||||
|
|
||||||
m := SequenceMatcher{IsJunk: isJunk, autoJunk: autoJunk}
|
|
||||||
m.SetSeqs(a, b)
|
|
||||||
return &m
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set two sequences to be compared.
|
|
||||||
func (m *SequenceMatcher) SetSeqs(a, b []string) {
|
|
||||||
m.SetSeq1(a)
|
|
||||||
m.SetSeq2(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the first sequence to be compared. The second sequence to be compared is
|
|
||||||
// not changed.
|
|
||||||
//
|
|
||||||
// SequenceMatcher computes and caches detailed information about the second
|
|
||||||
// sequence, so if you want to compare one sequence S against many sequences,
|
|
||||||
// use .SetSeq2(s) once and call .SetSeq1(x) repeatedly for each of the other
|
|
||||||
// sequences.
|
|
||||||
//
|
|
||||||
// See also SetSeqs() and SetSeq2().
|
|
||||||
func (m *SequenceMatcher) SetSeq1(a []string) {
|
|
||||||
if &a == &m.a {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
m.a = a
|
|
||||||
m.matchingBlocks = nil
|
|
||||||
m.opCodes = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the second sequence to be compared. The first sequence to be compared is
|
|
||||||
// not changed.
|
|
||||||
func (m *SequenceMatcher) SetSeq2(b []string) {
|
|
||||||
if &b == &m.b {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
m.b = b
|
|
||||||
m.matchingBlocks = nil
|
|
||||||
m.opCodes = nil
|
|
||||||
m.fullBCount = nil
|
|
||||||
m.chainB()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *SequenceMatcher) chainB() {
|
|
||||||
// Populate line -> index mapping
|
|
||||||
b2j := map[string][]int{}
|
|
||||||
for i, s := range m.b {
|
|
||||||
indices := b2j[s]
|
|
||||||
indices = append(indices, i)
|
|
||||||
b2j[s] = indices
|
|
||||||
}
|
|
||||||
|
|
||||||
// Purge junk elements
|
|
||||||
m.bJunk = map[string]struct{}{}
|
|
||||||
if m.IsJunk != nil {
|
|
||||||
junk := m.bJunk
|
|
||||||
for s, _ := range b2j {
|
|
||||||
if m.IsJunk(s) {
|
|
||||||
junk[s] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for s, _ := range junk {
|
|
||||||
delete(b2j, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Purge remaining popular elements
|
|
||||||
popular := map[string]struct{}{}
|
|
||||||
n := len(m.b)
|
|
||||||
if m.autoJunk && n >= 200 {
|
|
||||||
ntest := n/100 + 1
|
|
||||||
for s, indices := range b2j {
|
|
||||||
if len(indices) > ntest {
|
|
||||||
popular[s] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for s, _ := range popular {
|
|
||||||
delete(b2j, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m.bPopular = popular
|
|
||||||
m.b2j = b2j
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *SequenceMatcher) isBJunk(s string) bool {
|
|
||||||
_, ok := m.bJunk[s]
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find longest matching block in a[alo:ahi] and b[blo:bhi].
|
|
||||||
//
|
|
||||||
// If IsJunk is not defined:
|
|
||||||
//
|
|
||||||
// Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where
|
|
||||||
// alo <= i <= i+k <= ahi
|
|
||||||
// blo <= j <= j+k <= bhi
|
|
||||||
// and for all (i',j',k') meeting those conditions,
|
|
||||||
// k >= k'
|
|
||||||
// i <= i'
|
|
||||||
// and if i == i', j <= j'
|
|
||||||
//
|
|
||||||
// In other words, of all maximal matching blocks, return one that
|
|
||||||
// starts earliest in a, and of all those maximal matching blocks that
|
|
||||||
// start earliest in a, return the one that starts earliest in b.
|
|
||||||
//
|
|
||||||
// If IsJunk is defined, first the longest matching block is
|
|
||||||
// determined as above, but with the additional restriction that no
|
|
||||||
// junk element appears in the block. Then that block is extended as
|
|
||||||
// far as possible by matching (only) junk elements on both sides. So
|
|
||||||
// the resulting block never matches on junk except as identical junk
|
|
||||||
// happens to be adjacent to an "interesting" match.
|
|
||||||
//
|
|
||||||
// If no blocks match, return (alo, blo, 0).
|
|
||||||
func (m *SequenceMatcher) findLongestMatch(alo, ahi, blo, bhi int) Match {
|
|
||||||
// CAUTION: stripping common prefix or suffix would be incorrect.
|
|
||||||
// E.g.,
|
|
||||||
// ab
|
|
||||||
// acab
|
|
||||||
// Longest matching block is "ab", but if common prefix is
|
|
||||||
// stripped, it's "a" (tied with "b"). UNIX(tm) diff does so
|
|
||||||
// strip, so ends up claiming that ab is changed to acab by
|
|
||||||
// inserting "ca" in the middle. That's minimal but unintuitive:
|
|
||||||
// "it's obvious" that someone inserted "ac" at the front.
|
|
||||||
// Windiff ends up at the same place as diff, but by pairing up
|
|
||||||
// the unique 'b's and then matching the first two 'a's.
|
|
||||||
besti, bestj, bestsize := alo, blo, 0
|
|
||||||
|
|
||||||
// find longest junk-free match
|
|
||||||
// during an iteration of the loop, j2len[j] = length of longest
|
|
||||||
// junk-free match ending with a[i-1] and b[j]
|
|
||||||
j2len := map[int]int{}
|
|
||||||
for i := alo; i != ahi; i++ {
|
|
||||||
// look at all instances of a[i] in b; note that because
|
|
||||||
// b2j has no junk keys, the loop is skipped if a[i] is junk
|
|
||||||
newj2len := map[int]int{}
|
|
||||||
for _, j := range m.b2j[m.a[i]] {
|
|
||||||
// a[i] matches b[j]
|
|
||||||
if j < blo {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if j >= bhi {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
k := j2len[j-1] + 1
|
|
||||||
newj2len[j] = k
|
|
||||||
if k > bestsize {
|
|
||||||
besti, bestj, bestsize = i-k+1, j-k+1, k
|
|
||||||
}
|
|
||||||
}
|
|
||||||
j2len = newj2len
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extend the best by non-junk elements on each end. In particular,
|
|
||||||
// "popular" non-junk elements aren't in b2j, which greatly speeds
|
|
||||||
// the inner loop above, but also means "the best" match so far
|
|
||||||
// doesn't contain any junk *or* popular non-junk elements.
|
|
||||||
for besti > alo && bestj > blo && !m.isBJunk(m.b[bestj-1]) &&
|
|
||||||
m.a[besti-1] == m.b[bestj-1] {
|
|
||||||
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
|
|
||||||
}
|
|
||||||
for besti+bestsize < ahi && bestj+bestsize < bhi &&
|
|
||||||
!m.isBJunk(m.b[bestj+bestsize]) &&
|
|
||||||
m.a[besti+bestsize] == m.b[bestj+bestsize] {
|
|
||||||
bestsize += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now that we have a wholly interesting match (albeit possibly
|
|
||||||
// empty!), we may as well suck up the matching junk on each
|
|
||||||
// side of it too. Can't think of a good reason not to, and it
|
|
||||||
// saves post-processing the (possibly considerable) expense of
|
|
||||||
// figuring out what to do with it. In the case of an empty
|
|
||||||
// interesting match, this is clearly the right thing to do,
|
|
||||||
// because no other kind of match is possible in the regions.
|
|
||||||
for besti > alo && bestj > blo && m.isBJunk(m.b[bestj-1]) &&
|
|
||||||
m.a[besti-1] == m.b[bestj-1] {
|
|
||||||
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
|
|
||||||
}
|
|
||||||
for besti+bestsize < ahi && bestj+bestsize < bhi &&
|
|
||||||
m.isBJunk(m.b[bestj+bestsize]) &&
|
|
||||||
m.a[besti+bestsize] == m.b[bestj+bestsize] {
|
|
||||||
bestsize += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return Match{A: besti, B: bestj, Size: bestsize}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return list of triples describing matching subsequences.
|
|
||||||
//
|
|
||||||
// Each triple is of the form (i, j, n), and means that
|
|
||||||
// a[i:i+n] == b[j:j+n]. The triples are monotonically increasing in
|
|
||||||
// i and in j. It's also guaranteed that if (i, j, n) and (i', j', n') are
|
|
||||||
// adjacent triples in the list, and the second is not the last triple in the
|
|
||||||
// list, then i+n != i' or j+n != j'. IOW, adjacent triples never describe
|
|
||||||
// adjacent equal blocks.
|
|
||||||
//
|
|
||||||
// The last triple is a dummy, (len(a), len(b), 0), and is the only
|
|
||||||
// triple with n==0.
|
|
||||||
func (m *SequenceMatcher) GetMatchingBlocks() []Match {
|
|
||||||
if m.matchingBlocks != nil {
|
|
||||||
return m.matchingBlocks
|
|
||||||
}
|
|
||||||
|
|
||||||
var matchBlocks func(alo, ahi, blo, bhi int, matched []Match) []Match
|
|
||||||
matchBlocks = func(alo, ahi, blo, bhi int, matched []Match) []Match {
|
|
||||||
match := m.findLongestMatch(alo, ahi, blo, bhi)
|
|
||||||
i, j, k := match.A, match.B, match.Size
|
|
||||||
if match.Size > 0 {
|
|
||||||
if alo < i && blo < j {
|
|
||||||
matched = matchBlocks(alo, i, blo, j, matched)
|
|
||||||
}
|
|
||||||
matched = append(matched, match)
|
|
||||||
if i+k < ahi && j+k < bhi {
|
|
||||||
matched = matchBlocks(i+k, ahi, j+k, bhi, matched)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return matched
|
|
||||||
}
|
|
||||||
matched := matchBlocks(0, len(m.a), 0, len(m.b), nil)
|
|
||||||
|
|
||||||
// It's possible that we have adjacent equal blocks in the
|
|
||||||
// matching_blocks list now.
|
|
||||||
nonAdjacent := []Match{}
|
|
||||||
i1, j1, k1 := 0, 0, 0
|
|
||||||
for _, b := range matched {
|
|
||||||
// Is this block adjacent to i1, j1, k1?
|
|
||||||
i2, j2, k2 := b.A, b.B, b.Size
|
|
||||||
if i1+k1 == i2 && j1+k1 == j2 {
|
|
||||||
// Yes, so collapse them -- this just increases the length of
|
|
||||||
// the first block by the length of the second, and the first
|
|
||||||
// block so lengthened remains the block to compare against.
|
|
||||||
k1 += k2
|
|
||||||
} else {
|
|
||||||
// Not adjacent. Remember the first block (k1==0 means it's
|
|
||||||
// the dummy we started with), and make the second block the
|
|
||||||
// new block to compare against.
|
|
||||||
if k1 > 0 {
|
|
||||||
nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
|
|
||||||
}
|
|
||||||
i1, j1, k1 = i2, j2, k2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if k1 > 0 {
|
|
||||||
nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
|
|
||||||
}
|
|
||||||
|
|
||||||
nonAdjacent = append(nonAdjacent, Match{len(m.a), len(m.b), 0})
|
|
||||||
m.matchingBlocks = nonAdjacent
|
|
||||||
return m.matchingBlocks
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return list of 5-tuples describing how to turn a into b.
|
|
||||||
//
|
|
||||||
// Each tuple is of the form (tag, i1, i2, j1, j2). The first tuple
|
|
||||||
// has i1 == j1 == 0, and remaining tuples have i1 == the i2 from the
|
|
||||||
// tuple preceding it, and likewise for j1 == the previous j2.
|
|
||||||
//
|
|
||||||
// The tags are characters, with these meanings:
|
|
||||||
//
|
|
||||||
// 'r' (replace): a[i1:i2] should be replaced by b[j1:j2]
|
|
||||||
//
|
|
||||||
// 'd' (delete): a[i1:i2] should be deleted, j1==j2 in this case.
|
|
||||||
//
|
|
||||||
// 'i' (insert): b[j1:j2] should be inserted at a[i1:i1], i1==i2 in this case.
|
|
||||||
//
|
|
||||||
// 'e' (equal): a[i1:i2] == b[j1:j2]
|
|
||||||
func (m *SequenceMatcher) GetOpCodes() []OpCode {
|
|
||||||
if m.opCodes != nil {
|
|
||||||
return m.opCodes
|
|
||||||
}
|
|
||||||
i, j := 0, 0
|
|
||||||
matching := m.GetMatchingBlocks()
|
|
||||||
opCodes := make([]OpCode, 0, len(matching))
|
|
||||||
for _, m := range matching {
|
|
||||||
// invariant: we've pumped out correct diffs to change
|
|
||||||
// a[:i] into b[:j], and the next matching block is
|
|
||||||
// a[ai:ai+size] == b[bj:bj+size]. So we need to pump
|
|
||||||
// out a diff to change a[i:ai] into b[j:bj], pump out
|
|
||||||
// the matching block, and move (i,j) beyond the match
|
|
||||||
ai, bj, size := m.A, m.B, m.Size
|
|
||||||
tag := byte(0)
|
|
||||||
if i < ai && j < bj {
|
|
||||||
tag = 'r'
|
|
||||||
} else if i < ai {
|
|
||||||
tag = 'd'
|
|
||||||
} else if j < bj {
|
|
||||||
tag = 'i'
|
|
||||||
}
|
|
||||||
if tag > 0 {
|
|
||||||
opCodes = append(opCodes, OpCode{tag, i, ai, j, bj})
|
|
||||||
}
|
|
||||||
i, j = ai+size, bj+size
|
|
||||||
// the list of matching blocks is terminated by a
|
|
||||||
// sentinel with size 0
|
|
||||||
if size > 0 {
|
|
||||||
opCodes = append(opCodes, OpCode{'e', ai, i, bj, j})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m.opCodes = opCodes
|
|
||||||
return m.opCodes
|
|
||||||
}
|
|
||||||
|
|
||||||
// Isolate change clusters by eliminating ranges with no changes.
|
|
||||||
//
|
|
||||||
// Return a generator of groups with up to n lines of context.
|
|
||||||
// Each group is in the same format as returned by GetOpCodes().
|
|
||||||
func (m *SequenceMatcher) GetGroupedOpCodes(n int) [][]OpCode {
|
|
||||||
if n < 0 {
|
|
||||||
n = 3
|
|
||||||
}
|
|
||||||
codes := m.GetOpCodes()
|
|
||||||
if len(codes) == 0 {
|
|
||||||
codes = []OpCode{OpCode{'e', 0, 1, 0, 1}}
|
|
||||||
}
|
|
||||||
// Fixup leading and trailing groups if they show no changes.
|
|
||||||
if codes[0].Tag == 'e' {
|
|
||||||
c := codes[0]
|
|
||||||
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
|
||||||
codes[0] = OpCode{c.Tag, max(i1, i2-n), i2, max(j1, j2-n), j2}
|
|
||||||
}
|
|
||||||
if codes[len(codes)-1].Tag == 'e' {
|
|
||||||
c := codes[len(codes)-1]
|
|
||||||
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
|
||||||
codes[len(codes)-1] = OpCode{c.Tag, i1, min(i2, i1+n), j1, min(j2, j1+n)}
|
|
||||||
}
|
|
||||||
nn := n + n
|
|
||||||
groups := [][]OpCode{}
|
|
||||||
group := []OpCode{}
|
|
||||||
for _, c := range codes {
|
|
||||||
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
|
||||||
// End the current group and start a new one whenever
|
|
||||||
// there is a large range with no changes.
|
|
||||||
if c.Tag == 'e' && i2-i1 > nn {
|
|
||||||
group = append(group, OpCode{c.Tag, i1, min(i2, i1+n),
|
|
||||||
j1, min(j2, j1+n)})
|
|
||||||
groups = append(groups, group)
|
|
||||||
group = []OpCode{}
|
|
||||||
i1, j1 = max(i1, i2-n), max(j1, j2-n)
|
|
||||||
}
|
|
||||||
group = append(group, OpCode{c.Tag, i1, i2, j1, j2})
|
|
||||||
}
|
|
||||||
if len(group) > 0 && !(len(group) == 1 && group[0].Tag == 'e') {
|
|
||||||
groups = append(groups, group)
|
|
||||||
}
|
|
||||||
return groups
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return a measure of the sequences' similarity (float in [0,1]).
|
|
||||||
//
|
|
||||||
// Where T is the total number of elements in both sequences, and
|
|
||||||
// M is the number of matches, this is 2.0*M / T.
|
|
||||||
// Note that this is 1 if the sequences are identical, and 0 if
|
|
||||||
// they have nothing in common.
|
|
||||||
//
|
|
||||||
// .Ratio() is expensive to compute if you haven't already computed
|
|
||||||
// .GetMatchingBlocks() or .GetOpCodes(), in which case you may
|
|
||||||
// want to try .QuickRatio() or .RealQuickRation() first to get an
|
|
||||||
// upper bound.
|
|
||||||
func (m *SequenceMatcher) Ratio() float64 {
|
|
||||||
matches := 0
|
|
||||||
for _, m := range m.GetMatchingBlocks() {
|
|
||||||
matches += m.Size
|
|
||||||
}
|
|
||||||
return calculateRatio(matches, len(m.a)+len(m.b))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return an upper bound on ratio() relatively quickly.
|
|
||||||
//
|
|
||||||
// This isn't defined beyond that it is an upper bound on .Ratio(), and
|
|
||||||
// is faster to compute.
|
|
||||||
func (m *SequenceMatcher) QuickRatio() float64 {
|
|
||||||
// viewing a and b as multisets, set matches to the cardinality
|
|
||||||
// of their intersection; this counts the number of matches
|
|
||||||
// without regard to order, so is clearly an upper bound
|
|
||||||
if m.fullBCount == nil {
|
|
||||||
m.fullBCount = map[string]int{}
|
|
||||||
for _, s := range m.b {
|
|
||||||
m.fullBCount[s] = m.fullBCount[s] + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// avail[x] is the number of times x appears in 'b' less the
|
|
||||||
// number of times we've seen it in 'a' so far ... kinda
|
|
||||||
avail := map[string]int{}
|
|
||||||
matches := 0
|
|
||||||
for _, s := range m.a {
|
|
||||||
n, ok := avail[s]
|
|
||||||
if !ok {
|
|
||||||
n = m.fullBCount[s]
|
|
||||||
}
|
|
||||||
avail[s] = n - 1
|
|
||||||
if n > 0 {
|
|
||||||
matches += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return calculateRatio(matches, len(m.a)+len(m.b))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return an upper bound on ratio() very quickly.
|
|
||||||
//
|
|
||||||
// This isn't defined beyond that it is an upper bound on .Ratio(), and
|
|
||||||
// is faster to compute than either .Ratio() or .QuickRatio().
|
|
||||||
func (m *SequenceMatcher) RealQuickRatio() float64 {
|
|
||||||
la, lb := len(m.a), len(m.b)
|
|
||||||
return calculateRatio(min(la, lb), la+lb)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert range to the "ed" format
|
|
||||||
func formatRangeUnified(start, stop int) string {
|
|
||||||
// Per the diff spec at http://www.unix.org/single_unix_specification/
|
|
||||||
beginning := start + 1 // lines start numbering with one
|
|
||||||
length := stop - start
|
|
||||||
if length == 1 {
|
|
||||||
return fmt.Sprintf("%d", beginning)
|
|
||||||
}
|
|
||||||
if length == 0 {
|
|
||||||
beginning -= 1 // empty ranges begin at line just before the range
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%d,%d", beginning, length)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unified diff parameters
|
|
||||||
type UnifiedDiff struct {
|
|
||||||
A []string // First sequence lines
|
|
||||||
FromFile string // First file name
|
|
||||||
FromDate string // First file time
|
|
||||||
B []string // Second sequence lines
|
|
||||||
ToFile string // Second file name
|
|
||||||
ToDate string // Second file time
|
|
||||||
Eol string // Headers end of line, defaults to LF
|
|
||||||
Context int // Number of context lines
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare two sequences of lines; generate the delta as a unified diff.
|
|
||||||
//
|
|
||||||
// Unified diffs are a compact way of showing line changes and a few
|
|
||||||
// lines of context. The number of context lines is set by 'n' which
|
|
||||||
// defaults to three.
|
|
||||||
//
|
|
||||||
// By default, the diff control lines (those with ---, +++, or @@) are
|
|
||||||
// created with a trailing newline. This is helpful so that inputs
|
|
||||||
// created from file.readlines() result in diffs that are suitable for
|
|
||||||
// file.writelines() since both the inputs and outputs have trailing
|
|
||||||
// newlines.
|
|
||||||
//
|
|
||||||
// For inputs that do not have trailing newlines, set the lineterm
|
|
||||||
// argument to "" so that the output will be uniformly newline free.
|
|
||||||
//
|
|
||||||
// The unidiff format normally has a header for filenames and modification
|
|
||||||
// times. Any or all of these may be specified using strings for
|
|
||||||
// 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'.
|
|
||||||
// The modification times are normally expressed in the ISO 8601 format.
|
|
||||||
func WriteUnifiedDiff(writer io.Writer, diff UnifiedDiff) error {
|
|
||||||
buf := bufio.NewWriter(writer)
|
|
||||||
defer buf.Flush()
|
|
||||||
wf := func(format string, args ...interface{}) error {
|
|
||||||
_, err := buf.WriteString(fmt.Sprintf(format, args...))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ws := func(s string) error {
|
|
||||||
_, err := buf.WriteString(s)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(diff.Eol) == 0 {
|
|
||||||
diff.Eol = "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
started := false
|
|
||||||
m := NewMatcher(diff.A, diff.B)
|
|
||||||
for _, g := range m.GetGroupedOpCodes(diff.Context) {
|
|
||||||
if !started {
|
|
||||||
started = true
|
|
||||||
fromDate := ""
|
|
||||||
if len(diff.FromDate) > 0 {
|
|
||||||
fromDate = "\t" + diff.FromDate
|
|
||||||
}
|
|
||||||
toDate := ""
|
|
||||||
if len(diff.ToDate) > 0 {
|
|
||||||
toDate = "\t" + diff.ToDate
|
|
||||||
}
|
|
||||||
if diff.FromFile != "" || diff.ToFile != "" {
|
|
||||||
err := wf("--- %s%s%s", diff.FromFile, fromDate, diff.Eol)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = wf("+++ %s%s%s", diff.ToFile, toDate, diff.Eol)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
first, last := g[0], g[len(g)-1]
|
|
||||||
range1 := formatRangeUnified(first.I1, last.I2)
|
|
||||||
range2 := formatRangeUnified(first.J1, last.J2)
|
|
||||||
if err := wf("@@ -%s +%s @@%s", range1, range2, diff.Eol); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, c := range g {
|
|
||||||
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
|
||||||
if c.Tag == 'e' {
|
|
||||||
for _, line := range diff.A[i1:i2] {
|
|
||||||
if err := ws(" " + line); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if c.Tag == 'r' || c.Tag == 'd' {
|
|
||||||
for _, line := range diff.A[i1:i2] {
|
|
||||||
if err := ws("-" + line); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c.Tag == 'r' || c.Tag == 'i' {
|
|
||||||
for _, line := range diff.B[j1:j2] {
|
|
||||||
if err := ws("+" + line); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Like WriteUnifiedDiff but returns the diff a string.
|
|
||||||
func GetUnifiedDiffString(diff UnifiedDiff) (string, error) {
|
|
||||||
w := &bytes.Buffer{}
|
|
||||||
err := WriteUnifiedDiff(w, diff)
|
|
||||||
return string(w.Bytes()), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert range to the "ed" format.
|
|
||||||
func formatRangeContext(start, stop int) string {
|
|
||||||
// Per the diff spec at http://www.unix.org/single_unix_specification/
|
|
||||||
beginning := start + 1 // lines start numbering with one
|
|
||||||
length := stop - start
|
|
||||||
if length == 0 {
|
|
||||||
beginning -= 1 // empty ranges begin at line just before the range
|
|
||||||
}
|
|
||||||
if length <= 1 {
|
|
||||||
return fmt.Sprintf("%d", beginning)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%d,%d", beginning, beginning+length-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ContextDiff UnifiedDiff
|
|
||||||
|
|
||||||
// Compare two sequences of lines; generate the delta as a context diff.
|
|
||||||
//
|
|
||||||
// Context diffs are a compact way of showing line changes and a few
|
|
||||||
// lines of context. The number of context lines is set by diff.Context
|
|
||||||
// which defaults to three.
|
|
||||||
//
|
|
||||||
// By default, the diff control lines (those with *** or ---) are
|
|
||||||
// created with a trailing newline.
|
|
||||||
//
|
|
||||||
// For inputs that do not have trailing newlines, set the diff.Eol
|
|
||||||
// argument to "" so that the output will be uniformly newline free.
|
|
||||||
//
|
|
||||||
// The context diff format normally has a header for filenames and
|
|
||||||
// modification times. Any or all of these may be specified using
|
|
||||||
// strings for diff.FromFile, diff.ToFile, diff.FromDate, diff.ToDate.
|
|
||||||
// The modification times are normally expressed in the ISO 8601 format.
|
|
||||||
// If not specified, the strings default to blanks.
|
|
||||||
func WriteContextDiff(writer io.Writer, diff ContextDiff) error {
|
|
||||||
buf := bufio.NewWriter(writer)
|
|
||||||
defer buf.Flush()
|
|
||||||
var diffErr error
|
|
||||||
wf := func(format string, args ...interface{}) {
|
|
||||||
_, err := buf.WriteString(fmt.Sprintf(format, args...))
|
|
||||||
if diffErr == nil && err != nil {
|
|
||||||
diffErr = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ws := func(s string) {
|
|
||||||
_, err := buf.WriteString(s)
|
|
||||||
if diffErr == nil && err != nil {
|
|
||||||
diffErr = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(diff.Eol) == 0 {
|
|
||||||
diff.Eol = "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
prefix := map[byte]string{
|
|
||||||
'i': "+ ",
|
|
||||||
'd': "- ",
|
|
||||||
'r': "! ",
|
|
||||||
'e': " ",
|
|
||||||
}
|
|
||||||
|
|
||||||
started := false
|
|
||||||
m := NewMatcher(diff.A, diff.B)
|
|
||||||
for _, g := range m.GetGroupedOpCodes(diff.Context) {
|
|
||||||
if !started {
|
|
||||||
started = true
|
|
||||||
fromDate := ""
|
|
||||||
if len(diff.FromDate) > 0 {
|
|
||||||
fromDate = "\t" + diff.FromDate
|
|
||||||
}
|
|
||||||
toDate := ""
|
|
||||||
if len(diff.ToDate) > 0 {
|
|
||||||
toDate = "\t" + diff.ToDate
|
|
||||||
}
|
|
||||||
if diff.FromFile != "" || diff.ToFile != "" {
|
|
||||||
wf("*** %s%s%s", diff.FromFile, fromDate, diff.Eol)
|
|
||||||
wf("--- %s%s%s", diff.ToFile, toDate, diff.Eol)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
first, last := g[0], g[len(g)-1]
|
|
||||||
ws("***************" + diff.Eol)
|
|
||||||
|
|
||||||
range1 := formatRangeContext(first.I1, last.I2)
|
|
||||||
wf("*** %s ****%s", range1, diff.Eol)
|
|
||||||
for _, c := range g {
|
|
||||||
if c.Tag == 'r' || c.Tag == 'd' {
|
|
||||||
for _, cc := range g {
|
|
||||||
if cc.Tag == 'i' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, line := range diff.A[cc.I1:cc.I2] {
|
|
||||||
ws(prefix[cc.Tag] + line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
range2 := formatRangeContext(first.J1, last.J2)
|
|
||||||
wf("--- %s ----%s", range2, diff.Eol)
|
|
||||||
for _, c := range g {
|
|
||||||
if c.Tag == 'r' || c.Tag == 'i' {
|
|
||||||
for _, cc := range g {
|
|
||||||
if cc.Tag == 'd' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, line := range diff.B[cc.J1:cc.J2] {
|
|
||||||
ws(prefix[cc.Tag] + line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return diffErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Like WriteContextDiff but returns the diff a string.
|
|
||||||
func GetContextDiffString(diff ContextDiff) (string, error) {
|
|
||||||
w := &bytes.Buffer{}
|
|
||||||
err := WriteContextDiff(w, diff)
|
|
||||||
return string(w.Bytes()), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Split a string on "\n" while preserving them. The output can be used
|
|
||||||
// as input for UnifiedDiff and ContextDiff structures.
|
|
||||||
func SplitLines(s string) []string {
|
|
||||||
lines := strings.SplitAfter(s, "\n")
|
|
||||||
lines[len(lines)-1] += "\n"
|
|
||||||
return lines
|
|
||||||
}
|
|
|
@ -1,426 +0,0 @@
|
||||||
package difflib
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func assertAlmostEqual(t *testing.T, a, b float64, places int) {
|
|
||||||
if math.Abs(a-b) > math.Pow10(-places) {
|
|
||||||
t.Errorf("%.7f != %.7f", a, b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertEqual(t *testing.T, a, b interface{}) {
|
|
||||||
if !reflect.DeepEqual(a, b) {
|
|
||||||
t.Errorf("%v != %v", a, b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitChars(s string) []string {
|
|
||||||
chars := make([]string, 0, len(s))
|
|
||||||
// Assume ASCII inputs
|
|
||||||
for i := 0; i != len(s); i++ {
|
|
||||||
chars = append(chars, string(s[i]))
|
|
||||||
}
|
|
||||||
return chars
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSequenceMatcherRatio(t *testing.T) {
|
|
||||||
s := NewMatcher(splitChars("abcd"), splitChars("bcde"))
|
|
||||||
assertEqual(t, s.Ratio(), 0.75)
|
|
||||||
assertEqual(t, s.QuickRatio(), 0.75)
|
|
||||||
assertEqual(t, s.RealQuickRatio(), 1.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetOptCodes(t *testing.T) {
|
|
||||||
a := "qabxcd"
|
|
||||||
b := "abycdf"
|
|
||||||
s := NewMatcher(splitChars(a), splitChars(b))
|
|
||||||
w := &bytes.Buffer{}
|
|
||||||
for _, op := range s.GetOpCodes() {
|
|
||||||
fmt.Fprintf(w, "%s a[%d:%d], (%s) b[%d:%d] (%s)\n", string(op.Tag),
|
|
||||||
op.I1, op.I2, a[op.I1:op.I2], op.J1, op.J2, b[op.J1:op.J2])
|
|
||||||
}
|
|
||||||
result := string(w.Bytes())
|
|
||||||
expected := `d a[0:1], (q) b[0:0] ()
|
|
||||||
e a[1:3], (ab) b[0:2] (ab)
|
|
||||||
r a[3:4], (x) b[2:3] (y)
|
|
||||||
e a[4:6], (cd) b[3:5] (cd)
|
|
||||||
i a[6:6], () b[5:6] (f)
|
|
||||||
`
|
|
||||||
if expected != result {
|
|
||||||
t.Errorf("unexpected op codes: \n%s", result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGroupedOpCodes(t *testing.T) {
|
|
||||||
a := []string{}
|
|
||||||
for i := 0; i != 39; i++ {
|
|
||||||
a = append(a, fmt.Sprintf("%02d", i))
|
|
||||||
}
|
|
||||||
b := []string{}
|
|
||||||
b = append(b, a[:8]...)
|
|
||||||
b = append(b, " i")
|
|
||||||
b = append(b, a[8:19]...)
|
|
||||||
b = append(b, " x")
|
|
||||||
b = append(b, a[20:22]...)
|
|
||||||
b = append(b, a[27:34]...)
|
|
||||||
b = append(b, " y")
|
|
||||||
b = append(b, a[35:]...)
|
|
||||||
s := NewMatcher(a, b)
|
|
||||||
w := &bytes.Buffer{}
|
|
||||||
for _, g := range s.GetGroupedOpCodes(-1) {
|
|
||||||
fmt.Fprintf(w, "group\n")
|
|
||||||
for _, op := range g {
|
|
||||||
fmt.Fprintf(w, " %s, %d, %d, %d, %d\n", string(op.Tag),
|
|
||||||
op.I1, op.I2, op.J1, op.J2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result := string(w.Bytes())
|
|
||||||
expected := `group
|
|
||||||
e, 5, 8, 5, 8
|
|
||||||
i, 8, 8, 8, 9
|
|
||||||
e, 8, 11, 9, 12
|
|
||||||
group
|
|
||||||
e, 16, 19, 17, 20
|
|
||||||
r, 19, 20, 20, 21
|
|
||||||
e, 20, 22, 21, 23
|
|
||||||
d, 22, 27, 23, 23
|
|
||||||
e, 27, 30, 23, 26
|
|
||||||
group
|
|
||||||
e, 31, 34, 27, 30
|
|
||||||
r, 34, 35, 30, 31
|
|
||||||
e, 35, 38, 31, 34
|
|
||||||
`
|
|
||||||
if expected != result {
|
|
||||||
t.Errorf("unexpected op codes: \n%s", result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleGetUnifiedDiffCode() {
|
|
||||||
a := `one
|
|
||||||
two
|
|
||||||
three
|
|
||||||
four
|
|
||||||
fmt.Printf("%s,%T",a,b)`
|
|
||||||
b := `zero
|
|
||||||
one
|
|
||||||
three
|
|
||||||
four`
|
|
||||||
diff := UnifiedDiff{
|
|
||||||
A: SplitLines(a),
|
|
||||||
B: SplitLines(b),
|
|
||||||
FromFile: "Original",
|
|
||||||
FromDate: "2005-01-26 23:30:50",
|
|
||||||
ToFile: "Current",
|
|
||||||
ToDate: "2010-04-02 10:20:52",
|
|
||||||
Context: 3,
|
|
||||||
}
|
|
||||||
result, _ := GetUnifiedDiffString(diff)
|
|
||||||
fmt.Println(strings.Replace(result, "\t", " ", -1))
|
|
||||||
// Output:
|
|
||||||
// --- Original 2005-01-26 23:30:50
|
|
||||||
// +++ Current 2010-04-02 10:20:52
|
|
||||||
// @@ -1,5 +1,4 @@
|
|
||||||
// +zero
|
|
||||||
// one
|
|
||||||
// -two
|
|
||||||
// three
|
|
||||||
// four
|
|
||||||
// -fmt.Printf("%s,%T",a,b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleGetContextDiffCode() {
|
|
||||||
a := `one
|
|
||||||
two
|
|
||||||
three
|
|
||||||
four
|
|
||||||
fmt.Printf("%s,%T",a,b)`
|
|
||||||
b := `zero
|
|
||||||
one
|
|
||||||
tree
|
|
||||||
four`
|
|
||||||
diff := ContextDiff{
|
|
||||||
A: SplitLines(a),
|
|
||||||
B: SplitLines(b),
|
|
||||||
FromFile: "Original",
|
|
||||||
ToFile: "Current",
|
|
||||||
Context: 3,
|
|
||||||
Eol: "\n",
|
|
||||||
}
|
|
||||||
result, _ := GetContextDiffString(diff)
|
|
||||||
fmt.Print(strings.Replace(result, "\t", " ", -1))
|
|
||||||
// Output:
|
|
||||||
// *** Original
|
|
||||||
// --- Current
|
|
||||||
// ***************
|
|
||||||
// *** 1,5 ****
|
|
||||||
// one
|
|
||||||
// ! two
|
|
||||||
// ! three
|
|
||||||
// four
|
|
||||||
// - fmt.Printf("%s,%T",a,b)
|
|
||||||
// --- 1,4 ----
|
|
||||||
// + zero
|
|
||||||
// one
|
|
||||||
// ! tree
|
|
||||||
// four
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleGetContextDiffString() {
|
|
||||||
a := `one
|
|
||||||
two
|
|
||||||
three
|
|
||||||
four`
|
|
||||||
b := `zero
|
|
||||||
one
|
|
||||||
tree
|
|
||||||
four`
|
|
||||||
diff := ContextDiff{
|
|
||||||
A: SplitLines(a),
|
|
||||||
B: SplitLines(b),
|
|
||||||
FromFile: "Original",
|
|
||||||
ToFile: "Current",
|
|
||||||
Context: 3,
|
|
||||||
Eol: "\n",
|
|
||||||
}
|
|
||||||
result, _ := GetContextDiffString(diff)
|
|
||||||
fmt.Printf(strings.Replace(result, "\t", " ", -1))
|
|
||||||
// Output:
|
|
||||||
// *** Original
|
|
||||||
// --- Current
|
|
||||||
// ***************
|
|
||||||
// *** 1,4 ****
|
|
||||||
// one
|
|
||||||
// ! two
|
|
||||||
// ! three
|
|
||||||
// four
|
|
||||||
// --- 1,4 ----
|
|
||||||
// + zero
|
|
||||||
// one
|
|
||||||
// ! tree
|
|
||||||
// four
|
|
||||||
}
|
|
||||||
|
|
||||||
func rep(s string, count int) string {
|
|
||||||
return strings.Repeat(s, count)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithAsciiOneInsert(t *testing.T) {
|
|
||||||
sm := NewMatcher(splitChars(rep("b", 100)),
|
|
||||||
splitChars("a"+rep("b", 100)))
|
|
||||||
assertAlmostEqual(t, sm.Ratio(), 0.995, 3)
|
|
||||||
assertEqual(t, sm.GetOpCodes(),
|
|
||||||
[]OpCode{{'i', 0, 0, 0, 1}, {'e', 0, 100, 1, 101}})
|
|
||||||
assertEqual(t, len(sm.bPopular), 0)
|
|
||||||
|
|
||||||
sm = NewMatcher(splitChars(rep("b", 100)),
|
|
||||||
splitChars(rep("b", 50)+"a"+rep("b", 50)))
|
|
||||||
assertAlmostEqual(t, sm.Ratio(), 0.995, 3)
|
|
||||||
assertEqual(t, sm.GetOpCodes(),
|
|
||||||
[]OpCode{{'e', 0, 50, 0, 50}, {'i', 50, 50, 50, 51}, {'e', 50, 100, 51, 101}})
|
|
||||||
assertEqual(t, len(sm.bPopular), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithAsciiOnDelete(t *testing.T) {
|
|
||||||
sm := NewMatcher(splitChars(rep("a", 40)+"c"+rep("b", 40)),
|
|
||||||
splitChars(rep("a", 40)+rep("b", 40)))
|
|
||||||
assertAlmostEqual(t, sm.Ratio(), 0.994, 3)
|
|
||||||
assertEqual(t, sm.GetOpCodes(),
|
|
||||||
[]OpCode{{'e', 0, 40, 0, 40}, {'d', 40, 41, 40, 40}, {'e', 41, 81, 40, 80}})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWithAsciiBJunk(t *testing.T) {
|
|
||||||
isJunk := func(s string) bool {
|
|
||||||
return s == " "
|
|
||||||
}
|
|
||||||
sm := NewMatcherWithJunk(splitChars(rep("a", 40)+rep("b", 40)),
|
|
||||||
splitChars(rep("a", 44)+rep("b", 40)), true, isJunk)
|
|
||||||
assertEqual(t, sm.bJunk, map[string]struct{}{})
|
|
||||||
|
|
||||||
sm = NewMatcherWithJunk(splitChars(rep("a", 40)+rep("b", 40)),
|
|
||||||
splitChars(rep("a", 44)+rep("b", 40)+rep(" ", 20)), false, isJunk)
|
|
||||||
assertEqual(t, sm.bJunk, map[string]struct{}{" ": struct{}{}})
|
|
||||||
|
|
||||||
isJunk = func(s string) bool {
|
|
||||||
return s == " " || s == "b"
|
|
||||||
}
|
|
||||||
sm = NewMatcherWithJunk(splitChars(rep("a", 40)+rep("b", 40)),
|
|
||||||
splitChars(rep("a", 44)+rep("b", 40)+rep(" ", 20)), false, isJunk)
|
|
||||||
assertEqual(t, sm.bJunk, map[string]struct{}{" ": struct{}{}, "b": struct{}{}})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSFBugsRatioForNullSeqn(t *testing.T) {
|
|
||||||
sm := NewMatcher(nil, nil)
|
|
||||||
assertEqual(t, sm.Ratio(), 1.0)
|
|
||||||
assertEqual(t, sm.QuickRatio(), 1.0)
|
|
||||||
assertEqual(t, sm.RealQuickRatio(), 1.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSFBugsComparingEmptyLists(t *testing.T) {
|
|
||||||
groups := NewMatcher(nil, nil).GetGroupedOpCodes(-1)
|
|
||||||
assertEqual(t, len(groups), 0)
|
|
||||||
diff := UnifiedDiff{
|
|
||||||
FromFile: "Original",
|
|
||||||
ToFile: "Current",
|
|
||||||
Context: 3,
|
|
||||||
}
|
|
||||||
result, err := GetUnifiedDiffString(diff)
|
|
||||||
assertEqual(t, err, nil)
|
|
||||||
assertEqual(t, result, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOutputFormatRangeFormatUnified(t *testing.T) {
|
|
||||||
// Per the diff spec at http://www.unix.org/single_unix_specification/
|
|
||||||
//
|
|
||||||
// Each <range> field shall be of the form:
|
|
||||||
// %1d", <beginning line number> if the range contains exactly one line,
|
|
||||||
// and:
|
|
||||||
// "%1d,%1d", <beginning line number>, <number of lines> otherwise.
|
|
||||||
// If a range is empty, its beginning line number shall be the number of
|
|
||||||
// the line just before the range, or 0 if the empty range starts the file.
|
|
||||||
fm := formatRangeUnified
|
|
||||||
assertEqual(t, fm(3, 3), "3,0")
|
|
||||||
assertEqual(t, fm(3, 4), "4")
|
|
||||||
assertEqual(t, fm(3, 5), "4,2")
|
|
||||||
assertEqual(t, fm(3, 6), "4,3")
|
|
||||||
assertEqual(t, fm(0, 0), "0,0")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOutputFormatRangeFormatContext(t *testing.T) {
|
|
||||||
// Per the diff spec at http://www.unix.org/single_unix_specification/
|
|
||||||
//
|
|
||||||
// The range of lines in file1 shall be written in the following format
|
|
||||||
// if the range contains two or more lines:
|
|
||||||
// "*** %d,%d ****\n", <beginning line number>, <ending line number>
|
|
||||||
// and the following format otherwise:
|
|
||||||
// "*** %d ****\n", <ending line number>
|
|
||||||
// The ending line number of an empty range shall be the number of the preceding line,
|
|
||||||
// or 0 if the range is at the start of the file.
|
|
||||||
//
|
|
||||||
// Next, the range of lines in file2 shall be written in the following format
|
|
||||||
// if the range contains two or more lines:
|
|
||||||
// "--- %d,%d ----\n", <beginning line number>, <ending line number>
|
|
||||||
// and the following format otherwise:
|
|
||||||
// "--- %d ----\n", <ending line number>
|
|
||||||
fm := formatRangeContext
|
|
||||||
assertEqual(t, fm(3, 3), "3")
|
|
||||||
assertEqual(t, fm(3, 4), "4")
|
|
||||||
assertEqual(t, fm(3, 5), "4,5")
|
|
||||||
assertEqual(t, fm(3, 6), "4,6")
|
|
||||||
assertEqual(t, fm(0, 0), "0")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOutputFormatTabDelimiter(t *testing.T) {
|
|
||||||
diff := UnifiedDiff{
|
|
||||||
A: splitChars("one"),
|
|
||||||
B: splitChars("two"),
|
|
||||||
FromFile: "Original",
|
|
||||||
FromDate: "2005-01-26 23:30:50",
|
|
||||||
ToFile: "Current",
|
|
||||||
ToDate: "2010-04-12 10:20:52",
|
|
||||||
Eol: "\n",
|
|
||||||
}
|
|
||||||
ud, err := GetUnifiedDiffString(diff)
|
|
||||||
assertEqual(t, err, nil)
|
|
||||||
assertEqual(t, SplitLines(ud)[:2], []string{
|
|
||||||
"--- Original\t2005-01-26 23:30:50\n",
|
|
||||||
"+++ Current\t2010-04-12 10:20:52\n",
|
|
||||||
})
|
|
||||||
cd, err := GetContextDiffString(ContextDiff(diff))
|
|
||||||
assertEqual(t, err, nil)
|
|
||||||
assertEqual(t, SplitLines(cd)[:2], []string{
|
|
||||||
"*** Original\t2005-01-26 23:30:50\n",
|
|
||||||
"--- Current\t2010-04-12 10:20:52\n",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOutputFormatNoTrailingTabOnEmptyFiledate(t *testing.T) {
|
|
||||||
diff := UnifiedDiff{
|
|
||||||
A: splitChars("one"),
|
|
||||||
B: splitChars("two"),
|
|
||||||
FromFile: "Original",
|
|
||||||
ToFile: "Current",
|
|
||||||
Eol: "\n",
|
|
||||||
}
|
|
||||||
ud, err := GetUnifiedDiffString(diff)
|
|
||||||
assertEqual(t, err, nil)
|
|
||||||
assertEqual(t, SplitLines(ud)[:2], []string{"--- Original\n", "+++ Current\n"})
|
|
||||||
|
|
||||||
cd, err := GetContextDiffString(ContextDiff(diff))
|
|
||||||
assertEqual(t, err, nil)
|
|
||||||
assertEqual(t, SplitLines(cd)[:2], []string{"*** Original\n", "--- Current\n"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOmitFilenames(t *testing.T) {
|
|
||||||
diff := UnifiedDiff{
|
|
||||||
A: SplitLines("o\nn\ne\n"),
|
|
||||||
B: SplitLines("t\nw\no\n"),
|
|
||||||
Eol: "\n",
|
|
||||||
}
|
|
||||||
ud, err := GetUnifiedDiffString(diff)
|
|
||||||
assertEqual(t, err, nil)
|
|
||||||
assertEqual(t, SplitLines(ud), []string{
|
|
||||||
"@@ -0,0 +1,2 @@\n",
|
|
||||||
"+t\n",
|
|
||||||
"+w\n",
|
|
||||||
"@@ -2,2 +3,0 @@\n",
|
|
||||||
"-n\n",
|
|
||||||
"-e\n",
|
|
||||||
"\n",
|
|
||||||
})
|
|
||||||
|
|
||||||
cd, err := GetContextDiffString(ContextDiff(diff))
|
|
||||||
assertEqual(t, err, nil)
|
|
||||||
assertEqual(t, SplitLines(cd), []string{
|
|
||||||
"***************\n",
|
|
||||||
"*** 0 ****\n",
|
|
||||||
"--- 1,2 ----\n",
|
|
||||||
"+ t\n",
|
|
||||||
"+ w\n",
|
|
||||||
"***************\n",
|
|
||||||
"*** 2,3 ****\n",
|
|
||||||
"- n\n",
|
|
||||||
"- e\n",
|
|
||||||
"--- 3 ----\n",
|
|
||||||
"\n",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSplitLines(t *testing.T) {
|
|
||||||
allTests := []struct {
|
|
||||||
input string
|
|
||||||
want []string
|
|
||||||
}{
|
|
||||||
{"foo", []string{"foo\n"}},
|
|
||||||
{"foo\nbar", []string{"foo\n", "bar\n"}},
|
|
||||||
{"foo\nbar\n", []string{"foo\n", "bar\n", "\n"}},
|
|
||||||
}
|
|
||||||
for _, test := range allTests {
|
|
||||||
assertEqual(t, SplitLines(test.input), test.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func benchmarkSplitLines(b *testing.B, count int) {
|
|
||||||
str := strings.Repeat("foo\n", count)
|
|
||||||
|
|
||||||
b.ResetTimer()
|
|
||||||
|
|
||||||
n := 0
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
n += len(SplitLines(str))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkSplitLines100(b *testing.B) {
|
|
||||||
benchmarkSplitLines(b, 100)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkSplitLines10000(b *testing.B) {
|
|
||||||
benchmarkSplitLines(b, 10000)
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,48 +0,0 @@
|
||||||
package blackfriday
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestEsc(t *testing.T) {
|
|
||||||
tests := []string{
|
|
||||||
"abc", "abc",
|
|
||||||
"a&c", "a&c",
|
|
||||||
"<", "<",
|
|
||||||
"[]:<", "[]:<",
|
|
||||||
"Hello <!--", "Hello <!--",
|
|
||||||
}
|
|
||||||
for i := 0; i < len(tests); i += 2 {
|
|
||||||
var b bytes.Buffer
|
|
||||||
escapeHTML(&b, []byte(tests[i]))
|
|
||||||
if !bytes.Equal(b.Bytes(), []byte(tests[i+1])) {
|
|
||||||
t.Errorf("\nInput [%#v]\nExpected[%#v]\nActual [%#v]",
|
|
||||||
tests[i], tests[i+1], b.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkEscapeHTML(b *testing.B) {
|
|
||||||
tests := [][]byte{
|
|
||||||
[]byte(""),
|
|
||||||
[]byte("AT&T has an ampersand in their name."),
|
|
||||||
[]byte("AT&T is another way to write it."),
|
|
||||||
[]byte("This & that."),
|
|
||||||
[]byte("4 < 5."),
|
|
||||||
[]byte("6 > 5."),
|
|
||||||
[]byte("Here's a [link] [1] with an ampersand in the URL."),
|
|
||||||
[]byte("Here's a link with an ampersand in the link text: [AT&T] [2]."),
|
|
||||||
[]byte("Here's an inline [link](/script?foo=1&bar=2)."),
|
|
||||||
[]byte("Here's an inline [link](</script?foo=1&bar=2>)."),
|
|
||||||
[]byte("[1]: http://example.com/?foo=1&bar=2"),
|
|
||||||
[]byte("[2]: http://att.com/ \"AT&T\""),
|
|
||||||
}
|
|
||||||
var buf bytes.Buffer
|
|
||||||
for n := 0; n < b.N; n++ {
|
|
||||||
for _, t := range tests {
|
|
||||||
escapeHTML(&buf, t)
|
|
||||||
buf.Reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,186 +0,0 @@
|
||||||
//
|
|
||||||
// Blackfriday Markdown Processor
|
|
||||||
// Available at http://github.com/russross/blackfriday
|
|
||||||
//
|
|
||||||
// Copyright © 2011 Russ Ross <russ@russross.com>.
|
|
||||||
// Distributed under the Simplified BSD License.
|
|
||||||
// See README.md for details.
|
|
||||||
//
|
|
||||||
|
|
||||||
//
|
|
||||||
// Helper functions for unit testing
|
|
||||||
//
|
|
||||||
|
|
||||||
package blackfriday
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TestParams struct {
|
|
||||||
extensions Extensions
|
|
||||||
referenceOverride ReferenceOverrideFunc
|
|
||||||
HTMLFlags
|
|
||||||
HTMLRendererParameters
|
|
||||||
}
|
|
||||||
|
|
||||||
func execRecoverableTestSuite(t *testing.T, tests []string, params TestParams, suite func(candidate *string)) {
|
|
||||||
// Catch and report panics. This is useful when running 'go test -v' on
|
|
||||||
// the integration server. When developing, though, crash dump is often
|
|
||||||
// preferable, so recovery can be easily turned off with doRecover = false.
|
|
||||||
var candidate string
|
|
||||||
const doRecover = true
|
|
||||||
if doRecover {
|
|
||||||
defer func() {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
t.Errorf("\npanic while processing [%#v]: %s\n", candidate, err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
suite(&candidate)
|
|
||||||
}
|
|
||||||
|
|
||||||
func runMarkdown(input string, params TestParams) string {
|
|
||||||
params.HTMLRendererParameters.Flags = params.HTMLFlags
|
|
||||||
renderer := NewHTMLRenderer(params.HTMLRendererParameters)
|
|
||||||
return string(Run([]byte(input), WithRenderer(renderer),
|
|
||||||
WithExtensions(params.extensions),
|
|
||||||
WithRefOverride(params.referenceOverride)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// doTests runs full document tests using MarkdownCommon configuration.
|
|
||||||
func doTests(t *testing.T, tests []string) {
|
|
||||||
doTestsParam(t, tests, TestParams{
|
|
||||||
extensions: CommonExtensions,
|
|
||||||
HTMLRendererParameters: HTMLRendererParameters{
|
|
||||||
Flags: CommonHTMLFlags,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func doTestsBlock(t *testing.T, tests []string, extensions Extensions) {
|
|
||||||
doTestsParam(t, tests, TestParams{
|
|
||||||
extensions: extensions,
|
|
||||||
HTMLFlags: UseXHTML,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func doTestsParam(t *testing.T, tests []string, params TestParams) {
|
|
||||||
execRecoverableTestSuite(t, tests, params, func(candidate *string) {
|
|
||||||
for i := 0; i+1 < len(tests); i += 2 {
|
|
||||||
input := tests[i]
|
|
||||||
*candidate = input
|
|
||||||
expected := tests[i+1]
|
|
||||||
actual := runMarkdown(*candidate, params)
|
|
||||||
if actual != expected {
|
|
||||||
t.Errorf("\nInput [%#v]\nExpected[%#v]\nActual [%#v]",
|
|
||||||
*candidate, expected, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
// now test every substring to stress test bounds checking
|
|
||||||
if !testing.Short() {
|
|
||||||
for start := 0; start < len(input); start++ {
|
|
||||||
for end := start + 1; end <= len(input); end++ {
|
|
||||||
*candidate = input[start:end]
|
|
||||||
runMarkdown(*candidate, params)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func doTestsInline(t *testing.T, tests []string) {
|
|
||||||
doTestsInlineParam(t, tests, TestParams{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func doLinkTestsInline(t *testing.T, tests []string) {
|
|
||||||
doTestsInline(t, tests)
|
|
||||||
|
|
||||||
prefix := "http://localhost"
|
|
||||||
params := HTMLRendererParameters{AbsolutePrefix: prefix}
|
|
||||||
transformTests := transformLinks(tests, prefix)
|
|
||||||
doTestsInlineParam(t, transformTests, TestParams{
|
|
||||||
HTMLRendererParameters: params,
|
|
||||||
})
|
|
||||||
doTestsInlineParam(t, transformTests, TestParams{
|
|
||||||
HTMLFlags: UseXHTML,
|
|
||||||
HTMLRendererParameters: params,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func doSafeTestsInline(t *testing.T, tests []string) {
|
|
||||||
doTestsInlineParam(t, tests, TestParams{HTMLFlags: Safelink})
|
|
||||||
|
|
||||||
// All the links in this test should not have the prefix appended, so
|
|
||||||
// just rerun it with different parameters and the same expectations.
|
|
||||||
prefix := "http://localhost"
|
|
||||||
params := HTMLRendererParameters{AbsolutePrefix: prefix}
|
|
||||||
transformTests := transformLinks(tests, prefix)
|
|
||||||
doTestsInlineParam(t, transformTests, TestParams{
|
|
||||||
HTMLFlags: Safelink,
|
|
||||||
HTMLRendererParameters: params,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func doTestsInlineParam(t *testing.T, tests []string, params TestParams) {
|
|
||||||
params.extensions |= Autolink | Strikethrough
|
|
||||||
params.HTMLFlags |= UseXHTML
|
|
||||||
doTestsParam(t, tests, params)
|
|
||||||
}
|
|
||||||
|
|
||||||
func transformLinks(tests []string, prefix string) []string {
|
|
||||||
newTests := make([]string, len(tests))
|
|
||||||
anchorRe := regexp.MustCompile(`<a href="/(.*?)"`)
|
|
||||||
imgRe := regexp.MustCompile(`<img src="/(.*?)"`)
|
|
||||||
for i, test := range tests {
|
|
||||||
if i%2 == 1 {
|
|
||||||
test = anchorRe.ReplaceAllString(test, `<a href="`+prefix+`/$1"`)
|
|
||||||
test = imgRe.ReplaceAllString(test, `<img src="`+prefix+`/$1"`)
|
|
||||||
}
|
|
||||||
newTests[i] = test
|
|
||||||
}
|
|
||||||
return newTests
|
|
||||||
}
|
|
||||||
|
|
||||||
func doTestsReference(t *testing.T, files []string, flag Extensions) {
|
|
||||||
params := TestParams{extensions: flag}
|
|
||||||
execRecoverableTestSuite(t, files, params, func(candidate *string) {
|
|
||||||
for _, basename := range files {
|
|
||||||
filename := filepath.Join("testdata", basename+".text")
|
|
||||||
inputBytes, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Couldn't open '%s', error: %v\n", filename, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
input := string(inputBytes)
|
|
||||||
|
|
||||||
filename = filepath.Join("testdata", basename+".html")
|
|
||||||
expectedBytes, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Couldn't open '%s', error: %v\n", filename, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
expected := string(expectedBytes)
|
|
||||||
|
|
||||||
actual := string(runMarkdown(input, params))
|
|
||||||
if actual != expected {
|
|
||||||
t.Errorf("\n [%#v]\nExpected[%#v]\nActual [%#v]",
|
|
||||||
basename+".text", expected, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
// now test every prefix of every input to check for
|
|
||||||
// bounds checking
|
|
||||||
if !testing.Short() {
|
|
||||||
start, max := 0, len(input)
|
|
||||||
for end := start + 1; end <= max; end++ {
|
|
||||||
*candidate = input[start:end]
|
|
||||||
runMarkdown(*candidate, params)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,38 +0,0 @@
|
||||||
//
|
|
||||||
// Blackfriday Markdown Processor
|
|
||||||
// Available at http://github.com/russross/blackfriday
|
|
||||||
//
|
|
||||||
// Copyright © 2011 Russ Ross <russ@russross.com>.
|
|
||||||
// Distributed under the Simplified BSD License.
|
|
||||||
// See README.md for details.
|
|
||||||
//
|
|
||||||
|
|
||||||
//
|
|
||||||
// Unit tests for full document parsing and rendering
|
|
||||||
//
|
|
||||||
|
|
||||||
package blackfriday
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestDocument(t *testing.T) {
|
|
||||||
var tests = []string{
|
|
||||||
// Empty document.
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
|
|
||||||
" ",
|
|
||||||
"",
|
|
||||||
|
|
||||||
// This shouldn't panic.
|
|
||||||
// https://github.com/russross/blackfriday/issues/172
|
|
||||||
"[]:<",
|
|
||||||
"<p>[]:<</p>\n",
|
|
||||||
|
|
||||||
// This shouldn't panic.
|
|
||||||
// https://github.com/russross/blackfriday/issues/173
|
|
||||||
" [",
|
|
||||||
"<p>[</p>\n",
|
|
||||||
}
|
|
||||||
doTests(t, tests)
|
|
||||||
}
|
|
|
@ -1,124 +0,0 @@
|
||||||
//
|
|
||||||
// Blackfriday Markdown Processor
|
|
||||||
// Available at http://github.com/russross/blackfriday
|
|
||||||
//
|
|
||||||
// Copyright © 2011 Russ Ross <russ@russross.com>.
|
|
||||||
// Distributed under the Simplified BSD License.
|
|
||||||
// See README.md for details.
|
|
||||||
//
|
|
||||||
|
|
||||||
//
|
|
||||||
// Markdown 1.0.3 reference tests
|
|
||||||
//
|
|
||||||
|
|
||||||
package blackfriday
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestReference(t *testing.T) {
|
|
||||||
files := []string{
|
|
||||||
"Amps and angle encoding",
|
|
||||||
"Auto links",
|
|
||||||
"Backslash escapes",
|
|
||||||
"Blockquotes with code blocks",
|
|
||||||
"Code Blocks",
|
|
||||||
"Code Spans",
|
|
||||||
"Hard-wrapped paragraphs with list-like lines",
|
|
||||||
"Horizontal rules",
|
|
||||||
"Inline HTML (Advanced)",
|
|
||||||
"Inline HTML (Simple)",
|
|
||||||
"Inline HTML comments",
|
|
||||||
"Links, inline style",
|
|
||||||
"Links, reference style",
|
|
||||||
"Links, shortcut references",
|
|
||||||
"Literal quotes in titles",
|
|
||||||
"Markdown Documentation - Basics",
|
|
||||||
"Markdown Documentation - Syntax",
|
|
||||||
"Nested blockquotes",
|
|
||||||
"Ordered and unordered lists",
|
|
||||||
"Strong and em together",
|
|
||||||
"Tabs",
|
|
||||||
"Tidyness",
|
|
||||||
}
|
|
||||||
doTestsReference(t, files, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReference_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) {
|
|
||||||
files := []string{
|
|
||||||
"Amps and angle encoding",
|
|
||||||
"Auto links",
|
|
||||||
"Backslash escapes",
|
|
||||||
"Blockquotes with code blocks",
|
|
||||||
"Code Blocks",
|
|
||||||
"Code Spans",
|
|
||||||
"Hard-wrapped paragraphs with list-like lines no empty line before block",
|
|
||||||
"Horizontal rules",
|
|
||||||
"Inline HTML (Advanced)",
|
|
||||||
"Inline HTML (Simple)",
|
|
||||||
"Inline HTML comments",
|
|
||||||
"Links, inline style",
|
|
||||||
"Links, reference style",
|
|
||||||
"Links, shortcut references",
|
|
||||||
"Literal quotes in titles",
|
|
||||||
"Markdown Documentation - Basics",
|
|
||||||
"Markdown Documentation - Syntax",
|
|
||||||
"Nested blockquotes",
|
|
||||||
"Ordered and unordered lists",
|
|
||||||
"Strong and em together",
|
|
||||||
"Tabs",
|
|
||||||
"Tidyness",
|
|
||||||
}
|
|
||||||
doTestsReference(t, files, NoEmptyLineBeforeBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
// benchResultAnchor is an anchor variable to store the result of a benchmarked
|
|
||||||
// code so that compiler could never optimize away the call to runMarkdown()
|
|
||||||
var benchResultAnchor string
|
|
||||||
|
|
||||||
func BenchmarkReference(b *testing.B) {
|
|
||||||
params := TestParams{extensions: CommonExtensions}
|
|
||||||
files := []string{
|
|
||||||
"Amps and angle encoding",
|
|
||||||
"Auto links",
|
|
||||||
"Backslash escapes",
|
|
||||||
"Blockquotes with code blocks",
|
|
||||||
"Code Blocks",
|
|
||||||
"Code Spans",
|
|
||||||
"Hard-wrapped paragraphs with list-like lines",
|
|
||||||
"Horizontal rules",
|
|
||||||
"Inline HTML (Advanced)",
|
|
||||||
"Inline HTML (Simple)",
|
|
||||||
"Inline HTML comments",
|
|
||||||
"Links, inline style",
|
|
||||||
"Links, reference style",
|
|
||||||
"Links, shortcut references",
|
|
||||||
"Literal quotes in titles",
|
|
||||||
"Markdown Documentation - Basics",
|
|
||||||
"Markdown Documentation - Syntax",
|
|
||||||
"Nested blockquotes",
|
|
||||||
"Ordered and unordered lists",
|
|
||||||
"Strong and em together",
|
|
||||||
"Tabs",
|
|
||||||
"Tidyness",
|
|
||||||
}
|
|
||||||
var tests []string
|
|
||||||
for _, basename := range files {
|
|
||||||
filename := filepath.Join("testdata", basename+".text")
|
|
||||||
inputBytes, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
b.Errorf("Couldn't open '%s', error: %v\n", filename, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
tests = append(tests, string(inputBytes))
|
|
||||||
}
|
|
||||||
b.ResetTimer()
|
|
||||||
for n := 0; n < b.N; n++ {
|
|
||||||
for _, test := range tests {
|
|
||||||
benchResultAnchor = runMarkdown(test, params)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
<p>AT&T has an ampersand in their name.</p>
|
|
||||||
|
|
||||||
<p>AT&T is another way to write it.</p>
|
|
||||||
|
|
||||||
<p>This & that.</p>
|
|
||||||
|
|
||||||
<p>4 < 5.</p>
|
|
||||||
|
|
||||||
<p>6 > 5.</p>
|
|
||||||
|
|
||||||
<p>Here's a <a href="http://example.com/?foo=1&bar=2">link</a> with an ampersand in the URL.</p>
|
|
||||||
|
|
||||||
<p>Here's a link with an amersand in the link text: <a href="http://att.com/" title="AT&T">AT&T</a>.</p>
|
|
||||||
|
|
||||||
<p>Here's an inline <a href="/script?foo=1&bar=2">link</a>.</p>
|
|
||||||
|
|
||||||
<p>Here's an inline <a href="/script?foo=1&bar=2">link</a>.</p>
|
|
|
@ -1,21 +0,0 @@
|
||||||
AT&T has an ampersand in their name.
|
|
||||||
|
|
||||||
AT&T is another way to write it.
|
|
||||||
|
|
||||||
This & that.
|
|
||||||
|
|
||||||
4 < 5.
|
|
||||||
|
|
||||||
6 > 5.
|
|
||||||
|
|
||||||
Here's a [link] [1] with an ampersand in the URL.
|
|
||||||
|
|
||||||
Here's a link with an amersand in the link text: [AT&T] [2].
|
|
||||||
|
|
||||||
Here's an inline [link](/script?foo=1&bar=2).
|
|
||||||
|
|
||||||
Here's an inline [link](</script?foo=1&bar=2>).
|
|
||||||
|
|
||||||
|
|
||||||
[1]: http://example.com/?foo=1&bar=2
|
|
||||||
[2]: http://att.com/ "AT&T"
|
|
|
@ -1,18 +0,0 @@
|
||||||
<p>Link: <a href="http://example.com/">http://example.com/</a>.</p>
|
|
||||||
|
|
||||||
<p>With an ampersand: <a href="http://example.com/?foo=1&bar=2">http://example.com/?foo=1&bar=2</a></p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>In a list?</li>
|
|
||||||
<li><a href="http://example.com/">http://example.com/</a></li>
|
|
||||||
<li>It should.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<blockquote>
|
|
||||||
<p>Blockquoted: <a href="http://example.com/">http://example.com/</a></p>
|
|
||||||
</blockquote>
|
|
||||||
|
|
||||||
<p>Auto-links should not occur here: <code><http://example.com/></code></p>
|
|
||||||
|
|
||||||
<pre><code>or here: <http://example.com/>
|
|
||||||
</code></pre>
|
|
|
@ -1,13 +0,0 @@
|
||||||
Link: <http://example.com/>.
|
|
||||||
|
|
||||||
With an ampersand: <http://example.com/?foo=1&bar=2>
|
|
||||||
|
|
||||||
* In a list?
|
|
||||||
* <http://example.com/>
|
|
||||||
* It should.
|
|
||||||
|
|
||||||
> Blockquoted: <http://example.com/>
|
|
||||||
|
|
||||||
Auto-links should not occur here: `<http://example.com/>`
|
|
||||||
|
|
||||||
or here: <http://example.com/>
|
|
|
@ -1,123 +0,0 @@
|
||||||
<p>These should all get escaped:</p>
|
|
||||||
|
|
||||||
<p>Backslash: \</p>
|
|
||||||
|
|
||||||
<p>Backtick: `</p>
|
|
||||||
|
|
||||||
<p>Asterisk: *</p>
|
|
||||||
|
|
||||||
<p>Underscore: _</p>
|
|
||||||
|
|
||||||
<p>Left brace: {</p>
|
|
||||||
|
|
||||||
<p>Right brace: }</p>
|
|
||||||
|
|
||||||
<p>Left bracket: [</p>
|
|
||||||
|
|
||||||
<p>Right bracket: ]</p>
|
|
||||||
|
|
||||||
<p>Left paren: (</p>
|
|
||||||
|
|
||||||
<p>Right paren: )</p>
|
|
||||||
|
|
||||||
<p>Greater-than: ></p>
|
|
||||||
|
|
||||||
<p>Hash: #</p>
|
|
||||||
|
|
||||||
<p>Period: .</p>
|
|
||||||
|
|
||||||
<p>Bang: !</p>
|
|
||||||
|
|
||||||
<p>Plus: +</p>
|
|
||||||
|
|
||||||
<p>Minus: -</p>
|
|
||||||
|
|
||||||
<p>Tilde: ~</p>
|
|
||||||
|
|
||||||
<p>These should not, because they occur within a code block:</p>
|
|
||||||
|
|
||||||
<pre><code>Backslash: \\
|
|
||||||
|
|
||||||
Backtick: \`
|
|
||||||
|
|
||||||
Asterisk: \*
|
|
||||||
|
|
||||||
Underscore: \_
|
|
||||||
|
|
||||||
Left brace: \{
|
|
||||||
|
|
||||||
Right brace: \}
|
|
||||||
|
|
||||||
Left bracket: \[
|
|
||||||
|
|
||||||
Right bracket: \]
|
|
||||||
|
|
||||||
Left paren: \(
|
|
||||||
|
|
||||||
Right paren: \)
|
|
||||||
|
|
||||||
Greater-than: \>
|
|
||||||
|
|
||||||
Hash: \#
|
|
||||||
|
|
||||||
Period: \.
|
|
||||||
|
|
||||||
Bang: \!
|
|
||||||
|
|
||||||
Plus: \+
|
|
||||||
|
|
||||||
Minus: \-
|
|
||||||
|
|
||||||
Tilde: \~
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Nor should these, which occur in code spans:</p>
|
|
||||||
|
|
||||||
<p>Backslash: <code>\\</code></p>
|
|
||||||
|
|
||||||
<p>Backtick: <code>\`</code></p>
|
|
||||||
|
|
||||||
<p>Asterisk: <code>\*</code></p>
|
|
||||||
|
|
||||||
<p>Underscore: <code>\_</code></p>
|
|
||||||
|
|
||||||
<p>Left brace: <code>\{</code></p>
|
|
||||||
|
|
||||||
<p>Right brace: <code>\}</code></p>
|
|
||||||
|
|
||||||
<p>Left bracket: <code>\[</code></p>
|
|
||||||
|
|
||||||
<p>Right bracket: <code>\]</code></p>
|
|
||||||
|
|
||||||
<p>Left paren: <code>\(</code></p>
|
|
||||||
|
|
||||||
<p>Right paren: <code>\)</code></p>
|
|
||||||
|
|
||||||
<p>Greater-than: <code>\></code></p>
|
|
||||||
|
|
||||||
<p>Hash: <code>\#</code></p>
|
|
||||||
|
|
||||||
<p>Period: <code>\.</code></p>
|
|
||||||
|
|
||||||
<p>Bang: <code>\!</code></p>
|
|
||||||
|
|
||||||
<p>Plus: <code>\+</code></p>
|
|
||||||
|
|
||||||
<p>Minus: <code>\-</code></p>
|
|
||||||
|
|
||||||
<p>Tilde: <code>\~</code></p>
|
|
||||||
|
|
||||||
<p>These should get escaped, even though they're matching pairs for
|
|
||||||
other Markdown constructs:</p>
|
|
||||||
|
|
||||||
<p>*asterisks*</p>
|
|
||||||
|
|
||||||
<p>_underscores_</p>
|
|
||||||
|
|
||||||
<p>`backticks`</p>
|
|
||||||
|
|
||||||
<p>This is a code span with a literal backslash-backtick sequence: <code>\`</code></p>
|
|
||||||
|
|
||||||
<p>This is a tag with unescaped backticks <span attr='`ticks`'>bar</span>.</p>
|
|
||||||
|
|
||||||
<p>This is a tag with backslashes <span attr='\\backslashes\\'>bar</span>.</p>
|
|
|
@ -1,126 +0,0 @@
|
||||||
These should all get escaped:
|
|
||||||
|
|
||||||
Backslash: \\
|
|
||||||
|
|
||||||
Backtick: \`
|
|
||||||
|
|
||||||
Asterisk: \*
|
|
||||||
|
|
||||||
Underscore: \_
|
|
||||||
|
|
||||||
Left brace: \{
|
|
||||||
|
|
||||||
Right brace: \}
|
|
||||||
|
|
||||||
Left bracket: \[
|
|
||||||
|
|
||||||
Right bracket: \]
|
|
||||||
|
|
||||||
Left paren: \(
|
|
||||||
|
|
||||||
Right paren: \)
|
|
||||||
|
|
||||||
Greater-than: \>
|
|
||||||
|
|
||||||
Hash: \#
|
|
||||||
|
|
||||||
Period: \.
|
|
||||||
|
|
||||||
Bang: \!
|
|
||||||
|
|
||||||
Plus: \+
|
|
||||||
|
|
||||||
Minus: \-
|
|
||||||
|
|
||||||
Tilde: \~
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
These should not, because they occur within a code block:
|
|
||||||
|
|
||||||
Backslash: \\
|
|
||||||
|
|
||||||
Backtick: \`
|
|
||||||
|
|
||||||
Asterisk: \*
|
|
||||||
|
|
||||||
Underscore: \_
|
|
||||||
|
|
||||||
Left brace: \{
|
|
||||||
|
|
||||||
Right brace: \}
|
|
||||||
|
|
||||||
Left bracket: \[
|
|
||||||
|
|
||||||
Right bracket: \]
|
|
||||||
|
|
||||||
Left paren: \(
|
|
||||||
|
|
||||||
Right paren: \)
|
|
||||||
|
|
||||||
Greater-than: \>
|
|
||||||
|
|
||||||
Hash: \#
|
|
||||||
|
|
||||||
Period: \.
|
|
||||||
|
|
||||||
Bang: \!
|
|
||||||
|
|
||||||
Plus: \+
|
|
||||||
|
|
||||||
Minus: \-
|
|
||||||
|
|
||||||
Tilde: \~
|
|
||||||
|
|
||||||
|
|
||||||
Nor should these, which occur in code spans:
|
|
||||||
|
|
||||||
Backslash: `\\`
|
|
||||||
|
|
||||||
Backtick: `` \` ``
|
|
||||||
|
|
||||||
Asterisk: `\*`
|
|
||||||
|
|
||||||
Underscore: `\_`
|
|
||||||
|
|
||||||
Left brace: `\{`
|
|
||||||
|
|
||||||
Right brace: `\}`
|
|
||||||
|
|
||||||
Left bracket: `\[`
|
|
||||||
|
|
||||||
Right bracket: `\]`
|
|
||||||
|
|
||||||
Left paren: `\(`
|
|
||||||
|
|
||||||
Right paren: `\)`
|
|
||||||
|
|
||||||
Greater-than: `\>`
|
|
||||||
|
|
||||||
Hash: `\#`
|
|
||||||
|
|
||||||
Period: `\.`
|
|
||||||
|
|
||||||
Bang: `\!`
|
|
||||||
|
|
||||||
Plus: `\+`
|
|
||||||
|
|
||||||
Minus: `\-`
|
|
||||||
|
|
||||||
Tilde: `\~`
|
|
||||||
|
|
||||||
|
|
||||||
These should get escaped, even though they're matching pairs for
|
|
||||||
other Markdown constructs:
|
|
||||||
|
|
||||||
\*asterisks\*
|
|
||||||
|
|
||||||
\_underscores\_
|
|
||||||
|
|
||||||
\`backticks\`
|
|
||||||
|
|
||||||
This is a code span with a literal backslash-backtick sequence: `` \` ``
|
|
||||||
|
|
||||||
This is a tag with unescaped backticks <span attr='`ticks`'>bar</span>.
|
|
||||||
|
|
||||||
This is a tag with backslashes <span attr='\\backslashes\\'>bar</span>.
|
|
15
vendor/github.com/russross/blackfriday/testdata/Blockquotes with code blocks.html
generated
vendored
15
vendor/github.com/russross/blackfriday/testdata/Blockquotes with code blocks.html
generated
vendored
|
@ -1,15 +0,0 @@
|
||||||
<blockquote>
|
|
||||||
<p>Example:</p>
|
|
||||||
|
|
||||||
<pre><code>sub status {
|
|
||||||
print "working";
|
|
||||||
}
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Or:</p>
|
|
||||||
|
|
||||||
<pre><code>sub status {
|
|
||||||
return "working";
|
|
||||||
}
|
|
||||||
</code></pre>
|
|
||||||
</blockquote>
|
|
11
vendor/github.com/russross/blackfriday/testdata/Blockquotes with code blocks.text
generated
vendored
11
vendor/github.com/russross/blackfriday/testdata/Blockquotes with code blocks.text
generated
vendored
|
@ -1,11 +0,0 @@
|
||||||
> Example:
|
|
||||||
>
|
|
||||||
> sub status {
|
|
||||||
> print "working";
|
|
||||||
> }
|
|
||||||
>
|
|
||||||
> Or:
|
|
||||||
>
|
|
||||||
> sub status {
|
|
||||||
> return "working";
|
|
||||||
> }
|
|
|
@ -1,18 +0,0 @@
|
||||||
<pre><code>code block on the first line
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Regular text.</p>
|
|
||||||
|
|
||||||
<pre><code>code block indented by spaces
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Regular text.</p>
|
|
||||||
|
|
||||||
<pre><code>the lines in this block
|
|
||||||
all contain trailing spaces
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Regular Text.</p>
|
|
||||||
|
|
||||||
<pre><code>code block on the last line
|
|
||||||
</code></pre>
|
|
|
@ -1,14 +0,0 @@
|
||||||
code block on the first line
|
|
||||||
|
|
||||||
Regular text.
|
|
||||||
|
|
||||||
code block indented by spaces
|
|
||||||
|
|
||||||
Regular text.
|
|
||||||
|
|
||||||
the lines in this block
|
|
||||||
all contain trailing spaces
|
|
||||||
|
|
||||||
Regular Text.
|
|
||||||
|
|
||||||
code block on the last line
|
|
|
@ -1,5 +0,0 @@
|
||||||
<p><code><test a="</code> content of attribute <code>"></code></p>
|
|
||||||
|
|
||||||
<p>Fix for backticks within HTML tag: <span attr='`ticks`'>like this</span></p>
|
|
||||||
|
|
||||||
<p>Here's how you put <code>`backticks`</code> in a code span.</p>
|
|
|
@ -1,6 +0,0 @@
|
||||||
`<test a="` content of attribute `">`
|
|
||||||
|
|
||||||
Fix for backticks within HTML tag: <span attr='`ticks`'>like this</span>
|
|
||||||
|
|
||||||
Here's how you put `` `backticks` `` in a code span.
|
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
<p>In Markdown 1.0.0 and earlier. Version</p>
|
|
||||||
|
|
||||||
<ol>
|
|
||||||
<li>This line turns into a list item.
|
|
||||||
Because a hard-wrapped line in the
|
|
||||||
middle of a paragraph looked like a
|
|
||||||
list item.</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
<p>Here's one with a bullet.</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>criminey.</li>
|
|
||||||
</ul>
|
|
|
@ -1,8 +0,0 @@
|
||||||
In Markdown 1.0.0 and earlier. Version
|
|
||||||
8. This line turns into a list item.
|
|
||||||
Because a hard-wrapped line in the
|
|
||||||
middle of a paragraph looked like a
|
|
||||||
list item.
|
|
||||||
|
|
||||||
Here's one with a bullet.
|
|
||||||
* criminey.
|
|
|
@ -1,8 +0,0 @@
|
||||||
<p>In Markdown 1.0.0 and earlier. Version
|
|
||||||
8. This line turns into a list item.
|
|
||||||
Because a hard-wrapped line in the
|
|
||||||
middle of a paragraph looked like a
|
|
||||||
list item.</p>
|
|
||||||
|
|
||||||
<p>Here's one with a bullet.
|
|
||||||
* criminey.</p>
|
|
|
@ -1,8 +0,0 @@
|
||||||
In Markdown 1.0.0 and earlier. Version
|
|
||||||
8. This line turns into a list item.
|
|
||||||
Because a hard-wrapped line in the
|
|
||||||
middle of a paragraph looked like a
|
|
||||||
list item.
|
|
||||||
|
|
||||||
Here's one with a bullet.
|
|
||||||
* criminey.
|
|
|
@ -1,71 +0,0 @@
|
||||||
<p>Dashes:</p>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<pre><code>---
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<pre><code>- - -
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Asterisks:</p>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<pre><code>***
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<pre><code>* * *
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Underscores:</p>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<pre><code>___
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<pre><code>_ _ _
|
|
||||||
</code></pre>
|
|
|
@ -1,67 +0,0 @@
|
||||||
Dashes:
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
- - -
|
|
||||||
|
|
||||||
- - -
|
|
||||||
|
|
||||||
- - -
|
|
||||||
|
|
||||||
- - -
|
|
||||||
|
|
||||||
- - -
|
|
||||||
|
|
||||||
|
|
||||||
Asterisks:
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
* * *
|
|
||||||
|
|
||||||
* * *
|
|
||||||
|
|
||||||
* * *
|
|
||||||
|
|
||||||
* * *
|
|
||||||
|
|
||||||
* * *
|
|
||||||
|
|
||||||
|
|
||||||
Underscores:
|
|
||||||
|
|
||||||
___
|
|
||||||
|
|
||||||
___
|
|
||||||
|
|
||||||
___
|
|
||||||
|
|
||||||
___
|
|
||||||
|
|
||||||
___
|
|
||||||
|
|
||||||
_ _ _
|
|
||||||
|
|
||||||
_ _ _
|
|
||||||
|
|
||||||
_ _ _
|
|
||||||
|
|
||||||
_ _ _
|
|
||||||
|
|
||||||
_ _ _
|
|
|
@ -1,15 +0,0 @@
|
||||||
<p>Simple block on one line:</p>
|
|
||||||
|
|
||||||
<div>foo</div>
|
|
||||||
|
|
||||||
<p>And nested without indentation:</p>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
foo
|
|
||||||
</div>
|
|
||||||
<div style=">"/>
|
|
||||||
</div>
|
|
||||||
<div>bar</div>
|
|
||||||
</div>
|
|
|
@ -1,15 +0,0 @@
|
||||||
Simple block on one line:
|
|
||||||
|
|
||||||
<div>foo</div>
|
|
||||||
|
|
||||||
And nested without indentation:
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
foo
|
|
||||||
</div>
|
|
||||||
<div style=">"/>
|
|
||||||
</div>
|
|
||||||
<div>bar</div>
|
|
||||||
</div>
|
|
|
@ -1,72 +0,0 @@
|
||||||
<p>Here's a simple block:</p>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
foo
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p>This should be a code block, though:</p>
|
|
||||||
|
|
||||||
<pre><code><div>
|
|
||||||
foo
|
|
||||||
</div>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>As should this:</p>
|
|
||||||
|
|
||||||
<pre><code><div>foo</div>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Now, nested:</p>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
foo
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p>This should just be an HTML comment:</p>
|
|
||||||
|
|
||||||
<!-- Comment -->
|
|
||||||
|
|
||||||
<p>Multiline:</p>
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Blah
|
|
||||||
Blah
|
|
||||||
-->
|
|
||||||
|
|
||||||
<p>Code block:</p>
|
|
||||||
|
|
||||||
<pre><code><!-- Comment -->
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Just plain comment, with trailing spaces on the line:</p>
|
|
||||||
|
|
||||||
<!-- foo -->
|
|
||||||
|
|
||||||
<p>Code:</p>
|
|
||||||
|
|
||||||
<pre><code><hr />
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Hr's:</p>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<hr/>
|
|
||||||
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<hr/>
|
|
||||||
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<hr class="foo" id="bar" />
|
|
||||||
|
|
||||||
<hr class="foo" id="bar"/>
|
|
||||||
|
|
||||||
<hr class="foo" id="bar" >
|
|
|
@ -1,69 +0,0 @@
|
||||||
Here's a simple block:
|
|
||||||
|
|
||||||
<div>
|
|
||||||
foo
|
|
||||||
</div>
|
|
||||||
|
|
||||||
This should be a code block, though:
|
|
||||||
|
|
||||||
<div>
|
|
||||||
foo
|
|
||||||
</div>
|
|
||||||
|
|
||||||
As should this:
|
|
||||||
|
|
||||||
<div>foo</div>
|
|
||||||
|
|
||||||
Now, nested:
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
foo
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
This should just be an HTML comment:
|
|
||||||
|
|
||||||
<!-- Comment -->
|
|
||||||
|
|
||||||
Multiline:
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Blah
|
|
||||||
Blah
|
|
||||||
-->
|
|
||||||
|
|
||||||
Code block:
|
|
||||||
|
|
||||||
<!-- Comment -->
|
|
||||||
|
|
||||||
Just plain comment, with trailing spaces on the line:
|
|
||||||
|
|
||||||
<!-- foo -->
|
|
||||||
|
|
||||||
Code:
|
|
||||||
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
Hr's:
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<hr/>
|
|
||||||
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<hr/>
|
|
||||||
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<hr class="foo" id="bar" />
|
|
||||||
|
|
||||||
<hr class="foo" id="bar"/>
|
|
||||||
|
|
||||||
<hr class="foo" id="bar" >
|
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
<p>Paragraph one.</p>
|
|
||||||
|
|
||||||
<!-- This is a simple comment -->
|
|
||||||
|
|
||||||
<!--
|
|
||||||
This is another comment.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<p>Paragraph two.</p>
|
|
||||||
|
|
||||||
<!-- one comment block -- -- with two comments -->
|
|
||||||
|
|
||||||
<p>The end.</p>
|
|
|
@ -1,13 +0,0 @@
|
||||||
Paragraph one.
|
|
||||||
|
|
||||||
<!-- This is a simple comment -->
|
|
||||||
|
|
||||||
<!--
|
|
||||||
This is another comment.
|
|
||||||
-->
|
|
||||||
|
|
||||||
Paragraph two.
|
|
||||||
|
|
||||||
<!-- one comment block -- -- with two comments -->
|
|
||||||
|
|
||||||
The end.
|
|
|
@ -1,11 +0,0 @@
|
||||||
<p>Just a <a href="/url/">URL</a>.</p>
|
|
||||||
|
|
||||||
<p><a href="/url/" title="title">URL and title</a>.</p>
|
|
||||||
|
|
||||||
<p><a href="/url/" title="title preceded by two spaces">URL and title</a>.</p>
|
|
||||||
|
|
||||||
<p><a href="/url/" title="title preceded by a tab">URL and title</a>.</p>
|
|
||||||
|
|
||||||
<p><a href="/url/" title="title has spaces afterward">URL and title</a>.</p>
|
|
||||||
|
|
||||||
<p>[Empty]().</p>
|
|
|
@ -1,12 +0,0 @@
|
||||||
Just a [URL](/url/).
|
|
||||||
|
|
||||||
[URL and title](/url/ "title").
|
|
||||||
|
|
||||||
[URL and title](/url/ "title preceded by two spaces").
|
|
||||||
|
|
||||||
[URL and title](/url/ "title preceded by a tab").
|
|
||||||
|
|
||||||
[URL and title](/url/ "title has spaces afterward" ).
|
|
||||||
|
|
||||||
|
|
||||||
[Empty]().
|
|
|
@ -1,52 +0,0 @@
|
||||||
<p>Foo <a href="/url/" title="Title">bar</a>.</p>
|
|
||||||
|
|
||||||
<p>Foo <a href="/url/" title="Title">bar</a>.</p>
|
|
||||||
|
|
||||||
<p>Foo <a href="/url/" title="Title">bar</a>.</p>
|
|
||||||
|
|
||||||
<p>With <a href="/url/">embedded [brackets]</a>.</p>
|
|
||||||
|
|
||||||
<p>Indented <a href="/url">once</a>.</p>
|
|
||||||
|
|
||||||
<p>Indented <a href="/url">twice</a>.</p>
|
|
||||||
|
|
||||||
<p>Indented <a href="/url">thrice</a>.</p>
|
|
||||||
|
|
||||||
<p>Indented [four][] times.</p>
|
|
||||||
|
|
||||||
<pre><code>[four]: /url
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<p><a href="foo">this</a> should work</p>
|
|
||||||
|
|
||||||
<p>So should <a href="foo">this</a>.</p>
|
|
||||||
|
|
||||||
<p>And <a href="foo">this</a>.</p>
|
|
||||||
|
|
||||||
<p>And <a href="foo">this</a>.</p>
|
|
||||||
|
|
||||||
<p>And <a href="foo">this</a>.</p>
|
|
||||||
|
|
||||||
<p>But not [that] [].</p>
|
|
||||||
|
|
||||||
<p>Nor [that][].</p>
|
|
||||||
|
|
||||||
<p>Nor [that].</p>
|
|
||||||
|
|
||||||
<p>[Something in brackets like <a href="foo">this</a> should work]</p>
|
|
||||||
|
|
||||||
<p>[Same with <a href="foo">this</a>.]</p>
|
|
||||||
|
|
||||||
<p>In this case, <a href="/somethingelse/">this</a> points to something else.</p>
|
|
||||||
|
|
||||||
<p>Backslashing should suppress [this] and [this].</p>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<p>Here's one where the <a href="/url/">link
|
|
||||||
breaks</a> across lines.</p>
|
|
||||||
|
|
||||||
<p>Here's another where the <a href="/url/">link
|
|
||||||
breaks</a> across lines, but with a line-ending space.</p>
|
|
|
@ -1,71 +0,0 @@
|
||||||
Foo [bar] [1].
|
|
||||||
|
|
||||||
Foo [bar][1].
|
|
||||||
|
|
||||||
Foo [bar]
|
|
||||||
[1].
|
|
||||||
|
|
||||||
[1]: /url/ "Title"
|
|
||||||
|
|
||||||
|
|
||||||
With [embedded [brackets]] [b].
|
|
||||||
|
|
||||||
|
|
||||||
Indented [once][].
|
|
||||||
|
|
||||||
Indented [twice][].
|
|
||||||
|
|
||||||
Indented [thrice][].
|
|
||||||
|
|
||||||
Indented [four][] times.
|
|
||||||
|
|
||||||
[once]: /url
|
|
||||||
|
|
||||||
[twice]: /url
|
|
||||||
|
|
||||||
[thrice]: /url
|
|
||||||
|
|
||||||
[four]: /url
|
|
||||||
|
|
||||||
|
|
||||||
[b]: /url/
|
|
||||||
|
|
||||||
* * *
|
|
||||||
|
|
||||||
[this] [this] should work
|
|
||||||
|
|
||||||
So should [this][this].
|
|
||||||
|
|
||||||
And [this] [].
|
|
||||||
|
|
||||||
And [this][].
|
|
||||||
|
|
||||||
And [this].
|
|
||||||
|
|
||||||
But not [that] [].
|
|
||||||
|
|
||||||
Nor [that][].
|
|
||||||
|
|
||||||
Nor [that].
|
|
||||||
|
|
||||||
[Something in brackets like [this][] should work]
|
|
||||||
|
|
||||||
[Same with [this].]
|
|
||||||
|
|
||||||
In this case, [this](/somethingelse/) points to something else.
|
|
||||||
|
|
||||||
Backslashing should suppress \[this] and [this\].
|
|
||||||
|
|
||||||
[this]: foo
|
|
||||||
|
|
||||||
|
|
||||||
* * *
|
|
||||||
|
|
||||||
Here's one where the [link
|
|
||||||
breaks] across lines.
|
|
||||||
|
|
||||||
Here's another where the [link
|
|
||||||
breaks] across lines, but with a line-ending space.
|
|
||||||
|
|
||||||
|
|
||||||
[link breaks]: /url/
|
|
9
vendor/github.com/russross/blackfriday/testdata/Links, shortcut references.html
generated
vendored
9
vendor/github.com/russross/blackfriday/testdata/Links, shortcut references.html
generated
vendored
|
@ -1,9 +0,0 @@
|
||||||
<p>This is the <a href="/simple">simple case</a>.</p>
|
|
||||||
|
|
||||||
<p>This one has a <a href="/foo">line
|
|
||||||
break</a>.</p>
|
|
||||||
|
|
||||||
<p>This one has a <a href="/foo">line
|
|
||||||
break</a> with a line-ending space.</p>
|
|
||||||
|
|
||||||
<p><a href="/that">this</a> and the <a href="/other">other</a></p>
|
|
20
vendor/github.com/russross/blackfriday/testdata/Links, shortcut references.text
generated
vendored
20
vendor/github.com/russross/blackfriday/testdata/Links, shortcut references.text
generated
vendored
|
@ -1,20 +0,0 @@
|
||||||
This is the [simple case].
|
|
||||||
|
|
||||||
[simple case]: /simple
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
This one has a [line
|
|
||||||
break].
|
|
||||||
|
|
||||||
This one has a [line
|
|
||||||
break] with a line-ending space.
|
|
||||||
|
|
||||||
[line break]: /foo
|
|
||||||
|
|
||||||
|
|
||||||
[this] [that] and the [other]
|
|
||||||
|
|
||||||
[this]: /this
|
|
||||||
[that]: /that
|
|
||||||
[other]: /other
|
|
|
@ -1,3 +0,0 @@
|
||||||
<p>Foo <a href="/url/" title="Title with "quotes" inside">bar</a>.</p>
|
|
||||||
|
|
||||||
<p>Foo <a href="/url/" title="Title with "quotes" inside">bar</a>.</p>
|
|
|
@ -1,7 +0,0 @@
|
||||||
Foo [bar][].
|
|
||||||
|
|
||||||
Foo [bar](/url/ "Title with "quotes" inside").
|
|
||||||
|
|
||||||
|
|
||||||
[bar]: /url/ "Title with "quotes" inside"
|
|
||||||
|
|
314
vendor/github.com/russross/blackfriday/testdata/Markdown Documentation - Basics.html
generated
vendored
314
vendor/github.com/russross/blackfriday/testdata/Markdown Documentation - Basics.html
generated
vendored
|
@ -1,314 +0,0 @@
|
||||||
<h1>Markdown: Basics</h1>
|
|
||||||
|
|
||||||
<ul id="ProjectSubmenu">
|
|
||||||
<li><a href="/projects/markdown/" title="Markdown Project Page">Main</a></li>
|
|
||||||
<li><a class="selected" title="Markdown Basics">Basics</a></li>
|
|
||||||
<li><a href="/projects/markdown/syntax" title="Markdown Syntax Documentation">Syntax</a></li>
|
|
||||||
<li><a href="/projects/markdown/license" title="Pricing and License Information">License</a></li>
|
|
||||||
<li><a href="/projects/markdown/dingus" title="Online Markdown Web Form">Dingus</a></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2>Getting the Gist of Markdown's Formatting Syntax</h2>
|
|
||||||
|
|
||||||
<p>This page offers a brief overview of what it's like to use Markdown.
|
|
||||||
The <a href="/projects/markdown/syntax" title="Markdown Syntax">syntax page</a> provides complete, detailed documentation for
|
|
||||||
every feature, but Markdown should be very easy to pick up simply by
|
|
||||||
looking at a few examples of it in action. The examples on this page
|
|
||||||
are written in a before/after style, showing example syntax and the
|
|
||||||
HTML output produced by Markdown.</p>
|
|
||||||
|
|
||||||
<p>It's also helpful to simply try Markdown out; the <a href="/projects/markdown/dingus" title="Markdown Dingus">Dingus</a> is a
|
|
||||||
web application that allows you type your own Markdown-formatted text
|
|
||||||
and translate it to XHTML.</p>
|
|
||||||
|
|
||||||
<p><strong>Note:</strong> This document is itself written using Markdown; you
|
|
||||||
can <a href="/projects/markdown/basics.text">see the source for it by adding '.text' to the URL</a>.</p>
|
|
||||||
|
|
||||||
<h2>Paragraphs, Headers, Blockquotes</h2>
|
|
||||||
|
|
||||||
<p>A paragraph is simply one or more consecutive lines of text, separated
|
|
||||||
by one or more blank lines. (A blank line is any line that looks like a
|
|
||||||
blank line -- a line containing nothing spaces or tabs is considered
|
|
||||||
blank.) Normal paragraphs should not be intended with spaces or tabs.</p>
|
|
||||||
|
|
||||||
<p>Markdown offers two styles of headers: <em>Setext</em> and <em>atx</em>.
|
|
||||||
Setext-style headers for <code><h1></code> and <code><h2></code> are created by
|
|
||||||
"underlining" with equal signs (<code>=</code>) and hyphens (<code>-</code>), respectively.
|
|
||||||
To create an atx-style header, you put 1-6 hash marks (<code>#</code>) at the
|
|
||||||
beginning of the line -- the number of hashes equals the resulting
|
|
||||||
HTML header level.</p>
|
|
||||||
|
|
||||||
<p>Blockquotes are indicated using email-style '<code>></code>' angle brackets.</p>
|
|
||||||
|
|
||||||
<p>Markdown:</p>
|
|
||||||
|
|
||||||
<pre><code>A First Level Header
|
|
||||||
====================
|
|
||||||
|
|
||||||
A Second Level Header
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
Now is the time for all good men to come to
|
|
||||||
the aid of their country. This is just a
|
|
||||||
regular paragraph.
|
|
||||||
|
|
||||||
The quick brown fox jumped over the lazy
|
|
||||||
dog's back.
|
|
||||||
|
|
||||||
### Header 3
|
|
||||||
|
|
||||||
> This is a blockquote.
|
|
||||||
>
|
|
||||||
> This is the second paragraph in the blockquote.
|
|
||||||
>
|
|
||||||
> ## This is an H2 in a blockquote
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Output:</p>
|
|
||||||
|
|
||||||
<pre><code><h1>A First Level Header</h1>
|
|
||||||
|
|
||||||
<h2>A Second Level Header</h2>
|
|
||||||
|
|
||||||
<p>Now is the time for all good men to come to
|
|
||||||
the aid of their country. This is just a
|
|
||||||
regular paragraph.</p>
|
|
||||||
|
|
||||||
<p>The quick brown fox jumped over the lazy
|
|
||||||
dog's back.</p>
|
|
||||||
|
|
||||||
<h3>Header 3</h3>
|
|
||||||
|
|
||||||
<blockquote>
|
|
||||||
<p>This is a blockquote.</p>
|
|
||||||
|
|
||||||
<p>This is the second paragraph in the blockquote.</p>
|
|
||||||
|
|
||||||
<h2>This is an H2 in a blockquote</h2>
|
|
||||||
</blockquote>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<h3>Phrase Emphasis</h3>
|
|
||||||
|
|
||||||
<p>Markdown uses asterisks and underscores to indicate spans of emphasis.</p>
|
|
||||||
|
|
||||||
<p>Markdown:</p>
|
|
||||||
|
|
||||||
<pre><code>Some of these words *are emphasized*.
|
|
||||||
Some of these words _are emphasized also_.
|
|
||||||
|
|
||||||
Use two asterisks for **strong emphasis**.
|
|
||||||
Or, if you prefer, __use two underscores instead__.
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Output:</p>
|
|
||||||
|
|
||||||
<pre><code><p>Some of these words <em>are emphasized</em>.
|
|
||||||
Some of these words <em>are emphasized also</em>.</p>
|
|
||||||
|
|
||||||
<p>Use two asterisks for <strong>strong emphasis</strong>.
|
|
||||||
Or, if you prefer, <strong>use two underscores instead</strong>.</p>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<h2>Lists</h2>
|
|
||||||
|
|
||||||
<p>Unordered (bulleted) lists use asterisks, pluses, and hyphens (<code>*</code>,
|
|
||||||
<code>+</code>, and <code>-</code>) as list markers. These three markers are
|
|
||||||
interchangable; this:</p>
|
|
||||||
|
|
||||||
<pre><code>* Candy.
|
|
||||||
* Gum.
|
|
||||||
* Booze.
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>this:</p>
|
|
||||||
|
|
||||||
<pre><code>+ Candy.
|
|
||||||
+ Gum.
|
|
||||||
+ Booze.
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>and this:</p>
|
|
||||||
|
|
||||||
<pre><code>- Candy.
|
|
||||||
- Gum.
|
|
||||||
- Booze.
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>all produce the same output:</p>
|
|
||||||
|
|
||||||
<pre><code><ul>
|
|
||||||
<li>Candy.</li>
|
|
||||||
<li>Gum.</li>
|
|
||||||
<li>Booze.</li>
|
|
||||||
</ul>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Ordered (numbered) lists use regular numbers, followed by periods, as
|
|
||||||
list markers:</p>
|
|
||||||
|
|
||||||
<pre><code>1. Red
|
|
||||||
2. Green
|
|
||||||
3. Blue
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Output:</p>
|
|
||||||
|
|
||||||
<pre><code><ol>
|
|
||||||
<li>Red</li>
|
|
||||||
<li>Green</li>
|
|
||||||
<li>Blue</li>
|
|
||||||
</ol>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>If you put blank lines between items, you'll get <code><p></code> tags for the
|
|
||||||
list item text. You can create multi-paragraph list items by indenting
|
|
||||||
the paragraphs by 4 spaces or 1 tab:</p>
|
|
||||||
|
|
||||||
<pre><code>* A list item.
|
|
||||||
|
|
||||||
With multiple paragraphs.
|
|
||||||
|
|
||||||
* Another item in the list.
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Output:</p>
|
|
||||||
|
|
||||||
<pre><code><ul>
|
|
||||||
<li><p>A list item.</p>
|
|
||||||
<p>With multiple paragraphs.</p></li>
|
|
||||||
<li><p>Another item in the list.</p></li>
|
|
||||||
</ul>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<h3>Links</h3>
|
|
||||||
|
|
||||||
<p>Markdown supports two styles for creating links: <em>inline</em> and
|
|
||||||
<em>reference</em>. With both styles, you use square brackets to delimit the
|
|
||||||
text you want to turn into a link.</p>
|
|
||||||
|
|
||||||
<p>Inline-style links use parentheses immediately after the link text.
|
|
||||||
For example:</p>
|
|
||||||
|
|
||||||
<pre><code>This is an [example link](http://example.com/).
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Output:</p>
|
|
||||||
|
|
||||||
<pre><code><p>This is an <a href="http://example.com/">
|
|
||||||
example link</a>.</p>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Optionally, you may include a title attribute in the parentheses:</p>
|
|
||||||
|
|
||||||
<pre><code>This is an [example link](http://example.com/ "With a Title").
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Output:</p>
|
|
||||||
|
|
||||||
<pre><code><p>This is an <a href="http://example.com/" title="With a Title">
|
|
||||||
example link</a>.</p>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Reference-style links allow you to refer to your links by names, which
|
|
||||||
you define elsewhere in your document:</p>
|
|
||||||
|
|
||||||
<pre><code>I get 10 times more traffic from [Google][1] than from
|
|
||||||
[Yahoo][2] or [MSN][3].
|
|
||||||
|
|
||||||
[1]: http://google.com/ "Google"
|
|
||||||
[2]: http://search.yahoo.com/ "Yahoo Search"
|
|
||||||
[3]: http://search.msn.com/ "MSN Search"
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Output:</p>
|
|
||||||
|
|
||||||
<pre><code><p>I get 10 times more traffic from <a href="http://google.com/"
|
|
||||||
title="Google">Google</a> than from <a href="http://search.yahoo.com/"
|
|
||||||
title="Yahoo Search">Yahoo</a> or <a href="http://search.msn.com/"
|
|
||||||
title="MSN Search">MSN</a>.</p>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>The title attribute is optional. Link names may contain letters,
|
|
||||||
numbers and spaces, but are <em>not</em> case sensitive:</p>
|
|
||||||
|
|
||||||
<pre><code>I start my morning with a cup of coffee and
|
|
||||||
[The New York Times][NY Times].
|
|
||||||
|
|
||||||
[ny times]: http://www.nytimes.com/
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Output:</p>
|
|
||||||
|
|
||||||
<pre><code><p>I start my morning with a cup of coffee and
|
|
||||||
<a href="http://www.nytimes.com/">The New York Times</a>.</p>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<h3>Images</h3>
|
|
||||||
|
|
||||||
<p>Image syntax is very much like link syntax.</p>
|
|
||||||
|
|
||||||
<p>Inline (titles are optional):</p>
|
|
||||||
|
|
||||||
<pre><code>![alt text](/path/to/img.jpg "Title")
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Reference-style:</p>
|
|
||||||
|
|
||||||
<pre><code>![alt text][id]
|
|
||||||
|
|
||||||
[id]: /path/to/img.jpg "Title"
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Both of the above examples produce the same output:</p>
|
|
||||||
|
|
||||||
<pre><code><img src="/path/to/img.jpg" alt="alt text" title="Title" />
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<h3>Code</h3>
|
|
||||||
|
|
||||||
<p>In a regular paragraph, you can create code span by wrapping text in
|
|
||||||
backtick quotes. Any ampersands (<code>&</code>) and angle brackets (<code><</code> or
|
|
||||||
<code>></code>) will automatically be translated into HTML entities. This makes
|
|
||||||
it easy to use Markdown to write about HTML example code:</p>
|
|
||||||
|
|
||||||
<pre><code>I strongly recommend against using any `<blink>` tags.
|
|
||||||
|
|
||||||
I wish SmartyPants used named entities like `&mdash;`
|
|
||||||
instead of decimal-encoded entites like `&#8212;`.
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Output:</p>
|
|
||||||
|
|
||||||
<pre><code><p>I strongly recommend against using any
|
|
||||||
<code>&lt;blink&gt;</code> tags.</p>
|
|
||||||
|
|
||||||
<p>I wish SmartyPants used named entities like
|
|
||||||
<code>&amp;mdash;</code> instead of decimal-encoded
|
|
||||||
entites like <code>&amp;#8212;</code>.</p>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>To specify an entire block of pre-formatted code, indent every line of
|
|
||||||
the block by 4 spaces or 1 tab. Just like with code spans, <code>&</code>, <code><</code>,
|
|
||||||
and <code>></code> characters will be escaped automatically.</p>
|
|
||||||
|
|
||||||
<p>Markdown:</p>
|
|
||||||
|
|
||||||
<pre><code>If you want your page to validate under XHTML 1.0 Strict,
|
|
||||||
you've got to put paragraph tags in your blockquotes:
|
|
||||||
|
|
||||||
<blockquote>
|
|
||||||
<p>For example.</p>
|
|
||||||
</blockquote>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Output:</p>
|
|
||||||
|
|
||||||
<pre><code><p>If you want your page to validate under XHTML 1.0 Strict,
|
|
||||||
you've got to put paragraph tags in your blockquotes:</p>
|
|
||||||
|
|
||||||
<pre><code>&lt;blockquote&gt;
|
|
||||||
&lt;p&gt;For example.&lt;/p&gt;
|
|
||||||
&lt;/blockquote&gt;
|
|
||||||
</code></pre>
|
|
||||||
</code></pre>
|
|
306
vendor/github.com/russross/blackfriday/testdata/Markdown Documentation - Basics.text
generated
vendored
306
vendor/github.com/russross/blackfriday/testdata/Markdown Documentation - Basics.text
generated
vendored
|
@ -1,306 +0,0 @@
|
||||||
Markdown: Basics
|
|
||||||
================
|
|
||||||
|
|
||||||
<ul id="ProjectSubmenu">
|
|
||||||
<li><a href="/projects/markdown/" title="Markdown Project Page">Main</a></li>
|
|
||||||
<li><a class="selected" title="Markdown Basics">Basics</a></li>
|
|
||||||
<li><a href="/projects/markdown/syntax" title="Markdown Syntax Documentation">Syntax</a></li>
|
|
||||||
<li><a href="/projects/markdown/license" title="Pricing and License Information">License</a></li>
|
|
||||||
<li><a href="/projects/markdown/dingus" title="Online Markdown Web Form">Dingus</a></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
|
|
||||||
Getting the Gist of Markdown's Formatting Syntax
|
|
||||||
------------------------------------------------
|
|
||||||
|
|
||||||
This page offers a brief overview of what it's like to use Markdown.
|
|
||||||
The [syntax page] [s] provides complete, detailed documentation for
|
|
||||||
every feature, but Markdown should be very easy to pick up simply by
|
|
||||||
looking at a few examples of it in action. The examples on this page
|
|
||||||
are written in a before/after style, showing example syntax and the
|
|
||||||
HTML output produced by Markdown.
|
|
||||||
|
|
||||||
It's also helpful to simply try Markdown out; the [Dingus] [d] is a
|
|
||||||
web application that allows you type your own Markdown-formatted text
|
|
||||||
and translate it to XHTML.
|
|
||||||
|
|
||||||
**Note:** This document is itself written using Markdown; you
|
|
||||||
can [see the source for it by adding '.text' to the URL] [src].
|
|
||||||
|
|
||||||
[s]: /projects/markdown/syntax "Markdown Syntax"
|
|
||||||
[d]: /projects/markdown/dingus "Markdown Dingus"
|
|
||||||
[src]: /projects/markdown/basics.text
|
|
||||||
|
|
||||||
|
|
||||||
## Paragraphs, Headers, Blockquotes ##
|
|
||||||
|
|
||||||
A paragraph is simply one or more consecutive lines of text, separated
|
|
||||||
by one or more blank lines. (A blank line is any line that looks like a
|
|
||||||
blank line -- a line containing nothing spaces or tabs is considered
|
|
||||||
blank.) Normal paragraphs should not be intended with spaces or tabs.
|
|
||||||
|
|
||||||
Markdown offers two styles of headers: *Setext* and *atx*.
|
|
||||||
Setext-style headers for `<h1>` and `<h2>` are created by
|
|
||||||
"underlining" with equal signs (`=`) and hyphens (`-`), respectively.
|
|
||||||
To create an atx-style header, you put 1-6 hash marks (`#`) at the
|
|
||||||
beginning of the line -- the number of hashes equals the resulting
|
|
||||||
HTML header level.
|
|
||||||
|
|
||||||
Blockquotes are indicated using email-style '`>`' angle brackets.
|
|
||||||
|
|
||||||
Markdown:
|
|
||||||
|
|
||||||
A First Level Header
|
|
||||||
====================
|
|
||||||
|
|
||||||
A Second Level Header
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
Now is the time for all good men to come to
|
|
||||||
the aid of their country. This is just a
|
|
||||||
regular paragraph.
|
|
||||||
|
|
||||||
The quick brown fox jumped over the lazy
|
|
||||||
dog's back.
|
|
||||||
|
|
||||||
### Header 3
|
|
||||||
|
|
||||||
> This is a blockquote.
|
|
||||||
>
|
|
||||||
> This is the second paragraph in the blockquote.
|
|
||||||
>
|
|
||||||
> ## This is an H2 in a blockquote
|
|
||||||
|
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
<h1>A First Level Header</h1>
|
|
||||||
|
|
||||||
<h2>A Second Level Header</h2>
|
|
||||||
|
|
||||||
<p>Now is the time for all good men to come to
|
|
||||||
the aid of their country. This is just a
|
|
||||||
regular paragraph.</p>
|
|
||||||
|
|
||||||
<p>The quick brown fox jumped over the lazy
|
|
||||||
dog's back.</p>
|
|
||||||
|
|
||||||
<h3>Header 3</h3>
|
|
||||||
|
|
||||||
<blockquote>
|
|
||||||
<p>This is a blockquote.</p>
|
|
||||||
|
|
||||||
<p>This is the second paragraph in the blockquote.</p>
|
|
||||||
|
|
||||||
<h2>This is an H2 in a blockquote</h2>
|
|
||||||
</blockquote>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Phrase Emphasis ###
|
|
||||||
|
|
||||||
Markdown uses asterisks and underscores to indicate spans of emphasis.
|
|
||||||
|
|
||||||
Markdown:
|
|
||||||
|
|
||||||
Some of these words *are emphasized*.
|
|
||||||
Some of these words _are emphasized also_.
|
|
||||||
|
|
||||||
Use two asterisks for **strong emphasis**.
|
|
||||||
Or, if you prefer, __use two underscores instead__.
|
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
<p>Some of these words <em>are emphasized</em>.
|
|
||||||
Some of these words <em>are emphasized also</em>.</p>
|
|
||||||
|
|
||||||
<p>Use two asterisks for <strong>strong emphasis</strong>.
|
|
||||||
Or, if you prefer, <strong>use two underscores instead</strong>.</p>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Lists ##
|
|
||||||
|
|
||||||
Unordered (bulleted) lists use asterisks, pluses, and hyphens (`*`,
|
|
||||||
`+`, and `-`) as list markers. These three markers are
|
|
||||||
interchangable; this:
|
|
||||||
|
|
||||||
* Candy.
|
|
||||||
* Gum.
|
|
||||||
* Booze.
|
|
||||||
|
|
||||||
this:
|
|
||||||
|
|
||||||
+ Candy.
|
|
||||||
+ Gum.
|
|
||||||
+ Booze.
|
|
||||||
|
|
||||||
and this:
|
|
||||||
|
|
||||||
- Candy.
|
|
||||||
- Gum.
|
|
||||||
- Booze.
|
|
||||||
|
|
||||||
all produce the same output:
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Candy.</li>
|
|
||||||
<li>Gum.</li>
|
|
||||||
<li>Booze.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
Ordered (numbered) lists use regular numbers, followed by periods, as
|
|
||||||
list markers:
|
|
||||||
|
|
||||||
1. Red
|
|
||||||
2. Green
|
|
||||||
3. Blue
|
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
<ol>
|
|
||||||
<li>Red</li>
|
|
||||||
<li>Green</li>
|
|
||||||
<li>Blue</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
If you put blank lines between items, you'll get `<p>` tags for the
|
|
||||||
list item text. You can create multi-paragraph list items by indenting
|
|
||||||
the paragraphs by 4 spaces or 1 tab:
|
|
||||||
|
|
||||||
* A list item.
|
|
||||||
|
|
||||||
With multiple paragraphs.
|
|
||||||
|
|
||||||
* Another item in the list.
|
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><p>A list item.</p>
|
|
||||||
<p>With multiple paragraphs.</p></li>
|
|
||||||
<li><p>Another item in the list.</p></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Links ###
|
|
||||||
|
|
||||||
Markdown supports two styles for creating links: *inline* and
|
|
||||||
*reference*. With both styles, you use square brackets to delimit the
|
|
||||||
text you want to turn into a link.
|
|
||||||
|
|
||||||
Inline-style links use parentheses immediately after the link text.
|
|
||||||
For example:
|
|
||||||
|
|
||||||
This is an [example link](http://example.com/).
|
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
<p>This is an <a href="http://example.com/">
|
|
||||||
example link</a>.</p>
|
|
||||||
|
|
||||||
Optionally, you may include a title attribute in the parentheses:
|
|
||||||
|
|
||||||
This is an [example link](http://example.com/ "With a Title").
|
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
<p>This is an <a href="http://example.com/" title="With a Title">
|
|
||||||
example link</a>.</p>
|
|
||||||
|
|
||||||
Reference-style links allow you to refer to your links by names, which
|
|
||||||
you define elsewhere in your document:
|
|
||||||
|
|
||||||
I get 10 times more traffic from [Google][1] than from
|
|
||||||
[Yahoo][2] or [MSN][3].
|
|
||||||
|
|
||||||
[1]: http://google.com/ "Google"
|
|
||||||
[2]: http://search.yahoo.com/ "Yahoo Search"
|
|
||||||
[3]: http://search.msn.com/ "MSN Search"
|
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
<p>I get 10 times more traffic from <a href="http://google.com/"
|
|
||||||
title="Google">Google</a> than from <a href="http://search.yahoo.com/"
|
|
||||||
title="Yahoo Search">Yahoo</a> or <a href="http://search.msn.com/"
|
|
||||||
title="MSN Search">MSN</a>.</p>
|
|
||||||
|
|
||||||
The title attribute is optional. Link names may contain letters,
|
|
||||||
numbers and spaces, but are *not* case sensitive:
|
|
||||||
|
|
||||||
I start my morning with a cup of coffee and
|
|
||||||
[The New York Times][NY Times].
|
|
||||||
|
|
||||||
[ny times]: http://www.nytimes.com/
|
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
<p>I start my morning with a cup of coffee and
|
|
||||||
<a href="http://www.nytimes.com/">The New York Times</a>.</p>
|
|
||||||
|
|
||||||
|
|
||||||
### Images ###
|
|
||||||
|
|
||||||
Image syntax is very much like link syntax.
|
|
||||||
|
|
||||||
Inline (titles are optional):
|
|
||||||
|
|
||||||
![alt text](/path/to/img.jpg "Title")
|
|
||||||
|
|
||||||
Reference-style:
|
|
||||||
|
|
||||||
![alt text][id]
|
|
||||||
|
|
||||||
[id]: /path/to/img.jpg "Title"
|
|
||||||
|
|
||||||
Both of the above examples produce the same output:
|
|
||||||
|
|
||||||
<img src="/path/to/img.jpg" alt="alt text" title="Title" />
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Code ###
|
|
||||||
|
|
||||||
In a regular paragraph, you can create code span by wrapping text in
|
|
||||||
backtick quotes. Any ampersands (`&`) and angle brackets (`<` or
|
|
||||||
`>`) will automatically be translated into HTML entities. This makes
|
|
||||||
it easy to use Markdown to write about HTML example code:
|
|
||||||
|
|
||||||
I strongly recommend against using any `<blink>` tags.
|
|
||||||
|
|
||||||
I wish SmartyPants used named entities like `—`
|
|
||||||
instead of decimal-encoded entites like `—`.
|
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
<p>I strongly recommend against using any
|
|
||||||
<code><blink></code> tags.</p>
|
|
||||||
|
|
||||||
<p>I wish SmartyPants used named entities like
|
|
||||||
<code>&mdash;</code> instead of decimal-encoded
|
|
||||||
entites like <code>&#8212;</code>.</p>
|
|
||||||
|
|
||||||
|
|
||||||
To specify an entire block of pre-formatted code, indent every line of
|
|
||||||
the block by 4 spaces or 1 tab. Just like with code spans, `&`, `<`,
|
|
||||||
and `>` characters will be escaped automatically.
|
|
||||||
|
|
||||||
Markdown:
|
|
||||||
|
|
||||||
If you want your page to validate under XHTML 1.0 Strict,
|
|
||||||
you've got to put paragraph tags in your blockquotes:
|
|
||||||
|
|
||||||
<blockquote>
|
|
||||||
<p>For example.</p>
|
|
||||||
</blockquote>
|
|
||||||
|
|
||||||
Output:
|
|
||||||
|
|
||||||
<p>If you want your page to validate under XHTML 1.0 Strict,
|
|
||||||
you've got to put paragraph tags in your blockquotes:</p>
|
|
||||||
|
|
||||||
<pre><code><blockquote>
|
|
||||||
<p>For example.</p>
|
|
||||||
</blockquote>
|
|
||||||
</code></pre>
|
|
946
vendor/github.com/russross/blackfriday/testdata/Markdown Documentation - Syntax.html
generated
vendored
946
vendor/github.com/russross/blackfriday/testdata/Markdown Documentation - Syntax.html
generated
vendored
|
@ -1,946 +0,0 @@
|
||||||
<h1>Markdown: Syntax</h1>
|
|
||||||
|
|
||||||
<ul id="ProjectSubmenu">
|
|
||||||
<li><a href="/projects/markdown/" title="Markdown Project Page">Main</a></li>
|
|
||||||
<li><a href="/projects/markdown/basics" title="Markdown Basics">Basics</a></li>
|
|
||||||
<li><a class="selected" title="Markdown Syntax Documentation">Syntax</a></li>
|
|
||||||
<li><a href="/projects/markdown/license" title="Pricing and License Information">License</a></li>
|
|
||||||
<li><a href="/projects/markdown/dingus" title="Online Markdown Web Form">Dingus</a></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="#overview">Overview</a>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="#philosophy">Philosophy</a></li>
|
|
||||||
<li><a href="#html">Inline HTML</a></li>
|
|
||||||
<li><a href="#autoescape">Automatic Escaping for Special Characters</a></li>
|
|
||||||
</ul></li>
|
|
||||||
<li><a href="#block">Block Elements</a>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="#p">Paragraphs and Line Breaks</a></li>
|
|
||||||
<li><a href="#header">Headers</a></li>
|
|
||||||
<li><a href="#blockquote">Blockquotes</a></li>
|
|
||||||
<li><a href="#list">Lists</a></li>
|
|
||||||
<li><a href="#precode">Code Blocks</a></li>
|
|
||||||
<li><a href="#hr">Horizontal Rules</a></li>
|
|
||||||
</ul></li>
|
|
||||||
<li><a href="#span">Span Elements</a>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="#link">Links</a></li>
|
|
||||||
<li><a href="#em">Emphasis</a></li>
|
|
||||||
<li><a href="#code">Code</a></li>
|
|
||||||
<li><a href="#img">Images</a></li>
|
|
||||||
</ul></li>
|
|
||||||
<li><a href="#misc">Miscellaneous</a>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="#backslash">Backslash Escapes</a></li>
|
|
||||||
<li><a href="#autolink">Automatic Links</a></li>
|
|
||||||
</ul></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p><strong>Note:</strong> This document is itself written using Markdown; you
|
|
||||||
can <a href="/projects/markdown/syntax.text">see the source for it by adding '.text' to the URL</a>.</p>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<h2 id="overview">Overview</h2>
|
|
||||||
|
|
||||||
<h3 id="philosophy">Philosophy</h3>
|
|
||||||
|
|
||||||
<p>Markdown is intended to be as easy-to-read and easy-to-write as is feasible.</p>
|
|
||||||
|
|
||||||
<p>Readability, however, is emphasized above all else. A Markdown-formatted
|
|
||||||
document should be publishable as-is, as plain text, without looking
|
|
||||||
like it's been marked up with tags or formatting instructions. While
|
|
||||||
Markdown's syntax has been influenced by several existing text-to-HTML
|
|
||||||
filters -- including <a href="http://docutils.sourceforge.net/mirror/setext.html">Setext</a>, <a href="http://www.aaronsw.com/2002/atx/">atx</a>, <a href="http://textism.com/tools/textile/">Textile</a>, <a href="http://docutils.sourceforge.net/rst.html">reStructuredText</a>,
|
|
||||||
<a href="http://www.triptico.com/software/grutatxt.html">Grutatext</a>, and <a href="http://ettext.taint.org/doc/">EtText</a> -- the single biggest source of
|
|
||||||
inspiration for Markdown's syntax is the format of plain text email.</p>
|
|
||||||
|
|
||||||
<p>To this end, Markdown's syntax is comprised entirely of punctuation
|
|
||||||
characters, which punctuation characters have been carefully chosen so
|
|
||||||
as to look like what they mean. E.g., asterisks around a word actually
|
|
||||||
look like *emphasis*. Markdown lists look like, well, lists. Even
|
|
||||||
blockquotes look like quoted passages of text, assuming you've ever
|
|
||||||
used email.</p>
|
|
||||||
|
|
||||||
<h3 id="html">Inline HTML</h3>
|
|
||||||
|
|
||||||
<p>Markdown's syntax is intended for one purpose: to be used as a
|
|
||||||
format for <em>writing</em> for the web.</p>
|
|
||||||
|
|
||||||
<p>Markdown is not a replacement for HTML, or even close to it. Its
|
|
||||||
syntax is very small, corresponding only to a very small subset of
|
|
||||||
HTML tags. The idea is <em>not</em> to create a syntax that makes it easier
|
|
||||||
to insert HTML tags. In my opinion, HTML tags are already easy to
|
|
||||||
insert. The idea for Markdown is to make it easy to read, write, and
|
|
||||||
edit prose. HTML is a <em>publishing</em> format; Markdown is a <em>writing</em>
|
|
||||||
format. Thus, Markdown's formatting syntax only addresses issues that
|
|
||||||
can be conveyed in plain text.</p>
|
|
||||||
|
|
||||||
<p>For any markup that is not covered by Markdown's syntax, you simply
|
|
||||||
use HTML itself. There's no need to preface it or delimit it to
|
|
||||||
indicate that you're switching from Markdown to HTML; you just use
|
|
||||||
the tags.</p>
|
|
||||||
|
|
||||||
<p>The only restrictions are that block-level HTML elements -- e.g. <code><div></code>,
|
|
||||||
<code><table></code>, <code><pre></code>, <code><p></code>, etc. -- must be separated from surrounding
|
|
||||||
content by blank lines, and the start and end tags of the block should
|
|
||||||
not be indented with tabs or spaces. Markdown is smart enough not
|
|
||||||
to add extra (unwanted) <code><p></code> tags around HTML block-level tags.</p>
|
|
||||||
|
|
||||||
<p>For example, to add an HTML table to a Markdown article:</p>
|
|
||||||
|
|
||||||
<pre><code>This is a regular paragraph.
|
|
||||||
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td>Foo</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
This is another regular paragraph.
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Note that Markdown formatting syntax is not processed within block-level
|
|
||||||
HTML tags. E.g., you can't use Markdown-style <code>*emphasis*</code> inside an
|
|
||||||
HTML block.</p>
|
|
||||||
|
|
||||||
<p>Span-level HTML tags -- e.g. <code><span></code>, <code><cite></code>, or <code><del></code> -- can be
|
|
||||||
used anywhere in a Markdown paragraph, list item, or header. If you
|
|
||||||
want, you can even use HTML tags instead of Markdown formatting; e.g. if
|
|
||||||
you'd prefer to use HTML <code><a></code> or <code><img></code> tags instead of Markdown's
|
|
||||||
link or image syntax, go right ahead.</p>
|
|
||||||
|
|
||||||
<p>Unlike block-level HTML tags, Markdown syntax <em>is</em> processed within
|
|
||||||
span-level tags.</p>
|
|
||||||
|
|
||||||
<h3 id="autoescape">Automatic Escaping for Special Characters</h3>
|
|
||||||
|
|
||||||
<p>In HTML, there are two characters that demand special treatment: <code><</code>
|
|
||||||
and <code>&</code>. Left angle brackets are used to start tags; ampersands are
|
|
||||||
used to denote HTML entities. If you want to use them as literal
|
|
||||||
characters, you must escape them as entities, e.g. <code>&lt;</code>, and
|
|
||||||
<code>&amp;</code>.</p>
|
|
||||||
|
|
||||||
<p>Ampersands in particular are bedeviling for web writers. If you want to
|
|
||||||
write about 'AT&T', you need to write '<code>AT&amp;T</code>'. You even need to
|
|
||||||
escape ampersands within URLs. Thus, if you want to link to:</p>
|
|
||||||
|
|
||||||
<pre><code>http://images.google.com/images?num=30&q=larry+bird
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>you need to encode the URL as:</p>
|
|
||||||
|
|
||||||
<pre><code>http://images.google.com/images?num=30&amp;q=larry+bird
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>in your anchor tag <code>href</code> attribute. Needless to say, this is easy to
|
|
||||||
forget, and is probably the single most common source of HTML validation
|
|
||||||
errors in otherwise well-marked-up web sites.</p>
|
|
||||||
|
|
||||||
<p>Markdown allows you to use these characters naturally, taking care of
|
|
||||||
all the necessary escaping for you. If you use an ampersand as part of
|
|
||||||
an HTML entity, it remains unchanged; otherwise it will be translated
|
|
||||||
into <code>&amp;</code>.</p>
|
|
||||||
|
|
||||||
<p>So, if you want to include a copyright symbol in your article, you can write:</p>
|
|
||||||
|
|
||||||
<pre><code>&copy;
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>and Markdown will leave it alone. But if you write:</p>
|
|
||||||
|
|
||||||
<pre><code>AT&T
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Markdown will translate it to:</p>
|
|
||||||
|
|
||||||
<pre><code>AT&amp;T
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Similarly, because Markdown supports <a href="#html">inline HTML</a>, if you use
|
|
||||||
angle brackets as delimiters for HTML tags, Markdown will treat them as
|
|
||||||
such. But if you write:</p>
|
|
||||||
|
|
||||||
<pre><code>4 < 5
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Markdown will translate it to:</p>
|
|
||||||
|
|
||||||
<pre><code>4 &lt; 5
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>However, inside Markdown code spans and blocks, angle brackets and
|
|
||||||
ampersands are <em>always</em> encoded automatically. This makes it easy to use
|
|
||||||
Markdown to write about HTML code. (As opposed to raw HTML, which is a
|
|
||||||
terrible format for writing about HTML syntax, because every single <code><</code>
|
|
||||||
and <code>&</code> in your example code needs to be escaped.)</p>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<h2 id="block">Block Elements</h2>
|
|
||||||
|
|
||||||
<h3 id="p">Paragraphs and Line Breaks</h3>
|
|
||||||
|
|
||||||
<p>A paragraph is simply one or more consecutive lines of text, separated
|
|
||||||
by one or more blank lines. (A blank line is any line that looks like a
|
|
||||||
blank line -- a line containing nothing but spaces or tabs is considered
|
|
||||||
blank.) Normal paragraphs should not be intended with spaces or tabs.</p>
|
|
||||||
|
|
||||||
<p>The implication of the "one or more consecutive lines of text" rule is
|
|
||||||
that Markdown supports "hard-wrapped" text paragraphs. This differs
|
|
||||||
significantly from most other text-to-HTML formatters (including Movable
|
|
||||||
Type's "Convert Line Breaks" option) which translate every line break
|
|
||||||
character in a paragraph into a <code><br /></code> tag.</p>
|
|
||||||
|
|
||||||
<p>When you <em>do</em> want to insert a <code><br /></code> break tag using Markdown, you
|
|
||||||
end a line with two or more spaces, then type return.</p>
|
|
||||||
|
|
||||||
<p>Yes, this takes a tad more effort to create a <code><br /></code>, but a simplistic
|
|
||||||
"every line break is a <code><br /></code>" rule wouldn't work for Markdown.
|
|
||||||
Markdown's email-style <a href="#blockquote">blockquoting</a> and multi-paragraph <a href="#list">list items</a>
|
|
||||||
work best -- and look better -- when you format them with hard breaks.</p>
|
|
||||||
|
|
||||||
<h3 id="header">Headers</h3>
|
|
||||||
|
|
||||||
<p>Markdown supports two styles of headers, <a href="http://docutils.sourceforge.net/mirror/setext.html">Setext</a> and <a href="http://www.aaronsw.com/2002/atx/">atx</a>.</p>
|
|
||||||
|
|
||||||
<p>Setext-style headers are "underlined" using equal signs (for first-level
|
|
||||||
headers) and dashes (for second-level headers). For example:</p>
|
|
||||||
|
|
||||||
<pre><code>This is an H1
|
|
||||||
=============
|
|
||||||
|
|
||||||
This is an H2
|
|
||||||
-------------
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Any number of underlining <code>=</code>'s or <code>-</code>'s will work.</p>
|
|
||||||
|
|
||||||
<p>Atx-style headers use 1-6 hash characters at the start of the line,
|
|
||||||
corresponding to header levels 1-6. For example:</p>
|
|
||||||
|
|
||||||
<pre><code># This is an H1
|
|
||||||
|
|
||||||
## This is an H2
|
|
||||||
|
|
||||||
###### This is an H6
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Optionally, you may "close" atx-style headers. This is purely
|
|
||||||
cosmetic -- you can use this if you think it looks better. The
|
|
||||||
closing hashes don't even need to match the number of hashes
|
|
||||||
used to open the header. (The number of opening hashes
|
|
||||||
determines the header level.) :</p>
|
|
||||||
|
|
||||||
<pre><code># This is an H1 #
|
|
||||||
|
|
||||||
## This is an H2 ##
|
|
||||||
|
|
||||||
### This is an H3 ######
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<h3 id="blockquote">Blockquotes</h3>
|
|
||||||
|
|
||||||
<p>Markdown uses email-style <code>></code> characters for blockquoting. If you're
|
|
||||||
familiar with quoting passages of text in an email message, then you
|
|
||||||
know how to create a blockquote in Markdown. It looks best if you hard
|
|
||||||
wrap the text and put a <code>></code> before every line:</p>
|
|
||||||
|
|
||||||
<pre><code>> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
|
|
||||||
> consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
|
|
||||||
> Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
|
|
||||||
>
|
|
||||||
> Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
|
|
||||||
> id sem consectetuer libero luctus adipiscing.
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Markdown allows you to be lazy and only put the <code>></code> before the first
|
|
||||||
line of a hard-wrapped paragraph:</p>
|
|
||||||
|
|
||||||
<pre><code>> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
|
|
||||||
consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
|
|
||||||
Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
|
|
||||||
|
|
||||||
> Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
|
|
||||||
id sem consectetuer libero luctus adipiscing.
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Blockquotes can be nested (i.e. a blockquote-in-a-blockquote) by
|
|
||||||
adding additional levels of <code>></code>:</p>
|
|
||||||
|
|
||||||
<pre><code>> This is the first level of quoting.
|
|
||||||
>
|
|
||||||
> > This is nested blockquote.
|
|
||||||
>
|
|
||||||
> Back to the first level.
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Blockquotes can contain other Markdown elements, including headers, lists,
|
|
||||||
and code blocks:</p>
|
|
||||||
|
|
||||||
<pre><code>> ## This is a header.
|
|
||||||
>
|
|
||||||
> 1. This is the first list item.
|
|
||||||
> 2. This is the second list item.
|
|
||||||
>
|
|
||||||
> Here's some example code:
|
|
||||||
>
|
|
||||||
> return shell_exec("echo $input | $markdown_script");
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Any decent text editor should make email-style quoting easy. For
|
|
||||||
example, with BBEdit, you can make a selection and choose Increase
|
|
||||||
Quote Level from the Text menu.</p>
|
|
||||||
|
|
||||||
<h3 id="list">Lists</h3>
|
|
||||||
|
|
||||||
<p>Markdown supports ordered (numbered) and unordered (bulleted) lists.</p>
|
|
||||||
|
|
||||||
<p>Unordered lists use asterisks, pluses, and hyphens -- interchangably
|
|
||||||
-- as list markers:</p>
|
|
||||||
|
|
||||||
<pre><code>* Red
|
|
||||||
* Green
|
|
||||||
* Blue
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>is equivalent to:</p>
|
|
||||||
|
|
||||||
<pre><code>+ Red
|
|
||||||
+ Green
|
|
||||||
+ Blue
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>and:</p>
|
|
||||||
|
|
||||||
<pre><code>- Red
|
|
||||||
- Green
|
|
||||||
- Blue
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Ordered lists use numbers followed by periods:</p>
|
|
||||||
|
|
||||||
<pre><code>1. Bird
|
|
||||||
2. McHale
|
|
||||||
3. Parish
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>It's important to note that the actual numbers you use to mark the
|
|
||||||
list have no effect on the HTML output Markdown produces. The HTML
|
|
||||||
Markdown produces from the above list is:</p>
|
|
||||||
|
|
||||||
<pre><code><ol>
|
|
||||||
<li>Bird</li>
|
|
||||||
<li>McHale</li>
|
|
||||||
<li>Parish</li>
|
|
||||||
</ol>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>If you instead wrote the list in Markdown like this:</p>
|
|
||||||
|
|
||||||
<pre><code>1. Bird
|
|
||||||
1. McHale
|
|
||||||
1. Parish
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>or even:</p>
|
|
||||||
|
|
||||||
<pre><code>3. Bird
|
|
||||||
1. McHale
|
|
||||||
8. Parish
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>you'd get the exact same HTML output. The point is, if you want to,
|
|
||||||
you can use ordinal numbers in your ordered Markdown lists, so that
|
|
||||||
the numbers in your source match the numbers in your published HTML.
|
|
||||||
But if you want to be lazy, you don't have to.</p>
|
|
||||||
|
|
||||||
<p>If you do use lazy list numbering, however, you should still start the
|
|
||||||
list with the number 1. At some point in the future, Markdown may support
|
|
||||||
starting ordered lists at an arbitrary number.</p>
|
|
||||||
|
|
||||||
<p>List markers typically start at the left margin, but may be indented by
|
|
||||||
up to three spaces. List markers must be followed by one or more spaces
|
|
||||||
or a tab.</p>
|
|
||||||
|
|
||||||
<p>To make lists look nice, you can wrap items with hanging indents:</p>
|
|
||||||
|
|
||||||
<pre><code>* Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
|
|
||||||
Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
|
|
||||||
viverra nec, fringilla in, laoreet vitae, risus.
|
|
||||||
* Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
|
|
||||||
Suspendisse id sem consectetuer libero luctus adipiscing.
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>But if you want to be lazy, you don't have to:</p>
|
|
||||||
|
|
||||||
<pre><code>* Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
|
|
||||||
Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
|
|
||||||
viverra nec, fringilla in, laoreet vitae, risus.
|
|
||||||
* Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
|
|
||||||
Suspendisse id sem consectetuer libero luctus adipiscing.
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>If list items are separated by blank lines, Markdown will wrap the
|
|
||||||
items in <code><p></code> tags in the HTML output. For example, this input:</p>
|
|
||||||
|
|
||||||
<pre><code>* Bird
|
|
||||||
* Magic
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>will turn into:</p>
|
|
||||||
|
|
||||||
<pre><code><ul>
|
|
||||||
<li>Bird</li>
|
|
||||||
<li>Magic</li>
|
|
||||||
</ul>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>But this:</p>
|
|
||||||
|
|
||||||
<pre><code>* Bird
|
|
||||||
|
|
||||||
* Magic
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>will turn into:</p>
|
|
||||||
|
|
||||||
<pre><code><ul>
|
|
||||||
<li><p>Bird</p></li>
|
|
||||||
<li><p>Magic</p></li>
|
|
||||||
</ul>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>List items may consist of multiple paragraphs. Each subsequent
|
|
||||||
paragraph in a list item must be intended by either 4 spaces
|
|
||||||
or one tab:</p>
|
|
||||||
|
|
||||||
<pre><code>1. This is a list item with two paragraphs. Lorem ipsum dolor
|
|
||||||
sit amet, consectetuer adipiscing elit. Aliquam hendrerit
|
|
||||||
mi posuere lectus.
|
|
||||||
|
|
||||||
Vestibulum enim wisi, viverra nec, fringilla in, laoreet
|
|
||||||
vitae, risus. Donec sit amet nisl. Aliquam semper ipsum
|
|
||||||
sit amet velit.
|
|
||||||
|
|
||||||
2. Suspendisse id sem consectetuer libero luctus adipiscing.
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>It looks nice if you indent every line of the subsequent
|
|
||||||
paragraphs, but here again, Markdown will allow you to be
|
|
||||||
lazy:</p>
|
|
||||||
|
|
||||||
<pre><code>* This is a list item with two paragraphs.
|
|
||||||
|
|
||||||
This is the second paragraph in the list item. You're
|
|
||||||
only required to indent the first line. Lorem ipsum dolor
|
|
||||||
sit amet, consectetuer adipiscing elit.
|
|
||||||
|
|
||||||
* Another item in the same list.
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>To put a blockquote within a list item, the blockquote's <code>></code>
|
|
||||||
delimiters need to be indented:</p>
|
|
||||||
|
|
||||||
<pre><code>* A list item with a blockquote:
|
|
||||||
|
|
||||||
> This is a blockquote
|
|
||||||
> inside a list item.
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>To put a code block within a list item, the code block needs
|
|
||||||
to be indented <em>twice</em> -- 8 spaces or two tabs:</p>
|
|
||||||
|
|
||||||
<pre><code>* A list item with a code block:
|
|
||||||
|
|
||||||
<code goes here>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>It's worth noting that it's possible to trigger an ordered list by
|
|
||||||
accident, by writing something like this:</p>
|
|
||||||
|
|
||||||
<pre><code>1986. What a great season.
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>In other words, a <em>number-period-space</em> sequence at the beginning of a
|
|
||||||
line. To avoid this, you can backslash-escape the period:</p>
|
|
||||||
|
|
||||||
<pre><code>1986\. What a great season.
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<h3 id="precode">Code Blocks</h3>
|
|
||||||
|
|
||||||
<p>Pre-formatted code blocks are used for writing about programming or
|
|
||||||
markup source code. Rather than forming normal paragraphs, the lines
|
|
||||||
of a code block are interpreted literally. Markdown wraps a code block
|
|
||||||
in both <code><pre></code> and <code><code></code> tags.</p>
|
|
||||||
|
|
||||||
<p>To produce a code block in Markdown, simply indent every line of the
|
|
||||||
block by at least 4 spaces or 1 tab. For example, given this input:</p>
|
|
||||||
|
|
||||||
<pre><code>This is a normal paragraph:
|
|
||||||
|
|
||||||
This is a code block.
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Markdown will generate:</p>
|
|
||||||
|
|
||||||
<pre><code><p>This is a normal paragraph:</p>
|
|
||||||
|
|
||||||
<pre><code>This is a code block.
|
|
||||||
</code></pre>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>One level of indentation -- 4 spaces or 1 tab -- is removed from each
|
|
||||||
line of the code block. For example, this:</p>
|
|
||||||
|
|
||||||
<pre><code>Here is an example of AppleScript:
|
|
||||||
|
|
||||||
tell application "Foo"
|
|
||||||
beep
|
|
||||||
end tell
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>will turn into:</p>
|
|
||||||
|
|
||||||
<pre><code><p>Here is an example of AppleScript:</p>
|
|
||||||
|
|
||||||
<pre><code>tell application "Foo"
|
|
||||||
beep
|
|
||||||
end tell
|
|
||||||
</code></pre>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>A code block continues until it reaches a line that is not indented
|
|
||||||
(or the end of the article).</p>
|
|
||||||
|
|
||||||
<p>Within a code block, ampersands (<code>&</code>) and angle brackets (<code><</code> and <code>></code>)
|
|
||||||
are automatically converted into HTML entities. This makes it very
|
|
||||||
easy to include example HTML source code using Markdown -- just paste
|
|
||||||
it and indent it, and Markdown will handle the hassle of encoding the
|
|
||||||
ampersands and angle brackets. For example, this:</p>
|
|
||||||
|
|
||||||
<pre><code> <div class="footer">
|
|
||||||
&copy; 2004 Foo Corporation
|
|
||||||
</div>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>will turn into:</p>
|
|
||||||
|
|
||||||
<pre><code><pre><code>&lt;div class="footer"&gt;
|
|
||||||
&amp;copy; 2004 Foo Corporation
|
|
||||||
&lt;/div&gt;
|
|
||||||
</code></pre>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Regular Markdown syntax is not processed within code blocks. E.g.,
|
|
||||||
asterisks are just literal asterisks within a code block. This means
|
|
||||||
it's also easy to use Markdown to write about Markdown's own syntax.</p>
|
|
||||||
|
|
||||||
<h3 id="hr">Horizontal Rules</h3>
|
|
||||||
|
|
||||||
<p>You can produce a horizontal rule tag (<code><hr /></code>) by placing three or
|
|
||||||
more hyphens, asterisks, or underscores on a line by themselves. If you
|
|
||||||
wish, you may use spaces between the hyphens or asterisks. Each of the
|
|
||||||
following lines will produce a horizontal rule:</p>
|
|
||||||
|
|
||||||
<pre><code>* * *
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
*****
|
|
||||||
|
|
||||||
- - -
|
|
||||||
|
|
||||||
---------------------------------------
|
|
||||||
|
|
||||||
_ _ _
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<h2 id="span">Span Elements</h2>
|
|
||||||
|
|
||||||
<h3 id="link">Links</h3>
|
|
||||||
|
|
||||||
<p>Markdown supports two style of links: <em>inline</em> and <em>reference</em>.</p>
|
|
||||||
|
|
||||||
<p>In both styles, the link text is delimited by [square brackets].</p>
|
|
||||||
|
|
||||||
<p>To create an inline link, use a set of regular parentheses immediately
|
|
||||||
after the link text's closing square bracket. Inside the parentheses,
|
|
||||||
put the URL where you want the link to point, along with an <em>optional</em>
|
|
||||||
title for the link, surrounded in quotes. For example:</p>
|
|
||||||
|
|
||||||
<pre><code>This is [an example](http://example.com/ "Title") inline link.
|
|
||||||
|
|
||||||
[This link](http://example.net/) has no title attribute.
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Will produce:</p>
|
|
||||||
|
|
||||||
<pre><code><p>This is <a href="http://example.com/" title="Title">
|
|
||||||
an example</a> inline link.</p>
|
|
||||||
|
|
||||||
<p><a href="http://example.net/">This link</a> has no
|
|
||||||
title attribute.</p>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>If you're referring to a local resource on the same server, you can
|
|
||||||
use relative paths:</p>
|
|
||||||
|
|
||||||
<pre><code>See my [About](/about/) page for details.
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Reference-style links use a second set of square brackets, inside
|
|
||||||
which you place a label of your choosing to identify the link:</p>
|
|
||||||
|
|
||||||
<pre><code>This is [an example][id] reference-style link.
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>You can optionally use a space to separate the sets of brackets:</p>
|
|
||||||
|
|
||||||
<pre><code>This is [an example] [id] reference-style link.
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Then, anywhere in the document, you define your link label like this,
|
|
||||||
on a line by itself:</p>
|
|
||||||
|
|
||||||
<pre><code>[id]: http://example.com/ "Optional Title Here"
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>That is:</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Square brackets containing the link identifier (optionally
|
|
||||||
indented from the left margin using up to three spaces);</li>
|
|
||||||
<li>followed by a colon;</li>
|
|
||||||
<li>followed by one or more spaces (or tabs);</li>
|
|
||||||
<li>followed by the URL for the link;</li>
|
|
||||||
<li>optionally followed by a title attribute for the link, enclosed
|
|
||||||
in double or single quotes.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>The link URL may, optionally, be surrounded by angle brackets:</p>
|
|
||||||
|
|
||||||
<pre><code>[id]: <http://example.com/> "Optional Title Here"
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>You can put the title attribute on the next line and use extra spaces
|
|
||||||
or tabs for padding, which tends to look better with longer URLs:</p>
|
|
||||||
|
|
||||||
<pre><code>[id]: http://example.com/longish/path/to/resource/here
|
|
||||||
"Optional Title Here"
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Link definitions are only used for creating links during Markdown
|
|
||||||
processing, and are stripped from your document in the HTML output.</p>
|
|
||||||
|
|
||||||
<p>Link definition names may constist of letters, numbers, spaces, and punctuation -- but they are <em>not</em> case sensitive. E.g. these two links:</p>
|
|
||||||
|
|
||||||
<pre><code>[link text][a]
|
|
||||||
[link text][A]
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>are equivalent.</p>
|
|
||||||
|
|
||||||
<p>The <em>implicit link name</em> shortcut allows you to omit the name of the
|
|
||||||
link, in which case the link text itself is used as the name.
|
|
||||||
Just use an empty set of square brackets -- e.g., to link the word
|
|
||||||
"Google" to the google.com web site, you could simply write:</p>
|
|
||||||
|
|
||||||
<pre><code>[Google][]
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>And then define the link:</p>
|
|
||||||
|
|
||||||
<pre><code>[Google]: http://google.com/
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Because link names may contain spaces, this shortcut even works for
|
|
||||||
multiple words in the link text:</p>
|
|
||||||
|
|
||||||
<pre><code>Visit [Daring Fireball][] for more information.
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>And then define the link:</p>
|
|
||||||
|
|
||||||
<pre><code>[Daring Fireball]: http://daringfireball.net/
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Link definitions can be placed anywhere in your Markdown document. I
|
|
||||||
tend to put them immediately after each paragraph in which they're
|
|
||||||
used, but if you want, you can put them all at the end of your
|
|
||||||
document, sort of like footnotes.</p>
|
|
||||||
|
|
||||||
<p>Here's an example of reference links in action:</p>
|
|
||||||
|
|
||||||
<pre><code>I get 10 times more traffic from [Google] [1] than from
|
|
||||||
[Yahoo] [2] or [MSN] [3].
|
|
||||||
|
|
||||||
[1]: http://google.com/ "Google"
|
|
||||||
[2]: http://search.yahoo.com/ "Yahoo Search"
|
|
||||||
[3]: http://search.msn.com/ "MSN Search"
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Using the implicit link name shortcut, you could instead write:</p>
|
|
||||||
|
|
||||||
<pre><code>I get 10 times more traffic from [Google][] than from
|
|
||||||
[Yahoo][] or [MSN][].
|
|
||||||
|
|
||||||
[google]: http://google.com/ "Google"
|
|
||||||
[yahoo]: http://search.yahoo.com/ "Yahoo Search"
|
|
||||||
[msn]: http://search.msn.com/ "MSN Search"
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Both of the above examples will produce the following HTML output:</p>
|
|
||||||
|
|
||||||
<pre><code><p>I get 10 times more traffic from <a href="http://google.com/"
|
|
||||||
title="Google">Google</a> than from
|
|
||||||
<a href="http://search.yahoo.com/" title="Yahoo Search">Yahoo</a>
|
|
||||||
or <a href="http://search.msn.com/" title="MSN Search">MSN</a>.</p>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>For comparison, here is the same paragraph written using
|
|
||||||
Markdown's inline link style:</p>
|
|
||||||
|
|
||||||
<pre><code>I get 10 times more traffic from [Google](http://google.com/ "Google")
|
|
||||||
than from [Yahoo](http://search.yahoo.com/ "Yahoo Search") or
|
|
||||||
[MSN](http://search.msn.com/ "MSN Search").
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>The point of reference-style links is not that they're easier to
|
|
||||||
write. The point is that with reference-style links, your document
|
|
||||||
source is vastly more readable. Compare the above examples: using
|
|
||||||
reference-style links, the paragraph itself is only 81 characters
|
|
||||||
long; with inline-style links, it's 176 characters; and as raw HTML,
|
|
||||||
it's 234 characters. In the raw HTML, there's more markup than there
|
|
||||||
is text.</p>
|
|
||||||
|
|
||||||
<p>With Markdown's reference-style links, a source document much more
|
|
||||||
closely resembles the final output, as rendered in a browser. By
|
|
||||||
allowing you to move the markup-related metadata out of the paragraph,
|
|
||||||
you can add links without interrupting the narrative flow of your
|
|
||||||
prose.</p>
|
|
||||||
|
|
||||||
<h3 id="em">Emphasis</h3>
|
|
||||||
|
|
||||||
<p>Markdown treats asterisks (<code>*</code>) and underscores (<code>_</code>) as indicators of
|
|
||||||
emphasis. Text wrapped with one <code>*</code> or <code>_</code> will be wrapped with an
|
|
||||||
HTML <code><em></code> tag; double <code>*</code>'s or <code>_</code>'s will be wrapped with an HTML
|
|
||||||
<code><strong></code> tag. E.g., this input:</p>
|
|
||||||
|
|
||||||
<pre><code>*single asterisks*
|
|
||||||
|
|
||||||
_single underscores_
|
|
||||||
|
|
||||||
**double asterisks**
|
|
||||||
|
|
||||||
__double underscores__
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>will produce:</p>
|
|
||||||
|
|
||||||
<pre><code><em>single asterisks</em>
|
|
||||||
|
|
||||||
<em>single underscores</em>
|
|
||||||
|
|
||||||
<strong>double asterisks</strong>
|
|
||||||
|
|
||||||
<strong>double underscores</strong>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>You can use whichever style you prefer; the lone restriction is that
|
|
||||||
the same character must be used to open and close an emphasis span.</p>
|
|
||||||
|
|
||||||
<p>Emphasis can be used in the middle of a word:</p>
|
|
||||||
|
|
||||||
<pre><code>un*fucking*believable
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>But if you surround an <code>*</code> or <code>_</code> with spaces, it'll be treated as a
|
|
||||||
literal asterisk or underscore.</p>
|
|
||||||
|
|
||||||
<p>To produce a literal asterisk or underscore at a position where it
|
|
||||||
would otherwise be used as an emphasis delimiter, you can backslash
|
|
||||||
escape it:</p>
|
|
||||||
|
|
||||||
<pre><code>\*this text is surrounded by literal asterisks\*
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<h3 id="code">Code</h3>
|
|
||||||
|
|
||||||
<p>To indicate a span of code, wrap it with backtick quotes (<code>`</code>).
|
|
||||||
Unlike a pre-formatted code block, a code span indicates code within a
|
|
||||||
normal paragraph. For example:</p>
|
|
||||||
|
|
||||||
<pre><code>Use the `printf()` function.
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>will produce:</p>
|
|
||||||
|
|
||||||
<pre><code><p>Use the <code>printf()</code> function.</p>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>To include a literal backtick character within a code span, you can use
|
|
||||||
multiple backticks as the opening and closing delimiters:</p>
|
|
||||||
|
|
||||||
<pre><code>``There is a literal backtick (`) here.``
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>which will produce this:</p>
|
|
||||||
|
|
||||||
<pre><code><p><code>There is a literal backtick (`) here.</code></p>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>The backtick delimiters surrounding a code span may include spaces --
|
|
||||||
one after the opening, one before the closing. This allows you to place
|
|
||||||
literal backtick characters at the beginning or end of a code span:</p>
|
|
||||||
|
|
||||||
<pre><code>A single backtick in a code span: `` ` ``
|
|
||||||
|
|
||||||
A backtick-delimited string in a code span: `` `foo` ``
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>will produce:</p>
|
|
||||||
|
|
||||||
<pre><code><p>A single backtick in a code span: <code>`</code></p>
|
|
||||||
|
|
||||||
<p>A backtick-delimited string in a code span: <code>`foo`</code></p>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>With a code span, ampersands and angle brackets are encoded as HTML
|
|
||||||
entities automatically, which makes it easy to include example HTML
|
|
||||||
tags. Markdown will turn this:</p>
|
|
||||||
|
|
||||||
<pre><code>Please don't use any `<blink>` tags.
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>into:</p>
|
|
||||||
|
|
||||||
<pre><code><p>Please don't use any <code>&lt;blink&gt;</code> tags.</p>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>You can write this:</p>
|
|
||||||
|
|
||||||
<pre><code>`&#8212;` is the decimal-encoded equivalent of `&mdash;`.
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>to produce:</p>
|
|
||||||
|
|
||||||
<pre><code><p><code>&amp;#8212;</code> is the decimal-encoded
|
|
||||||
equivalent of <code>&amp;mdash;</code>.</p>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<h3 id="img">Images</h3>
|
|
||||||
|
|
||||||
<p>Admittedly, it's fairly difficult to devise a "natural" syntax for
|
|
||||||
placing images into a plain text document format.</p>
|
|
||||||
|
|
||||||
<p>Markdown uses an image syntax that is intended to resemble the syntax
|
|
||||||
for links, allowing for two styles: <em>inline</em> and <em>reference</em>.</p>
|
|
||||||
|
|
||||||
<p>Inline image syntax looks like this:</p>
|
|
||||||
|
|
||||||
<pre><code>![Alt text](/path/to/img.jpg)
|
|
||||||
|
|
||||||
![Alt text](/path/to/img.jpg "Optional title")
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>That is:</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>An exclamation mark: <code>!</code>;</li>
|
|
||||||
<li>followed by a set of square brackets, containing the <code>alt</code>
|
|
||||||
attribute text for the image;</li>
|
|
||||||
<li>followed by a set of parentheses, containing the URL or path to
|
|
||||||
the image, and an optional <code>title</code> attribute enclosed in double
|
|
||||||
or single quotes.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Reference-style image syntax looks like this:</p>
|
|
||||||
|
|
||||||
<pre><code>![Alt text][id]
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Where "id" is the name of a defined image reference. Image references
|
|
||||||
are defined using syntax identical to link references:</p>
|
|
||||||
|
|
||||||
<pre><code>[id]: url/to/image "Optional title attribute"
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>As of this writing, Markdown has no syntax for specifying the
|
|
||||||
dimensions of an image; if this is important to you, you can simply
|
|
||||||
use regular HTML <code><img></code> tags.</p>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<h2 id="misc">Miscellaneous</h2>
|
|
||||||
|
|
||||||
<h3 id="autolink">Automatic Links</h3>
|
|
||||||
|
|
||||||
<p>Markdown supports a shortcut style for creating "automatic" links for URLs and email addresses: simply surround the URL or email address with angle brackets. What this means is that if you want to show the actual text of a URL or email address, and also have it be a clickable link, you can do this:</p>
|
|
||||||
|
|
||||||
<pre><code><http://example.com/>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Markdown will turn this into:</p>
|
|
||||||
|
|
||||||
<pre><code><a href="http://example.com/">http://example.com/</a>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Automatic links for email addresses work similarly, except that
|
|
||||||
Markdown will also perform a bit of randomized decimal and hex
|
|
||||||
entity-encoding to help obscure your address from address-harvesting
|
|
||||||
spambots. For example, Markdown will turn this:</p>
|
|
||||||
|
|
||||||
<pre><code><address@example.com>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>into something like this:</p>
|
|
||||||
|
|
||||||
<pre><code><a href="&#x6D;&#x61;i&#x6C;&#x74;&#x6F;:&#x61;&#x64;&#x64;&#x72;&#x65;
|
|
||||||
&#115;&#115;&#64;&#101;&#120;&#x61;&#109;&#x70;&#x6C;e&#x2E;&#99;&#111;
|
|
||||||
&#109;">&#x61;&#x64;&#x64;&#x72;&#x65;&#115;&#115;&#64;&#101;&#120;&#x61;
|
|
||||||
&#109;&#x70;&#x6C;e&#x2E;&#99;&#111;&#109;</a>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>which will render in a browser as a clickable link to "address@example.com".</p>
|
|
||||||
|
|
||||||
<p>(This sort of entity-encoding trick will indeed fool many, if not
|
|
||||||
most, address-harvesting bots, but it definitely won't fool all of
|
|
||||||
them. It's better than nothing, but an address published in this way
|
|
||||||
will probably eventually start receiving spam.)</p>
|
|
||||||
|
|
||||||
<h3 id="backslash">Backslash Escapes</h3>
|
|
||||||
|
|
||||||
<p>Markdown allows you to use backslash escapes to generate literal
|
|
||||||
characters which would otherwise have special meaning in Markdown's
|
|
||||||
formatting syntax. For example, if you wanted to surround a word with
|
|
||||||
literal asterisks (instead of an HTML <code><em></code> tag), you can backslashes
|
|
||||||
before the asterisks, like this:</p>
|
|
||||||
|
|
||||||
<pre><code>\*literal asterisks\*
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
<p>Markdown provides backslash escapes for the following characters:</p>
|
|
||||||
|
|
||||||
<pre><code>\ backslash
|
|
||||||
` backtick
|
|
||||||
* asterisk
|
|
||||||
_ underscore
|
|
||||||
{} curly braces
|
|
||||||
[] square brackets
|
|
||||||
() parentheses
|
|
||||||
# hash mark
|
|
||||||
+ plus sign
|
|
||||||
- minus sign (hyphen)
|
|
||||||
. dot
|
|
||||||
! exclamation mark
|
|
||||||
</code></pre>
|
|
888
vendor/github.com/russross/blackfriday/testdata/Markdown Documentation - Syntax.text
generated
vendored
888
vendor/github.com/russross/blackfriday/testdata/Markdown Documentation - Syntax.text
generated
vendored
|
@ -1,888 +0,0 @@
|
||||||
Markdown: Syntax
|
|
||||||
================
|
|
||||||
|
|
||||||
<ul id="ProjectSubmenu">
|
|
||||||
<li><a href="/projects/markdown/" title="Markdown Project Page">Main</a></li>
|
|
||||||
<li><a href="/projects/markdown/basics" title="Markdown Basics">Basics</a></li>
|
|
||||||
<li><a class="selected" title="Markdown Syntax Documentation">Syntax</a></li>
|
|
||||||
<li><a href="/projects/markdown/license" title="Pricing and License Information">License</a></li>
|
|
||||||
<li><a href="/projects/markdown/dingus" title="Online Markdown Web Form">Dingus</a></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
|
|
||||||
* [Overview](#overview)
|
|
||||||
* [Philosophy](#philosophy)
|
|
||||||
* [Inline HTML](#html)
|
|
||||||
* [Automatic Escaping for Special Characters](#autoescape)
|
|
||||||
* [Block Elements](#block)
|
|
||||||
* [Paragraphs and Line Breaks](#p)
|
|
||||||
* [Headers](#header)
|
|
||||||
* [Blockquotes](#blockquote)
|
|
||||||
* [Lists](#list)
|
|
||||||
* [Code Blocks](#precode)
|
|
||||||
* [Horizontal Rules](#hr)
|
|
||||||
* [Span Elements](#span)
|
|
||||||
* [Links](#link)
|
|
||||||
* [Emphasis](#em)
|
|
||||||
* [Code](#code)
|
|
||||||
* [Images](#img)
|
|
||||||
* [Miscellaneous](#misc)
|
|
||||||
* [Backslash Escapes](#backslash)
|
|
||||||
* [Automatic Links](#autolink)
|
|
||||||
|
|
||||||
|
|
||||||
**Note:** This document is itself written using Markdown; you
|
|
||||||
can [see the source for it by adding '.text' to the URL][src].
|
|
||||||
|
|
||||||
[src]: /projects/markdown/syntax.text
|
|
||||||
|
|
||||||
* * *
|
|
||||||
|
|
||||||
<h2 id="overview">Overview</h2>
|
|
||||||
|
|
||||||
<h3 id="philosophy">Philosophy</h3>
|
|
||||||
|
|
||||||
Markdown is intended to be as easy-to-read and easy-to-write as is feasible.
|
|
||||||
|
|
||||||
Readability, however, is emphasized above all else. A Markdown-formatted
|
|
||||||
document should be publishable as-is, as plain text, without looking
|
|
||||||
like it's been marked up with tags or formatting instructions. While
|
|
||||||
Markdown's syntax has been influenced by several existing text-to-HTML
|
|
||||||
filters -- including [Setext] [1], [atx] [2], [Textile] [3], [reStructuredText] [4],
|
|
||||||
[Grutatext] [5], and [EtText] [6] -- the single biggest source of
|
|
||||||
inspiration for Markdown's syntax is the format of plain text email.
|
|
||||||
|
|
||||||
[1]: http://docutils.sourceforge.net/mirror/setext.html
|
|
||||||
[2]: http://www.aaronsw.com/2002/atx/
|
|
||||||
[3]: http://textism.com/tools/textile/
|
|
||||||
[4]: http://docutils.sourceforge.net/rst.html
|
|
||||||
[5]: http://www.triptico.com/software/grutatxt.html
|
|
||||||
[6]: http://ettext.taint.org/doc/
|
|
||||||
|
|
||||||
To this end, Markdown's syntax is comprised entirely of punctuation
|
|
||||||
characters, which punctuation characters have been carefully chosen so
|
|
||||||
as to look like what they mean. E.g., asterisks around a word actually
|
|
||||||
look like \*emphasis\*. Markdown lists look like, well, lists. Even
|
|
||||||
blockquotes look like quoted passages of text, assuming you've ever
|
|
||||||
used email.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<h3 id="html">Inline HTML</h3>
|
|
||||||
|
|
||||||
Markdown's syntax is intended for one purpose: to be used as a
|
|
||||||
format for *writing* for the web.
|
|
||||||
|
|
||||||
Markdown is not a replacement for HTML, or even close to it. Its
|
|
||||||
syntax is very small, corresponding only to a very small subset of
|
|
||||||
HTML tags. The idea is *not* to create a syntax that makes it easier
|
|
||||||
to insert HTML tags. In my opinion, HTML tags are already easy to
|
|
||||||
insert. The idea for Markdown is to make it easy to read, write, and
|
|
||||||
edit prose. HTML is a *publishing* format; Markdown is a *writing*
|
|
||||||
format. Thus, Markdown's formatting syntax only addresses issues that
|
|
||||||
can be conveyed in plain text.
|
|
||||||
|
|
||||||
For any markup that is not covered by Markdown's syntax, you simply
|
|
||||||
use HTML itself. There's no need to preface it or delimit it to
|
|
||||||
indicate that you're switching from Markdown to HTML; you just use
|
|
||||||
the tags.
|
|
||||||
|
|
||||||
The only restrictions are that block-level HTML elements -- e.g. `<div>`,
|
|
||||||
`<table>`, `<pre>`, `<p>`, etc. -- must be separated from surrounding
|
|
||||||
content by blank lines, and the start and end tags of the block should
|
|
||||||
not be indented with tabs or spaces. Markdown is smart enough not
|
|
||||||
to add extra (unwanted) `<p>` tags around HTML block-level tags.
|
|
||||||
|
|
||||||
For example, to add an HTML table to a Markdown article:
|
|
||||||
|
|
||||||
This is a regular paragraph.
|
|
||||||
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td>Foo</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
This is another regular paragraph.
|
|
||||||
|
|
||||||
Note that Markdown formatting syntax is not processed within block-level
|
|
||||||
HTML tags. E.g., you can't use Markdown-style `*emphasis*` inside an
|
|
||||||
HTML block.
|
|
||||||
|
|
||||||
Span-level HTML tags -- e.g. `<span>`, `<cite>`, or `<del>` -- can be
|
|
||||||
used anywhere in a Markdown paragraph, list item, or header. If you
|
|
||||||
want, you can even use HTML tags instead of Markdown formatting; e.g. if
|
|
||||||
you'd prefer to use HTML `<a>` or `<img>` tags instead of Markdown's
|
|
||||||
link or image syntax, go right ahead.
|
|
||||||
|
|
||||||
Unlike block-level HTML tags, Markdown syntax *is* processed within
|
|
||||||
span-level tags.
|
|
||||||
|
|
||||||
|
|
||||||
<h3 id="autoescape">Automatic Escaping for Special Characters</h3>
|
|
||||||
|
|
||||||
In HTML, there are two characters that demand special treatment: `<`
|
|
||||||
and `&`. Left angle brackets are used to start tags; ampersands are
|
|
||||||
used to denote HTML entities. If you want to use them as literal
|
|
||||||
characters, you must escape them as entities, e.g. `<`, and
|
|
||||||
`&`.
|
|
||||||
|
|
||||||
Ampersands in particular are bedeviling for web writers. If you want to
|
|
||||||
write about 'AT&T', you need to write '`AT&T`'. You even need to
|
|
||||||
escape ampersands within URLs. Thus, if you want to link to:
|
|
||||||
|
|
||||||
http://images.google.com/images?num=30&q=larry+bird
|
|
||||||
|
|
||||||
you need to encode the URL as:
|
|
||||||
|
|
||||||
http://images.google.com/images?num=30&q=larry+bird
|
|
||||||
|
|
||||||
in your anchor tag `href` attribute. Needless to say, this is easy to
|
|
||||||
forget, and is probably the single most common source of HTML validation
|
|
||||||
errors in otherwise well-marked-up web sites.
|
|
||||||
|
|
||||||
Markdown allows you to use these characters naturally, taking care of
|
|
||||||
all the necessary escaping for you. If you use an ampersand as part of
|
|
||||||
an HTML entity, it remains unchanged; otherwise it will be translated
|
|
||||||
into `&`.
|
|
||||||
|
|
||||||
So, if you want to include a copyright symbol in your article, you can write:
|
|
||||||
|
|
||||||
©
|
|
||||||
|
|
||||||
and Markdown will leave it alone. But if you write:
|
|
||||||
|
|
||||||
AT&T
|
|
||||||
|
|
||||||
Markdown will translate it to:
|
|
||||||
|
|
||||||
AT&T
|
|
||||||
|
|
||||||
Similarly, because Markdown supports [inline HTML](#html), if you use
|
|
||||||
angle brackets as delimiters for HTML tags, Markdown will treat them as
|
|
||||||
such. But if you write:
|
|
||||||
|
|
||||||
4 < 5
|
|
||||||
|
|
||||||
Markdown will translate it to:
|
|
||||||
|
|
||||||
4 < 5
|
|
||||||
|
|
||||||
However, inside Markdown code spans and blocks, angle brackets and
|
|
||||||
ampersands are *always* encoded automatically. This makes it easy to use
|
|
||||||
Markdown to write about HTML code. (As opposed to raw HTML, which is a
|
|
||||||
terrible format for writing about HTML syntax, because every single `<`
|
|
||||||
and `&` in your example code needs to be escaped.)
|
|
||||||
|
|
||||||
|
|
||||||
* * *
|
|
||||||
|
|
||||||
|
|
||||||
<h2 id="block">Block Elements</h2>
|
|
||||||
|
|
||||||
|
|
||||||
<h3 id="p">Paragraphs and Line Breaks</h3>
|
|
||||||
|
|
||||||
A paragraph is simply one or more consecutive lines of text, separated
|
|
||||||
by one or more blank lines. (A blank line is any line that looks like a
|
|
||||||
blank line -- a line containing nothing but spaces or tabs is considered
|
|
||||||
blank.) Normal paragraphs should not be intended with spaces or tabs.
|
|
||||||
|
|
||||||
The implication of the "one or more consecutive lines of text" rule is
|
|
||||||
that Markdown supports "hard-wrapped" text paragraphs. This differs
|
|
||||||
significantly from most other text-to-HTML formatters (including Movable
|
|
||||||
Type's "Convert Line Breaks" option) which translate every line break
|
|
||||||
character in a paragraph into a `<br />` tag.
|
|
||||||
|
|
||||||
When you *do* want to insert a `<br />` break tag using Markdown, you
|
|
||||||
end a line with two or more spaces, then type return.
|
|
||||||
|
|
||||||
Yes, this takes a tad more effort to create a `<br />`, but a simplistic
|
|
||||||
"every line break is a `<br />`" rule wouldn't work for Markdown.
|
|
||||||
Markdown's email-style [blockquoting][bq] and multi-paragraph [list items][l]
|
|
||||||
work best -- and look better -- when you format them with hard breaks.
|
|
||||||
|
|
||||||
[bq]: #blockquote
|
|
||||||
[l]: #list
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<h3 id="header">Headers</h3>
|
|
||||||
|
|
||||||
Markdown supports two styles of headers, [Setext] [1] and [atx] [2].
|
|
||||||
|
|
||||||
Setext-style headers are "underlined" using equal signs (for first-level
|
|
||||||
headers) and dashes (for second-level headers). For example:
|
|
||||||
|
|
||||||
This is an H1
|
|
||||||
=============
|
|
||||||
|
|
||||||
This is an H2
|
|
||||||
-------------
|
|
||||||
|
|
||||||
Any number of underlining `=`'s or `-`'s will work.
|
|
||||||
|
|
||||||
Atx-style headers use 1-6 hash characters at the start of the line,
|
|
||||||
corresponding to header levels 1-6. For example:
|
|
||||||
|
|
||||||
# This is an H1
|
|
||||||
|
|
||||||
## This is an H2
|
|
||||||
|
|
||||||
###### This is an H6
|
|
||||||
|
|
||||||
Optionally, you may "close" atx-style headers. This is purely
|
|
||||||
cosmetic -- you can use this if you think it looks better. The
|
|
||||||
closing hashes don't even need to match the number of hashes
|
|
||||||
used to open the header. (The number of opening hashes
|
|
||||||
determines the header level.) :
|
|
||||||
|
|
||||||
# This is an H1 #
|
|
||||||
|
|
||||||
## This is an H2 ##
|
|
||||||
|
|
||||||
### This is an H3 ######
|
|
||||||
|
|
||||||
|
|
||||||
<h3 id="blockquote">Blockquotes</h3>
|
|
||||||
|
|
||||||
Markdown uses email-style `>` characters for blockquoting. If you're
|
|
||||||
familiar with quoting passages of text in an email message, then you
|
|
||||||
know how to create a blockquote in Markdown. It looks best if you hard
|
|
||||||
wrap the text and put a `>` before every line:
|
|
||||||
|
|
||||||
> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
|
|
||||||
> consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
|
|
||||||
> Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
|
|
||||||
>
|
|
||||||
> Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
|
|
||||||
> id sem consectetuer libero luctus adipiscing.
|
|
||||||
|
|
||||||
Markdown allows you to be lazy and only put the `>` before the first
|
|
||||||
line of a hard-wrapped paragraph:
|
|
||||||
|
|
||||||
> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
|
|
||||||
consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
|
|
||||||
Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
|
|
||||||
|
|
||||||
> Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
|
|
||||||
id sem consectetuer libero luctus adipiscing.
|
|
||||||
|
|
||||||
Blockquotes can be nested (i.e. a blockquote-in-a-blockquote) by
|
|
||||||
adding additional levels of `>`:
|
|
||||||
|
|
||||||
> This is the first level of quoting.
|
|
||||||
>
|
|
||||||
> > This is nested blockquote.
|
|
||||||
>
|
|
||||||
> Back to the first level.
|
|
||||||
|
|
||||||
Blockquotes can contain other Markdown elements, including headers, lists,
|
|
||||||
and code blocks:
|
|
||||||
|
|
||||||
> ## This is a header.
|
|
||||||
>
|
|
||||||
> 1. This is the first list item.
|
|
||||||
> 2. This is the second list item.
|
|
||||||
>
|
|
||||||
> Here's some example code:
|
|
||||||
>
|
|
||||||
> return shell_exec("echo $input | $markdown_script");
|
|
||||||
|
|
||||||
Any decent text editor should make email-style quoting easy. For
|
|
||||||
example, with BBEdit, you can make a selection and choose Increase
|
|
||||||
Quote Level from the Text menu.
|
|
||||||
|
|
||||||
|
|
||||||
<h3 id="list">Lists</h3>
|
|
||||||
|
|
||||||
Markdown supports ordered (numbered) and unordered (bulleted) lists.
|
|
||||||
|
|
||||||
Unordered lists use asterisks, pluses, and hyphens -- interchangably
|
|
||||||
-- as list markers:
|
|
||||||
|
|
||||||
* Red
|
|
||||||
* Green
|
|
||||||
* Blue
|
|
||||||
|
|
||||||
is equivalent to:
|
|
||||||
|
|
||||||
+ Red
|
|
||||||
+ Green
|
|
||||||
+ Blue
|
|
||||||
|
|
||||||
and:
|
|
||||||
|
|
||||||
- Red
|
|
||||||
- Green
|
|
||||||
- Blue
|
|
||||||
|
|
||||||
Ordered lists use numbers followed by periods:
|
|
||||||
|
|
||||||
1. Bird
|
|
||||||
2. McHale
|
|
||||||
3. Parish
|
|
||||||
|
|
||||||
It's important to note that the actual numbers you use to mark the
|
|
||||||
list have no effect on the HTML output Markdown produces. The HTML
|
|
||||||
Markdown produces from the above list is:
|
|
||||||
|
|
||||||
<ol>
|
|
||||||
<li>Bird</li>
|
|
||||||
<li>McHale</li>
|
|
||||||
<li>Parish</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
If you instead wrote the list in Markdown like this:
|
|
||||||
|
|
||||||
1. Bird
|
|
||||||
1. McHale
|
|
||||||
1. Parish
|
|
||||||
|
|
||||||
or even:
|
|
||||||
|
|
||||||
3. Bird
|
|
||||||
1. McHale
|
|
||||||
8. Parish
|
|
||||||
|
|
||||||
you'd get the exact same HTML output. The point is, if you want to,
|
|
||||||
you can use ordinal numbers in your ordered Markdown lists, so that
|
|
||||||
the numbers in your source match the numbers in your published HTML.
|
|
||||||
But if you want to be lazy, you don't have to.
|
|
||||||
|
|
||||||
If you do use lazy list numbering, however, you should still start the
|
|
||||||
list with the number 1. At some point in the future, Markdown may support
|
|
||||||
starting ordered lists at an arbitrary number.
|
|
||||||
|
|
||||||
List markers typically start at the left margin, but may be indented by
|
|
||||||
up to three spaces. List markers must be followed by one or more spaces
|
|
||||||
or a tab.
|
|
||||||
|
|
||||||
To make lists look nice, you can wrap items with hanging indents:
|
|
||||||
|
|
||||||
* Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
|
|
||||||
Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
|
|
||||||
viverra nec, fringilla in, laoreet vitae, risus.
|
|
||||||
* Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
|
|
||||||
Suspendisse id sem consectetuer libero luctus adipiscing.
|
|
||||||
|
|
||||||
But if you want to be lazy, you don't have to:
|
|
||||||
|
|
||||||
* Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
|
|
||||||
Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
|
|
||||||
viverra nec, fringilla in, laoreet vitae, risus.
|
|
||||||
* Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
|
|
||||||
Suspendisse id sem consectetuer libero luctus adipiscing.
|
|
||||||
|
|
||||||
If list items are separated by blank lines, Markdown will wrap the
|
|
||||||
items in `<p>` tags in the HTML output. For example, this input:
|
|
||||||
|
|
||||||
* Bird
|
|
||||||
* Magic
|
|
||||||
|
|
||||||
will turn into:
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Bird</li>
|
|
||||||
<li>Magic</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
But this:
|
|
||||||
|
|
||||||
* Bird
|
|
||||||
|
|
||||||
* Magic
|
|
||||||
|
|
||||||
will turn into:
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><p>Bird</p></li>
|
|
||||||
<li><p>Magic</p></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
List items may consist of multiple paragraphs. Each subsequent
|
|
||||||
paragraph in a list item must be intended by either 4 spaces
|
|
||||||
or one tab:
|
|
||||||
|
|
||||||
1. This is a list item with two paragraphs. Lorem ipsum dolor
|
|
||||||
sit amet, consectetuer adipiscing elit. Aliquam hendrerit
|
|
||||||
mi posuere lectus.
|
|
||||||
|
|
||||||
Vestibulum enim wisi, viverra nec, fringilla in, laoreet
|
|
||||||
vitae, risus. Donec sit amet nisl. Aliquam semper ipsum
|
|
||||||
sit amet velit.
|
|
||||||
|
|
||||||
2. Suspendisse id sem consectetuer libero luctus adipiscing.
|
|
||||||
|
|
||||||
It looks nice if you indent every line of the subsequent
|
|
||||||
paragraphs, but here again, Markdown will allow you to be
|
|
||||||
lazy:
|
|
||||||
|
|
||||||
* This is a list item with two paragraphs.
|
|
||||||
|
|
||||||
This is the second paragraph in the list item. You're
|
|
||||||
only required to indent the first line. Lorem ipsum dolor
|
|
||||||
sit amet, consectetuer adipiscing elit.
|
|
||||||
|
|
||||||
* Another item in the same list.
|
|
||||||
|
|
||||||
To put a blockquote within a list item, the blockquote's `>`
|
|
||||||
delimiters need to be indented:
|
|
||||||
|
|
||||||
* A list item with a blockquote:
|
|
||||||
|
|
||||||
> This is a blockquote
|
|
||||||
> inside a list item.
|
|
||||||
|
|
||||||
To put a code block within a list item, the code block needs
|
|
||||||
to be indented *twice* -- 8 spaces or two tabs:
|
|
||||||
|
|
||||||
* A list item with a code block:
|
|
||||||
|
|
||||||
<code goes here>
|
|
||||||
|
|
||||||
|
|
||||||
It's worth noting that it's possible to trigger an ordered list by
|
|
||||||
accident, by writing something like this:
|
|
||||||
|
|
||||||
1986. What a great season.
|
|
||||||
|
|
||||||
In other words, a *number-period-space* sequence at the beginning of a
|
|
||||||
line. To avoid this, you can backslash-escape the period:
|
|
||||||
|
|
||||||
1986\. What a great season.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<h3 id="precode">Code Blocks</h3>
|
|
||||||
|
|
||||||
Pre-formatted code blocks are used for writing about programming or
|
|
||||||
markup source code. Rather than forming normal paragraphs, the lines
|
|
||||||
of a code block are interpreted literally. Markdown wraps a code block
|
|
||||||
in both `<pre>` and `<code>` tags.
|
|
||||||
|
|
||||||
To produce a code block in Markdown, simply indent every line of the
|
|
||||||
block by at least 4 spaces or 1 tab. For example, given this input:
|
|
||||||
|
|
||||||
This is a normal paragraph:
|
|
||||||
|
|
||||||
This is a code block.
|
|
||||||
|
|
||||||
Markdown will generate:
|
|
||||||
|
|
||||||
<p>This is a normal paragraph:</p>
|
|
||||||
|
|
||||||
<pre><code>This is a code block.
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
One level of indentation -- 4 spaces or 1 tab -- is removed from each
|
|
||||||
line of the code block. For example, this:
|
|
||||||
|
|
||||||
Here is an example of AppleScript:
|
|
||||||
|
|
||||||
tell application "Foo"
|
|
||||||
beep
|
|
||||||
end tell
|
|
||||||
|
|
||||||
will turn into:
|
|
||||||
|
|
||||||
<p>Here is an example of AppleScript:</p>
|
|
||||||
|
|
||||||
<pre><code>tell application "Foo"
|
|
||||||
beep
|
|
||||||
end tell
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
A code block continues until it reaches a line that is not indented
|
|
||||||
(or the end of the article).
|
|
||||||
|
|
||||||
Within a code block, ampersands (`&`) and angle brackets (`<` and `>`)
|
|
||||||
are automatically converted into HTML entities. This makes it very
|
|
||||||
easy to include example HTML source code using Markdown -- just paste
|
|
||||||
it and indent it, and Markdown will handle the hassle of encoding the
|
|
||||||
ampersands and angle brackets. For example, this:
|
|
||||||
|
|
||||||
<div class="footer">
|
|
||||||
© 2004 Foo Corporation
|
|
||||||
</div>
|
|
||||||
|
|
||||||
will turn into:
|
|
||||||
|
|
||||||
<pre><code><div class="footer">
|
|
||||||
&copy; 2004 Foo Corporation
|
|
||||||
</div>
|
|
||||||
</code></pre>
|
|
||||||
|
|
||||||
Regular Markdown syntax is not processed within code blocks. E.g.,
|
|
||||||
asterisks are just literal asterisks within a code block. This means
|
|
||||||
it's also easy to use Markdown to write about Markdown's own syntax.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<h3 id="hr">Horizontal Rules</h3>
|
|
||||||
|
|
||||||
You can produce a horizontal rule tag (`<hr />`) by placing three or
|
|
||||||
more hyphens, asterisks, or underscores on a line by themselves. If you
|
|
||||||
wish, you may use spaces between the hyphens or asterisks. Each of the
|
|
||||||
following lines will produce a horizontal rule:
|
|
||||||
|
|
||||||
* * *
|
|
||||||
|
|
||||||
***
|
|
||||||
|
|
||||||
*****
|
|
||||||
|
|
||||||
- - -
|
|
||||||
|
|
||||||
---------------------------------------
|
|
||||||
|
|
||||||
_ _ _
|
|
||||||
|
|
||||||
|
|
||||||
* * *
|
|
||||||
|
|
||||||
<h2 id="span">Span Elements</h2>
|
|
||||||
|
|
||||||
<h3 id="link">Links</h3>
|
|
||||||
|
|
||||||
Markdown supports two style of links: *inline* and *reference*.
|
|
||||||
|
|
||||||
In both styles, the link text is delimited by [square brackets].
|
|
||||||
|
|
||||||
To create an inline link, use a set of regular parentheses immediately
|
|
||||||
after the link text's closing square bracket. Inside the parentheses,
|
|
||||||
put the URL where you want the link to point, along with an *optional*
|
|
||||||
title for the link, surrounded in quotes. For example:
|
|
||||||
|
|
||||||
This is [an example](http://example.com/ "Title") inline link.
|
|
||||||
|
|
||||||
[This link](http://example.net/) has no title attribute.
|
|
||||||
|
|
||||||
Will produce:
|
|
||||||
|
|
||||||
<p>This is <a href="http://example.com/" title="Title">
|
|
||||||
an example</a> inline link.</p>
|
|
||||||
|
|
||||||
<p><a href="http://example.net/">This link</a> has no
|
|
||||||
title attribute.</p>
|
|
||||||
|
|
||||||
If you're referring to a local resource on the same server, you can
|
|
||||||
use relative paths:
|
|
||||||
|
|
||||||
See my [About](/about/) page for details.
|
|
||||||
|
|
||||||
Reference-style links use a second set of square brackets, inside
|
|
||||||
which you place a label of your choosing to identify the link:
|
|
||||||
|
|
||||||
This is [an example][id] reference-style link.
|
|
||||||
|
|
||||||
You can optionally use a space to separate the sets of brackets:
|
|
||||||
|
|
||||||
This is [an example] [id] reference-style link.
|
|
||||||
|
|
||||||
Then, anywhere in the document, you define your link label like this,
|
|
||||||
on a line by itself:
|
|
||||||
|
|
||||||
[id]: http://example.com/ "Optional Title Here"
|
|
||||||
|
|
||||||
That is:
|
|
||||||
|
|
||||||
* Square brackets containing the link identifier (optionally
|
|
||||||
indented from the left margin using up to three spaces);
|
|
||||||
* followed by a colon;
|
|
||||||
* followed by one or more spaces (or tabs);
|
|
||||||
* followed by the URL for the link;
|
|
||||||
* optionally followed by a title attribute for the link, enclosed
|
|
||||||
in double or single quotes.
|
|
||||||
|
|
||||||
The link URL may, optionally, be surrounded by angle brackets:
|
|
||||||
|
|
||||||
[id]: <http://example.com/> "Optional Title Here"
|
|
||||||
|
|
||||||
You can put the title attribute on the next line and use extra spaces
|
|
||||||
or tabs for padding, which tends to look better with longer URLs:
|
|
||||||
|
|
||||||
[id]: http://example.com/longish/path/to/resource/here
|
|
||||||
"Optional Title Here"
|
|
||||||
|
|
||||||
Link definitions are only used for creating links during Markdown
|
|
||||||
processing, and are stripped from your document in the HTML output.
|
|
||||||
|
|
||||||
Link definition names may constist of letters, numbers, spaces, and punctuation -- but they are *not* case sensitive. E.g. these two links:
|
|
||||||
|
|
||||||
[link text][a]
|
|
||||||
[link text][A]
|
|
||||||
|
|
||||||
are equivalent.
|
|
||||||
|
|
||||||
The *implicit link name* shortcut allows you to omit the name of the
|
|
||||||
link, in which case the link text itself is used as the name.
|
|
||||||
Just use an empty set of square brackets -- e.g., to link the word
|
|
||||||
"Google" to the google.com web site, you could simply write:
|
|
||||||
|
|
||||||
[Google][]
|
|
||||||
|
|
||||||
And then define the link:
|
|
||||||
|
|
||||||
[Google]: http://google.com/
|
|
||||||
|
|
||||||
Because link names may contain spaces, this shortcut even works for
|
|
||||||
multiple words in the link text:
|
|
||||||
|
|
||||||
Visit [Daring Fireball][] for more information.
|
|
||||||
|
|
||||||
And then define the link:
|
|
||||||
|
|
||||||
[Daring Fireball]: http://daringfireball.net/
|
|
||||||
|
|
||||||
Link definitions can be placed anywhere in your Markdown document. I
|
|
||||||
tend to put them immediately after each paragraph in which they're
|
|
||||||
used, but if you want, you can put them all at the end of your
|
|
||||||
document, sort of like footnotes.
|
|
||||||
|
|
||||||
Here's an example of reference links in action:
|
|
||||||
|
|
||||||
I get 10 times more traffic from [Google] [1] than from
|
|
||||||
[Yahoo] [2] or [MSN] [3].
|
|
||||||
|
|
||||||
[1]: http://google.com/ "Google"
|
|
||||||
[2]: http://search.yahoo.com/ "Yahoo Search"
|
|
||||||
[3]: http://search.msn.com/ "MSN Search"
|
|
||||||
|
|
||||||
Using the implicit link name shortcut, you could instead write:
|
|
||||||
|
|
||||||
I get 10 times more traffic from [Google][] than from
|
|
||||||
[Yahoo][] or [MSN][].
|
|
||||||
|
|
||||||
[google]: http://google.com/ "Google"
|
|
||||||
[yahoo]: http://search.yahoo.com/ "Yahoo Search"
|
|
||||||
[msn]: http://search.msn.com/ "MSN Search"
|
|
||||||
|
|
||||||
Both of the above examples will produce the following HTML output:
|
|
||||||
|
|
||||||
<p>I get 10 times more traffic from <a href="http://google.com/"
|
|
||||||
title="Google">Google</a> than from
|
|
||||||
<a href="http://search.yahoo.com/" title="Yahoo Search">Yahoo</a>
|
|
||||||
or <a href="http://search.msn.com/" title="MSN Search">MSN</a>.</p>
|
|
||||||
|
|
||||||
For comparison, here is the same paragraph written using
|
|
||||||
Markdown's inline link style:
|
|
||||||
|
|
||||||
I get 10 times more traffic from [Google](http://google.com/ "Google")
|
|
||||||
than from [Yahoo](http://search.yahoo.com/ "Yahoo Search") or
|
|
||||||
[MSN](http://search.msn.com/ "MSN Search").
|
|
||||||
|
|
||||||
The point of reference-style links is not that they're easier to
|
|
||||||
write. The point is that with reference-style links, your document
|
|
||||||
source is vastly more readable. Compare the above examples: using
|
|
||||||
reference-style links, the paragraph itself is only 81 characters
|
|
||||||
long; with inline-style links, it's 176 characters; and as raw HTML,
|
|
||||||
it's 234 characters. In the raw HTML, there's more markup than there
|
|
||||||
is text.
|
|
||||||
|
|
||||||
With Markdown's reference-style links, a source document much more
|
|
||||||
closely resembles the final output, as rendered in a browser. By
|
|
||||||
allowing you to move the markup-related metadata out of the paragraph,
|
|
||||||
you can add links without interrupting the narrative flow of your
|
|
||||||
prose.
|
|
||||||
|
|
||||||
|
|
||||||
<h3 id="em">Emphasis</h3>
|
|
||||||
|
|
||||||
Markdown treats asterisks (`*`) and underscores (`_`) as indicators of
|
|
||||||
emphasis. Text wrapped with one `*` or `_` will be wrapped with an
|
|
||||||
HTML `<em>` tag; double `*`'s or `_`'s will be wrapped with an HTML
|
|
||||||
`<strong>` tag. E.g., this input:
|
|
||||||
|
|
||||||
*single asterisks*
|
|
||||||
|
|
||||||
_single underscores_
|
|
||||||
|
|
||||||
**double asterisks**
|
|
||||||
|
|
||||||
__double underscores__
|
|
||||||
|
|
||||||
will produce:
|
|
||||||
|
|
||||||
<em>single asterisks</em>
|
|
||||||
|
|
||||||
<em>single underscores</em>
|
|
||||||
|
|
||||||
<strong>double asterisks</strong>
|
|
||||||
|
|
||||||
<strong>double underscores</strong>
|
|
||||||
|
|
||||||
You can use whichever style you prefer; the lone restriction is that
|
|
||||||
the same character must be used to open and close an emphasis span.
|
|
||||||
|
|
||||||
Emphasis can be used in the middle of a word:
|
|
||||||
|
|
||||||
un*fucking*believable
|
|
||||||
|
|
||||||
But if you surround an `*` or `_` with spaces, it'll be treated as a
|
|
||||||
literal asterisk or underscore.
|
|
||||||
|
|
||||||
To produce a literal asterisk or underscore at a position where it
|
|
||||||
would otherwise be used as an emphasis delimiter, you can backslash
|
|
||||||
escape it:
|
|
||||||
|
|
||||||
\*this text is surrounded by literal asterisks\*
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<h3 id="code">Code</h3>
|
|
||||||
|
|
||||||
To indicate a span of code, wrap it with backtick quotes (`` ` ``).
|
|
||||||
Unlike a pre-formatted code block, a code span indicates code within a
|
|
||||||
normal paragraph. For example:
|
|
||||||
|
|
||||||
Use the `printf()` function.
|
|
||||||
|
|
||||||
will produce:
|
|
||||||
|
|
||||||
<p>Use the <code>printf()</code> function.</p>
|
|
||||||
|
|
||||||
To include a literal backtick character within a code span, you can use
|
|
||||||
multiple backticks as the opening and closing delimiters:
|
|
||||||
|
|
||||||
``There is a literal backtick (`) here.``
|
|
||||||
|
|
||||||
which will produce this:
|
|
||||||
|
|
||||||
<p><code>There is a literal backtick (`) here.</code></p>
|
|
||||||
|
|
||||||
The backtick delimiters surrounding a code span may include spaces --
|
|
||||||
one after the opening, one before the closing. This allows you to place
|
|
||||||
literal backtick characters at the beginning or end of a code span:
|
|
||||||
|
|
||||||
A single backtick in a code span: `` ` ``
|
|
||||||
|
|
||||||
A backtick-delimited string in a code span: `` `foo` ``
|
|
||||||
|
|
||||||
will produce:
|
|
||||||
|
|
||||||
<p>A single backtick in a code span: <code>`</code></p>
|
|
||||||
|
|
||||||
<p>A backtick-delimited string in a code span: <code>`foo`</code></p>
|
|
||||||
|
|
||||||
With a code span, ampersands and angle brackets are encoded as HTML
|
|
||||||
entities automatically, which makes it easy to include example HTML
|
|
||||||
tags. Markdown will turn this:
|
|
||||||
|
|
||||||
Please don't use any `<blink>` tags.
|
|
||||||
|
|
||||||
into:
|
|
||||||
|
|
||||||
<p>Please don't use any <code><blink></code> tags.</p>
|
|
||||||
|
|
||||||
You can write this:
|
|
||||||
|
|
||||||
`—` is the decimal-encoded equivalent of `—`.
|
|
||||||
|
|
||||||
to produce:
|
|
||||||
|
|
||||||
<p><code>&#8212;</code> is the decimal-encoded
|
|
||||||
equivalent of <code>&mdash;</code>.</p>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<h3 id="img">Images</h3>
|
|
||||||
|
|
||||||
Admittedly, it's fairly difficult to devise a "natural" syntax for
|
|
||||||
placing images into a plain text document format.
|
|
||||||
|
|
||||||
Markdown uses an image syntax that is intended to resemble the syntax
|
|
||||||
for links, allowing for two styles: *inline* and *reference*.
|
|
||||||
|
|
||||||
Inline image syntax looks like this:
|
|
||||||
|
|
||||||
![Alt text](/path/to/img.jpg)
|
|
||||||
|
|
||||||
![Alt text](/path/to/img.jpg "Optional title")
|
|
||||||
|
|
||||||
That is:
|
|
||||||
|
|
||||||
* An exclamation mark: `!`;
|
|
||||||
* followed by a set of square brackets, containing the `alt`
|
|
||||||
attribute text for the image;
|
|
||||||
* followed by a set of parentheses, containing the URL or path to
|
|
||||||
the image, and an optional `title` attribute enclosed in double
|
|
||||||
or single quotes.
|
|
||||||
|
|
||||||
Reference-style image syntax looks like this:
|
|
||||||
|
|
||||||
![Alt text][id]
|
|
||||||
|
|
||||||
Where "id" is the name of a defined image reference. Image references
|
|
||||||
are defined using syntax identical to link references:
|
|
||||||
|
|
||||||
[id]: url/to/image "Optional title attribute"
|
|
||||||
|
|
||||||
As of this writing, Markdown has no syntax for specifying the
|
|
||||||
dimensions of an image; if this is important to you, you can simply
|
|
||||||
use regular HTML `<img>` tags.
|
|
||||||
|
|
||||||
|
|
||||||
* * *
|
|
||||||
|
|
||||||
|
|
||||||
<h2 id="misc">Miscellaneous</h2>
|
|
||||||
|
|
||||||
<h3 id="autolink">Automatic Links</h3>
|
|
||||||
|
|
||||||
Markdown supports a shortcut style for creating "automatic" links for URLs and email addresses: simply surround the URL or email address with angle brackets. What this means is that if you want to show the actual text of a URL or email address, and also have it be a clickable link, you can do this:
|
|
||||||
|
|
||||||
<http://example.com/>
|
|
||||||
|
|
||||||
Markdown will turn this into:
|
|
||||||
|
|
||||||
<a href="http://example.com/">http://example.com/</a>
|
|
||||||
|
|
||||||
Automatic links for email addresses work similarly, except that
|
|
||||||
Markdown will also perform a bit of randomized decimal and hex
|
|
||||||
entity-encoding to help obscure your address from address-harvesting
|
|
||||||
spambots. For example, Markdown will turn this:
|
|
||||||
|
|
||||||
<address@example.com>
|
|
||||||
|
|
||||||
into something like this:
|
|
||||||
|
|
||||||
<a href="mailto:addre
|
|
||||||
ss@example.co
|
|
||||||
m">address@exa
|
|
||||||
mple.com</a>
|
|
||||||
|
|
||||||
which will render in a browser as a clickable link to "address@example.com".
|
|
||||||
|
|
||||||
(This sort of entity-encoding trick will indeed fool many, if not
|
|
||||||
most, address-harvesting bots, but it definitely won't fool all of
|
|
||||||
them. It's better than nothing, but an address published in this way
|
|
||||||
will probably eventually start receiving spam.)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<h3 id="backslash">Backslash Escapes</h3>
|
|
||||||
|
|
||||||
Markdown allows you to use backslash escapes to generate literal
|
|
||||||
characters which would otherwise have special meaning in Markdown's
|
|
||||||
formatting syntax. For example, if you wanted to surround a word with
|
|
||||||
literal asterisks (instead of an HTML `<em>` tag), you can backslashes
|
|
||||||
before the asterisks, like this:
|
|
||||||
|
|
||||||
\*literal asterisks\*
|
|
||||||
|
|
||||||
Markdown provides backslash escapes for the following characters:
|
|
||||||
|
|
||||||
\ backslash
|
|
||||||
` backtick
|
|
||||||
* asterisk
|
|
||||||
_ underscore
|
|
||||||
{} curly braces
|
|
||||||
[] square brackets
|
|
||||||
() parentheses
|
|
||||||
# hash mark
|
|
||||||
+ plus sign
|
|
||||||
- minus sign (hyphen)
|
|
||||||
. dot
|
|
||||||
! exclamation mark
|
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
<blockquote>
|
|
||||||
<p>foo</p>
|
|
||||||
|
|
||||||
<blockquote>
|
|
||||||
<p>bar</p>
|
|
||||||
</blockquote>
|
|
||||||
|
|
||||||
<p>foo</p>
|
|
||||||
</blockquote>
|
|
|
@ -1,5 +0,0 @@
|
||||||
> foo
|
|
||||||
>
|
|
||||||
> > bar
|
|
||||||
>
|
|
||||||
> foo
|
|
166
vendor/github.com/russross/blackfriday/testdata/Ordered and unordered lists.html
generated
vendored
166
vendor/github.com/russross/blackfriday/testdata/Ordered and unordered lists.html
generated
vendored
|
@ -1,166 +0,0 @@
|
||||||
<h2>Unordered</h2>
|
|
||||||
|
|
||||||
<p>Asterisks tight:</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>asterisk 1</li>
|
|
||||||
<li>asterisk 2</li>
|
|
||||||
<li>asterisk 3</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Asterisks loose:</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><p>asterisk 1</p></li>
|
|
||||||
|
|
||||||
<li><p>asterisk 2</p></li>
|
|
||||||
|
|
||||||
<li><p>asterisk 3</p></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<p>Pluses tight:</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Plus 1</li>
|
|
||||||
<li>Plus 2</li>
|
|
||||||
<li>Plus 3</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Pluses loose:</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><p>Plus 1</p></li>
|
|
||||||
|
|
||||||
<li><p>Plus 2</p></li>
|
|
||||||
|
|
||||||
<li><p>Plus 3</p></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<p>Minuses tight:</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Minus 1</li>
|
|
||||||
<li>Minus 2</li>
|
|
||||||
<li>Minus 3</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Minuses loose:</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><p>Minus 1</p></li>
|
|
||||||
|
|
||||||
<li><p>Minus 2</p></li>
|
|
||||||
|
|
||||||
<li><p>Minus 3</p></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h2>Ordered</h2>
|
|
||||||
|
|
||||||
<p>Tight:</p>
|
|
||||||
|
|
||||||
<ol>
|
|
||||||
<li>First</li>
|
|
||||||
<li>Second</li>
|
|
||||||
<li>Third</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
<p>and:</p>
|
|
||||||
|
|
||||||
<ol>
|
|
||||||
<li>One</li>
|
|
||||||
<li>Two</li>
|
|
||||||
<li>Three</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
<p>Loose using tabs:</p>
|
|
||||||
|
|
||||||
<ol>
|
|
||||||
<li><p>First</p></li>
|
|
||||||
|
|
||||||
<li><p>Second</p></li>
|
|
||||||
|
|
||||||
<li><p>Third</p></li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
<p>and using spaces:</p>
|
|
||||||
|
|
||||||
<ol>
|
|
||||||
<li><p>One</p></li>
|
|
||||||
|
|
||||||
<li><p>Two</p></li>
|
|
||||||
|
|
||||||
<li><p>Three</p></li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
<p>Multiple paragraphs:</p>
|
|
||||||
|
|
||||||
<ol>
|
|
||||||
<li><p>Item 1, graf one.</p>
|
|
||||||
|
|
||||||
<p>Item 2. graf two. The quick brown fox jumped over the lazy dog's
|
|
||||||
back.</p></li>
|
|
||||||
|
|
||||||
<li><p>Item 2.</p></li>
|
|
||||||
|
|
||||||
<li><p>Item 3.</p></li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
<h2>Nested</h2>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Tab
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Tab
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Tab</li>
|
|
||||||
</ul></li>
|
|
||||||
</ul></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Here's another:</p>
|
|
||||||
|
|
||||||
<ol>
|
|
||||||
<li>First</li>
|
|
||||||
<li>Second:
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Fee</li>
|
|
||||||
<li>Fie</li>
|
|
||||||
<li>Foe</li>
|
|
||||||
</ul></li>
|
|
||||||
<li>Third</li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
<p>Same thing but with paragraphs:</p>
|
|
||||||
|
|
||||||
<ol>
|
|
||||||
<li><p>First</p></li>
|
|
||||||
|
|
||||||
<li><p>Second:</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Fee</li>
|
|
||||||
<li>Fie</li>
|
|
||||||
<li>Foe</li>
|
|
||||||
</ul></li>
|
|
||||||
|
|
||||||
<li><p>Third</p></li>
|
|
||||||
</ol>
|
|
||||||
|
|
||||||
<p>This was an error in Markdown 1.0.1:</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><p>this</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>sub</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>that</p></li>
|
|
||||||
</ul>
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue