Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
991c27d044 | ||
|
|
98adb53caf | ||
|
|
37408af647 | ||
|
|
6f7ff06e3f | ||
|
|
6ce1197f72 | ||
|
|
db79a43e95 | ||
|
|
2518c3f263 | ||
|
|
9c4c0da004 | ||
|
|
e388723199 | ||
|
|
e24cb20f3b | ||
|
|
8f3d0c2077 | ||
|
|
8d353b69f4 | ||
|
|
a2ea6d7756 | ||
|
|
9dc8eaf285 |
5
.gitignore
vendored
Normal file → Executable file
5
.gitignore
vendored
Normal file → Executable file
@@ -1,4 +1,7 @@
|
||||
rssmon3
|
||||
exec-rssmon3
|
||||
**.sw*
|
||||
**testdata
|
||||
**/testdata
|
||||
**/._*
|
||||
**/exec-*
|
||||
exec-*
|
||||
|
||||
0
config/config.go
Normal file → Executable file
0
config/config.go
Normal file → Executable file
0
config/encode.go
Normal file → Executable file
0
config/encode.go
Normal file → Executable file
0
config/encode_test.go
Normal file → Executable file
0
config/encode_test.go
Normal file → Executable file
0
config/new.go
Normal file → Executable file
0
config/new.go
Normal file → Executable file
0
config/new_test.go
Normal file → Executable file
0
config/new_test.go
Normal file → Executable file
0
config/stoppable.go
Normal file → Executable file
0
config/stoppable.go
Normal file → Executable file
7
handlers/handler.go
Normal file → Executable file
7
handlers/handler.go
Normal file → Executable file
@@ -41,12 +41,5 @@ func (h *Handler) Job(key string) error {
|
||||
if err := f.Pull(); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, tag := range f.Tags {
|
||||
if foo := ByTag(tag); foo != nil {
|
||||
if err := foo(key); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
package handlers
|
||||
|
||||
func ByTag(tag string) func(string) error {
|
||||
var foo func(string) error
|
||||
switch tag {
|
||||
case "torrent":
|
||||
foo = torrent
|
||||
case "podcast":
|
||||
foo = podcast
|
||||
}
|
||||
return foo
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import "errors"
|
||||
|
||||
func podcast(key string) error {
|
||||
return errors.New("not impl")
|
||||
}
|
||||
234
handlers/podcast/main.go
Executable file
234
handlers/podcast/main.go
Executable file
@@ -0,0 +1,234 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"local/args"
|
||||
"local/storage"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mmcdole/gofeed"
|
||||
)
|
||||
|
||||
const sessionHeader = "X-Transmission-Session-Id"
|
||||
|
||||
type Config struct {
|
||||
url string
|
||||
vpntor string
|
||||
outdir string
|
||||
interval time.Duration
|
||||
last time.Time
|
||||
db storage.DB
|
||||
ctx context.Context
|
||||
can context.CancelFunc
|
||||
}
|
||||
|
||||
func main() {
|
||||
config, err := config()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
log.Println(config)
|
||||
|
||||
for {
|
||||
if err := mainLoop(config); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mainLoop(config *Config) error {
|
||||
block := config.interval - time.Since(config.last)
|
||||
log.Printf("Blocking %v", block)
|
||||
select {
|
||||
case <-time.After(block):
|
||||
if err := pull(config.db, config.vpntor, config.outdir, config.url); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
config.last = time.Now()
|
||||
case <-config.ctx.Done():
|
||||
if err := config.ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func config() (*Config, error) {
|
||||
as := args.NewArgSet()
|
||||
as.Append(args.STRING, "url", "url of rss feed", "http://192.168.0.86:33419/api/tag/podcast")
|
||||
as.Append(args.STRING, "vpntor", "url of vpntor", "http://192.168.0.86:9091/transmission/rpc")
|
||||
as.Append(args.DURATION, "interval", "interval to check feed", "30m")
|
||||
as.Append(args.STRING, "outdir", "save dir", "/data/completed-rss")
|
||||
as.Append(args.STRING, "db", "db type", "map")
|
||||
as.Append(args.STRING, "addr", "db addr", "")
|
||||
as.Append(args.STRING, "user", "db user", "")
|
||||
as.Append(args.STRING, "pass", "db pass", "")
|
||||
if err := as.Parse(); err != nil {
|
||||
return &Config{}, err
|
||||
}
|
||||
|
||||
db, err := storage.New(
|
||||
storage.TypeFromString(as.Get("db").GetString()),
|
||||
as.Get("addr").GetString(),
|
||||
as.Get("user").GetString(),
|
||||
as.Get("pass").GetString(),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ctx, can := context.WithCancel(context.Background())
|
||||
return &Config{
|
||||
url: as.Get("url").GetString(),
|
||||
vpntor: as.Get("vpntor").GetString(),
|
||||
interval: as.Get("interval").GetDuration(),
|
||||
outdir: as.Get("outdir").GetString(),
|
||||
db: db,
|
||||
ctx: ctx,
|
||||
can: can,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func pull(db storage.DB, vpntor, outdir, url string) error {
|
||||
gofeed, err := getGoFeed(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("feed: %v", gofeed.Title)
|
||||
for _, item := range gofeed.Items {
|
||||
if ok, err := isDone(db, item.Link); err != nil {
|
||||
return err
|
||||
} else if ok {
|
||||
continue
|
||||
}
|
||||
s, err := getItemContent(item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := handle(vpntor, outdir, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := db.Set(item.Link, []byte{}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getGoFeed(url string) (*gofeed.Feed, error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return gofeed.NewParser().Parse(resp.Body)
|
||||
}
|
||||
|
||||
func getItemContent(item *gofeed.Item) (string, error) {
|
||||
s := item.Description
|
||||
if s == "" {
|
||||
s = item.Content
|
||||
}
|
||||
if s == "" {
|
||||
resp, err := http.Get(item.Link)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
s = string(b)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func isDone(db storage.DB, url string) (bool, error) {
|
||||
_, err := db.Get(url)
|
||||
if err == storage.ErrNotFound {
|
||||
return false, nil
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
|
||||
func handle(vpntor, outdir, content string) error {
|
||||
links := findMagnets(content)
|
||||
sort.Strings(links)
|
||||
for i := range links {
|
||||
link := links[i]
|
||||
if i > 0 && link == links[i-1] {
|
||||
continue
|
||||
}
|
||||
log.Println(link)
|
||||
if err := fetch(link, outdir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func findMagnets(s string) []string {
|
||||
magnetRegexp := regexp.MustCompile(`http[^"]*[0-9]+\.mp3`)
|
||||
return magnetRegexp.FindAllString(s, -1)
|
||||
}
|
||||
|
||||
func fetch(link, outdir string) error {
|
||||
out := path.Join(outdir, path.Base(link))
|
||||
if _, err := os.Stat(out); err == nil {
|
||||
return nil
|
||||
} else if os.IsNotExist(err) {
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := http.Get(link)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
f, err := os.Create(out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
io.Copy(f, resp.Body)
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildReqBody(outdir, magnet string) io.Reader {
|
||||
return strings.NewReader(fmt.Sprintf(`
|
||||
{
|
||||
"method": "torrent-add",
|
||||
"arguments": {
|
||||
"filename": %q,
|
||||
"download-dir": %q
|
||||
}
|
||||
}
|
||||
`, magnet, outdir))
|
||||
}
|
||||
|
||||
func getSessionID(vpntor string) (string, error) {
|
||||
resp, err := http.Get(vpntor)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
id := resp.Header.Get(sessionHeader)
|
||||
if id == "" {
|
||||
err = errors.New("session id header not found")
|
||||
}
|
||||
return id, err
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import "errors"
|
||||
|
||||
func torrent(key string) error {
|
||||
return errors.New("not impl")
|
||||
}
|
||||
241
handlers/torrent/main.go
Executable file
241
handlers/torrent/main.go
Executable file
@@ -0,0 +1,241 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"local/args"
|
||||
"local/storage"
|
||||
"log"
|
||||
"net/http"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mmcdole/gofeed"
|
||||
)
|
||||
|
||||
const sessionHeader = "X-Transmission-Session-Id"
|
||||
|
||||
type Config struct {
|
||||
url string
|
||||
vpntor string
|
||||
outdir string
|
||||
interval time.Duration
|
||||
last time.Time
|
||||
db storage.DB
|
||||
ctx context.Context
|
||||
can context.CancelFunc
|
||||
}
|
||||
|
||||
func main() {
|
||||
config, err := config()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
log.Println(config)
|
||||
|
||||
for {
|
||||
if err := mainLoop(config); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mainLoop(config *Config) error {
|
||||
block := config.interval - time.Since(config.last)
|
||||
log.Printf("Blocking %v", block)
|
||||
select {
|
||||
case <-time.After(block):
|
||||
if err := pull(config.db, config.vpntor, config.outdir, config.url); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
config.last = time.Now()
|
||||
case <-config.ctx.Done():
|
||||
if err := config.ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func config() (*Config, error) {
|
||||
as := args.NewArgSet()
|
||||
as.Append(args.STRING, "url", "url of rss feed", "http://192.168.0.86:33419/api/tag/torrent")
|
||||
as.Append(args.STRING, "vpntor", "url of vpntor", "http://192.168.0.86:9091/transmission/rpc")
|
||||
as.Append(args.DURATION, "interval", "interval to check feed", "30m")
|
||||
as.Append(args.STRING, "outdir", "save dir", "/data/completed-rss")
|
||||
as.Append(args.STRING, "db", "db type", "map")
|
||||
as.Append(args.STRING, "addr", "db addr", "")
|
||||
as.Append(args.STRING, "user", "db user", "")
|
||||
as.Append(args.STRING, "pass", "db pass", "")
|
||||
if err := as.Parse(); err != nil {
|
||||
return &Config{}, err
|
||||
}
|
||||
|
||||
db, err := storage.New(
|
||||
storage.TypeFromString(as.Get("db").GetString()),
|
||||
as.Get("addr").GetString(),
|
||||
as.Get("user").GetString(),
|
||||
as.Get("pass").GetString(),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ctx, can := context.WithCancel(context.Background())
|
||||
return &Config{
|
||||
url: as.Get("url").GetString(),
|
||||
vpntor: as.Get("vpntor").GetString(),
|
||||
interval: as.Get("interval").GetDuration(),
|
||||
outdir: as.Get("outdir").GetString(),
|
||||
db: db,
|
||||
ctx: ctx,
|
||||
can: can,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func pull(db storage.DB, vpntor, outdir, url string) error {
|
||||
gfeed, err := getGoFeed(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("feed: %v", gfeed.Title)
|
||||
for _, item := range gfeed.Items {
|
||||
if ok, err := isDone(db, item.Link); err != nil {
|
||||
return err
|
||||
} else if ok {
|
||||
continue
|
||||
}
|
||||
s, err := getItemContent(item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if item.Author == nil {
|
||||
item.Author = &gofeed.Person{Name: "."}
|
||||
}
|
||||
if err := handle(vpntor, path.Join(outdir, item.Author.Name), s); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := db.Set(item.Link, []byte{}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getGoFeed(url string) (*gofeed.Feed, error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return gofeed.NewParser().Parse(resp.Body)
|
||||
}
|
||||
|
||||
func getItemContent(item *gofeed.Item) (string, error) {
|
||||
s := item.Description
|
||||
if s == "" {
|
||||
s = item.Content
|
||||
}
|
||||
if s == "" {
|
||||
resp, err := http.Get(item.Link)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
s = string(b)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func isDone(db storage.DB, url string) (bool, error) {
|
||||
_, err := db.Get(url)
|
||||
if err == storage.ErrNotFound {
|
||||
return false, nil
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
|
||||
func handle(vpntor, outdir, content string) error {
|
||||
log.Printf("magnets: %v", findMagnets(content))
|
||||
for _, magnet := range findMagnets(content) {
|
||||
resp, err := submit(vpntor, outdir, magnet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := succeeded(resp.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func findMagnets(s string) []string {
|
||||
magnetRegexp := regexp.MustCompile(`magnet:.xt[^ $"]*`)
|
||||
return magnetRegexp.FindAllString(s, -1)
|
||||
}
|
||||
|
||||
func submit(vpntor, outdir, magnet string) (*http.Response, error) {
|
||||
session, err := getSessionID(vpntor)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err := http.NewRequest("POST", vpntor, buildReqBody(outdir, magnet))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Add(sessionHeader, session)
|
||||
return (&http.Client{}).Do(req)
|
||||
}
|
||||
|
||||
func succeeded(body io.ReadCloser) error {
|
||||
defer body.Close()
|
||||
b, err := ioutil.ReadAll(body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var result struct {
|
||||
Result string `json:"result"`
|
||||
}
|
||||
if err := json.Unmarshal(b, &result); err != nil {
|
||||
return err
|
||||
}
|
||||
if result.Result != "success" {
|
||||
return fmt.Errorf("denied: %s", b)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildReqBody(outdir, magnet string) io.Reader {
|
||||
return strings.NewReader(fmt.Sprintf(`
|
||||
{
|
||||
"method": "torrent-add",
|
||||
"arguments": {
|
||||
"filename": %q,
|
||||
"download-dir": %q
|
||||
}
|
||||
}
|
||||
`, magnet, outdir))
|
||||
}
|
||||
|
||||
func getSessionID(vpntor string) (string, error) {
|
||||
resp, err := http.Get(vpntor)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
id := resp.Header.Get(sessionHeader)
|
||||
if id == "" {
|
||||
err = errors.New("session id header not found")
|
||||
}
|
||||
return id, err
|
||||
}
|
||||
250
handlers/torrent/main_test.go
Executable file
250
handlers/torrent/main_test.go
Executable file
@@ -0,0 +1,250 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"local/storage"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/mmcdole/gofeed"
|
||||
)
|
||||
|
||||
type stringReaderCloser struct {
|
||||
io.Reader
|
||||
}
|
||||
|
||||
func mockReadClose(s string) io.ReadCloser {
|
||||
reader := strings.NewReader(s)
|
||||
return stringReaderCloser{Reader: reader}
|
||||
}
|
||||
|
||||
func (src stringReaderCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func fakeRSSServer() *httptest.Server {
|
||||
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<item>
|
||||
<title>Item Title</title>
|
||||
<link>https://roosterteeth.com/episode/rooster-teeth-podcast-2019-549</link>
|
||||
<pubDate>Tue, 18 Jun 2019 19:00:00 +0000</pubDate>
|
||||
<description>Gavin Free discuss raditation, toilet paper face, Chris's continued haircuts, and more on this week's RT Podcast! magnet:-xt1 magnet:-xt2 <a href="magnet:-xt3">link</a></description>
|
||||
<enclosure url="http://www.podtrac.com/pts/redirect.mp3/traffic.libsyn.com/roosterteethpodcast/Rooster_Teeth_Podcast_549.mp3" type="audio/mpeg" />
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
`))
|
||||
}))
|
||||
}
|
||||
|
||||
func TestMainLoopCtx(t *testing.T) {
|
||||
ctx, can := context.WithCancel(context.Background())
|
||||
can()
|
||||
c := &Config{
|
||||
interval: time.Hour,
|
||||
ctx: ctx,
|
||||
}
|
||||
if err := mainLoop(c); err == nil || !strings.Contains(err.Error(), "cancel") {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfig(t *testing.T) {
|
||||
was := os.Args[:]
|
||||
defer func() {
|
||||
os.Args = was
|
||||
}()
|
||||
os.Args = []string{"a"}
|
||||
if _, err := config(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
os.Args = []string{"a", "-interval", "not a duration"}
|
||||
stderr := os.Stderr
|
||||
f, _ := os.Open("/dev/null")
|
||||
os.Stderr = f
|
||||
defer func() {
|
||||
os.Stderr = stderr
|
||||
}()
|
||||
if _, err := config(); err == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetGoFeed(t *testing.T) {
|
||||
s := fakeRSSServer()
|
||||
defer s.Close()
|
||||
|
||||
f, err := getGoFeed(s.URL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(f.Items) != 1 {
|
||||
t.Fatal(len(f.Items))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetItemContent(t *testing.T) {
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`Hello`))
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
cases := []struct {
|
||||
item gofeed.Item
|
||||
body string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
item: gofeed.Item{
|
||||
Description: "hi",
|
||||
Content: "hi2",
|
||||
},
|
||||
body: "hi",
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
item: gofeed.Item{
|
||||
Content: "hi2",
|
||||
},
|
||||
body: "hi2",
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
item: gofeed.Item{
|
||||
Link: s.URL,
|
||||
},
|
||||
body: "Hello",
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for i, c := range cases {
|
||||
body, err := getItemContent(&c.item)
|
||||
cerrS := fmt.Sprintf("%v", c.err)
|
||||
errS := fmt.Sprintf("%v", err)
|
||||
if cerrS != errS {
|
||||
t.Errorf("[%d] unexpected err %v, want %v", i, err, c.err)
|
||||
}
|
||||
if body != c.body {
|
||||
t.Errorf("[%d] unexpected body %v, want %v", i, body, c.body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDone(t *testing.T) {
|
||||
db, _ := storage.New(storage.MAP)
|
||||
db.Set("a", []byte("hi"))
|
||||
if ok, err := isDone(db, "a"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if !ok {
|
||||
t.Fatal(ok)
|
||||
}
|
||||
|
||||
if ok, err := isDone(db, "b"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if ok {
|
||||
t.Fatal(ok)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSessionID(t *testing.T) {
|
||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add(sessionHeader, "id")
|
||||
w.Write([]byte(`Hello`))
|
||||
}))
|
||||
defer s.Close()
|
||||
|
||||
session, err := getSessionID(s.URL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if session != "id" {
|
||||
t.Fatal(session)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildReqBody(t *testing.T) {
|
||||
var want struct {
|
||||
Method string `json:"method"`
|
||||
Arguments struct {
|
||||
Filename string `json:"filename"`
|
||||
DownloadDir string `json:"download-dir"`
|
||||
} `json:"arguments"`
|
||||
}
|
||||
|
||||
b := buildReqBody("out", "mag")
|
||||
if err := json.NewDecoder(b).Decode(&want); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if want.Method != "torrent-add" {
|
||||
t.Fatal(want.Method)
|
||||
}
|
||||
if want.Arguments.Filename != "mag" {
|
||||
t.Fatal(want.Arguments.Filename)
|
||||
}
|
||||
if want.Arguments.DownloadDir != "out" {
|
||||
t.Fatal(want.Arguments.DownloadDir)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSucceeded(t *testing.T) {
|
||||
cases := []struct {
|
||||
s string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
s: `{"result":"success"}`,
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
s: `this isnt json`,
|
||||
err: errors.New("invalid character 'h' in literal true (expecting 'r')"),
|
||||
},
|
||||
{
|
||||
s: `{"result":"failure"}`,
|
||||
err: errors.New(`denied: {"result":"failure"}`),
|
||||
},
|
||||
}
|
||||
|
||||
for i, c := range cases {
|
||||
err := succeeded(mockReadClose(c.s))
|
||||
cerrS := fmt.Sprintf("%v", c.err)
|
||||
errS := fmt.Sprintf("%v", err)
|
||||
if cerrS != errS {
|
||||
t.Errorf("[%d] unexpected err %v, want %v", i, err, c.err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindMagnets(t *testing.T) {
|
||||
cases := []struct {
|
||||
s string
|
||||
l int
|
||||
}{
|
||||
{
|
||||
s: `here is some magnet:-xt1 and magnet:-xt2 another one <a href="magnet:-xt3">link</a>`,
|
||||
l: 3,
|
||||
},
|
||||
}
|
||||
|
||||
for i, c := range cases {
|
||||
out := findMagnets(c.s)
|
||||
if len(out) != c.l {
|
||||
t.Errorf("[%d] found %v magnets, want %v", i, len(out), c.l)
|
||||
}
|
||||
}
|
||||
}
|
||||
0
monitor/item.go
Normal file → Executable file
0
monitor/item.go
Normal file → Executable file
0
monitor/item_test.go
Normal file → Executable file
0
monitor/item_test.go
Normal file → Executable file
0
monitor/monitor.go
Normal file → Executable file
0
monitor/monitor.go
Normal file → Executable file
0
monitor/monitor_test.go
Normal file → Executable file
0
monitor/monitor_test.go
Normal file → Executable file
0
monitor/queue.go
Normal file → Executable file
0
monitor/queue.go
Normal file → Executable file
0
monitor/queue_test.go
Normal file → Executable file
0
monitor/queue_test.go
Normal file → Executable file
9
rss/feed.go
Normal file → Executable file
9
rss/feed.go
Normal file → Executable file
@@ -7,6 +7,7 @@ import (
|
||||
"net/http"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/mmcdole/gofeed"
|
||||
@@ -21,6 +22,7 @@ type Feed struct {
|
||||
TitleFilter string
|
||||
ContentFilter string
|
||||
Tags []string
|
||||
Copyright string
|
||||
}
|
||||
|
||||
func SubmitFeed(f *Feed) error {
|
||||
@@ -72,7 +74,7 @@ func (f *Feed) Pull() error {
|
||||
|
||||
itemTSs := []*time.Time{}
|
||||
for _, i := range gofeed.Items {
|
||||
item, err := newItem(i, f.ContentFilter)
|
||||
item, err := newItem(i, f.ContentFilter, f.Copyright)
|
||||
if err != nil {
|
||||
log.Println("[Pull]", err)
|
||||
continue
|
||||
@@ -83,9 +85,10 @@ func (f *Feed) Pull() error {
|
||||
continue
|
||||
}
|
||||
if ok := regexp.MustCompile(f.TitleFilter).MatchString(item.Title); !ok {
|
||||
log.Println("[Pull]", "Skipping bad titled item")
|
||||
log.Printf("[Pull] Skipping bad titled item: %v doesn't match /%v/", item.Title, f.TitleFilter)
|
||||
continue
|
||||
}
|
||||
log.Printf("[Pull] Saving item %v for %v /%v/", f.Key, f.URL, f.TitleFilter)
|
||||
if err := item.save(f.Key); err != nil {
|
||||
log.Println("[Pull]", err)
|
||||
continue
|
||||
@@ -123,7 +126,7 @@ func (f *Feed) Items(limit int) ([]*Item, error) {
|
||||
}
|
||||
|
||||
func (f *Feed) List(limit int) ([]string, error) {
|
||||
keys, err := config.Values().DB.List([]string{nsItems, f.Key})
|
||||
keys, err := config.Values().DB.List([]string{nsItems, f.Key}, " ", "}}}}}", strconv.Itoa(limit), "-")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
34
rss/feed_test.go
Normal file → Executable file
34
rss/feed_test.go
Normal file → Executable file
@@ -177,3 +177,37 @@ func TestRSSFeedPull(t *testing.T) {
|
||||
t.Fatal(i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRSSFeedListLimitedDescending(t *testing.T) {
|
||||
initRSSFeed()
|
||||
|
||||
s := mockRSS()
|
||||
defer s.Close()
|
||||
|
||||
f := newFeed("key")
|
||||
f.TitleFilter = "50."
|
||||
f.ContentFilter = "b"
|
||||
f.Tags = []string{"c"}
|
||||
f.URL = s.URL
|
||||
|
||||
log.SetOutput(bytes.NewBuffer(nil))
|
||||
defer log.SetOutput(os.Stderr)
|
||||
if err := f.Pull(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
log.SetOutput(os.Stderr)
|
||||
|
||||
itemKeys, err := f.List(5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(itemKeys) != 5 {
|
||||
t.Fatal(len(itemKeys))
|
||||
}
|
||||
|
||||
for i := range itemKeys {
|
||||
if i > 0 && itemKeys[i] > itemKeys[i-1] {
|
||||
t.Error(itemKeys[i], ">", itemKeys[i-1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
40
rss/item.go
Normal file → Executable file
40
rss/item.go
Normal file → Executable file
@@ -16,20 +16,22 @@ import (
|
||||
const nsItems = "nsItems"
|
||||
|
||||
type Item struct {
|
||||
Title string
|
||||
Link string
|
||||
Content string
|
||||
TS time.Time
|
||||
Title string
|
||||
Link string
|
||||
Content string
|
||||
TS time.Time
|
||||
Copyright string
|
||||
}
|
||||
|
||||
type Items []*Item
|
||||
|
||||
func newItem(i *gofeed.Item, contentFilter string) (*Item, error) {
|
||||
func newItem(i *gofeed.Item, contentFilter, copyright string) (*Item, error) {
|
||||
item := &Item{
|
||||
Title: i.Title,
|
||||
Link: i.Link,
|
||||
Content: i.Content,
|
||||
TS: latestTSPtr(i.UpdatedParsed, i.PublishedParsed),
|
||||
Title: i.Title,
|
||||
Link: i.Link,
|
||||
Content: i.Content,
|
||||
TS: latestTSPtr(i.UpdatedParsed, i.PublishedParsed),
|
||||
Copyright: copyright,
|
||||
}
|
||||
|
||||
if item.Content == "" {
|
||||
@@ -47,6 +49,9 @@ func newItem(i *gofeed.Item, contentFilter string) (*Item, error) {
|
||||
}
|
||||
item.Content = string(b)
|
||||
}
|
||||
for _, enclosure := range i.Enclosures {
|
||||
item.Content += fmt.Sprintf(`<br><a href="%s">%s</a>`, enclosure.URL, enclosure.URL)
|
||||
}
|
||||
if unescaped, err := url.QueryUnescape(item.Content); err == nil {
|
||||
item.Content = unescaped
|
||||
}
|
||||
@@ -58,6 +63,8 @@ func newItem(i *gofeed.Item, contentFilter string) (*Item, error) {
|
||||
|
||||
item.Content = fmt.Sprintf(`<a href="%s">%s</a><br>%s`, item.Link, item.Title, item.Content)
|
||||
|
||||
item.Content = clearBlankLines(item.Content)
|
||||
|
||||
return item, nil
|
||||
}
|
||||
|
||||
@@ -88,7 +95,11 @@ func (i *Item) Load(key, ns1 string, ns ...string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return config.Decode(b, i)
|
||||
err = config.Decode(b, i)
|
||||
if err == nil {
|
||||
i.Content = clearBlankLines(i.Content)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (is Items) Len() int {
|
||||
@@ -104,3 +115,12 @@ func (is Items) Swap(i, j int) {
|
||||
is[i] = is[j]
|
||||
is[j] = k
|
||||
}
|
||||
|
||||
func clearBlankLines(s string) string {
|
||||
r := regexp.MustCompile(`<br/>`)
|
||||
s = r.ReplaceAllLiteralString(s, "<br>")
|
||||
r = regexp.MustCompile(`(?m)<br>\s*(<br>\s*)*`)
|
||||
s = r.ReplaceAllLiteralString(s, "<br>")
|
||||
r = regexp.MustCompile(`(?m)\s\s*`)
|
||||
return r.ReplaceAllLiteralString(s, "\n")
|
||||
}
|
||||
|
||||
80
rss/item_test.go
Normal file → Executable file
80
rss/item_test.go
Normal file → Executable file
@@ -3,6 +3,7 @@ package rss
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mmcdole/gofeed"
|
||||
@@ -30,12 +31,12 @@ func TestRSSItemNewEncodeDecode(t *testing.T) {
|
||||
gofeed.Items[0].Content = ""
|
||||
gofeed.Items[0].Description = ""
|
||||
|
||||
item, err := newItem(gofeed.Items[0], ".*")
|
||||
item, err := newItem(gofeed.Items[0], ".*", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
itemB, err := newItem(gofeed.Items[0], "Podcast")
|
||||
itemB, err := newItem(gofeed.Items[0], "Podcast", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -75,3 +76,78 @@ func TestRSSItemNewEncodeDecode(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClearBlankLines(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
in string
|
||||
outLines int
|
||||
}{
|
||||
"remove with and without whitespace": {
|
||||
in: `<html>
|
||||
<head>
|
||||
<script>something</script>
|
||||
<style>body{hello:"blue";}</style>
|
||||
</head>
|
||||
<body>
|
||||
<br>
|
||||
<br>
|
||||
<br>
|
||||
<br/><br/>
|
||||
<br/> <br/>
|
||||
</body>
|
||||
</html>`,
|
||||
outLines: 1,
|
||||
},
|
||||
"sample content": {
|
||||
in: `<html>
|
||||
Linux
|
||||
Blog
|
||||
<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
|
||||
<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
|
||||
<img src="https://www.jeremymorgan.com/images/programming-blog.png" alt="Programming
|
||||
Blog"/><h1><a href="https://www.jeremymorgan.com/" rel="noopener noreferrer" target="_blank" referrerpolicy="no-referrer">Jeremy
|
||||
Morgan</a></h1><h2>Mostly
|
||||
Coherent
|
||||
Ramblings
|
||||
of
|
||||
a
|
||||
Silicon
|
||||
Forest
|
||||
Software
|
||||
Developer</h2><br/><br/><br/>
|
||||
<ul><br/>
|
||||
<li>RSS</li><br/>
|
||||
<br/></ul><br/>
|
||||
<br/><br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/><br/>
|
||||
<br/><ul><li><a href="https://www.jeremymorgan.com/" rel="noopener noreferrer" target="_blank" referrerpolicy="no-referrer">Home</a></li><li><a href="https://www.jeremymorgan.com/tutorials/" rel="noopener noreferrer" target="_blank" referrerpolicy="no-referrer">Tutorials</a></li><li><a href="https://www.jeremymorgan.com/blog/programming/" rel="noopener noreferrer" target="_blank" referrerpolicy="no-referrer">Programming</a></li><li><a href="https://www.jeremymorgan.com/blog/raspberry-pi/" rel="noopener noreferrer" target="_blank" referrerpolicy="no-referrer">Raspberry
|
||||
Pi</a></li><li><a href="https://www.jeremymorgan.com/blog/linux/" rel="noopener noreferrer" target="_blank" referrerpolicy="no-referrer">Linux</a></li><li><a href="https://www.jeremymorgan.com/blog/dotnet/" rel="noopener noreferrer" target="_blank" referrerpolicy="no-referrer">.Net</a></li><li><a href="https://github.com/JeremyMorgan" rel="noopener noreferrer" target="_blank" referrerpolicy="no-referrer">My
|
||||
Projects</a></li><li><a href="https://www.jeremymorgan.com/contact/" rel="noopener noreferrer" target="_blank" referrerpolicy="no-referrer">Contact
|
||||
Me</a></li></ul><br/><br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/><br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<br/>
|
||||
<h1>Trying
|
||||
Out
|
||||
</html>`,
|
||||
outLines: 6,
|
||||
},
|
||||
}
|
||||
|
||||
for name, c := range cases {
|
||||
out := clearBlankLines(c.in)
|
||||
cnts := []int{
|
||||
strings.Count(out, "<br>"),
|
||||
strings.Count(out, "<br/>"),
|
||||
}
|
||||
if v := cnts[0] + cnts[1]; v != c.outLines {
|
||||
t.Errorf("%v: want %v lines, got %v from %q: %q", name, c.outLines, v, c.in, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
0
rss/rss.go
Normal file → Executable file
0
rss/rss.go
Normal file → Executable file
0
rss/rss_test.go
Normal file → Executable file
0
rss/rss_test.go
Normal file → Executable file
1
rss/serialize.go
Normal file → Executable file
1
rss/serialize.go
Normal file → Executable file
@@ -43,6 +43,7 @@ func WriteFeed(w io.Writer, tag string, items []*Item) error {
|
||||
Title: item.Title,
|
||||
Link: &feeds.Link{Href: item.Link},
|
||||
Description: item.Content,
|
||||
Author: &feeds.Author{Name: item.Copyright},
|
||||
Created: item.TS,
|
||||
}
|
||||
}
|
||||
|
||||
1
rss/serialize_test.go
Normal file → Executable file
1
rss/serialize_test.go
Normal file → Executable file
@@ -47,6 +47,7 @@ func TestRSSWriteFeed(t *testing.T) {
|
||||
f.ContentFilter = "b"
|
||||
f.Tags = []string{"c"}
|
||||
f.URL = s.URL
|
||||
f.Copyright = "copyright"
|
||||
|
||||
log.SetOutput(bytes.NewBuffer(nil))
|
||||
defer log.SetOutput(os.Stderr)
|
||||
|
||||
6119
rss/testdata/rss.xml
vendored
Executable file
6119
rss/testdata/rss.xml
vendored
Executable file
File diff suppressed because it is too large
Load Diff
1
rss/testdata/rss.xml.REMOVED.git-id
vendored
1
rss/testdata/rss.xml.REMOVED.git-id
vendored
@@ -1 +0,0 @@
|
||||
d36f6c94dfbbaaeac339bfaec9d0dd13b0ff099b
|
||||
0
server/new.go
Normal file → Executable file
0
server/new.go
Normal file → Executable file
0
server/new_test.go
Normal file → Executable file
0
server/new_test.go
Normal file → Executable file
9
server/routes.go
Normal file → Executable file
9
server/routes.go
Normal file → Executable file
@@ -9,6 +9,7 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -50,6 +51,10 @@ func (s *Server) tag(w http.ResponseWriter, r *http.Request) {
|
||||
s.notFound(w, r)
|
||||
return
|
||||
}
|
||||
limit, err := strconv.Atoi(r.URL.Query().Get("n"))
|
||||
if err != nil {
|
||||
limit = 20
|
||||
}
|
||||
tag := regexp.MustCompile("^.*\\/").ReplaceAllString(r.URL.Path, "")
|
||||
feeds, err := rss.TaggedFeeds(tag)
|
||||
if err != nil {
|
||||
@@ -58,7 +63,7 @@ func (s *Server) tag(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
items := []*rss.Item{}
|
||||
for _, feed := range feeds {
|
||||
feedItems, err := feed.Items(20)
|
||||
feedItems, err := feed.Items(limit)
|
||||
if err != nil {
|
||||
s.error(w, r, err)
|
||||
}
|
||||
@@ -79,6 +84,7 @@ func (s *Server) feed(w http.ResponseWriter, r *http.Request) {
|
||||
URL string `json:"url"`
|
||||
Interval string `json:"refresh"`
|
||||
TitleFilter string `json:"items"`
|
||||
Copyright string `json:"copyright"`
|
||||
ContentFilter string `json:"content"`
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
@@ -97,6 +103,7 @@ func (s *Server) feed(w http.ResponseWriter, r *http.Request) {
|
||||
TitleFilter: putFeed.TitleFilter,
|
||||
ContentFilter: putFeed.ContentFilter,
|
||||
Tags: putFeed.Tags,
|
||||
Copyright: putFeed.Copyright,
|
||||
}
|
||||
if err := rss.SubmitFeed(f); err != nil {
|
||||
s.error(w, r, err)
|
||||
|
||||
0
server/routes_test.go
Normal file → Executable file
0
server/routes_test.go
Normal file → Executable file
0
server/server.go
Normal file → Executable file
0
server/server.go
Normal file → Executable file
0
server/server_test.go
Normal file → Executable file
0
server/server_test.go
Normal file → Executable file
68
testdata/2add
vendored
68
testdata/2add
vendored
@@ -1,68 +0,0 @@
|
||||
#! /bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
remote="${remote:-192.168.0.86}"
|
||||
|
||||
feeds='
|
||||
http://lizclimo.tumblr.com/rss comic na na "24h"
|
||||
https://mangadex.org/rss/follows/ryZGFkVgv9DAPqf45EsTHuQcxmteKBN6 manga na na "1h"
|
||||
https://siftrss.com/f/WXPo6AKQLX manga na na "1h"
|
||||
https://xkcd.com/atom.xml comic na na "24h"
|
||||
http://yaoi-blcd.tumblr.com/tagged/19-days/rss manga "Old.Xian.update.of..19" na "4h"
|
||||
http://www.shanaproject.com/feeds/secure/user/5310/5MUGNU9B3I/ anime na na "24h"
|
||||
http://www.manga.megchan.com/blog/?feed=rss2 manga na na "12h"
|
||||
https://siftrss.com/f/6Q8a0ZK0RmL manga na na "12h"
|
||||
https://dropoutmanga.wordpress.com/feed/ manga "(Hatsukoi|Tomo.chan)" na "6h"
|
||||
http://merakiscans.com/manga-rss/solo-leveling/ manga na na "8h"
|
||||
http://www.mangago.me/r/rsslink/a_man_like_you.xml manga na na "24h"
|
||||
http://www.mangago.me/r/rsslink/unriped_expression.xml manga na na "24h"
|
||||
http://www.mangago.me/r/rsslink/heesu_in_class_2.xml manga na na "24h"
|
||||
http://www.mangago.me/r/rsslink/bj_alex.xml manga na na "24h"
|
||||
https://heroscans.tumblr.com/rss manga "(Saizu)" na "24h"
|
||||
http://feeds.pinboard.in/rss/popular/ news ".*" ".*" "24h"
|
||||
https://www.vox.com/rss/index.xml news ".*" ".*" "24h"
|
||||
https://www.techdirt.com/techdirt_rss.xml news ".*" ".*" "24h"
|
||||
http://hotdiggedydemon.com/ comic ".*" ".*" "24h"
|
||||
https://nyaa.si/?page=rss&u=Nauti anime "(akegurui.*XX)" ".*Anime...English.translated.*" "3h"
|
||||
https://tapas.io/rss/series/72832 manga na na "24h"
|
||||
https://tapas.io/rss/series/88570 manga na na "24h"
|
||||
https://nyaa.si/?page=rss&q=kakegurui&c=0_0&f=0&u=Nauti anime "(akegurui.XX)" ".*Anime...English.translated.*" "3h"
|
||||
https://nyaa.si/?page=rss&q=horriblesubs+mob+psycho+720&c=0_0&f=0 anime ".*" ".*Anime...English.translated.*" "3h"
|
||||
https://www.youtube.com/feeds/videos.xml?channel_id=UC9CuvdOVfMPvKCiwdGKL3cQ youtube","gampegrumps ".*" ".*" "6h"
|
||||
https://www.youtube.com/feeds/videos.xml?channel_id=UCbGO1tCyjMlzqIT-tyrJNqQ youtube","kshaway ".*" ".*" "6h"
|
||||
https://www.youtube.com/feeds/videos.xml?channel_id=UC9YLd0REiXxLqQQH_CpJKZQ youtube","instalok ".*" ".*" "6h"
|
||||
https://www.youtube.com/feeds/videos.xml?channel_id=UCsvn_Po0SmunchJYOWpOxMg youtube","videogamedunkey ".*" ".*" "6h"
|
||||
https://eztv.ag/ezrss.xml momma","torrent "^(Wheel.[oO]f.Fortune.[S2]|Jeopardy.20|Gold.Rush.*720.*WEB|Survivor.S.*720p|Gold.Rush.*S.*E.*720.*WEB|Archer|Pointless.*720p.*WEB|Mock.The.Week.*480p|8.Out.Of.10.Cats.*480p|Masterchef.*Australia.*720p|Price.[iI]s.[rR]ight|Let.*[mM]ake.[aA].[dD]eal)" "<a.*?href=.magnet:.*?</a>" "2h"
|
||||
https://www.youtube.com/feeds/videos.xml?channel_id=UCs-w7E2HZWwXmjt9RTvBB_A youtube","settled ".*" ".*" "6h"
|
||||
https://roosterteeth.com/show/rt-podcast/feed/mp3 podcast ".*" ".*" "6h"
|
||||
'
|
||||
feedsB="$(printf "%s\n" "$feeds" | grep -v '^$' | tail -n 1)"
|
||||
feeds="$feedsB"
|
||||
|
||||
defaultIF='""'
|
||||
defaultCF='"<img.*?/(img)?>"'
|
||||
defaultIN='"6h"'
|
||||
|
||||
feeds="${feeds// /,,}"
|
||||
|
||||
for feed in $feeds; do
|
||||
feed="${feed//,,/ }"
|
||||
link="$(echo "$feed" | awk '{print $1}')"
|
||||
tag="$(echo "$feed" | awk '{print $2}')"
|
||||
if="$(echo "$feed" | awk '{print $3}')"
|
||||
cf="$(echo "$feed" | awk '{print $4}')"
|
||||
in="$(echo "$feed" | awk '{print $5}')"
|
||||
if [ "$if" == "na" ]; then
|
||||
if="$defaultIF"
|
||||
fi
|
||||
if [ "$cf" == "na" ]; then
|
||||
cf="$defaultCF"
|
||||
fi
|
||||
if [ "$in" == "na" ]; then
|
||||
in="$defaultIN"
|
||||
fi
|
||||
cmd="curl ${PROXY:-} -i ${remote}:33419/api/feed -X PUT --data '{\"url\":\"$link\", \"refresh\":$in, \"items\":$if, \"content\":$cf, \"tags\":[\"$tag\"]}'"
|
||||
echo "$cmd" >&2
|
||||
eval "$cmd"
|
||||
done
|
||||
7
testdata/2do
vendored
7
testdata/2do
vendored
@@ -1,7 +0,0 @@
|
||||
* implement torret+podcast handlers
|
||||
* fetch <img> and embed?
|
||||
|
||||
x render tags -> feeds for server
|
||||
x load feed jobs on startup and queue
|
||||
x push new jobs/queue items from server
|
||||
x implement endpoints for server
|
||||
202
vendor/cloud.google.com/go/LICENSE
generated
vendored
Executable file
202
vendor/cloud.google.com/go/LICENSE
generated
vendored
Executable file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
513
vendor/cloud.google.com/go/compute/metadata/metadata.go
generated
vendored
Executable file
513
vendor/cloud.google.com/go/compute/metadata/metadata.go
generated
vendored
Executable file
@@ -0,0 +1,513 @@
|
||||
// Copyright 2014 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package metadata provides access to Google Compute Engine (GCE)
|
||||
// metadata and API service accounts.
|
||||
//
|
||||
// This package is a wrapper around the GCE metadata service,
|
||||
// as documented at https://developers.google.com/compute/docs/metadata.
|
||||
package metadata // import "cloud.google.com/go/compute/metadata"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// metadataIP is the documented metadata server IP address.
|
||||
metadataIP = "169.254.169.254"
|
||||
|
||||
// metadataHostEnv is the environment variable specifying the
|
||||
// GCE metadata hostname. If empty, the default value of
|
||||
// metadataIP ("169.254.169.254") is used instead.
|
||||
// This is variable name is not defined by any spec, as far as
|
||||
// I know; it was made up for the Go package.
|
||||
metadataHostEnv = "GCE_METADATA_HOST"
|
||||
|
||||
userAgent = "gcloud-golang/0.1"
|
||||
)
|
||||
|
||||
type cachedValue struct {
|
||||
k string
|
||||
trim bool
|
||||
mu sync.Mutex
|
||||
v string
|
||||
}
|
||||
|
||||
var (
|
||||
projID = &cachedValue{k: "project/project-id", trim: true}
|
||||
projNum = &cachedValue{k: "project/numeric-project-id", trim: true}
|
||||
instID = &cachedValue{k: "instance/id", trim: true}
|
||||
)
|
||||
|
||||
var (
|
||||
defaultClient = &Client{hc: &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 2 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
ResponseHeaderTimeout: 2 * time.Second,
|
||||
},
|
||||
}}
|
||||
subscribeClient = &Client{hc: &http.Client{
|
||||
Transport: &http.Transport{
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 2 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
},
|
||||
}}
|
||||
)
|
||||
|
||||
// NotDefinedError is returned when requested metadata is not defined.
|
||||
//
|
||||
// The underlying string is the suffix after "/computeMetadata/v1/".
|
||||
//
|
||||
// This error is not returned if the value is defined to be the empty
|
||||
// string.
|
||||
type NotDefinedError string
|
||||
|
||||
func (suffix NotDefinedError) Error() string {
|
||||
return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix))
|
||||
}
|
||||
|
||||
func (c *cachedValue) get(cl *Client) (v string, err error) {
|
||||
defer c.mu.Unlock()
|
||||
c.mu.Lock()
|
||||
if c.v != "" {
|
||||
return c.v, nil
|
||||
}
|
||||
if c.trim {
|
||||
v, err = cl.getTrimmed(c.k)
|
||||
} else {
|
||||
v, err = cl.Get(c.k)
|
||||
}
|
||||
if err == nil {
|
||||
c.v = v
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
onGCEOnce sync.Once
|
||||
onGCE bool
|
||||
)
|
||||
|
||||
// OnGCE reports whether this process is running on Google Compute Engine.
|
||||
func OnGCE() bool {
|
||||
onGCEOnce.Do(initOnGCE)
|
||||
return onGCE
|
||||
}
|
||||
|
||||
func initOnGCE() {
|
||||
onGCE = testOnGCE()
|
||||
}
|
||||
|
||||
func testOnGCE() bool {
|
||||
// The user explicitly said they're on GCE, so trust them.
|
||||
if os.Getenv(metadataHostEnv) != "" {
|
||||
return true
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
resc := make(chan bool, 2)
|
||||
|
||||
// Try two strategies in parallel.
|
||||
// See https://github.com/googleapis/google-cloud-go/issues/194
|
||||
go func() {
|
||||
req, _ := http.NewRequest("GET", "http://"+metadataIP, nil)
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
res, err := defaultClient.hc.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
resc <- false
|
||||
return
|
||||
}
|
||||
defer res.Body.Close()
|
||||
resc <- res.Header.Get("Metadata-Flavor") == "Google"
|
||||
}()
|
||||
|
||||
go func() {
|
||||
addrs, err := net.LookupHost("metadata.google.internal")
|
||||
if err != nil || len(addrs) == 0 {
|
||||
resc <- false
|
||||
return
|
||||
}
|
||||
resc <- strsContains(addrs, metadataIP)
|
||||
}()
|
||||
|
||||
tryHarder := systemInfoSuggestsGCE()
|
||||
if tryHarder {
|
||||
res := <-resc
|
||||
if res {
|
||||
// The first strategy succeeded, so let's use it.
|
||||
return true
|
||||
}
|
||||
// Wait for either the DNS or metadata server probe to
|
||||
// contradict the other one and say we are running on
|
||||
// GCE. Give it a lot of time to do so, since the system
|
||||
// info already suggests we're running on a GCE BIOS.
|
||||
timer := time.NewTimer(5 * time.Second)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case res = <-resc:
|
||||
return res
|
||||
case <-timer.C:
|
||||
// Too slow. Who knows what this system is.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// There's no hint from the system info that we're running on
|
||||
// GCE, so use the first probe's result as truth, whether it's
|
||||
// true or false. The goal here is to optimize for speed for
|
||||
// users who are NOT running on GCE. We can't assume that
|
||||
// either a DNS lookup or an HTTP request to a blackholed IP
|
||||
// address is fast. Worst case this should return when the
|
||||
// metaClient's Transport.ResponseHeaderTimeout or
|
||||
// Transport.Dial.Timeout fires (in two seconds).
|
||||
return <-resc
|
||||
}
|
||||
|
||||
// systemInfoSuggestsGCE reports whether the local system (without
|
||||
// doing network requests) suggests that we're running on GCE. If this
|
||||
// returns true, testOnGCE tries a bit harder to reach its metadata
|
||||
// server.
|
||||
func systemInfoSuggestsGCE() bool {
|
||||
if runtime.GOOS != "linux" {
|
||||
// We don't have any non-Linux clues available, at least yet.
|
||||
return false
|
||||
}
|
||||
slurp, _ := ioutil.ReadFile("/sys/class/dmi/id/product_name")
|
||||
name := strings.TrimSpace(string(slurp))
|
||||
return name == "Google" || name == "Google Compute Engine"
|
||||
}
|
||||
|
||||
// Subscribe calls Client.Subscribe on a client designed for subscribing (one with no
|
||||
// ResponseHeaderTimeout).
|
||||
func Subscribe(suffix string, fn func(v string, ok bool) error) error {
|
||||
return subscribeClient.Subscribe(suffix, fn)
|
||||
}
|
||||
|
||||
// Get calls Client.Get on the default client.
|
||||
func Get(suffix string) (string, error) { return defaultClient.Get(suffix) }
|
||||
|
||||
// ProjectID returns the current instance's project ID string.
|
||||
func ProjectID() (string, error) { return defaultClient.ProjectID() }
|
||||
|
||||
// NumericProjectID returns the current instance's numeric project ID.
|
||||
func NumericProjectID() (string, error) { return defaultClient.NumericProjectID() }
|
||||
|
||||
// InternalIP returns the instance's primary internal IP address.
|
||||
func InternalIP() (string, error) { return defaultClient.InternalIP() }
|
||||
|
||||
// ExternalIP returns the instance's primary external (public) IP address.
|
||||
func ExternalIP() (string, error) { return defaultClient.ExternalIP() }
|
||||
|
||||
// Hostname returns the instance's hostname. This will be of the form
|
||||
// "<instanceID>.c.<projID>.internal".
|
||||
func Hostname() (string, error) { return defaultClient.Hostname() }
|
||||
|
||||
// InstanceTags returns the list of user-defined instance tags,
|
||||
// assigned when initially creating a GCE instance.
|
||||
func InstanceTags() ([]string, error) { return defaultClient.InstanceTags() }
|
||||
|
||||
// InstanceID returns the current VM's numeric instance ID.
|
||||
func InstanceID() (string, error) { return defaultClient.InstanceID() }
|
||||
|
||||
// InstanceName returns the current VM's instance ID string.
|
||||
func InstanceName() (string, error) { return defaultClient.InstanceName() }
|
||||
|
||||
// Zone returns the current VM's zone, such as "us-central1-b".
|
||||
func Zone() (string, error) { return defaultClient.Zone() }
|
||||
|
||||
// InstanceAttributes calls Client.InstanceAttributes on the default client.
|
||||
func InstanceAttributes() ([]string, error) { return defaultClient.InstanceAttributes() }
|
||||
|
||||
// ProjectAttributes calls Client.ProjectAttributes on the default client.
|
||||
func ProjectAttributes() ([]string, error) { return defaultClient.ProjectAttributes() }
|
||||
|
||||
// InstanceAttributeValue calls Client.InstanceAttributeValue on the default client.
|
||||
func InstanceAttributeValue(attr string) (string, error) {
|
||||
return defaultClient.InstanceAttributeValue(attr)
|
||||
}
|
||||
|
||||
// ProjectAttributeValue calls Client.ProjectAttributeValue on the default client.
|
||||
func ProjectAttributeValue(attr string) (string, error) {
|
||||
return defaultClient.ProjectAttributeValue(attr)
|
||||
}
|
||||
|
||||
// Scopes calls Client.Scopes on the default client.
|
||||
func Scopes(serviceAccount string) ([]string, error) { return defaultClient.Scopes(serviceAccount) }
|
||||
|
||||
func strsContains(ss []string, s string) bool {
|
||||
for _, v := range ss {
|
||||
if v == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// A Client provides metadata.
|
||||
type Client struct {
|
||||
hc *http.Client
|
||||
}
|
||||
|
||||
// NewClient returns a Client that can be used to fetch metadata. All HTTP requests
|
||||
// will use the given http.Client instead of the default client.
|
||||
func NewClient(c *http.Client) *Client {
|
||||
return &Client{hc: c}
|
||||
}
|
||||
|
||||
// getETag returns a value from the metadata service as well as the associated ETag.
|
||||
// This func is otherwise equivalent to Get.
|
||||
func (c *Client) getETag(suffix string) (value, etag string, err error) {
|
||||
// Using a fixed IP makes it very difficult to spoof the metadata service in
|
||||
// a container, which is an important use-case for local testing of cloud
|
||||
// deployments. To enable spoofing of the metadata service, the environment
|
||||
// variable GCE_METADATA_HOST is first inspected to decide where metadata
|
||||
// requests shall go.
|
||||
host := os.Getenv(metadataHostEnv)
|
||||
if host == "" {
|
||||
// Using 169.254.169.254 instead of "metadata" here because Go
|
||||
// binaries built with the "netgo" tag and without cgo won't
|
||||
// know the search suffix for "metadata" is
|
||||
// ".google.internal", and this IP address is documented as
|
||||
// being stable anyway.
|
||||
host = metadataIP
|
||||
}
|
||||
u := "http://" + host + "/computeMetadata/v1/" + suffix
|
||||
req, _ := http.NewRequest("GET", u, nil)
|
||||
req.Header.Set("Metadata-Flavor", "Google")
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
res, err := c.hc.Do(req)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode == http.StatusNotFound {
|
||||
return "", "", NotDefinedError(suffix)
|
||||
}
|
||||
all, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
return "", "", &Error{Code: res.StatusCode, Message: string(all)}
|
||||
}
|
||||
return string(all), res.Header.Get("Etag"), nil
|
||||
}
|
||||
|
||||
// Get returns a value from the metadata service.
|
||||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||
//
|
||||
// If the GCE_METADATA_HOST environment variable is not defined, a default of
|
||||
// 169.254.169.254 will be used instead.
|
||||
//
|
||||
// If the requested metadata is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
func (c *Client) Get(suffix string) (string, error) {
|
||||
val, _, err := c.getETag(suffix)
|
||||
return val, err
|
||||
}
|
||||
|
||||
func (c *Client) getTrimmed(suffix string) (s string, err error) {
|
||||
s, err = c.Get(suffix)
|
||||
s = strings.TrimSpace(s)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) lines(suffix string) ([]string, error) {
|
||||
j, err := c.Get(suffix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := strings.Split(strings.TrimSpace(j), "\n")
|
||||
for i := range s {
|
||||
s[i] = strings.TrimSpace(s[i])
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// ProjectID returns the current instance's project ID string.
|
||||
func (c *Client) ProjectID() (string, error) { return projID.get(c) }
|
||||
|
||||
// NumericProjectID returns the current instance's numeric project ID.
|
||||
func (c *Client) NumericProjectID() (string, error) { return projNum.get(c) }
|
||||
|
||||
// InstanceID returns the current VM's numeric instance ID.
|
||||
func (c *Client) InstanceID() (string, error) { return instID.get(c) }
|
||||
|
||||
// InternalIP returns the instance's primary internal IP address.
|
||||
func (c *Client) InternalIP() (string, error) {
|
||||
return c.getTrimmed("instance/network-interfaces/0/ip")
|
||||
}
|
||||
|
||||
// ExternalIP returns the instance's primary external (public) IP address.
|
||||
func (c *Client) ExternalIP() (string, error) {
|
||||
return c.getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip")
|
||||
}
|
||||
|
||||
// Hostname returns the instance's hostname. This will be of the form
|
||||
// "<instanceID>.c.<projID>.internal".
|
||||
func (c *Client) Hostname() (string, error) {
|
||||
return c.getTrimmed("instance/hostname")
|
||||
}
|
||||
|
||||
// InstanceTags returns the list of user-defined instance tags,
|
||||
// assigned when initially creating a GCE instance.
|
||||
func (c *Client) InstanceTags() ([]string, error) {
|
||||
var s []string
|
||||
j, err := c.Get("instance/tags")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// InstanceName returns the current VM's instance ID string.
|
||||
func (c *Client) InstanceName() (string, error) {
|
||||
host, err := c.Hostname()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.Split(host, ".")[0], nil
|
||||
}
|
||||
|
||||
// Zone returns the current VM's zone, such as "us-central1-b".
|
||||
func (c *Client) Zone() (string, error) {
|
||||
zone, err := c.getTrimmed("instance/zone")
|
||||
// zone is of the form "projects/<projNum>/zones/<zoneName>".
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return zone[strings.LastIndex(zone, "/")+1:], nil
|
||||
}
|
||||
|
||||
// InstanceAttributes returns the list of user-defined attributes,
|
||||
// assigned when initially creating a GCE VM instance. The value of an
|
||||
// attribute can be obtained with InstanceAttributeValue.
|
||||
func (c *Client) InstanceAttributes() ([]string, error) { return c.lines("instance/attributes/") }
|
||||
|
||||
// ProjectAttributes returns the list of user-defined attributes
|
||||
// applying to the project as a whole, not just this VM. The value of
|
||||
// an attribute can be obtained with ProjectAttributeValue.
|
||||
func (c *Client) ProjectAttributes() ([]string, error) { return c.lines("project/attributes/") }
|
||||
|
||||
// InstanceAttributeValue returns the value of the provided VM
|
||||
// instance attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// InstanceAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
func (c *Client) InstanceAttributeValue(attr string) (string, error) {
|
||||
return c.Get("instance/attributes/" + attr)
|
||||
}
|
||||
|
||||
// ProjectAttributeValue returns the value of the provided
|
||||
// project attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// ProjectAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
func (c *Client) ProjectAttributeValue(attr string) (string, error) {
|
||||
return c.Get("project/attributes/" + attr)
|
||||
}
|
||||
|
||||
// Scopes returns the service account scopes for the given account.
|
||||
// The account may be empty or the string "default" to use the instance's
|
||||
// main account.
|
||||
func (c *Client) Scopes(serviceAccount string) ([]string, error) {
|
||||
if serviceAccount == "" {
|
||||
serviceAccount = "default"
|
||||
}
|
||||
return c.lines("instance/service-accounts/" + serviceAccount + "/scopes")
|
||||
}
|
||||
|
||||
// Subscribe subscribes to a value from the metadata service.
|
||||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||
// The suffix may contain query parameters.
|
||||
//
|
||||
// Subscribe calls fn with the latest metadata value indicated by the provided
|
||||
// suffix. If the metadata value is deleted, fn is called with the empty string
|
||||
// and ok false. Subscribe blocks until fn returns a non-nil error or the value
|
||||
// is deleted. Subscribe returns the error value returned from the last call to
|
||||
// fn, which may be nil when ok == false.
|
||||
func (c *Client) Subscribe(suffix string, fn func(v string, ok bool) error) error {
|
||||
const failedSubscribeSleep = time.Second * 5
|
||||
|
||||
// First check to see if the metadata value exists at all.
|
||||
val, lastETag, err := c.getETag(suffix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := fn(val, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ok := true
|
||||
if strings.ContainsRune(suffix, '?') {
|
||||
suffix += "&wait_for_change=true&last_etag="
|
||||
} else {
|
||||
suffix += "?wait_for_change=true&last_etag="
|
||||
}
|
||||
for {
|
||||
val, etag, err := c.getETag(suffix + url.QueryEscape(lastETag))
|
||||
if err != nil {
|
||||
if _, deleted := err.(NotDefinedError); !deleted {
|
||||
time.Sleep(failedSubscribeSleep)
|
||||
continue // Retry on other errors.
|
||||
}
|
||||
ok = false
|
||||
}
|
||||
lastETag = etag
|
||||
|
||||
if err := fn(val, ok); err != nil || !ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Error contains an error response from the server.
|
||||
type Error struct {
|
||||
// Code is the HTTP response status code.
|
||||
Code int
|
||||
// Message is the server response message.
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("compute: Received %d `%s`", e.Code, e.Message)
|
||||
}
|
||||
12
vendor/github.com/PuerkitoBio/goquery/LICENSE
generated
vendored
Normal file
12
vendor/github.com/PuerkitoBio/goquery/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
Copyright (c) 2012-2016, Martin Angers & Contributors
|
||||
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 the author 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 HOLDER 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.
|
||||
182
vendor/github.com/PuerkitoBio/goquery/README.md
generated
vendored
Normal file
182
vendor/github.com/PuerkitoBio/goquery/README.md
generated
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
# goquery - a little like that j-thing, only in Go
|
||||
[](http://travis-ci.org/PuerkitoBio/goquery) [](http://godoc.org/github.com/PuerkitoBio/goquery) [](https://sourcegraph.com/github.com/PuerkitoBio/goquery?badge)
|
||||
|
||||
goquery brings a syntax and a set of features similar to [jQuery][] to the [Go language][go]. It is based on Go's [net/html package][html] and the CSS Selector library [cascadia][]. Since the net/html parser returns nodes, and not a full-featured DOM tree, jQuery's stateful manipulation functions (like height(), css(), detach()) have been left off.
|
||||
|
||||
Also, because the net/html parser requires UTF-8 encoding, so does goquery: it is the caller's responsibility to ensure that the source document provides UTF-8 encoded HTML. See the [wiki][] for various options to do this.
|
||||
|
||||
Syntax-wise, it is as close as possible to jQuery, with the same function names when possible, and that warm and fuzzy chainable interface. jQuery being the ultra-popular library that it is, I felt that writing a similar HTML-manipulating library was better to follow its API than to start anew (in the same spirit as Go's `fmt` package), even though some of its methods are less than intuitive (looking at you, [index()][index]...).
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Installation](#installation)
|
||||
* [Changelog](#changelog)
|
||||
* [API](#api)
|
||||
* [Examples](#examples)
|
||||
* [Related Projects](#related-projects)
|
||||
* [Support](#support)
|
||||
* [License](#license)
|
||||
|
||||
## Installation
|
||||
|
||||
Please note that because of the net/html dependency, goquery requires Go1.1+.
|
||||
|
||||
$ go get github.com/PuerkitoBio/goquery
|
||||
|
||||
(optional) To run unit tests:
|
||||
|
||||
$ cd $GOPATH/src/github.com/PuerkitoBio/goquery
|
||||
$ go test
|
||||
|
||||
(optional) To run benchmarks (warning: it runs for a few minutes):
|
||||
|
||||
$ cd $GOPATH/src/github.com/PuerkitoBio/goquery
|
||||
$ go test -bench=".*"
|
||||
|
||||
## Changelog
|
||||
|
||||
**Note that goquery's API is now stable, and will not break.**
|
||||
|
||||
* **2018-11-15 (v1.5.0)** : Go module support (thanks @Zaba505).
|
||||
* **2018-06-07 (v1.4.1)** : Add `NewDocumentFromReader` examples.
|
||||
* **2018-03-24 (v1.4.0)** : Deprecate `NewDocument(url)` and `NewDocumentFromResponse(response)`.
|
||||
* **2018-01-28 (v1.3.0)** : Add `ToEnd` constant to `Slice` until the end of the selection (thanks to @davidjwilkins for raising the issue).
|
||||
* **2018-01-11 (v1.2.0)** : Add `AddBack*` and deprecate `AndSelf` (thanks to @davidjwilkins).
|
||||
* **2017-02-12 (v1.1.0)** : Add `SetHtml` and `SetText` (thanks to @glebtv).
|
||||
* **2016-12-29 (v1.0.2)** : Optimize allocations for `Selection.Text` (thanks to @radovskyb).
|
||||
* **2016-08-28 (v1.0.1)** : Optimize performance for large documents.
|
||||
* **2016-07-27 (v1.0.0)** : Tag version 1.0.0.
|
||||
* **2016-06-15** : Invalid selector strings internally compile to a `Matcher` implementation that never matches any node (instead of a panic). So for example, `doc.Find("~")` returns an empty `*Selection` object.
|
||||
* **2016-02-02** : Add `NodeName` utility function similar to the DOM's `nodeName` property. It returns the tag name of the first element in a selection, and other relevant values of non-element nodes (see godoc for details). Add `OuterHtml` utility function similar to the DOM's `outerHTML` property (named `OuterHtml` in small caps for consistency with the existing `Html` method on the `Selection`).
|
||||
* **2015-04-20** : Add `AttrOr` helper method to return the attribute's value or a default value if absent. Thanks to [piotrkowalczuk][piotr].
|
||||
* **2015-02-04** : Add more manipulation functions - Prepend* - thanks again to [Andrew Stone][thatguystone].
|
||||
* **2014-11-28** : Add more manipulation functions - ReplaceWith*, Wrap* and Unwrap - thanks again to [Andrew Stone][thatguystone].
|
||||
* **2014-11-07** : Add manipulation functions (thanks to [Andrew Stone][thatguystone]) and `*Matcher` functions, that receive compiled cascadia selectors instead of selector strings, thus avoiding potential panics thrown by goquery via `cascadia.MustCompile` calls. This results in better performance (selectors can be compiled once and reused) and more idiomatic error handling (you can handle cascadia's compilation errors, instead of recovering from panics, which had been bugging me for a long time). Note that the actual type expected is a `Matcher` interface, that `cascadia.Selector` implements. Other matcher implementations could be used.
|
||||
* **2014-11-06** : Change import paths of net/html to golang.org/x/net/html (see https://groups.google.com/forum/#!topic/golang-nuts/eD8dh3T9yyA). Make sure to update your code to use the new import path too when you call goquery with `html.Node`s.
|
||||
* **v0.3.2** : Add `NewDocumentFromReader()` (thanks jweir) which allows creating a goquery document from an io.Reader.
|
||||
* **v0.3.1** : Add `NewDocumentFromResponse()` (thanks assassingj) which allows creating a goquery document from an http response.
|
||||
* **v0.3.0** : Add `EachWithBreak()` which allows to break out of an `Each()` loop by returning false. This function was added instead of changing the existing `Each()` to avoid breaking compatibility.
|
||||
* **v0.2.1** : Make go-getable, now that [go.net/html is Go1.0-compatible][gonet] (thanks to @matrixik for pointing this out).
|
||||
* **v0.2.0** : Add support for negative indices in Slice(). **BREAKING CHANGE** `Document.Root` is removed, `Document` is now a `Selection` itself (a selection of one, the root element, just like `Document.Root` was before). Add jQuery's Closest() method.
|
||||
* **v0.1.1** : Add benchmarks to use as baseline for refactorings, refactor Next...() and Prev...() methods to use the new html package's linked list features (Next/PrevSibling, FirstChild). Good performance boost (40+% in some cases).
|
||||
* **v0.1.0** : Initial release.
|
||||
|
||||
## API
|
||||
|
||||
goquery exposes two structs, `Document` and `Selection`, and the `Matcher` interface. Unlike jQuery, which is loaded as part of a DOM document, and thus acts on its containing document, goquery doesn't know which HTML document to act upon. So it needs to be told, and that's what the `Document` type is for. It holds the root document node as the initial Selection value to manipulate.
|
||||
|
||||
jQuery often has many variants for the same function (no argument, a selector string argument, a jQuery object argument, a DOM element argument, ...). Instead of exposing the same features in goquery as a single method with variadic empty interface arguments, statically-typed signatures are used following this naming convention:
|
||||
|
||||
* When the jQuery equivalent can be called with no argument, it has the same name as jQuery for the no argument signature (e.g.: `Prev()`), and the version with a selector string argument is called `XxxFiltered()` (e.g.: `PrevFiltered()`)
|
||||
* When the jQuery equivalent **requires** one argument, the same name as jQuery is used for the selector string version (e.g.: `Is()`)
|
||||
* The signatures accepting a jQuery object as argument are defined in goquery as `XxxSelection()` and take a `*Selection` object as argument (e.g.: `FilterSelection()`)
|
||||
* The signatures accepting a DOM element as argument in jQuery are defined in goquery as `XxxNodes()` and take a variadic argument of type `*html.Node` (e.g.: `FilterNodes()`)
|
||||
* The signatures accepting a function as argument in jQuery are defined in goquery as `XxxFunction()` and take a function as argument (e.g.: `FilterFunction()`)
|
||||
* The goquery methods that can be called with a selector string have a corresponding version that take a `Matcher` interface and are defined as `XxxMatcher()` (e.g.: `IsMatcher()`)
|
||||
|
||||
Utility functions that are not in jQuery but are useful in Go are implemented as functions (that take a `*Selection` as parameter), to avoid a potential naming clash on the `*Selection`'s methods (reserved for jQuery-equivalent behaviour).
|
||||
|
||||
The complete [godoc reference documentation can be found here][doc].
|
||||
|
||||
Please note that Cascadia's selectors do not necessarily match all supported selectors of jQuery (Sizzle). See the [cascadia project][cascadia] for details. Invalid selector strings compile to a `Matcher` that fails to match any node. Behaviour of the various functions that take a selector string as argument follows from that fact, e.g. (where `~` is an invalid selector string):
|
||||
|
||||
* `Find("~")` returns an empty selection because the selector string doesn't match anything.
|
||||
* `Add("~")` returns a new selection that holds the same nodes as the original selection, because it didn't add any node (selector string didn't match anything).
|
||||
* `ParentsFiltered("~")` returns an empty selection because the selector string doesn't match anything.
|
||||
* `ParentsUntil("~")` returns all parents of the selection because the selector string didn't match any element to stop before the top element.
|
||||
|
||||
## Examples
|
||||
|
||||
See some tips and tricks in the [wiki][].
|
||||
|
||||
Adapted from example_test.go:
|
||||
|
||||
```Go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
)
|
||||
|
||||
func ExampleScrape() {
|
||||
// Request the HTML page.
|
||||
res, err := http.Get("http://metalsucks.net")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != 200 {
|
||||
log.Fatalf("status code error: %d %s", res.StatusCode, res.Status)
|
||||
}
|
||||
|
||||
// Load the HTML document
|
||||
doc, err := goquery.NewDocumentFromReader(res.Body)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Find the review items
|
||||
doc.Find(".sidebar-reviews article .content-block").Each(func(i int, s *goquery.Selection) {
|
||||
// For each item found, get the band and title
|
||||
band := s.Find("a").Text()
|
||||
title := s.Find("i").Text()
|
||||
fmt.Printf("Review %d: %s - %s\n", i, band, title)
|
||||
})
|
||||
}
|
||||
|
||||
func main() {
|
||||
ExampleScrape()
|
||||
}
|
||||
```
|
||||
|
||||
## Related Projects
|
||||
|
||||
- [Goq][goq], an HTML deserialization and scraping library based on goquery and struct tags.
|
||||
- [andybalholm/cascadia][cascadia], the CSS selector library used by goquery.
|
||||
- [suntong/cascadia][cascadiacli], a command-line interface to the cascadia CSS selector library, useful to test selectors.
|
||||
- [gocolly/colly](https://github.com/gocolly/colly), a lightning fast and elegant Scraping Framework
|
||||
- [gnulnx/goperf](https://github.com/gnulnx/goperf), a website performance test tool that also fetches static assets.
|
||||
- [MontFerret/ferret](https://github.com/MontFerret/ferret), declarative web scraping.
|
||||
- [tacusci/berrycms](https://github.com/tacusci/berrycms), a modern simple to use CMS with easy to write plugins
|
||||
- [Dataflow kit](https://github.com/slotix/dataflowkit), Web Scraping framework for Gophers.
|
||||
- [Geziyor](https://github.com/geziyor/geziyor), a fast web crawling & scraping framework for Go. Supports JS rendering.
|
||||
|
||||
## Support
|
||||
|
||||
There are a number of ways you can support the project:
|
||||
|
||||
* Use it, star it, build something with it, spread the word!
|
||||
- If you do build something open-source or otherwise publicly-visible, let me know so I can add it to the [Related Projects](#related-projects) section!
|
||||
* Raise issues to improve the project (note: doc typos and clarifications are issues too!)
|
||||
- Please search existing issues before opening a new one - it may have already been adressed.
|
||||
* Pull requests: please discuss new code in an issue first, unless the fix is really trivial.
|
||||
- Make sure new code is tested.
|
||||
- Be mindful of existing code - PRs that break existing code have a high probability of being declined, unless it fixes a serious issue.
|
||||
|
||||
If you desperately want to send money my way, I have a BuyMeACoffee.com page:
|
||||
|
||||
<a href="https://www.buymeacoffee.com/mna" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" ></a>
|
||||
|
||||
## License
|
||||
|
||||
The [BSD 3-Clause license][bsd], the same as the [Go language][golic]. Cascadia's license is [here][caslic].
|
||||
|
||||
[jquery]: http://jquery.com/
|
||||
[go]: http://golang.org/
|
||||
[cascadia]: https://github.com/andybalholm/cascadia
|
||||
[cascadiacli]: https://github.com/suntong/cascadia
|
||||
[bsd]: http://opensource.org/licenses/BSD-3-Clause
|
||||
[golic]: http://golang.org/LICENSE
|
||||
[caslic]: https://github.com/andybalholm/cascadia/blob/master/LICENSE
|
||||
[doc]: http://godoc.org/github.com/PuerkitoBio/goquery
|
||||
[index]: http://api.jquery.com/index/
|
||||
[gonet]: https://github.com/golang/net/
|
||||
[html]: http://godoc.org/golang.org/x/net/html
|
||||
[wiki]: https://github.com/PuerkitoBio/goquery/wiki/Tips-and-tricks
|
||||
[thatguystone]: https://github.com/thatguystone
|
||||
[piotr]: https://github.com/piotrkowalczuk
|
||||
[goq]: https://github.com/andrewstuart/goq
|
||||
124
vendor/github.com/PuerkitoBio/goquery/array.go
generated
vendored
Normal file
124
vendor/github.com/PuerkitoBio/goquery/array.go
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
package goquery
|
||||
|
||||
import (
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
const (
|
||||
maxUint = ^uint(0)
|
||||
maxInt = int(maxUint >> 1)
|
||||
|
||||
// ToEnd is a special index value that can be used as end index in a call
|
||||
// to Slice so that all elements are selected until the end of the Selection.
|
||||
// It is equivalent to passing (*Selection).Length().
|
||||
ToEnd = maxInt
|
||||
)
|
||||
|
||||
// First reduces the set of matched elements to the first in the set.
|
||||
// It returns a new Selection object, and an empty Selection object if the
|
||||
// the selection is empty.
|
||||
func (s *Selection) First() *Selection {
|
||||
return s.Eq(0)
|
||||
}
|
||||
|
||||
// Last reduces the set of matched elements to the last in the set.
|
||||
// It returns a new Selection object, and an empty Selection object if
|
||||
// the selection is empty.
|
||||
func (s *Selection) Last() *Selection {
|
||||
return s.Eq(-1)
|
||||
}
|
||||
|
||||
// Eq reduces the set of matched elements to the one at the specified index.
|
||||
// If a negative index is given, it counts backwards starting at the end of the
|
||||
// set. It returns a new Selection object, and an empty Selection object if the
|
||||
// index is invalid.
|
||||
func (s *Selection) Eq(index int) *Selection {
|
||||
if index < 0 {
|
||||
index += len(s.Nodes)
|
||||
}
|
||||
|
||||
if index >= len(s.Nodes) || index < 0 {
|
||||
return newEmptySelection(s.document)
|
||||
}
|
||||
|
||||
return s.Slice(index, index+1)
|
||||
}
|
||||
|
||||
// Slice reduces the set of matched elements to a subset specified by a range
|
||||
// of indices. The start index is 0-based and indicates the index of the first
|
||||
// element to select. The end index is 0-based and indicates the index at which
|
||||
// the elements stop being selected (the end index is not selected).
|
||||
//
|
||||
// The indices may be negative, in which case they represent an offset from the
|
||||
// end of the selection.
|
||||
//
|
||||
// The special value ToEnd may be specified as end index, in which case all elements
|
||||
// until the end are selected. This works both for a positive and negative start
|
||||
// index.
|
||||
func (s *Selection) Slice(start, end int) *Selection {
|
||||
if start < 0 {
|
||||
start += len(s.Nodes)
|
||||
}
|
||||
if end == ToEnd {
|
||||
end = len(s.Nodes)
|
||||
} else if end < 0 {
|
||||
end += len(s.Nodes)
|
||||
}
|
||||
return pushStack(s, s.Nodes[start:end])
|
||||
}
|
||||
|
||||
// Get retrieves the underlying node at the specified index.
|
||||
// Get without parameter is not implemented, since the node array is available
|
||||
// on the Selection object.
|
||||
func (s *Selection) Get(index int) *html.Node {
|
||||
if index < 0 {
|
||||
index += len(s.Nodes) // Negative index gets from the end
|
||||
}
|
||||
return s.Nodes[index]
|
||||
}
|
||||
|
||||
// Index returns the position of the first element within the Selection object
|
||||
// relative to its sibling elements.
|
||||
func (s *Selection) Index() int {
|
||||
if len(s.Nodes) > 0 {
|
||||
return newSingleSelection(s.Nodes[0], s.document).PrevAll().Length()
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// IndexSelector returns the position of the first element within the
|
||||
// Selection object relative to the elements matched by the selector, or -1 if
|
||||
// not found.
|
||||
func (s *Selection) IndexSelector(selector string) int {
|
||||
if len(s.Nodes) > 0 {
|
||||
sel := s.document.Find(selector)
|
||||
return indexInSlice(sel.Nodes, s.Nodes[0])
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// IndexMatcher returns the position of the first element within the
|
||||
// Selection object relative to the elements matched by the matcher, or -1 if
|
||||
// not found.
|
||||
func (s *Selection) IndexMatcher(m Matcher) int {
|
||||
if len(s.Nodes) > 0 {
|
||||
sel := s.document.FindMatcher(m)
|
||||
return indexInSlice(sel.Nodes, s.Nodes[0])
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// IndexOfNode returns the position of the specified node within the Selection
|
||||
// object, or -1 if not found.
|
||||
func (s *Selection) IndexOfNode(node *html.Node) int {
|
||||
return indexInSlice(s.Nodes, node)
|
||||
}
|
||||
|
||||
// IndexOfSelection returns the position of the first node in the specified
|
||||
// Selection object within this Selection object, or -1 if not found.
|
||||
func (s *Selection) IndexOfSelection(sel *Selection) int {
|
||||
if sel != nil && len(sel.Nodes) > 0 {
|
||||
return indexInSlice(s.Nodes, sel.Nodes[0])
|
||||
}
|
||||
return -1
|
||||
}
|
||||
123
vendor/github.com/PuerkitoBio/goquery/doc.go
generated
vendored
Normal file
123
vendor/github.com/PuerkitoBio/goquery/doc.go
generated
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
// Copyright (c) 2012-2016, Martin Angers & Contributors
|
||||
// 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 the author 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 HOLDER 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.
|
||||
|
||||
/*
|
||||
Package goquery implements features similar to jQuery, including the chainable
|
||||
syntax, to manipulate and query an HTML document.
|
||||
|
||||
It brings a syntax and a set of features similar to jQuery to the Go language.
|
||||
It is based on Go's net/html package and the CSS Selector library cascadia.
|
||||
Since the net/html parser returns nodes, and not a full-featured DOM
|
||||
tree, jQuery's stateful manipulation functions (like height(), css(), detach())
|
||||
have been left off.
|
||||
|
||||
Also, because the net/html parser requires UTF-8 encoding, so does goquery: it is
|
||||
the caller's responsibility to ensure that the source document provides UTF-8 encoded HTML.
|
||||
See the repository's wiki for various options on how to do this.
|
||||
|
||||
Syntax-wise, it is as close as possible to jQuery, with the same method names when
|
||||
possible, and that warm and fuzzy chainable interface. jQuery being the
|
||||
ultra-popular library that it is, writing a similar HTML-manipulating
|
||||
library was better to follow its API than to start anew (in the same spirit as
|
||||
Go's fmt package), even though some of its methods are less than intuitive (looking
|
||||
at you, index()...).
|
||||
|
||||
It is hosted on GitHub, along with additional documentation in the README.md
|
||||
file: https://github.com/puerkitobio/goquery
|
||||
|
||||
Please note that because of the net/html dependency, goquery requires Go1.1+.
|
||||
|
||||
The various methods are split into files based on the category of behavior.
|
||||
The three dots (...) indicate that various "overloads" are available.
|
||||
|
||||
* array.go : array-like positional manipulation of the selection.
|
||||
- Eq()
|
||||
- First()
|
||||
- Get()
|
||||
- Index...()
|
||||
- Last()
|
||||
- Slice()
|
||||
|
||||
* expand.go : methods that expand or augment the selection's set.
|
||||
- Add...()
|
||||
- AndSelf()
|
||||
- Union(), which is an alias for AddSelection()
|
||||
|
||||
* filter.go : filtering methods, that reduce the selection's set.
|
||||
- End()
|
||||
- Filter...()
|
||||
- Has...()
|
||||
- Intersection(), which is an alias of FilterSelection()
|
||||
- Not...()
|
||||
|
||||
* iteration.go : methods to loop over the selection's nodes.
|
||||
- Each()
|
||||
- EachWithBreak()
|
||||
- Map()
|
||||
|
||||
* manipulation.go : methods for modifying the document
|
||||
- After...()
|
||||
- Append...()
|
||||
- Before...()
|
||||
- Clone()
|
||||
- Empty()
|
||||
- Prepend...()
|
||||
- Remove...()
|
||||
- ReplaceWith...()
|
||||
- Unwrap()
|
||||
- Wrap...()
|
||||
- WrapAll...()
|
||||
- WrapInner...()
|
||||
|
||||
* property.go : methods that inspect and get the node's properties values.
|
||||
- Attr*(), RemoveAttr(), SetAttr()
|
||||
- AddClass(), HasClass(), RemoveClass(), ToggleClass()
|
||||
- Html()
|
||||
- Length()
|
||||
- Size(), which is an alias for Length()
|
||||
- Text()
|
||||
|
||||
* query.go : methods that query, or reflect, a node's identity.
|
||||
- Contains()
|
||||
- Is...()
|
||||
|
||||
* traversal.go : methods to traverse the HTML document tree.
|
||||
- Children...()
|
||||
- Contents()
|
||||
- Find...()
|
||||
- Next...()
|
||||
- Parent[s]...()
|
||||
- Prev...()
|
||||
- Siblings...()
|
||||
|
||||
* type.go : definition of the types exposed by goquery.
|
||||
- Document
|
||||
- Selection
|
||||
- Matcher
|
||||
|
||||
* utilities.go : definition of helper functions (and not methods on a *Selection)
|
||||
that are not part of jQuery, but are useful to goquery.
|
||||
- NodeName
|
||||
- OuterHtml
|
||||
*/
|
||||
package goquery
|
||||
70
vendor/github.com/PuerkitoBio/goquery/expand.go
generated
vendored
Normal file
70
vendor/github.com/PuerkitoBio/goquery/expand.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
package goquery
|
||||
|
||||
import "golang.org/x/net/html"
|
||||
|
||||
// Add adds the selector string's matching nodes to those in the current
|
||||
// selection and returns a new Selection object.
|
||||
// The selector string is run in the context of the document of the current
|
||||
// Selection object.
|
||||
func (s *Selection) Add(selector string) *Selection {
|
||||
return s.AddNodes(findWithMatcher([]*html.Node{s.document.rootNode}, compileMatcher(selector))...)
|
||||
}
|
||||
|
||||
// AddMatcher adds the matcher's matching nodes to those in the current
|
||||
// selection and returns a new Selection object.
|
||||
// The matcher is run in the context of the document of the current
|
||||
// Selection object.
|
||||
func (s *Selection) AddMatcher(m Matcher) *Selection {
|
||||
return s.AddNodes(findWithMatcher([]*html.Node{s.document.rootNode}, m)...)
|
||||
}
|
||||
|
||||
// AddSelection adds the specified Selection object's nodes to those in the
|
||||
// current selection and returns a new Selection object.
|
||||
func (s *Selection) AddSelection(sel *Selection) *Selection {
|
||||
if sel == nil {
|
||||
return s.AddNodes()
|
||||
}
|
||||
return s.AddNodes(sel.Nodes...)
|
||||
}
|
||||
|
||||
// Union is an alias for AddSelection.
|
||||
func (s *Selection) Union(sel *Selection) *Selection {
|
||||
return s.AddSelection(sel)
|
||||
}
|
||||
|
||||
// AddNodes adds the specified nodes to those in the
|
||||
// current selection and returns a new Selection object.
|
||||
func (s *Selection) AddNodes(nodes ...*html.Node) *Selection {
|
||||
return pushStack(s, appendWithoutDuplicates(s.Nodes, nodes, nil))
|
||||
}
|
||||
|
||||
// AndSelf adds the previous set of elements on the stack to the current set.
|
||||
// It returns a new Selection object containing the current Selection combined
|
||||
// with the previous one.
|
||||
// Deprecated: This function has been deprecated and is now an alias for AddBack().
|
||||
func (s *Selection) AndSelf() *Selection {
|
||||
return s.AddBack()
|
||||
}
|
||||
|
||||
// AddBack adds the previous set of elements on the stack to the current set.
|
||||
// It returns a new Selection object containing the current Selection combined
|
||||
// with the previous one.
|
||||
func (s *Selection) AddBack() *Selection {
|
||||
return s.AddSelection(s.prevSel)
|
||||
}
|
||||
|
||||
// AddBackFiltered reduces the previous set of elements on the stack to those that
|
||||
// match the selector string, and adds them to the current set.
|
||||
// It returns a new Selection object containing the current Selection combined
|
||||
// with the filtered previous one
|
||||
func (s *Selection) AddBackFiltered(selector string) *Selection {
|
||||
return s.AddSelection(s.prevSel.Filter(selector))
|
||||
}
|
||||
|
||||
// AddBackMatcher reduces the previous set of elements on the stack to those that match
|
||||
// the mateher, and adds them to the curernt set.
|
||||
// It returns a new Selection object containing the current Selection combined
|
||||
// with the filtered previous one
|
||||
func (s *Selection) AddBackMatcher(m Matcher) *Selection {
|
||||
return s.AddSelection(s.prevSel.FilterMatcher(m))
|
||||
}
|
||||
163
vendor/github.com/PuerkitoBio/goquery/filter.go
generated
vendored
Normal file
163
vendor/github.com/PuerkitoBio/goquery/filter.go
generated
vendored
Normal file
@@ -0,0 +1,163 @@
|
||||
package goquery
|
||||
|
||||
import "golang.org/x/net/html"
|
||||
|
||||
// Filter reduces the set of matched elements to those that match the selector string.
|
||||
// It returns a new Selection object for this subset of matching elements.
|
||||
func (s *Selection) Filter(selector string) *Selection {
|
||||
return s.FilterMatcher(compileMatcher(selector))
|
||||
}
|
||||
|
||||
// FilterMatcher reduces the set of matched elements to those that match
|
||||
// the given matcher. It returns a new Selection object for this subset
|
||||
// of matching elements.
|
||||
func (s *Selection) FilterMatcher(m Matcher) *Selection {
|
||||
return pushStack(s, winnow(s, m, true))
|
||||
}
|
||||
|
||||
// Not removes elements from the Selection that match the selector string.
|
||||
// It returns a new Selection object with the matching elements removed.
|
||||
func (s *Selection) Not(selector string) *Selection {
|
||||
return s.NotMatcher(compileMatcher(selector))
|
||||
}
|
||||
|
||||
// NotMatcher removes elements from the Selection that match the given matcher.
|
||||
// It returns a new Selection object with the matching elements removed.
|
||||
func (s *Selection) NotMatcher(m Matcher) *Selection {
|
||||
return pushStack(s, winnow(s, m, false))
|
||||
}
|
||||
|
||||
// FilterFunction reduces the set of matched elements to those that pass the function's test.
|
||||
// It returns a new Selection object for this subset of elements.
|
||||
func (s *Selection) FilterFunction(f func(int, *Selection) bool) *Selection {
|
||||
return pushStack(s, winnowFunction(s, f, true))
|
||||
}
|
||||
|
||||
// NotFunction removes elements from the Selection that pass the function's test.
|
||||
// It returns a new Selection object with the matching elements removed.
|
||||
func (s *Selection) NotFunction(f func(int, *Selection) bool) *Selection {
|
||||
return pushStack(s, winnowFunction(s, f, false))
|
||||
}
|
||||
|
||||
// FilterNodes reduces the set of matched elements to those that match the specified nodes.
|
||||
// It returns a new Selection object for this subset of elements.
|
||||
func (s *Selection) FilterNodes(nodes ...*html.Node) *Selection {
|
||||
return pushStack(s, winnowNodes(s, nodes, true))
|
||||
}
|
||||
|
||||
// NotNodes removes elements from the Selection that match the specified nodes.
|
||||
// It returns a new Selection object with the matching elements removed.
|
||||
func (s *Selection) NotNodes(nodes ...*html.Node) *Selection {
|
||||
return pushStack(s, winnowNodes(s, nodes, false))
|
||||
}
|
||||
|
||||
// FilterSelection reduces the set of matched elements to those that match a
|
||||
// node in the specified Selection object.
|
||||
// It returns a new Selection object for this subset of elements.
|
||||
func (s *Selection) FilterSelection(sel *Selection) *Selection {
|
||||
if sel == nil {
|
||||
return pushStack(s, winnowNodes(s, nil, true))
|
||||
}
|
||||
return pushStack(s, winnowNodes(s, sel.Nodes, true))
|
||||
}
|
||||
|
||||
// NotSelection removes elements from the Selection that match a node in the specified
|
||||
// Selection object. It returns a new Selection object with the matching elements removed.
|
||||
func (s *Selection) NotSelection(sel *Selection) *Selection {
|
||||
if sel == nil {
|
||||
return pushStack(s, winnowNodes(s, nil, false))
|
||||
}
|
||||
return pushStack(s, winnowNodes(s, sel.Nodes, false))
|
||||
}
|
||||
|
||||
// Intersection is an alias for FilterSelection.
|
||||
func (s *Selection) Intersection(sel *Selection) *Selection {
|
||||
return s.FilterSelection(sel)
|
||||
}
|
||||
|
||||
// Has reduces the set of matched elements to those that have a descendant
|
||||
// that matches the selector.
|
||||
// It returns a new Selection object with the matching elements.
|
||||
func (s *Selection) Has(selector string) *Selection {
|
||||
return s.HasSelection(s.document.Find(selector))
|
||||
}
|
||||
|
||||
// HasMatcher reduces the set of matched elements to those that have a descendant
|
||||
// that matches the matcher.
|
||||
// It returns a new Selection object with the matching elements.
|
||||
func (s *Selection) HasMatcher(m Matcher) *Selection {
|
||||
return s.HasSelection(s.document.FindMatcher(m))
|
||||
}
|
||||
|
||||
// HasNodes reduces the set of matched elements to those that have a
|
||||
// descendant that matches one of the nodes.
|
||||
// It returns a new Selection object with the matching elements.
|
||||
func (s *Selection) HasNodes(nodes ...*html.Node) *Selection {
|
||||
return s.FilterFunction(func(_ int, sel *Selection) bool {
|
||||
// Add all nodes that contain one of the specified nodes
|
||||
for _, n := range nodes {
|
||||
if sel.Contains(n) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
// HasSelection reduces the set of matched elements to those that have a
|
||||
// descendant that matches one of the nodes of the specified Selection object.
|
||||
// It returns a new Selection object with the matching elements.
|
||||
func (s *Selection) HasSelection(sel *Selection) *Selection {
|
||||
if sel == nil {
|
||||
return s.HasNodes()
|
||||
}
|
||||
return s.HasNodes(sel.Nodes...)
|
||||
}
|
||||
|
||||
// End ends the most recent filtering operation in the current chain and
|
||||
// returns the set of matched elements to its previous state.
|
||||
func (s *Selection) End() *Selection {
|
||||
if s.prevSel != nil {
|
||||
return s.prevSel
|
||||
}
|
||||
return newEmptySelection(s.document)
|
||||
}
|
||||
|
||||
// Filter based on the matcher, and the indicator to keep (Filter) or
|
||||
// to get rid of (Not) the matching elements.
|
||||
func winnow(sel *Selection, m Matcher, keep bool) []*html.Node {
|
||||
// Optimize if keep is requested
|
||||
if keep {
|
||||
return m.Filter(sel.Nodes)
|
||||
}
|
||||
// Use grep
|
||||
return grep(sel, func(i int, s *Selection) bool {
|
||||
return !m.Match(s.Get(0))
|
||||
})
|
||||
}
|
||||
|
||||
// Filter based on an array of nodes, and the indicator to keep (Filter) or
|
||||
// to get rid of (Not) the matching elements.
|
||||
func winnowNodes(sel *Selection, nodes []*html.Node, keep bool) []*html.Node {
|
||||
if len(nodes)+len(sel.Nodes) < minNodesForSet {
|
||||
return grep(sel, func(i int, s *Selection) bool {
|
||||
return isInSlice(nodes, s.Get(0)) == keep
|
||||
})
|
||||
}
|
||||
|
||||
set := make(map[*html.Node]bool)
|
||||
for _, n := range nodes {
|
||||
set[n] = true
|
||||
}
|
||||
return grep(sel, func(i int, s *Selection) bool {
|
||||
return set[s.Get(0)] == keep
|
||||
})
|
||||
}
|
||||
|
||||
// Filter based on a function test, and the indicator to keep (Filter) or
|
||||
// to get rid of (Not) the matching elements.
|
||||
func winnowFunction(sel *Selection, f func(int, *Selection) bool, keep bool) []*html.Node {
|
||||
return grep(sel, func(i int, s *Selection) bool {
|
||||
return f(i, s) == keep
|
||||
})
|
||||
}
|
||||
6
vendor/github.com/PuerkitoBio/goquery/go.mod
generated
vendored
Normal file
6
vendor/github.com/PuerkitoBio/goquery/go.mod
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
module github.com/PuerkitoBio/goquery
|
||||
|
||||
require (
|
||||
github.com/andybalholm/cascadia v1.0.0
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a
|
||||
)
|
||||
5
vendor/github.com/PuerkitoBio/goquery/go.sum
generated
vendored
Normal file
5
vendor/github.com/PuerkitoBio/goquery/go.sum
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o=
|
||||
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
39
vendor/github.com/PuerkitoBio/goquery/iteration.go
generated
vendored
Normal file
39
vendor/github.com/PuerkitoBio/goquery/iteration.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
package goquery
|
||||
|
||||
// Each iterates over a Selection object, executing a function for each
|
||||
// matched element. It returns the current Selection object. The function
|
||||
// f is called for each element in the selection with the index of the
|
||||
// element in that selection starting at 0, and a *Selection that contains
|
||||
// only that element.
|
||||
func (s *Selection) Each(f func(int, *Selection)) *Selection {
|
||||
for i, n := range s.Nodes {
|
||||
f(i, newSingleSelection(n, s.document))
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// EachWithBreak iterates over a Selection object, executing a function for each
|
||||
// matched element. It is identical to Each except that it is possible to break
|
||||
// out of the loop by returning false in the callback function. It returns the
|
||||
// current Selection object.
|
||||
func (s *Selection) EachWithBreak(f func(int, *Selection) bool) *Selection {
|
||||
for i, n := range s.Nodes {
|
||||
if !f(i, newSingleSelection(n, s.document)) {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Map passes each element in the current matched set through a function,
|
||||
// producing a slice of string holding the returned values. The function
|
||||
// f is called for each element in the selection with the index of the
|
||||
// element in that selection starting at 0, and a *Selection that contains
|
||||
// only that element.
|
||||
func (s *Selection) Map(f func(int, *Selection) string) (result []string) {
|
||||
for i, n := range s.Nodes {
|
||||
result = append(result, f(i, newSingleSelection(n, s.document)))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
574
vendor/github.com/PuerkitoBio/goquery/manipulation.go
generated
vendored
Normal file
574
vendor/github.com/PuerkitoBio/goquery/manipulation.go
generated
vendored
Normal file
@@ -0,0 +1,574 @@
|
||||
package goquery
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
// After applies the selector from the root document and inserts the matched elements
|
||||
// after the elements in the set of matched elements.
|
||||
//
|
||||
// If one of the matched elements in the selection is not currently in the
|
||||
// document, it's impossible to insert nodes after it, so it will be ignored.
|
||||
//
|
||||
// This follows the same rules as Selection.Append.
|
||||
func (s *Selection) After(selector string) *Selection {
|
||||
return s.AfterMatcher(compileMatcher(selector))
|
||||
}
|
||||
|
||||
// AfterMatcher applies the matcher from the root document and inserts the matched elements
|
||||
// after the elements in the set of matched elements.
|
||||
//
|
||||
// If one of the matched elements in the selection is not currently in the
|
||||
// document, it's impossible to insert nodes after it, so it will be ignored.
|
||||
//
|
||||
// This follows the same rules as Selection.Append.
|
||||
func (s *Selection) AfterMatcher(m Matcher) *Selection {
|
||||
return s.AfterNodes(m.MatchAll(s.document.rootNode)...)
|
||||
}
|
||||
|
||||
// AfterSelection inserts the elements in the selection after each element in the set of matched
|
||||
// elements.
|
||||
//
|
||||
// This follows the same rules as Selection.Append.
|
||||
func (s *Selection) AfterSelection(sel *Selection) *Selection {
|
||||
return s.AfterNodes(sel.Nodes...)
|
||||
}
|
||||
|
||||
// AfterHtml parses the html and inserts it after the set of matched elements.
|
||||
//
|
||||
// This follows the same rules as Selection.Append.
|
||||
func (s *Selection) AfterHtml(html string) *Selection {
|
||||
return s.AfterNodes(parseHtml(html)...)
|
||||
}
|
||||
|
||||
// AfterNodes inserts the nodes after each element in the set of matched elements.
|
||||
//
|
||||
// This follows the same rules as Selection.Append.
|
||||
func (s *Selection) AfterNodes(ns ...*html.Node) *Selection {
|
||||
return s.manipulateNodes(ns, true, func(sn *html.Node, n *html.Node) {
|
||||
if sn.Parent != nil {
|
||||
sn.Parent.InsertBefore(n, sn.NextSibling)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Append appends the elements specified by the selector to the end of each element
|
||||
// in the set of matched elements, following those rules:
|
||||
//
|
||||
// 1) The selector is applied to the root document.
|
||||
//
|
||||
// 2) Elements that are part of the document will be moved to the new location.
|
||||
//
|
||||
// 3) If there are multiple locations to append to, cloned nodes will be
|
||||
// appended to all target locations except the last one, which will be moved
|
||||
// as noted in (2).
|
||||
func (s *Selection) Append(selector string) *Selection {
|
||||
return s.AppendMatcher(compileMatcher(selector))
|
||||
}
|
||||
|
||||
// AppendMatcher appends the elements specified by the matcher to the end of each element
|
||||
// in the set of matched elements.
|
||||
//
|
||||
// This follows the same rules as Selection.Append.
|
||||
func (s *Selection) AppendMatcher(m Matcher) *Selection {
|
||||
return s.AppendNodes(m.MatchAll(s.document.rootNode)...)
|
||||
}
|
||||
|
||||
// AppendSelection appends the elements in the selection to the end of each element
|
||||
// in the set of matched elements.
|
||||
//
|
||||
// This follows the same rules as Selection.Append.
|
||||
func (s *Selection) AppendSelection(sel *Selection) *Selection {
|
||||
return s.AppendNodes(sel.Nodes...)
|
||||
}
|
||||
|
||||
// AppendHtml parses the html and appends it to the set of matched elements.
|
||||
func (s *Selection) AppendHtml(html string) *Selection {
|
||||
return s.AppendNodes(parseHtml(html)...)
|
||||
}
|
||||
|
||||
// AppendNodes appends the specified nodes to each node in the set of matched elements.
|
||||
//
|
||||
// This follows the same rules as Selection.Append.
|
||||
func (s *Selection) AppendNodes(ns ...*html.Node) *Selection {
|
||||
return s.manipulateNodes(ns, false, func(sn *html.Node, n *html.Node) {
|
||||
sn.AppendChild(n)
|
||||
})
|
||||
}
|
||||
|
||||
// Before inserts the matched elements before each element in the set of matched elements.
|
||||
//
|
||||
// This follows the same rules as Selection.Append.
|
||||
func (s *Selection) Before(selector string) *Selection {
|
||||
return s.BeforeMatcher(compileMatcher(selector))
|
||||
}
|
||||
|
||||
// BeforeMatcher inserts the matched elements before each element in the set of matched elements.
|
||||
//
|
||||
// This follows the same rules as Selection.Append.
|
||||
func (s *Selection) BeforeMatcher(m Matcher) *Selection {
|
||||
return s.BeforeNodes(m.MatchAll(s.document.rootNode)...)
|
||||
}
|
||||
|
||||
// BeforeSelection inserts the elements in the selection before each element in the set of matched
|
||||
// elements.
|
||||
//
|
||||
// This follows the same rules as Selection.Append.
|
||||
func (s *Selection) BeforeSelection(sel *Selection) *Selection {
|
||||
return s.BeforeNodes(sel.Nodes...)
|
||||
}
|
||||
|
||||
// BeforeHtml parses the html and inserts it before the set of matched elements.
|
||||
//
|
||||
// This follows the same rules as Selection.Append.
|
||||
func (s *Selection) BeforeHtml(html string) *Selection {
|
||||
return s.BeforeNodes(parseHtml(html)...)
|
||||
}
|
||||
|
||||
// BeforeNodes inserts the nodes before each element in the set of matched elements.
|
||||
//
|
||||
// This follows the same rules as Selection.Append.
|
||||
func (s *Selection) BeforeNodes(ns ...*html.Node) *Selection {
|
||||
return s.manipulateNodes(ns, false, func(sn *html.Node, n *html.Node) {
|
||||
if sn.Parent != nil {
|
||||
sn.Parent.InsertBefore(n, sn)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Clone creates a deep copy of the set of matched nodes. The new nodes will not be
|
||||
// attached to the document.
|
||||
func (s *Selection) Clone() *Selection {
|
||||
ns := newEmptySelection(s.document)
|
||||
ns.Nodes = cloneNodes(s.Nodes)
|
||||
return ns
|
||||
}
|
||||
|
||||
// Empty removes all children nodes from the set of matched elements.
|
||||
// It returns the children nodes in a new Selection.
|
||||
func (s *Selection) Empty() *Selection {
|
||||
var nodes []*html.Node
|
||||
|
||||
for _, n := range s.Nodes {
|
||||
for c := n.FirstChild; c != nil; c = n.FirstChild {
|
||||
n.RemoveChild(c)
|
||||
nodes = append(nodes, c)
|
||||
}
|
||||
}
|
||||
|
||||
return pushStack(s, nodes)
|
||||
}
|
||||
|
||||
// Prepend prepends the elements specified by the selector to each element in
|
||||
// the set of matched elements, following the same rules as Append.
|
||||
func (s *Selection) Prepend(selector string) *Selection {
|
||||
return s.PrependMatcher(compileMatcher(selector))
|
||||
}
|
||||
|
||||
// PrependMatcher prepends the elements specified by the matcher to each
|
||||
// element in the set of matched elements.
|
||||
//
|
||||
// This follows the same rules as Selection.Append.
|
||||
func (s *Selection) PrependMatcher(m Matcher) *Selection {
|
||||
return s.PrependNodes(m.MatchAll(s.document.rootNode)...)
|
||||
}
|
||||
|
||||
// PrependSelection prepends the elements in the selection to each element in
|
||||
// the set of matched elements.
|
||||
//
|
||||
// This follows the same rules as Selection.Append.
|
||||
func (s *Selection) PrependSelection(sel *Selection) *Selection {
|
||||
return s.PrependNodes(sel.Nodes...)
|
||||
}
|
||||
|
||||
// PrependHtml parses the html and prepends it to the set of matched elements.
|
||||
func (s *Selection) PrependHtml(html string) *Selection {
|
||||
return s.PrependNodes(parseHtml(html)...)
|
||||
}
|
||||
|
||||
// PrependNodes prepends the specified nodes to each node in the set of
|
||||
// matched elements.
|
||||
//
|
||||
// This follows the same rules as Selection.Append.
|
||||
func (s *Selection) PrependNodes(ns ...*html.Node) *Selection {
|
||||
return s.manipulateNodes(ns, true, func(sn *html.Node, n *html.Node) {
|
||||
// sn.FirstChild may be nil, in which case this functions like
|
||||
// sn.AppendChild()
|
||||
sn.InsertBefore(n, sn.FirstChild)
|
||||
})
|
||||
}
|
||||
|
||||
// Remove removes the set of matched elements from the document.
|
||||
// It returns the same selection, now consisting of nodes not in the document.
|
||||
func (s *Selection) Remove() *Selection {
|
||||
for _, n := range s.Nodes {
|
||||
if n.Parent != nil {
|
||||
n.Parent.RemoveChild(n)
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// RemoveFiltered removes the set of matched elements by selector.
|
||||
// It returns the Selection of removed nodes.
|
||||
func (s *Selection) RemoveFiltered(selector string) *Selection {
|
||||
return s.RemoveMatcher(compileMatcher(selector))
|
||||
}
|
||||
|
||||
// RemoveMatcher removes the set of matched elements.
|
||||
// It returns the Selection of removed nodes.
|
||||
func (s *Selection) RemoveMatcher(m Matcher) *Selection {
|
||||
return s.FilterMatcher(m).Remove()
|
||||
}
|
||||
|
||||
// ReplaceWith replaces each element in the set of matched elements with the
|
||||
// nodes matched by the given selector.
|
||||
// It returns the removed elements.
|
||||
//
|
||||
// This follows the same rules as Selection.Append.
|
||||
func (s *Selection) ReplaceWith(selector string) *Selection {
|
||||
return s.ReplaceWithMatcher(compileMatcher(selector))
|
||||
}
|
||||
|
||||
// ReplaceWithMatcher replaces each element in the set of matched elements with
|
||||
// the nodes matched by the given Matcher.
|
||||
// It returns the removed elements.
|
||||
//
|
||||
// This follows the same rules as Selection.Append.
|
||||
func (s *Selection) ReplaceWithMatcher(m Matcher) *Selection {
|
||||
return s.ReplaceWithNodes(m.MatchAll(s.document.rootNode)...)
|
||||
}
|
||||
|
||||
// ReplaceWithSelection replaces each element in the set of matched elements with
|
||||
// the nodes from the given Selection.
|
||||
// It returns the removed elements.
|
||||
//
|
||||
// This follows the same rules as Selection.Append.
|
||||
func (s *Selection) ReplaceWithSelection(sel *Selection) *Selection {
|
||||
return s.ReplaceWithNodes(sel.Nodes...)
|
||||
}
|
||||
|
||||
// ReplaceWithHtml replaces each element in the set of matched elements with
|
||||
// the parsed HTML.
|
||||
// It returns the removed elements.
|
||||
//
|
||||
// This follows the same rules as Selection.Append.
|
||||
func (s *Selection) ReplaceWithHtml(html string) *Selection {
|
||||
return s.ReplaceWithNodes(parseHtml(html)...)
|
||||
}
|
||||
|
||||
// ReplaceWithNodes replaces each element in the set of matched elements with
|
||||
// the given nodes.
|
||||
// It returns the removed elements.
|
||||
//
|
||||
// This follows the same rules as Selection.Append.
|
||||
func (s *Selection) ReplaceWithNodes(ns ...*html.Node) *Selection {
|
||||
s.AfterNodes(ns...)
|
||||
return s.Remove()
|
||||
}
|
||||
|
||||
// SetHtml sets the html content of each element in the selection to
|
||||
// specified html string.
|
||||
func (s *Selection) SetHtml(html string) *Selection {
|
||||
return setHtmlNodes(s, parseHtml(html)...)
|
||||
}
|
||||
|
||||
// SetText sets the content of each element in the selection to specified content.
|
||||
// The provided text string is escaped.
|
||||
func (s *Selection) SetText(text string) *Selection {
|
||||
return s.SetHtml(html.EscapeString(text))
|
||||
}
|
||||
|
||||
// Unwrap removes the parents of the set of matched elements, leaving the matched
|
||||
// elements (and their siblings, if any) in their place.
|
||||
// It returns the original selection.
|
||||
func (s *Selection) Unwrap() *Selection {
|
||||
s.Parent().Each(func(i int, ss *Selection) {
|
||||
// For some reason, jquery allows unwrap to remove the <head> element, so
|
||||
// allowing it here too. Same for <html>. Why it allows those elements to
|
||||
// be unwrapped while not allowing body is a mystery to me.
|
||||
if ss.Nodes[0].Data != "body" {
|
||||
ss.ReplaceWithSelection(ss.Contents())
|
||||
}
|
||||
})
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// Wrap wraps each element in the set of matched elements inside the first
|
||||
// element matched by the given selector. The matched child is cloned before
|
||||
// being inserted into the document.
|
||||
//
|
||||
// It returns the original set of elements.
|
||||
func (s *Selection) Wrap(selector string) *Selection {
|
||||
return s.WrapMatcher(compileMatcher(selector))
|
||||
}
|
||||
|
||||
// WrapMatcher wraps each element in the set of matched elements inside the
|
||||
// first element matched by the given matcher. The matched child is cloned
|
||||
// before being inserted into the document.
|
||||
//
|
||||
// It returns the original set of elements.
|
||||
func (s *Selection) WrapMatcher(m Matcher) *Selection {
|
||||
return s.wrapNodes(m.MatchAll(s.document.rootNode)...)
|
||||
}
|
||||
|
||||
// WrapSelection wraps each element in the set of matched elements inside the
|
||||
// first element in the given Selection. The element is cloned before being
|
||||
// inserted into the document.
|
||||
//
|
||||
// It returns the original set of elements.
|
||||
func (s *Selection) WrapSelection(sel *Selection) *Selection {
|
||||
return s.wrapNodes(sel.Nodes...)
|
||||
}
|
||||
|
||||
// WrapHtml wraps each element in the set of matched elements inside the inner-
|
||||
// most child of the given HTML.
|
||||
//
|
||||
// It returns the original set of elements.
|
||||
func (s *Selection) WrapHtml(html string) *Selection {
|
||||
return s.wrapNodes(parseHtml(html)...)
|
||||
}
|
||||
|
||||
// WrapNode wraps each element in the set of matched elements inside the inner-
|
||||
// most child of the given node. The given node is copied before being inserted
|
||||
// into the document.
|
||||
//
|
||||
// It returns the original set of elements.
|
||||
func (s *Selection) WrapNode(n *html.Node) *Selection {
|
||||
return s.wrapNodes(n)
|
||||
}
|
||||
|
||||
func (s *Selection) wrapNodes(ns ...*html.Node) *Selection {
|
||||
s.Each(func(i int, ss *Selection) {
|
||||
ss.wrapAllNodes(ns...)
|
||||
})
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// WrapAll wraps a single HTML structure, matched by the given selector, around
|
||||
// all elements in the set of matched elements. The matched child is cloned
|
||||
// before being inserted into the document.
|
||||
//
|
||||
// It returns the original set of elements.
|
||||
func (s *Selection) WrapAll(selector string) *Selection {
|
||||
return s.WrapAllMatcher(compileMatcher(selector))
|
||||
}
|
||||
|
||||
// WrapAllMatcher wraps a single HTML structure, matched by the given Matcher,
|
||||
// around all elements in the set of matched elements. The matched child is
|
||||
// cloned before being inserted into the document.
|
||||
//
|
||||
// It returns the original set of elements.
|
||||
func (s *Selection) WrapAllMatcher(m Matcher) *Selection {
|
||||
return s.wrapAllNodes(m.MatchAll(s.document.rootNode)...)
|
||||
}
|
||||
|
||||
// WrapAllSelection wraps a single HTML structure, the first node of the given
|
||||
// Selection, around all elements in the set of matched elements. The matched
|
||||
// child is cloned before being inserted into the document.
|
||||
//
|
||||
// It returns the original set of elements.
|
||||
func (s *Selection) WrapAllSelection(sel *Selection) *Selection {
|
||||
return s.wrapAllNodes(sel.Nodes...)
|
||||
}
|
||||
|
||||
// WrapAllHtml wraps the given HTML structure around all elements in the set of
|
||||
// matched elements. The matched child is cloned before being inserted into the
|
||||
// document.
|
||||
//
|
||||
// It returns the original set of elements.
|
||||
func (s *Selection) WrapAllHtml(html string) *Selection {
|
||||
return s.wrapAllNodes(parseHtml(html)...)
|
||||
}
|
||||
|
||||
func (s *Selection) wrapAllNodes(ns ...*html.Node) *Selection {
|
||||
if len(ns) > 0 {
|
||||
return s.WrapAllNode(ns[0])
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// WrapAllNode wraps the given node around the first element in the Selection,
|
||||
// making all other nodes in the Selection children of the given node. The node
|
||||
// is cloned before being inserted into the document.
|
||||
//
|
||||
// It returns the original set of elements.
|
||||
func (s *Selection) WrapAllNode(n *html.Node) *Selection {
|
||||
if s.Size() == 0 {
|
||||
return s
|
||||
}
|
||||
|
||||
wrap := cloneNode(n)
|
||||
|
||||
first := s.Nodes[0]
|
||||
if first.Parent != nil {
|
||||
first.Parent.InsertBefore(wrap, first)
|
||||
first.Parent.RemoveChild(first)
|
||||
}
|
||||
|
||||
for c := getFirstChildEl(wrap); c != nil; c = getFirstChildEl(wrap) {
|
||||
wrap = c
|
||||
}
|
||||
|
||||
newSingleSelection(wrap, s.document).AppendSelection(s)
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// WrapInner wraps an HTML structure, matched by the given selector, around the
|
||||
// content of element in the set of matched elements. The matched child is
|
||||
// cloned before being inserted into the document.
|
||||
//
|
||||
// It returns the original set of elements.
|
||||
func (s *Selection) WrapInner(selector string) *Selection {
|
||||
return s.WrapInnerMatcher(compileMatcher(selector))
|
||||
}
|
||||
|
||||
// WrapInnerMatcher wraps an HTML structure, matched by the given selector,
|
||||
// around the content of element in the set of matched elements. The matched
|
||||
// child is cloned before being inserted into the document.
|
||||
//
|
||||
// It returns the original set of elements.
|
||||
func (s *Selection) WrapInnerMatcher(m Matcher) *Selection {
|
||||
return s.wrapInnerNodes(m.MatchAll(s.document.rootNode)...)
|
||||
}
|
||||
|
||||
// WrapInnerSelection wraps an HTML structure, matched by the given selector,
|
||||
// around the content of element in the set of matched elements. The matched
|
||||
// child is cloned before being inserted into the document.
|
||||
//
|
||||
// It returns the original set of elements.
|
||||
func (s *Selection) WrapInnerSelection(sel *Selection) *Selection {
|
||||
return s.wrapInnerNodes(sel.Nodes...)
|
||||
}
|
||||
|
||||
// WrapInnerHtml wraps an HTML structure, matched by the given selector, around
|
||||
// the content of element in the set of matched elements. The matched child is
|
||||
// cloned before being inserted into the document.
|
||||
//
|
||||
// It returns the original set of elements.
|
||||
func (s *Selection) WrapInnerHtml(html string) *Selection {
|
||||
return s.wrapInnerNodes(parseHtml(html)...)
|
||||
}
|
||||
|
||||
// WrapInnerNode wraps an HTML structure, matched by the given selector, around
|
||||
// the content of element in the set of matched elements. The matched child is
|
||||
// cloned before being inserted into the document.
|
||||
//
|
||||
// It returns the original set of elements.
|
||||
func (s *Selection) WrapInnerNode(n *html.Node) *Selection {
|
||||
return s.wrapInnerNodes(n)
|
||||
}
|
||||
|
||||
func (s *Selection) wrapInnerNodes(ns ...*html.Node) *Selection {
|
||||
if len(ns) == 0 {
|
||||
return s
|
||||
}
|
||||
|
||||
s.Each(func(i int, s *Selection) {
|
||||
contents := s.Contents()
|
||||
|
||||
if contents.Size() > 0 {
|
||||
contents.wrapAllNodes(ns...)
|
||||
} else {
|
||||
s.AppendNodes(cloneNode(ns[0]))
|
||||
}
|
||||
})
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func parseHtml(h string) []*html.Node {
|
||||
// Errors are only returned when the io.Reader returns any error besides
|
||||
// EOF, but strings.Reader never will
|
||||
nodes, err := html.ParseFragment(strings.NewReader(h), &html.Node{Type: html.ElementNode})
|
||||
if err != nil {
|
||||
panic("goquery: failed to parse HTML: " + err.Error())
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
func setHtmlNodes(s *Selection, ns ...*html.Node) *Selection {
|
||||
for _, n := range s.Nodes {
|
||||
for c := n.FirstChild; c != nil; c = n.FirstChild {
|
||||
n.RemoveChild(c)
|
||||
}
|
||||
for _, c := range ns {
|
||||
n.AppendChild(cloneNode(c))
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Get the first child that is an ElementNode
|
||||
func getFirstChildEl(n *html.Node) *html.Node {
|
||||
c := n.FirstChild
|
||||
for c != nil && c.Type != html.ElementNode {
|
||||
c = c.NextSibling
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// Deep copy a slice of nodes.
|
||||
func cloneNodes(ns []*html.Node) []*html.Node {
|
||||
cns := make([]*html.Node, 0, len(ns))
|
||||
|
||||
for _, n := range ns {
|
||||
cns = append(cns, cloneNode(n))
|
||||
}
|
||||
|
||||
return cns
|
||||
}
|
||||
|
||||
// Deep copy a node. The new node has clones of all the original node's
|
||||
// children but none of its parents or siblings.
|
||||
func cloneNode(n *html.Node) *html.Node {
|
||||
nn := &html.Node{
|
||||
Type: n.Type,
|
||||
DataAtom: n.DataAtom,
|
||||
Data: n.Data,
|
||||
Attr: make([]html.Attribute, len(n.Attr)),
|
||||
}
|
||||
|
||||
copy(nn.Attr, n.Attr)
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
nn.AppendChild(cloneNode(c))
|
||||
}
|
||||
|
||||
return nn
|
||||
}
|
||||
|
||||
func (s *Selection) manipulateNodes(ns []*html.Node, reverse bool,
|
||||
f func(sn *html.Node, n *html.Node)) *Selection {
|
||||
|
||||
lasti := s.Size() - 1
|
||||
|
||||
// net.Html doesn't provide document fragments for insertion, so to get
|
||||
// things in the correct order with After() and Prepend(), the callback
|
||||
// needs to be called on the reverse of the nodes.
|
||||
if reverse {
|
||||
for i, j := 0, len(ns)-1; i < j; i, j = i+1, j-1 {
|
||||
ns[i], ns[j] = ns[j], ns[i]
|
||||
}
|
||||
}
|
||||
|
||||
for i, sn := range s.Nodes {
|
||||
for _, n := range ns {
|
||||
if i != lasti {
|
||||
f(sn, cloneNode(n))
|
||||
} else {
|
||||
if n.Parent != nil {
|
||||
n.Parent.RemoveChild(n)
|
||||
}
|
||||
f(sn, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
275
vendor/github.com/PuerkitoBio/goquery/property.go
generated
vendored
Normal file
275
vendor/github.com/PuerkitoBio/goquery/property.go
generated
vendored
Normal file
@@ -0,0 +1,275 @@
|
||||
package goquery
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
var rxClassTrim = regexp.MustCompile("[\t\r\n]")
|
||||
|
||||
// Attr gets the specified attribute's value for the first element in the
|
||||
// Selection. To get the value for each element individually, use a looping
|
||||
// construct such as Each or Map method.
|
||||
func (s *Selection) Attr(attrName string) (val string, exists bool) {
|
||||
if len(s.Nodes) == 0 {
|
||||
return
|
||||
}
|
||||
return getAttributeValue(attrName, s.Nodes[0])
|
||||
}
|
||||
|
||||
// AttrOr works like Attr but returns default value if attribute is not present.
|
||||
func (s *Selection) AttrOr(attrName, defaultValue string) string {
|
||||
if len(s.Nodes) == 0 {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
val, exists := getAttributeValue(attrName, s.Nodes[0])
|
||||
if !exists {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
// RemoveAttr removes the named attribute from each element in the set of matched elements.
|
||||
func (s *Selection) RemoveAttr(attrName string) *Selection {
|
||||
for _, n := range s.Nodes {
|
||||
removeAttr(n, attrName)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// SetAttr sets the given attribute on each element in the set of matched elements.
|
||||
func (s *Selection) SetAttr(attrName, val string) *Selection {
|
||||
for _, n := range s.Nodes {
|
||||
attr := getAttributePtr(attrName, n)
|
||||
if attr == nil {
|
||||
n.Attr = append(n.Attr, html.Attribute{Key: attrName, Val: val})
|
||||
} else {
|
||||
attr.Val = val
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// Text gets the combined text contents of each element in the set of matched
|
||||
// elements, including their descendants.
|
||||
func (s *Selection) Text() string {
|
||||
var buf bytes.Buffer
|
||||
|
||||
// Slightly optimized vs calling Each: no single selection object created
|
||||
var f func(*html.Node)
|
||||
f = func(n *html.Node) {
|
||||
if n.Type == html.TextNode {
|
||||
// Keep newlines and spaces, like jQuery
|
||||
buf.WriteString(n.Data)
|
||||
}
|
||||
if n.FirstChild != nil {
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
f(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, n := range s.Nodes {
|
||||
f(n)
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Size is an alias for Length.
|
||||
func (s *Selection) Size() int {
|
||||
return s.Length()
|
||||
}
|
||||
|
||||
// Length returns the number of elements in the Selection object.
|
||||
func (s *Selection) Length() int {
|
||||
return len(s.Nodes)
|
||||
}
|
||||
|
||||
// Html gets the HTML contents of the first element in the set of matched
|
||||
// elements. It includes text and comment nodes.
|
||||
func (s *Selection) Html() (ret string, e error) {
|
||||
// Since there is no .innerHtml, the HTML content must be re-created from
|
||||
// the nodes using html.Render.
|
||||
var buf bytes.Buffer
|
||||
|
||||
if len(s.Nodes) > 0 {
|
||||
for c := s.Nodes[0].FirstChild; c != nil; c = c.NextSibling {
|
||||
e = html.Render(&buf, c)
|
||||
if e != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
ret = buf.String()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// AddClass adds the given class(es) to each element in the set of matched elements.
|
||||
// Multiple class names can be specified, separated by a space or via multiple arguments.
|
||||
func (s *Selection) AddClass(class ...string) *Selection {
|
||||
classStr := strings.TrimSpace(strings.Join(class, " "))
|
||||
|
||||
if classStr == "" {
|
||||
return s
|
||||
}
|
||||
|
||||
tcls := getClassesSlice(classStr)
|
||||
for _, n := range s.Nodes {
|
||||
curClasses, attr := getClassesAndAttr(n, true)
|
||||
for _, newClass := range tcls {
|
||||
if !strings.Contains(curClasses, " "+newClass+" ") {
|
||||
curClasses += newClass + " "
|
||||
}
|
||||
}
|
||||
|
||||
setClasses(n, attr, curClasses)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// HasClass determines whether any of the matched elements are assigned the
|
||||
// given class.
|
||||
func (s *Selection) HasClass(class string) bool {
|
||||
class = " " + class + " "
|
||||
for _, n := range s.Nodes {
|
||||
classes, _ := getClassesAndAttr(n, false)
|
||||
if strings.Contains(classes, class) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RemoveClass removes the given class(es) from each element in the set of matched elements.
|
||||
// Multiple class names can be specified, separated by a space or via multiple arguments.
|
||||
// If no class name is provided, all classes are removed.
|
||||
func (s *Selection) RemoveClass(class ...string) *Selection {
|
||||
var rclasses []string
|
||||
|
||||
classStr := strings.TrimSpace(strings.Join(class, " "))
|
||||
remove := classStr == ""
|
||||
|
||||
if !remove {
|
||||
rclasses = getClassesSlice(classStr)
|
||||
}
|
||||
|
||||
for _, n := range s.Nodes {
|
||||
if remove {
|
||||
removeAttr(n, "class")
|
||||
} else {
|
||||
classes, attr := getClassesAndAttr(n, true)
|
||||
for _, rcl := range rclasses {
|
||||
classes = strings.Replace(classes, " "+rcl+" ", " ", -1)
|
||||
}
|
||||
|
||||
setClasses(n, attr, classes)
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// ToggleClass adds or removes the given class(es) for each element in the set of matched elements.
|
||||
// Multiple class names can be specified, separated by a space or via multiple arguments.
|
||||
func (s *Selection) ToggleClass(class ...string) *Selection {
|
||||
classStr := strings.TrimSpace(strings.Join(class, " "))
|
||||
|
||||
if classStr == "" {
|
||||
return s
|
||||
}
|
||||
|
||||
tcls := getClassesSlice(classStr)
|
||||
|
||||
for _, n := range s.Nodes {
|
||||
classes, attr := getClassesAndAttr(n, true)
|
||||
for _, tcl := range tcls {
|
||||
if strings.Contains(classes, " "+tcl+" ") {
|
||||
classes = strings.Replace(classes, " "+tcl+" ", " ", -1)
|
||||
} else {
|
||||
classes += tcl + " "
|
||||
}
|
||||
}
|
||||
|
||||
setClasses(n, attr, classes)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func getAttributePtr(attrName string, n *html.Node) *html.Attribute {
|
||||
if n == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i, a := range n.Attr {
|
||||
if a.Key == attrName {
|
||||
return &n.Attr[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Private function to get the specified attribute's value from a node.
|
||||
func getAttributeValue(attrName string, n *html.Node) (val string, exists bool) {
|
||||
if a := getAttributePtr(attrName, n); a != nil {
|
||||
val = a.Val
|
||||
exists = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get and normalize the "class" attribute from the node.
|
||||
func getClassesAndAttr(n *html.Node, create bool) (classes string, attr *html.Attribute) {
|
||||
// Applies only to element nodes
|
||||
if n.Type == html.ElementNode {
|
||||
attr = getAttributePtr("class", n)
|
||||
if attr == nil && create {
|
||||
n.Attr = append(n.Attr, html.Attribute{
|
||||
Key: "class",
|
||||
Val: "",
|
||||
})
|
||||
attr = &n.Attr[len(n.Attr)-1]
|
||||
}
|
||||
}
|
||||
|
||||
if attr == nil {
|
||||
classes = " "
|
||||
} else {
|
||||
classes = rxClassTrim.ReplaceAllString(" "+attr.Val+" ", " ")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getClassesSlice(classes string) []string {
|
||||
return strings.Split(rxClassTrim.ReplaceAllString(" "+classes+" ", " "), " ")
|
||||
}
|
||||
|
||||
func removeAttr(n *html.Node, attrName string) {
|
||||
for i, a := range n.Attr {
|
||||
if a.Key == attrName {
|
||||
n.Attr[i], n.Attr[len(n.Attr)-1], n.Attr =
|
||||
n.Attr[len(n.Attr)-1], html.Attribute{}, n.Attr[:len(n.Attr)-1]
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setClasses(n *html.Node, attr *html.Attribute, classes string) {
|
||||
classes = strings.TrimSpace(classes)
|
||||
if classes == "" {
|
||||
removeAttr(n, "class")
|
||||
return
|
||||
}
|
||||
|
||||
attr.Val = classes
|
||||
}
|
||||
49
vendor/github.com/PuerkitoBio/goquery/query.go
generated
vendored
Normal file
49
vendor/github.com/PuerkitoBio/goquery/query.go
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
package goquery
|
||||
|
||||
import "golang.org/x/net/html"
|
||||
|
||||
// Is checks the current matched set of elements against a selector and
|
||||
// returns true if at least one of these elements matches.
|
||||
func (s *Selection) Is(selector string) bool {
|
||||
return s.IsMatcher(compileMatcher(selector))
|
||||
}
|
||||
|
||||
// IsMatcher checks the current matched set of elements against a matcher and
|
||||
// returns true if at least one of these elements matches.
|
||||
func (s *Selection) IsMatcher(m Matcher) bool {
|
||||
if len(s.Nodes) > 0 {
|
||||
if len(s.Nodes) == 1 {
|
||||
return m.Match(s.Nodes[0])
|
||||
}
|
||||
return len(m.Filter(s.Nodes)) > 0
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsFunction checks the current matched set of elements against a predicate and
|
||||
// returns true if at least one of these elements matches.
|
||||
func (s *Selection) IsFunction(f func(int, *Selection) bool) bool {
|
||||
return s.FilterFunction(f).Length() > 0
|
||||
}
|
||||
|
||||
// IsSelection checks the current matched set of elements against a Selection object
|
||||
// and returns true if at least one of these elements matches.
|
||||
func (s *Selection) IsSelection(sel *Selection) bool {
|
||||
return s.FilterSelection(sel).Length() > 0
|
||||
}
|
||||
|
||||
// IsNodes checks the current matched set of elements against the specified nodes
|
||||
// and returns true if at least one of these elements matches.
|
||||
func (s *Selection) IsNodes(nodes ...*html.Node) bool {
|
||||
return s.FilterNodes(nodes...).Length() > 0
|
||||
}
|
||||
|
||||
// Contains returns true if the specified Node is within,
|
||||
// at any depth, one of the nodes in the Selection object.
|
||||
// It is NOT inclusive, to behave like jQuery's implementation, and
|
||||
// unlike Javascript's .contains, so if the contained
|
||||
// node is itself in the selection, it returns false.
|
||||
func (s *Selection) Contains(n *html.Node) bool {
|
||||
return sliceContains(s.Nodes, n)
|
||||
}
|
||||
698
vendor/github.com/PuerkitoBio/goquery/traversal.go
generated
vendored
Normal file
698
vendor/github.com/PuerkitoBio/goquery/traversal.go
generated
vendored
Normal file
@@ -0,0 +1,698 @@
|
||||
package goquery
|
||||
|
||||
import "golang.org/x/net/html"
|
||||
|
||||
type siblingType int
|
||||
|
||||
// Sibling type, used internally when iterating over children at the same
|
||||
// level (siblings) to specify which nodes are requested.
|
||||
const (
|
||||
siblingPrevUntil siblingType = iota - 3
|
||||
siblingPrevAll
|
||||
siblingPrev
|
||||
siblingAll
|
||||
siblingNext
|
||||
siblingNextAll
|
||||
siblingNextUntil
|
||||
siblingAllIncludingNonElements
|
||||
)
|
||||
|
||||
// Find gets the descendants of each element in the current set of matched
|
||||
// elements, filtered by a selector. It returns a new Selection object
|
||||
// containing these matched elements.
|
||||
func (s *Selection) Find(selector string) *Selection {
|
||||
return pushStack(s, findWithMatcher(s.Nodes, compileMatcher(selector)))
|
||||
}
|
||||
|
||||
// FindMatcher gets the descendants of each element in the current set of matched
|
||||
// elements, filtered by the matcher. It returns a new Selection object
|
||||
// containing these matched elements.
|
||||
func (s *Selection) FindMatcher(m Matcher) *Selection {
|
||||
return pushStack(s, findWithMatcher(s.Nodes, m))
|
||||
}
|
||||
|
||||
// FindSelection gets the descendants of each element in the current
|
||||
// Selection, filtered by a Selection. It returns a new Selection object
|
||||
// containing these matched elements.
|
||||
func (s *Selection) FindSelection(sel *Selection) *Selection {
|
||||
if sel == nil {
|
||||
return pushStack(s, nil)
|
||||
}
|
||||
return s.FindNodes(sel.Nodes...)
|
||||
}
|
||||
|
||||
// FindNodes gets the descendants of each element in the current
|
||||
// Selection, filtered by some nodes. It returns a new Selection object
|
||||
// containing these matched elements.
|
||||
func (s *Selection) FindNodes(nodes ...*html.Node) *Selection {
|
||||
return pushStack(s, mapNodes(nodes, func(i int, n *html.Node) []*html.Node {
|
||||
if sliceContains(s.Nodes, n) {
|
||||
return []*html.Node{n}
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
|
||||
// Contents gets the children of each element in the Selection,
|
||||
// including text and comment nodes. It returns a new Selection object
|
||||
// containing these elements.
|
||||
func (s *Selection) Contents() *Selection {
|
||||
return pushStack(s, getChildrenNodes(s.Nodes, siblingAllIncludingNonElements))
|
||||
}
|
||||
|
||||
// ContentsFiltered gets the children of each element in the Selection,
|
||||
// filtered by the specified selector. It returns a new Selection
|
||||
// object containing these elements. Since selectors only act on Element nodes,
|
||||
// this function is an alias to ChildrenFiltered unless the selector is empty,
|
||||
// in which case it is an alias to Contents.
|
||||
func (s *Selection) ContentsFiltered(selector string) *Selection {
|
||||
if selector != "" {
|
||||
return s.ChildrenFiltered(selector)
|
||||
}
|
||||
return s.Contents()
|
||||
}
|
||||
|
||||
// ContentsMatcher gets the children of each element in the Selection,
|
||||
// filtered by the specified matcher. It returns a new Selection
|
||||
// object containing these elements. Since matchers only act on Element nodes,
|
||||
// this function is an alias to ChildrenMatcher.
|
||||
func (s *Selection) ContentsMatcher(m Matcher) *Selection {
|
||||
return s.ChildrenMatcher(m)
|
||||
}
|
||||
|
||||
// Children gets the child elements of each element in the Selection.
|
||||
// It returns a new Selection object containing these elements.
|
||||
func (s *Selection) Children() *Selection {
|
||||
return pushStack(s, getChildrenNodes(s.Nodes, siblingAll))
|
||||
}
|
||||
|
||||
// ChildrenFiltered gets the child elements of each element in the Selection,
|
||||
// filtered by the specified selector. It returns a new
|
||||
// Selection object containing these elements.
|
||||
func (s *Selection) ChildrenFiltered(selector string) *Selection {
|
||||
return filterAndPush(s, getChildrenNodes(s.Nodes, siblingAll), compileMatcher(selector))
|
||||
}
|
||||
|
||||
// ChildrenMatcher gets the child elements of each element in the Selection,
|
||||
// filtered by the specified matcher. It returns a new
|
||||
// Selection object containing these elements.
|
||||
func (s *Selection) ChildrenMatcher(m Matcher) *Selection {
|
||||
return filterAndPush(s, getChildrenNodes(s.Nodes, siblingAll), m)
|
||||
}
|
||||
|
||||
// Parent gets the parent of each element in the Selection. It returns a
|
||||
// new Selection object containing the matched elements.
|
||||
func (s *Selection) Parent() *Selection {
|
||||
return pushStack(s, getParentNodes(s.Nodes))
|
||||
}
|
||||
|
||||
// ParentFiltered gets the parent of each element in the Selection filtered by a
|
||||
// selector. It returns a new Selection object containing the matched elements.
|
||||
func (s *Selection) ParentFiltered(selector string) *Selection {
|
||||
return filterAndPush(s, getParentNodes(s.Nodes), compileMatcher(selector))
|
||||
}
|
||||
|
||||
// ParentMatcher gets the parent of each element in the Selection filtered by a
|
||||
// matcher. It returns a new Selection object containing the matched elements.
|
||||
func (s *Selection) ParentMatcher(m Matcher) *Selection {
|
||||
return filterAndPush(s, getParentNodes(s.Nodes), m)
|
||||
}
|
||||
|
||||
// Closest gets the first element that matches the selector by testing the
|
||||
// element itself and traversing up through its ancestors in the DOM tree.
|
||||
func (s *Selection) Closest(selector string) *Selection {
|
||||
cs := compileMatcher(selector)
|
||||
return s.ClosestMatcher(cs)
|
||||
}
|
||||
|
||||
// ClosestMatcher gets the first element that matches the matcher by testing the
|
||||
// element itself and traversing up through its ancestors in the DOM tree.
|
||||
func (s *Selection) ClosestMatcher(m Matcher) *Selection {
|
||||
return pushStack(s, mapNodes(s.Nodes, func(i int, n *html.Node) []*html.Node {
|
||||
// For each node in the selection, test the node itself, then each parent
|
||||
// until a match is found.
|
||||
for ; n != nil; n = n.Parent {
|
||||
if m.Match(n) {
|
||||
return []*html.Node{n}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
|
||||
// ClosestNodes gets the first element that matches one of the nodes by testing the
|
||||
// element itself and traversing up through its ancestors in the DOM tree.
|
||||
func (s *Selection) ClosestNodes(nodes ...*html.Node) *Selection {
|
||||
set := make(map[*html.Node]bool)
|
||||
for _, n := range nodes {
|
||||
set[n] = true
|
||||
}
|
||||
return pushStack(s, mapNodes(s.Nodes, func(i int, n *html.Node) []*html.Node {
|
||||
// For each node in the selection, test the node itself, then each parent
|
||||
// until a match is found.
|
||||
for ; n != nil; n = n.Parent {
|
||||
if set[n] {
|
||||
return []*html.Node{n}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
|
||||
// ClosestSelection gets the first element that matches one of the nodes in the
|
||||
// Selection by testing the element itself and traversing up through its ancestors
|
||||
// in the DOM tree.
|
||||
func (s *Selection) ClosestSelection(sel *Selection) *Selection {
|
||||
if sel == nil {
|
||||
return pushStack(s, nil)
|
||||
}
|
||||
return s.ClosestNodes(sel.Nodes...)
|
||||
}
|
||||
|
||||
// Parents gets the ancestors of each element in the current Selection. It
|
||||
// returns a new Selection object with the matched elements.
|
||||
func (s *Selection) Parents() *Selection {
|
||||
return pushStack(s, getParentsNodes(s.Nodes, nil, nil))
|
||||
}
|
||||
|
||||
// ParentsFiltered gets the ancestors of each element in the current
|
||||
// Selection. It returns a new Selection object with the matched elements.
|
||||
func (s *Selection) ParentsFiltered(selector string) *Selection {
|
||||
return filterAndPush(s, getParentsNodes(s.Nodes, nil, nil), compileMatcher(selector))
|
||||
}
|
||||
|
||||
// ParentsMatcher gets the ancestors of each element in the current
|
||||
// Selection. It returns a new Selection object with the matched elements.
|
||||
func (s *Selection) ParentsMatcher(m Matcher) *Selection {
|
||||
return filterAndPush(s, getParentsNodes(s.Nodes, nil, nil), m)
|
||||
}
|
||||
|
||||
// ParentsUntil gets the ancestors of each element in the Selection, up to but
|
||||
// not including the element matched by the selector. It returns a new Selection
|
||||
// object containing the matched elements.
|
||||
func (s *Selection) ParentsUntil(selector string) *Selection {
|
||||
return pushStack(s, getParentsNodes(s.Nodes, compileMatcher(selector), nil))
|
||||
}
|
||||
|
||||
// ParentsUntilMatcher gets the ancestors of each element in the Selection, up to but
|
||||
// not including the element matched by the matcher. It returns a new Selection
|
||||
// object containing the matched elements.
|
||||
func (s *Selection) ParentsUntilMatcher(m Matcher) *Selection {
|
||||
return pushStack(s, getParentsNodes(s.Nodes, m, nil))
|
||||
}
|
||||
|
||||
// ParentsUntilSelection gets the ancestors of each element in the Selection,
|
||||
// up to but not including the elements in the specified Selection. It returns a
|
||||
// new Selection object containing the matched elements.
|
||||
func (s *Selection) ParentsUntilSelection(sel *Selection) *Selection {
|
||||
if sel == nil {
|
||||
return s.Parents()
|
||||
}
|
||||
return s.ParentsUntilNodes(sel.Nodes...)
|
||||
}
|
||||
|
||||
// ParentsUntilNodes gets the ancestors of each element in the Selection,
|
||||
// up to but not including the specified nodes. It returns a
|
||||
// new Selection object containing the matched elements.
|
||||
func (s *Selection) ParentsUntilNodes(nodes ...*html.Node) *Selection {
|
||||
return pushStack(s, getParentsNodes(s.Nodes, nil, nodes))
|
||||
}
|
||||
|
||||
// ParentsFilteredUntil is like ParentsUntil, with the option to filter the
|
||||
// results based on a selector string. It returns a new Selection
|
||||
// object containing the matched elements.
|
||||
func (s *Selection) ParentsFilteredUntil(filterSelector, untilSelector string) *Selection {
|
||||
return filterAndPush(s, getParentsNodes(s.Nodes, compileMatcher(untilSelector), nil), compileMatcher(filterSelector))
|
||||
}
|
||||
|
||||
// ParentsFilteredUntilMatcher is like ParentsUntilMatcher, with the option to filter the
|
||||
// results based on a matcher. It returns a new Selection object containing the matched elements.
|
||||
func (s *Selection) ParentsFilteredUntilMatcher(filter, until Matcher) *Selection {
|
||||
return filterAndPush(s, getParentsNodes(s.Nodes, until, nil), filter)
|
||||
}
|
||||
|
||||
// ParentsFilteredUntilSelection is like ParentsUntilSelection, with the
|
||||
// option to filter the results based on a selector string. It returns a new
|
||||
// Selection object containing the matched elements.
|
||||
func (s *Selection) ParentsFilteredUntilSelection(filterSelector string, sel *Selection) *Selection {
|
||||
return s.ParentsMatcherUntilSelection(compileMatcher(filterSelector), sel)
|
||||
}
|
||||
|
||||
// ParentsMatcherUntilSelection is like ParentsUntilSelection, with the
|
||||
// option to filter the results based on a matcher. It returns a new
|
||||
// Selection object containing the matched elements.
|
||||
func (s *Selection) ParentsMatcherUntilSelection(filter Matcher, sel *Selection) *Selection {
|
||||
if sel == nil {
|
||||
return s.ParentsMatcher(filter)
|
||||
}
|
||||
return s.ParentsMatcherUntilNodes(filter, sel.Nodes...)
|
||||
}
|
||||
|
||||
// ParentsFilteredUntilNodes is like ParentsUntilNodes, with the
|
||||
// option to filter the results based on a selector string. It returns a new
|
||||
// Selection object containing the matched elements.
|
||||
func (s *Selection) ParentsFilteredUntilNodes(filterSelector string, nodes ...*html.Node) *Selection {
|
||||
return filterAndPush(s, getParentsNodes(s.Nodes, nil, nodes), compileMatcher(filterSelector))
|
||||
}
|
||||
|
||||
// ParentsMatcherUntilNodes is like ParentsUntilNodes, with the
|
||||
// option to filter the results based on a matcher. It returns a new
|
||||
// Selection object containing the matched elements.
|
||||
func (s *Selection) ParentsMatcherUntilNodes(filter Matcher, nodes ...*html.Node) *Selection {
|
||||
return filterAndPush(s, getParentsNodes(s.Nodes, nil, nodes), filter)
|
||||
}
|
||||
|
||||
// Siblings gets the siblings of each element in the Selection. It returns
|
||||
// a new Selection object containing the matched elements.
|
||||
func (s *Selection) Siblings() *Selection {
|
||||
return pushStack(s, getSiblingNodes(s.Nodes, siblingAll, nil, nil))
|
||||
}
|
||||
|
||||
// SiblingsFiltered gets the siblings of each element in the Selection
|
||||
// filtered by a selector. It returns a new Selection object containing the
|
||||
// matched elements.
|
||||
func (s *Selection) SiblingsFiltered(selector string) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingAll, nil, nil), compileMatcher(selector))
|
||||
}
|
||||
|
||||
// SiblingsMatcher gets the siblings of each element in the Selection
|
||||
// filtered by a matcher. It returns a new Selection object containing the
|
||||
// matched elements.
|
||||
func (s *Selection) SiblingsMatcher(m Matcher) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingAll, nil, nil), m)
|
||||
}
|
||||
|
||||
// Next gets the immediately following sibling of each element in the
|
||||
// Selection. It returns a new Selection object containing the matched elements.
|
||||
func (s *Selection) Next() *Selection {
|
||||
return pushStack(s, getSiblingNodes(s.Nodes, siblingNext, nil, nil))
|
||||
}
|
||||
|
||||
// NextFiltered gets the immediately following sibling of each element in the
|
||||
// Selection filtered by a selector. It returns a new Selection object
|
||||
// containing the matched elements.
|
||||
func (s *Selection) NextFiltered(selector string) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNext, nil, nil), compileMatcher(selector))
|
||||
}
|
||||
|
||||
// NextMatcher gets the immediately following sibling of each element in the
|
||||
// Selection filtered by a matcher. It returns a new Selection object
|
||||
// containing the matched elements.
|
||||
func (s *Selection) NextMatcher(m Matcher) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNext, nil, nil), m)
|
||||
}
|
||||
|
||||
// NextAll gets all the following siblings of each element in the
|
||||
// Selection. It returns a new Selection object containing the matched elements.
|
||||
func (s *Selection) NextAll() *Selection {
|
||||
return pushStack(s, getSiblingNodes(s.Nodes, siblingNextAll, nil, nil))
|
||||
}
|
||||
|
||||
// NextAllFiltered gets all the following siblings of each element in the
|
||||
// Selection filtered by a selector. It returns a new Selection object
|
||||
// containing the matched elements.
|
||||
func (s *Selection) NextAllFiltered(selector string) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextAll, nil, nil), compileMatcher(selector))
|
||||
}
|
||||
|
||||
// NextAllMatcher gets all the following siblings of each element in the
|
||||
// Selection filtered by a matcher. It returns a new Selection object
|
||||
// containing the matched elements.
|
||||
func (s *Selection) NextAllMatcher(m Matcher) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextAll, nil, nil), m)
|
||||
}
|
||||
|
||||
// Prev gets the immediately preceding sibling of each element in the
|
||||
// Selection. It returns a new Selection object containing the matched elements.
|
||||
func (s *Selection) Prev() *Selection {
|
||||
return pushStack(s, getSiblingNodes(s.Nodes, siblingPrev, nil, nil))
|
||||
}
|
||||
|
||||
// PrevFiltered gets the immediately preceding sibling of each element in the
|
||||
// Selection filtered by a selector. It returns a new Selection object
|
||||
// containing the matched elements.
|
||||
func (s *Selection) PrevFiltered(selector string) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrev, nil, nil), compileMatcher(selector))
|
||||
}
|
||||
|
||||
// PrevMatcher gets the immediately preceding sibling of each element in the
|
||||
// Selection filtered by a matcher. It returns a new Selection object
|
||||
// containing the matched elements.
|
||||
func (s *Selection) PrevMatcher(m Matcher) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrev, nil, nil), m)
|
||||
}
|
||||
|
||||
// PrevAll gets all the preceding siblings of each element in the
|
||||
// Selection. It returns a new Selection object containing the matched elements.
|
||||
func (s *Selection) PrevAll() *Selection {
|
||||
return pushStack(s, getSiblingNodes(s.Nodes, siblingPrevAll, nil, nil))
|
||||
}
|
||||
|
||||
// PrevAllFiltered gets all the preceding siblings of each element in the
|
||||
// Selection filtered by a selector. It returns a new Selection object
|
||||
// containing the matched elements.
|
||||
func (s *Selection) PrevAllFiltered(selector string) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevAll, nil, nil), compileMatcher(selector))
|
||||
}
|
||||
|
||||
// PrevAllMatcher gets all the preceding siblings of each element in the
|
||||
// Selection filtered by a matcher. It returns a new Selection object
|
||||
// containing the matched elements.
|
||||
func (s *Selection) PrevAllMatcher(m Matcher) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevAll, nil, nil), m)
|
||||
}
|
||||
|
||||
// NextUntil gets all following siblings of each element up to but not
|
||||
// including the element matched by the selector. It returns a new Selection
|
||||
// object containing the matched elements.
|
||||
func (s *Selection) NextUntil(selector string) *Selection {
|
||||
return pushStack(s, getSiblingNodes(s.Nodes, siblingNextUntil,
|
||||
compileMatcher(selector), nil))
|
||||
}
|
||||
|
||||
// NextUntilMatcher gets all following siblings of each element up to but not
|
||||
// including the element matched by the matcher. It returns a new Selection
|
||||
// object containing the matched elements.
|
||||
func (s *Selection) NextUntilMatcher(m Matcher) *Selection {
|
||||
return pushStack(s, getSiblingNodes(s.Nodes, siblingNextUntil,
|
||||
m, nil))
|
||||
}
|
||||
|
||||
// NextUntilSelection gets all following siblings of each element up to but not
|
||||
// including the element matched by the Selection. It returns a new Selection
|
||||
// object containing the matched elements.
|
||||
func (s *Selection) NextUntilSelection(sel *Selection) *Selection {
|
||||
if sel == nil {
|
||||
return s.NextAll()
|
||||
}
|
||||
return s.NextUntilNodes(sel.Nodes...)
|
||||
}
|
||||
|
||||
// NextUntilNodes gets all following siblings of each element up to but not
|
||||
// including the element matched by the nodes. It returns a new Selection
|
||||
// object containing the matched elements.
|
||||
func (s *Selection) NextUntilNodes(nodes ...*html.Node) *Selection {
|
||||
return pushStack(s, getSiblingNodes(s.Nodes, siblingNextUntil,
|
||||
nil, nodes))
|
||||
}
|
||||
|
||||
// PrevUntil gets all preceding siblings of each element up to but not
|
||||
// including the element matched by the selector. It returns a new Selection
|
||||
// object containing the matched elements.
|
||||
func (s *Selection) PrevUntil(selector string) *Selection {
|
||||
return pushStack(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
|
||||
compileMatcher(selector), nil))
|
||||
}
|
||||
|
||||
// PrevUntilMatcher gets all preceding siblings of each element up to but not
|
||||
// including the element matched by the matcher. It returns a new Selection
|
||||
// object containing the matched elements.
|
||||
func (s *Selection) PrevUntilMatcher(m Matcher) *Selection {
|
||||
return pushStack(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
|
||||
m, nil))
|
||||
}
|
||||
|
||||
// PrevUntilSelection gets all preceding siblings of each element up to but not
|
||||
// including the element matched by the Selection. It returns a new Selection
|
||||
// object containing the matched elements.
|
||||
func (s *Selection) PrevUntilSelection(sel *Selection) *Selection {
|
||||
if sel == nil {
|
||||
return s.PrevAll()
|
||||
}
|
||||
return s.PrevUntilNodes(sel.Nodes...)
|
||||
}
|
||||
|
||||
// PrevUntilNodes gets all preceding siblings of each element up to but not
|
||||
// including the element matched by the nodes. It returns a new Selection
|
||||
// object containing the matched elements.
|
||||
func (s *Selection) PrevUntilNodes(nodes ...*html.Node) *Selection {
|
||||
return pushStack(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
|
||||
nil, nodes))
|
||||
}
|
||||
|
||||
// NextFilteredUntil is like NextUntil, with the option to filter
|
||||
// the results based on a selector string.
|
||||
// It returns a new Selection object containing the matched elements.
|
||||
func (s *Selection) NextFilteredUntil(filterSelector, untilSelector string) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextUntil,
|
||||
compileMatcher(untilSelector), nil), compileMatcher(filterSelector))
|
||||
}
|
||||
|
||||
// NextFilteredUntilMatcher is like NextUntilMatcher, with the option to filter
|
||||
// the results based on a matcher.
|
||||
// It returns a new Selection object containing the matched elements.
|
||||
func (s *Selection) NextFilteredUntilMatcher(filter, until Matcher) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextUntil,
|
||||
until, nil), filter)
|
||||
}
|
||||
|
||||
// NextFilteredUntilSelection is like NextUntilSelection, with the
|
||||
// option to filter the results based on a selector string. It returns a new
|
||||
// Selection object containing the matched elements.
|
||||
func (s *Selection) NextFilteredUntilSelection(filterSelector string, sel *Selection) *Selection {
|
||||
return s.NextMatcherUntilSelection(compileMatcher(filterSelector), sel)
|
||||
}
|
||||
|
||||
// NextMatcherUntilSelection is like NextUntilSelection, with the
|
||||
// option to filter the results based on a matcher. It returns a new
|
||||
// Selection object containing the matched elements.
|
||||
func (s *Selection) NextMatcherUntilSelection(filter Matcher, sel *Selection) *Selection {
|
||||
if sel == nil {
|
||||
return s.NextMatcher(filter)
|
||||
}
|
||||
return s.NextMatcherUntilNodes(filter, sel.Nodes...)
|
||||
}
|
||||
|
||||
// NextFilteredUntilNodes is like NextUntilNodes, with the
|
||||
// option to filter the results based on a selector string. It returns a new
|
||||
// Selection object containing the matched elements.
|
||||
func (s *Selection) NextFilteredUntilNodes(filterSelector string, nodes ...*html.Node) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextUntil,
|
||||
nil, nodes), compileMatcher(filterSelector))
|
||||
}
|
||||
|
||||
// NextMatcherUntilNodes is like NextUntilNodes, with the
|
||||
// option to filter the results based on a matcher. It returns a new
|
||||
// Selection object containing the matched elements.
|
||||
func (s *Selection) NextMatcherUntilNodes(filter Matcher, nodes ...*html.Node) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextUntil,
|
||||
nil, nodes), filter)
|
||||
}
|
||||
|
||||
// PrevFilteredUntil is like PrevUntil, with the option to filter
|
||||
// the results based on a selector string.
|
||||
// It returns a new Selection object containing the matched elements.
|
||||
func (s *Selection) PrevFilteredUntil(filterSelector, untilSelector string) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
|
||||
compileMatcher(untilSelector), nil), compileMatcher(filterSelector))
|
||||
}
|
||||
|
||||
// PrevFilteredUntilMatcher is like PrevUntilMatcher, with the option to filter
|
||||
// the results based on a matcher.
|
||||
// It returns a new Selection object containing the matched elements.
|
||||
func (s *Selection) PrevFilteredUntilMatcher(filter, until Matcher) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
|
||||
until, nil), filter)
|
||||
}
|
||||
|
||||
// PrevFilteredUntilSelection is like PrevUntilSelection, with the
|
||||
// option to filter the results based on a selector string. It returns a new
|
||||
// Selection object containing the matched elements.
|
||||
func (s *Selection) PrevFilteredUntilSelection(filterSelector string, sel *Selection) *Selection {
|
||||
return s.PrevMatcherUntilSelection(compileMatcher(filterSelector), sel)
|
||||
}
|
||||
|
||||
// PrevMatcherUntilSelection is like PrevUntilSelection, with the
|
||||
// option to filter the results based on a matcher. It returns a new
|
||||
// Selection object containing the matched elements.
|
||||
func (s *Selection) PrevMatcherUntilSelection(filter Matcher, sel *Selection) *Selection {
|
||||
if sel == nil {
|
||||
return s.PrevMatcher(filter)
|
||||
}
|
||||
return s.PrevMatcherUntilNodes(filter, sel.Nodes...)
|
||||
}
|
||||
|
||||
// PrevFilteredUntilNodes is like PrevUntilNodes, with the
|
||||
// option to filter the results based on a selector string. It returns a new
|
||||
// Selection object containing the matched elements.
|
||||
func (s *Selection) PrevFilteredUntilNodes(filterSelector string, nodes ...*html.Node) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
|
||||
nil, nodes), compileMatcher(filterSelector))
|
||||
}
|
||||
|
||||
// PrevMatcherUntilNodes is like PrevUntilNodes, with the
|
||||
// option to filter the results based on a matcher. It returns a new
|
||||
// Selection object containing the matched elements.
|
||||
func (s *Selection) PrevMatcherUntilNodes(filter Matcher, nodes ...*html.Node) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
|
||||
nil, nodes), filter)
|
||||
}
|
||||
|
||||
// Filter and push filters the nodes based on a matcher, and pushes the results
|
||||
// on the stack, with the srcSel as previous selection.
|
||||
func filterAndPush(srcSel *Selection, nodes []*html.Node, m Matcher) *Selection {
|
||||
// Create a temporary Selection with the specified nodes to filter using winnow
|
||||
sel := &Selection{nodes, srcSel.document, nil}
|
||||
// Filter based on matcher and push on stack
|
||||
return pushStack(srcSel, winnow(sel, m, true))
|
||||
}
|
||||
|
||||
// Internal implementation of Find that return raw nodes.
|
||||
func findWithMatcher(nodes []*html.Node, m Matcher) []*html.Node {
|
||||
// Map nodes to find the matches within the children of each node
|
||||
return mapNodes(nodes, func(i int, n *html.Node) (result []*html.Node) {
|
||||
// Go down one level, becausejQuery's Find selects only within descendants
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
if c.Type == html.ElementNode {
|
||||
result = append(result, m.MatchAll(c)...)
|
||||
}
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// Internal implementation to get all parent nodes, stopping at the specified
|
||||
// node (or nil if no stop).
|
||||
func getParentsNodes(nodes []*html.Node, stopm Matcher, stopNodes []*html.Node) []*html.Node {
|
||||
return mapNodes(nodes, func(i int, n *html.Node) (result []*html.Node) {
|
||||
for p := n.Parent; p != nil; p = p.Parent {
|
||||
sel := newSingleSelection(p, nil)
|
||||
if stopm != nil {
|
||||
if sel.IsMatcher(stopm) {
|
||||
break
|
||||
}
|
||||
} else if len(stopNodes) > 0 {
|
||||
if sel.IsNodes(stopNodes...) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if p.Type == html.ElementNode {
|
||||
result = append(result, p)
|
||||
}
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// Internal implementation of sibling nodes that return a raw slice of matches.
|
||||
func getSiblingNodes(nodes []*html.Node, st siblingType, untilm Matcher, untilNodes []*html.Node) []*html.Node {
|
||||
var f func(*html.Node) bool
|
||||
|
||||
// If the requested siblings are ...Until, create the test function to
|
||||
// determine if the until condition is reached (returns true if it is)
|
||||
if st == siblingNextUntil || st == siblingPrevUntil {
|
||||
f = func(n *html.Node) bool {
|
||||
if untilm != nil {
|
||||
// Matcher-based condition
|
||||
sel := newSingleSelection(n, nil)
|
||||
return sel.IsMatcher(untilm)
|
||||
} else if len(untilNodes) > 0 {
|
||||
// Nodes-based condition
|
||||
sel := newSingleSelection(n, nil)
|
||||
return sel.IsNodes(untilNodes...)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return mapNodes(nodes, func(i int, n *html.Node) []*html.Node {
|
||||
return getChildrenWithSiblingType(n.Parent, st, n, f)
|
||||
})
|
||||
}
|
||||
|
||||
// Gets the children nodes of each node in the specified slice of nodes,
|
||||
// based on the sibling type request.
|
||||
func getChildrenNodes(nodes []*html.Node, st siblingType) []*html.Node {
|
||||
return mapNodes(nodes, func(i int, n *html.Node) []*html.Node {
|
||||
return getChildrenWithSiblingType(n, st, nil, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// Gets the children of the specified parent, based on the requested sibling
|
||||
// type, skipping a specified node if required.
|
||||
func getChildrenWithSiblingType(parent *html.Node, st siblingType, skipNode *html.Node,
|
||||
untilFunc func(*html.Node) bool) (result []*html.Node) {
|
||||
|
||||
// Create the iterator function
|
||||
var iter = func(cur *html.Node) (ret *html.Node) {
|
||||
// Based on the sibling type requested, iterate the right way
|
||||
for {
|
||||
switch st {
|
||||
case siblingAll, siblingAllIncludingNonElements:
|
||||
if cur == nil {
|
||||
// First iteration, start with first child of parent
|
||||
// Skip node if required
|
||||
if ret = parent.FirstChild; ret == skipNode && skipNode != nil {
|
||||
ret = skipNode.NextSibling
|
||||
}
|
||||
} else {
|
||||
// Skip node if required
|
||||
if ret = cur.NextSibling; ret == skipNode && skipNode != nil {
|
||||
ret = skipNode.NextSibling
|
||||
}
|
||||
}
|
||||
case siblingPrev, siblingPrevAll, siblingPrevUntil:
|
||||
if cur == nil {
|
||||
// Start with previous sibling of the skip node
|
||||
ret = skipNode.PrevSibling
|
||||
} else {
|
||||
ret = cur.PrevSibling
|
||||
}
|
||||
case siblingNext, siblingNextAll, siblingNextUntil:
|
||||
if cur == nil {
|
||||
// Start with next sibling of the skip node
|
||||
ret = skipNode.NextSibling
|
||||
} else {
|
||||
ret = cur.NextSibling
|
||||
}
|
||||
default:
|
||||
panic("Invalid sibling type.")
|
||||
}
|
||||
if ret == nil || ret.Type == html.ElementNode || st == siblingAllIncludingNonElements {
|
||||
return
|
||||
}
|
||||
// Not a valid node, try again from this one
|
||||
cur = ret
|
||||
}
|
||||
}
|
||||
|
||||
for c := iter(nil); c != nil; c = iter(c) {
|
||||
// If this is an ...Until case, test before append (returns true
|
||||
// if the until condition is reached)
|
||||
if st == siblingNextUntil || st == siblingPrevUntil {
|
||||
if untilFunc(c) {
|
||||
return
|
||||
}
|
||||
}
|
||||
result = append(result, c)
|
||||
if st == siblingNext || st == siblingPrev {
|
||||
// Only one node was requested (immediate next or previous), so exit
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Internal implementation of parent nodes that return a raw slice of Nodes.
|
||||
func getParentNodes(nodes []*html.Node) []*html.Node {
|
||||
return mapNodes(nodes, func(i int, n *html.Node) []*html.Node {
|
||||
if n.Parent != nil && n.Parent.Type == html.ElementNode {
|
||||
return []*html.Node{n.Parent}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Internal map function used by many traversing methods. Takes the source nodes
|
||||
// to iterate on and the mapping function that returns an array of nodes.
|
||||
// Returns an array of nodes mapped by calling the callback function once for
|
||||
// each node in the source nodes.
|
||||
func mapNodes(nodes []*html.Node, f func(int, *html.Node) []*html.Node) (result []*html.Node) {
|
||||
set := make(map[*html.Node]bool)
|
||||
for i, n := range nodes {
|
||||
if vals := f(i, n); len(vals) > 0 {
|
||||
result = appendWithoutDuplicates(result, vals, set)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
141
vendor/github.com/PuerkitoBio/goquery/type.go
generated
vendored
Normal file
141
vendor/github.com/PuerkitoBio/goquery/type.go
generated
vendored
Normal file
@@ -0,0 +1,141 @@
|
||||
package goquery
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/andybalholm/cascadia"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
// Document represents an HTML document to be manipulated. Unlike jQuery, which
|
||||
// is loaded as part of a DOM document, and thus acts upon its containing
|
||||
// document, GoQuery doesn't know which HTML document to act upon. So it needs
|
||||
// to be told, and that's what the Document class is for. It holds the root
|
||||
// document node to manipulate, and can make selections on this document.
|
||||
type Document struct {
|
||||
*Selection
|
||||
Url *url.URL
|
||||
rootNode *html.Node
|
||||
}
|
||||
|
||||
// NewDocumentFromNode is a Document constructor that takes a root html Node
|
||||
// as argument.
|
||||
func NewDocumentFromNode(root *html.Node) *Document {
|
||||
return newDocument(root, nil)
|
||||
}
|
||||
|
||||
// NewDocument is a Document constructor that takes a string URL as argument.
|
||||
// It loads the specified document, parses it, and stores the root Document
|
||||
// node, ready to be manipulated.
|
||||
//
|
||||
// Deprecated: Use the net/http standard library package to make the request
|
||||
// and validate the response before calling goquery.NewDocumentFromReader
|
||||
// with the response's body.
|
||||
func NewDocument(url string) (*Document, error) {
|
||||
// Load the URL
|
||||
res, e := http.Get(url)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
return NewDocumentFromResponse(res)
|
||||
}
|
||||
|
||||
// NewDocumentFromReader returns a Document from an io.Reader.
|
||||
// It returns an error as second value if the reader's data cannot be parsed
|
||||
// as html. It does not check if the reader is also an io.Closer, the
|
||||
// provided reader is never closed by this call. It is the responsibility
|
||||
// of the caller to close it if required.
|
||||
func NewDocumentFromReader(r io.Reader) (*Document, error) {
|
||||
root, e := html.Parse(r)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
return newDocument(root, nil), nil
|
||||
}
|
||||
|
||||
// NewDocumentFromResponse is another Document constructor that takes an http response as argument.
|
||||
// It loads the specified response's document, parses it, and stores the root Document
|
||||
// node, ready to be manipulated. The response's body is closed on return.
|
||||
//
|
||||
// Deprecated: Use goquery.NewDocumentFromReader with the response's body.
|
||||
func NewDocumentFromResponse(res *http.Response) (*Document, error) {
|
||||
if res == nil {
|
||||
return nil, errors.New("Response is nil")
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.Request == nil {
|
||||
return nil, errors.New("Response.Request is nil")
|
||||
}
|
||||
|
||||
// Parse the HTML into nodes
|
||||
root, e := html.Parse(res.Body)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
|
||||
// Create and fill the document
|
||||
return newDocument(root, res.Request.URL), nil
|
||||
}
|
||||
|
||||
// CloneDocument creates a deep-clone of a document.
|
||||
func CloneDocument(doc *Document) *Document {
|
||||
return newDocument(cloneNode(doc.rootNode), doc.Url)
|
||||
}
|
||||
|
||||
// Private constructor, make sure all fields are correctly filled.
|
||||
func newDocument(root *html.Node, url *url.URL) *Document {
|
||||
// Create and fill the document
|
||||
d := &Document{nil, url, root}
|
||||
d.Selection = newSingleSelection(root, d)
|
||||
return d
|
||||
}
|
||||
|
||||
// Selection represents a collection of nodes matching some criteria. The
|
||||
// initial Selection can be created by using Document.Find, and then
|
||||
// manipulated using the jQuery-like chainable syntax and methods.
|
||||
type Selection struct {
|
||||
Nodes []*html.Node
|
||||
document *Document
|
||||
prevSel *Selection
|
||||
}
|
||||
|
||||
// Helper constructor to create an empty selection
|
||||
func newEmptySelection(doc *Document) *Selection {
|
||||
return &Selection{nil, doc, nil}
|
||||
}
|
||||
|
||||
// Helper constructor to create a selection of only one node
|
||||
func newSingleSelection(node *html.Node, doc *Document) *Selection {
|
||||
return &Selection{[]*html.Node{node}, doc, nil}
|
||||
}
|
||||
|
||||
// Matcher is an interface that defines the methods to match
|
||||
// HTML nodes against a compiled selector string. Cascadia's
|
||||
// Selector implements this interface.
|
||||
type Matcher interface {
|
||||
Match(*html.Node) bool
|
||||
MatchAll(*html.Node) []*html.Node
|
||||
Filter([]*html.Node) []*html.Node
|
||||
}
|
||||
|
||||
// compileMatcher compiles the selector string s and returns
|
||||
// the corresponding Matcher. If s is an invalid selector string,
|
||||
// it returns a Matcher that fails all matches.
|
||||
func compileMatcher(s string) Matcher {
|
||||
cs, err := cascadia.Compile(s)
|
||||
if err != nil {
|
||||
return invalidMatcher{}
|
||||
}
|
||||
return cs
|
||||
}
|
||||
|
||||
// invalidMatcher is a Matcher that always fails to match.
|
||||
type invalidMatcher struct{}
|
||||
|
||||
func (invalidMatcher) Match(n *html.Node) bool { return false }
|
||||
func (invalidMatcher) MatchAll(n *html.Node) []*html.Node { return nil }
|
||||
func (invalidMatcher) Filter(ns []*html.Node) []*html.Node { return nil }
|
||||
161
vendor/github.com/PuerkitoBio/goquery/utilities.go
generated
vendored
Normal file
161
vendor/github.com/PuerkitoBio/goquery/utilities.go
generated
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
package goquery
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
// used to determine if a set (map[*html.Node]bool) should be used
|
||||
// instead of iterating over a slice. The set uses more memory and
|
||||
// is slower than slice iteration for small N.
|
||||
const minNodesForSet = 1000
|
||||
|
||||
var nodeNames = []string{
|
||||
html.ErrorNode: "#error",
|
||||
html.TextNode: "#text",
|
||||
html.DocumentNode: "#document",
|
||||
html.CommentNode: "#comment",
|
||||
}
|
||||
|
||||
// NodeName returns the node name of the first element in the selection.
|
||||
// It tries to behave in a similar way as the DOM's nodeName property
|
||||
// (https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName).
|
||||
//
|
||||
// Go's net/html package defines the following node types, listed with
|
||||
// the corresponding returned value from this function:
|
||||
//
|
||||
// ErrorNode : #error
|
||||
// TextNode : #text
|
||||
// DocumentNode : #document
|
||||
// ElementNode : the element's tag name
|
||||
// CommentNode : #comment
|
||||
// DoctypeNode : the name of the document type
|
||||
//
|
||||
func NodeName(s *Selection) string {
|
||||
if s.Length() == 0 {
|
||||
return ""
|
||||
}
|
||||
switch n := s.Get(0); n.Type {
|
||||
case html.ElementNode, html.DoctypeNode:
|
||||
return n.Data
|
||||
default:
|
||||
if n.Type >= 0 && int(n.Type) < len(nodeNames) {
|
||||
return nodeNames[n.Type]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// OuterHtml returns the outer HTML rendering of the first item in
|
||||
// the selection - that is, the HTML including the first element's
|
||||
// tag and attributes.
|
||||
//
|
||||
// Unlike InnerHtml, this is a function and not a method on the Selection,
|
||||
// because this is not a jQuery method (in javascript-land, this is
|
||||
// a property provided by the DOM).
|
||||
func OuterHtml(s *Selection) (string, error) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
if s.Length() == 0 {
|
||||
return "", nil
|
||||
}
|
||||
n := s.Get(0)
|
||||
if err := html.Render(&buf, n); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// Loop through all container nodes to search for the target node.
|
||||
func sliceContains(container []*html.Node, contained *html.Node) bool {
|
||||
for _, n := range container {
|
||||
if nodeContains(n, contained) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Checks if the contained node is within the container node.
|
||||
func nodeContains(container *html.Node, contained *html.Node) bool {
|
||||
// Check if the parent of the contained node is the container node, traversing
|
||||
// upward until the top is reached, or the container is found.
|
||||
for contained = contained.Parent; contained != nil; contained = contained.Parent {
|
||||
if container == contained {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Checks if the target node is in the slice of nodes.
|
||||
func isInSlice(slice []*html.Node, node *html.Node) bool {
|
||||
return indexInSlice(slice, node) > -1
|
||||
}
|
||||
|
||||
// Returns the index of the target node in the slice, or -1.
|
||||
func indexInSlice(slice []*html.Node, node *html.Node) int {
|
||||
if node != nil {
|
||||
for i, n := range slice {
|
||||
if n == node {
|
||||
return i
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Appends the new nodes to the target slice, making sure no duplicate is added.
|
||||
// There is no check to the original state of the target slice, so it may still
|
||||
// contain duplicates. The target slice is returned because append() may create
|
||||
// a new underlying array. If targetSet is nil, a local set is created with the
|
||||
// target if len(target) + len(nodes) is greater than minNodesForSet.
|
||||
func appendWithoutDuplicates(target []*html.Node, nodes []*html.Node, targetSet map[*html.Node]bool) []*html.Node {
|
||||
// if there are not that many nodes, don't use the map, faster to just use nested loops
|
||||
// (unless a non-nil targetSet is passed, in which case the caller knows better).
|
||||
if targetSet == nil && len(target)+len(nodes) < minNodesForSet {
|
||||
for _, n := range nodes {
|
||||
if !isInSlice(target, n) {
|
||||
target = append(target, n)
|
||||
}
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
// if a targetSet is passed, then assume it is reliable, otherwise create one
|
||||
// and initialize it with the current target contents.
|
||||
if targetSet == nil {
|
||||
targetSet = make(map[*html.Node]bool, len(target))
|
||||
for _, n := range target {
|
||||
targetSet[n] = true
|
||||
}
|
||||
}
|
||||
for _, n := range nodes {
|
||||
if !targetSet[n] {
|
||||
target = append(target, n)
|
||||
targetSet[n] = true
|
||||
}
|
||||
}
|
||||
|
||||
return target
|
||||
}
|
||||
|
||||
// Loop through a selection, returning only those nodes that pass the predicate
|
||||
// function.
|
||||
func grep(sel *Selection, predicate func(i int, s *Selection) bool) (result []*html.Node) {
|
||||
for i, n := range sel.Nodes {
|
||||
if predicate(i, newSingleSelection(n, sel.document)) {
|
||||
result = append(result, n)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Creates a new Selection object based on the specified nodes, and keeps the
|
||||
// source Selection object on the stack (linked list).
|
||||
func pushStack(fromSel *Selection, nodes []*html.Node) *Selection {
|
||||
result := &Selection{nodes, fromSel.document, fromSel}
|
||||
return result
|
||||
}
|
||||
191
vendor/github.com/Unknwon/goconfig/LICENSE
generated
vendored
Executable file
191
vendor/github.com/Unknwon/goconfig/LICENSE
generated
vendored
Executable file
@@ -0,0 +1,191 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
||||
owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities
|
||||
that control, are controlled by, or are under common control with that entity.
|
||||
For the purposes of this definition, "control" means (i) the power, direct or
|
||||
indirect, to cause the direction or management of such entity, whether by
|
||||
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including
|
||||
but not limited to software source code, documentation source, and configuration
|
||||
files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or
|
||||
translation of a Source form, including but not limited to compiled object code,
|
||||
generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made
|
||||
available under the License, as indicated by a copyright notice that is included
|
||||
in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
||||
is based on (or derived from) the Work and for which the editorial revisions,
|
||||
annotations, elaborations, or other modifications represent, as a whole, an
|
||||
original work of authorship. For the purposes of this License, Derivative Works
|
||||
shall not include works that remain separable from, or merely link (or bind by
|
||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version
|
||||
of the Work and any modifications or additions to that Work or Derivative Works
|
||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||
on behalf of the copyright owner. For the purposes of this definition,
|
||||
"submitted" means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems, and
|
||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||
the purpose of discussing and improving the Work, but excluding communication
|
||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||
owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
||||
of whom a Contribution has been received by Licensor and subsequently
|
||||
incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||
Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable (except as stated in this section) patent license to make, have
|
||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
||||
such license applies only to those patent claims licensable by such Contributor
|
||||
that are necessarily infringed by their Contribution(s) alone or by combination
|
||||
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||
submitted. If You institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||
Contribution incorporated within the Work constitutes direct or contributory
|
||||
patent infringement, then any patent licenses granted to You under this License
|
||||
for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution.
|
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||
in any medium, with or without modifications, and in Source or Object form,
|
||||
provided that You meet the following conditions:
|
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of
|
||||
this License; and
|
||||
You must cause any modified files to carry prominent notices stating that You
|
||||
changed the files; and
|
||||
You must retain, in the Source form of any Derivative Works that You distribute,
|
||||
all copyright, patent, trademark, and attribution notices from the Source form
|
||||
of the Work, excluding those notices that do not pertain to any part of the
|
||||
Derivative Works; and
|
||||
If the Work includes a "NOTICE" text file as part of its distribution, then any
|
||||
Derivative Works that You distribute must include a readable copy of the
|
||||
attribution notices contained within such NOTICE file, excluding those notices
|
||||
that do not pertain to any part of the Derivative Works, in at least one of the
|
||||
following places: within a NOTICE text file distributed as part of the
|
||||
Derivative Works; within the Source form or documentation, if provided along
|
||||
with the Derivative Works; or, within a display generated by the Derivative
|
||||
Works, if and wherever such third-party notices normally appear. The contents of
|
||||
the NOTICE file are for informational purposes only and do not modify the
|
||||
License. You may add Your own attribution notices within Derivative Works that
|
||||
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||
provided that such additional attribution notices cannot be construed as
|
||||
modifying the License.
|
||||
You may add Your own copyright statement to Your modifications and may provide
|
||||
additional or different license terms and conditions for use, reproduction, or
|
||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||
with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions.
|
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||
conditions of this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||
any separate license agreement you may have executed with Licensor regarding
|
||||
such Contributions.
|
||||
|
||||
6. Trademarks.
|
||||
|
||||
This License does not grant permission to use the trade names, trademarks,
|
||||
service marks, or product names of the Licensor, except as required for
|
||||
reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor provides the
|
||||
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
||||
including, without limitation, any warranties or conditions of TITLE,
|
||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
||||
solely responsible for determining the appropriateness of using or
|
||||
redistributing the Work and assume any risks associated with Your exercise of
|
||||
permissions under this License.
|
||||
|
||||
8. Limitation of Liability.
|
||||
|
||||
In no event and under no legal theory, whether in tort (including negligence),
|
||||
contract, or otherwise, unless required by applicable law (such as deliberate
|
||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special, incidental,
|
||||
or consequential damages of any character arising as a result of this License or
|
||||
out of the use or inability to use the Work (including but not limited to
|
||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||
any and all other commercial damages or losses), even if such Contributor has
|
||||
been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability.
|
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose to
|
||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||
other liability obligations and/or rights consistent with this License. However,
|
||||
in accepting such obligations, You may act only on Your own behalf and on Your
|
||||
sole responsibility, not on behalf of any other Contributor, and only if You
|
||||
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason of your
|
||||
accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate
|
||||
notice, with the fields enclosed by brackets "[]" replaced with your own
|
||||
identifying information. (Don't include the brackets!) The text should be
|
||||
enclosed in the appropriate comment syntax for the file format. We also
|
||||
recommend that a file or class name and description of purpose be included on
|
||||
the same "printed page" as the copyright notice for easier identification within
|
||||
third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
72
vendor/github.com/Unknwon/goconfig/README.md
generated
vendored
Executable file
72
vendor/github.com/Unknwon/goconfig/README.md
generated
vendored
Executable file
@@ -0,0 +1,72 @@
|
||||
goconfig [](https://drone.io/github.com/Unknwon/goconfig/latest) [](http://gowalker.org/github.com/Unknwon/goconfig)
|
||||
========
|
||||
|
||||
[中文文档](README_ZH.md)
|
||||
|
||||
**IMPORTANT**
|
||||
|
||||
- This library is under bug fix only mode, which means no more features will be added.
|
||||
- I'm continuing working on better Go code with a different library: [ini](https://github.com/go-ini/ini).
|
||||
|
||||
## About
|
||||
|
||||
Package goconfig is a easy-use, comments-support configuration file parser for the Go Programming Language, which provides a structure similar to what you would find on Microsoft Windows INI files.
|
||||
|
||||
The configuration file consists of sections, led by a `[section]` header and followed by `name:value` or `name=value` entries. Note that leading whitespace is removed from values. The optional values can contain format strings which refer to other values in the same section, or values in a special DEFAULT section. Comments are indicated by ";" or "#"; comments may begin anywhere on a single line.
|
||||
|
||||
## Features
|
||||
|
||||
- It simplified operation processes, easy to use and undersatnd; therefore, there are less chances to have errors.
|
||||
- It uses exactly the same way to access a configuration file as you use Windows APIs, so you don't need to change your code style.
|
||||
- It supports read recursion sections.
|
||||
- It supports auto increment of key.
|
||||
- It supports **READ** and **WRITE** configuration file with comments each section or key which all the other parsers don't support!!!!!!!
|
||||
- It supports get value through type bool, float64, int, int64 and string, methods that start with "Must" means ignore errors and get zero-value if error occurs, or you can specify a default value.
|
||||
- It's able to load multiple files to overwrite key values.
|
||||
|
||||
## Installation
|
||||
|
||||
go get github.com/Unknwon/goconfig
|
||||
|
||||
Or
|
||||
|
||||
gopm get github.com/Unknwon/goconfig
|
||||
|
||||
## API Documentation
|
||||
|
||||
[Go Walker](http://gowalker.org/github.com/Unknwon/goconfig).
|
||||
|
||||
## Example
|
||||
|
||||
Please see [conf.ini](testdata/conf.ini) as an example.
|
||||
|
||||
### Usage
|
||||
|
||||
- Function `LoadConfigFile` load file(s) depends on your situation, and return a variable with type `ConfigFile`.
|
||||
- `GetValue` gives basic functionality of getting a value of given section and key.
|
||||
- Methods like `Bool`, `Int`, `Int64` return corresponding type of values.
|
||||
- Methods start with `Must` return corresponding type of values and returns zero-value of given type if something goes wrong.
|
||||
- `SetValue` sets value to given section and key, and inserts somewhere if it does not exist.
|
||||
- `DeleteKey` deletes by given section and key.
|
||||
- Finally, `SaveConfigFile` saves your configuration to local file system.
|
||||
- Use method `Reload` in case someone else modified your file(s).
|
||||
- Methods contains `Comment` help you manipulate comments.
|
||||
- `LoadFromReader` allows loading data without an intermediate file.
|
||||
- `SaveConfigData` added, which writes configuration to an arbitrary writer.
|
||||
- `ReloadData` allows to reload data from memory.
|
||||
|
||||
Note that you cannot mix in-memory configuration with on-disk configuration.
|
||||
|
||||
## More Information
|
||||
|
||||
- All characters are CASE SENSITIVE, BE CAREFUL!
|
||||
|
||||
## Credits
|
||||
|
||||
- [goconf](http://code.google.com/p/goconf/)
|
||||
- [robfig/config](https://github.com/robfig/config)
|
||||
- [Delete an item from a slice](https://groups.google.com/forum/?fromgroups=#!topic/golang-nuts/lYz8ftASMQ0)
|
||||
|
||||
## License
|
||||
|
||||
This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.
|
||||
64
vendor/github.com/Unknwon/goconfig/README_ZH.md
generated
vendored
Executable file
64
vendor/github.com/Unknwon/goconfig/README_ZH.md
generated
vendored
Executable file
@@ -0,0 +1,64 @@
|
||||
goconfig [](https://drone.io/github.com/Unknwon/goconfig/latest) [](http://gowalker.org/github.com/Unknwon/goconfig)
|
||||
========
|
||||
|
||||
本库已被 [《Go名库讲解》](https://github.com/Unknwon/go-rock-libraries-showcases/tree/master/lectures/01-goconfig) 收录讲解,欢迎前往学习如何使用!
|
||||
|
||||
编码规范:基于 [Go 编码规范](https://github.com/Unknwon/go-code-convention)
|
||||
|
||||
## 关于
|
||||
|
||||
包 goconfig 是一个易于使用,支持注释的 Go 语言配置文件解析器,该文件的书写格式和 Windows 下的 INI 文件一样。
|
||||
|
||||
配置文件由形为 `[section]` 的节构成,内部使用 `name:value` 或 `name=value` 这样的键值对;每行开头和尾部的空白符号都将被忽略;如果未指定任何节,则会默认放入名为 `DEFAULT` 的节当中;可以使用 “;” 或 “#” 来作为注释的开头,并可以放置于任意的单独一行中。
|
||||
|
||||
## 特性
|
||||
|
||||
- 简化流程,易于理解,更少出错。
|
||||
- 提供与 Windows API 一模一样的操作方式。
|
||||
- 支持读取递归节。
|
||||
- 支持自增键名。
|
||||
- 支持对注释的 **读** 和 **写** 操作,其它所有解析器都不支持!!!!
|
||||
- 可以直接返回 bool, float64, int, int64 和 string 类型的值,如果使用 “Must” 开头的方法,则一定会返回这个类型的一个值而不返回错误,如果错误发生则会返回零值。
|
||||
- 支持加载多个文件来重写值。
|
||||
|
||||
## 安装
|
||||
|
||||
go get github.com/Unknwon/goconfig
|
||||
|
||||
或
|
||||
|
||||
gopm get github.com/Unknwon/goconfig
|
||||
|
||||
|
||||
## API 文档
|
||||
|
||||
[Go Walker](http://gowalker.org/github.com/Unknwon/goconfig).
|
||||
|
||||
## 示例
|
||||
|
||||
请查看 [conf.ini](testdata/conf.ini) 文件作为使用示例。
|
||||
|
||||
### 用例
|
||||
|
||||
- 函数 `LoadConfigFile` 加载一个或多个文件,然后返回一个类型为 `ConfigFile` 的变量。
|
||||
- `GetValue` 可以简单的获取某个值。
|
||||
- 像 `Bool`、`Int`、`Int64` 这样的方法会直接返回指定类型的值。
|
||||
- 以 `Must` 开头的方法不会返回错误,但当错误发生时会返回零值。
|
||||
- `SetValue` 可以设置某个值。
|
||||
- `DeleteKey` 可以删除某个键。
|
||||
- 最后,`SaveConfigFile` 可以保持您的配置到本地文件系统。
|
||||
- 使用方法 `Reload` 可以重载您的配置文件。
|
||||
|
||||
## 更多信息
|
||||
|
||||
- 所有字符都是大小写敏感的!
|
||||
|
||||
## 参考信息
|
||||
|
||||
- [goconf](http://code.google.com/p/goconf/)
|
||||
- [robfig/config](https://github.com/robfig/config)
|
||||
- [Delete an item from a slice](https://groups.google.com/forum/?fromgroups=#!topic/golang-nuts/lYz8ftASMQ0)
|
||||
|
||||
## 授权许可
|
||||
|
||||
本项目采用 Apache v2 开源授权许可证,完整的授权说明已放置在 [LICENSE](LICENSE) 文件中。
|
||||
555
vendor/github.com/Unknwon/goconfig/conf.go
generated
vendored
Executable file
555
vendor/github.com/Unknwon/goconfig/conf.go
generated
vendored
Executable file
@@ -0,0 +1,555 @@
|
||||
// Copyright 2013 Unknwon
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
// Package goconfig is a fully functional and comments-support configuration file(.ini) parser.
|
||||
package goconfig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Default section name.
|
||||
DEFAULT_SECTION = "DEFAULT"
|
||||
// Maximum allowed depth when recursively substituing variable names.
|
||||
_DEPTH_VALUES = 200
|
||||
)
|
||||
|
||||
type ParseError int
|
||||
|
||||
const (
|
||||
ERR_SECTION_NOT_FOUND ParseError = iota + 1
|
||||
ERR_KEY_NOT_FOUND
|
||||
ERR_BLANK_SECTION_NAME
|
||||
ERR_COULD_NOT_PARSE
|
||||
)
|
||||
|
||||
var LineBreak = "\n"
|
||||
|
||||
// Variable regexp pattern: %(variable)s
|
||||
var varPattern = regexp.MustCompile(`%\(([^\)]+)\)s`)
|
||||
|
||||
func init() {
|
||||
if runtime.GOOS == "windows" {
|
||||
LineBreak = "\r\n"
|
||||
}
|
||||
}
|
||||
|
||||
// A ConfigFile represents a INI formar configuration file.
|
||||
type ConfigFile struct {
|
||||
lock sync.RWMutex // Go map is not safe.
|
||||
fileNames []string // Support mutil-files.
|
||||
data map[string]map[string]string // Section -> key : value
|
||||
|
||||
// Lists can keep sections and keys in order.
|
||||
sectionList []string // Section name list.
|
||||
keyList map[string][]string // Section -> Key name list
|
||||
|
||||
sectionComments map[string]string // Sections comments.
|
||||
keyComments map[string]map[string]string // Keys comments.
|
||||
BlockMode bool // Indicates whether use lock or not.
|
||||
}
|
||||
|
||||
// newConfigFile creates an empty configuration representation.
|
||||
func newConfigFile(fileNames []string) *ConfigFile {
|
||||
c := new(ConfigFile)
|
||||
c.fileNames = fileNames
|
||||
c.data = make(map[string]map[string]string)
|
||||
c.keyList = make(map[string][]string)
|
||||
c.sectionComments = make(map[string]string)
|
||||
c.keyComments = make(map[string]map[string]string)
|
||||
c.BlockMode = true
|
||||
return c
|
||||
}
|
||||
|
||||
// SetValue adds a new section-key-value to the configuration.
|
||||
// It returns true if the key and value were inserted,
|
||||
// or returns false if the value was overwritten.
|
||||
// If the section does not exist in advance, it will be created.
|
||||
func (c *ConfigFile) SetValue(section, key, value string) bool {
|
||||
// Blank section name represents DEFAULT section.
|
||||
if len(section) == 0 {
|
||||
section = DEFAULT_SECTION
|
||||
}
|
||||
if len(key) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if c.BlockMode {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
}
|
||||
|
||||
// Check if section exists.
|
||||
if _, ok := c.data[section]; !ok {
|
||||
// Execute add operation.
|
||||
c.data[section] = make(map[string]string)
|
||||
// Append section to list.
|
||||
c.sectionList = append(c.sectionList, section)
|
||||
}
|
||||
|
||||
// Check if key exists.
|
||||
_, ok := c.data[section][key]
|
||||
c.data[section][key] = value
|
||||
if !ok {
|
||||
// If not exists, append to key list.
|
||||
c.keyList[section] = append(c.keyList[section], key)
|
||||
}
|
||||
return !ok
|
||||
}
|
||||
|
||||
// DeleteKey deletes the key in given section.
|
||||
// It returns true if the key was deleted,
|
||||
// or returns false if the section or key didn't exist.
|
||||
func (c *ConfigFile) DeleteKey(section, key string) bool {
|
||||
// Blank section name represents DEFAULT section.
|
||||
if len(section) == 0 {
|
||||
section = DEFAULT_SECTION
|
||||
}
|
||||
|
||||
if c.BlockMode {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
}
|
||||
|
||||
// Check if section exists.
|
||||
if _, ok := c.data[section]; !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if key exists.
|
||||
if _, ok := c.data[section][key]; ok {
|
||||
delete(c.data[section], key)
|
||||
// Remove comments of key.
|
||||
c.SetKeyComments(section, key, "")
|
||||
// Get index of key.
|
||||
i := 0
|
||||
for _, keyName := range c.keyList[section] {
|
||||
if keyName == key {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
// Remove from key list.
|
||||
c.keyList[section] = append(c.keyList[section][:i], c.keyList[section][i+1:]...)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetValue returns the value of key available in the given section.
|
||||
// If the value needs to be unfolded
|
||||
// (see e.g. %(google)s example in the GoConfig_test.go),
|
||||
// then String does this unfolding automatically, up to
|
||||
// _DEPTH_VALUES number of iterations.
|
||||
// It returns an error and empty string value if the section does not exist,
|
||||
// or key does not exist in DEFAULT and current sections.
|
||||
func (c *ConfigFile) GetValue(section, key string) (string, error) {
|
||||
if c.BlockMode {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
}
|
||||
|
||||
// Blank section name represents DEFAULT section.
|
||||
if len(section) == 0 {
|
||||
section = DEFAULT_SECTION
|
||||
}
|
||||
|
||||
// Check if section exists
|
||||
if _, ok := c.data[section]; !ok {
|
||||
// Section does not exist.
|
||||
return "", getError{ERR_SECTION_NOT_FOUND, section}
|
||||
}
|
||||
|
||||
// Section exists.
|
||||
// Check if key exists or empty value.
|
||||
value, ok := c.data[section][key]
|
||||
if !ok {
|
||||
// Check if it is a sub-section.
|
||||
if i := strings.LastIndex(section, "."); i > -1 {
|
||||
return c.GetValue(section[:i], key)
|
||||
}
|
||||
|
||||
// Return empty value.
|
||||
return "", getError{ERR_KEY_NOT_FOUND, key}
|
||||
}
|
||||
|
||||
// Key exists.
|
||||
var i int
|
||||
for i = 0; i < _DEPTH_VALUES; i++ {
|
||||
vr := varPattern.FindString(value)
|
||||
if len(vr) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// Take off leading '%(' and trailing ')s'.
|
||||
noption := strings.TrimLeft(vr, "%(")
|
||||
noption = strings.TrimRight(noption, ")s")
|
||||
|
||||
// Search variable in default section.
|
||||
nvalue, err := c.GetValue(DEFAULT_SECTION, noption)
|
||||
if err != nil && section != DEFAULT_SECTION {
|
||||
// Search in the same section.
|
||||
if _, ok := c.data[section][noption]; ok {
|
||||
nvalue = c.data[section][noption]
|
||||
}
|
||||
}
|
||||
|
||||
// Substitute by new value and take off leading '%(' and trailing ')s'.
|
||||
value = strings.Replace(value, vr, nvalue, -1)
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// Bool returns bool type value.
|
||||
func (c *ConfigFile) Bool(section, key string) (bool, error) {
|
||||
value, err := c.GetValue(section, key)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return strconv.ParseBool(value)
|
||||
}
|
||||
|
||||
// Float64 returns float64 type value.
|
||||
func (c *ConfigFile) Float64(section, key string) (float64, error) {
|
||||
value, err := c.GetValue(section, key)
|
||||
if err != nil {
|
||||
return 0.0, err
|
||||
}
|
||||
return strconv.ParseFloat(value, 64)
|
||||
}
|
||||
|
||||
// Int returns int type value.
|
||||
func (c *ConfigFile) Int(section, key string) (int, error) {
|
||||
value, err := c.GetValue(section, key)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return strconv.Atoi(value)
|
||||
}
|
||||
|
||||
// Int64 returns int64 type value.
|
||||
func (c *ConfigFile) Int64(section, key string) (int64, error) {
|
||||
value, err := c.GetValue(section, key)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return strconv.ParseInt(value, 10, 64)
|
||||
}
|
||||
|
||||
// MustValue always returns value without error.
|
||||
// It returns empty string if error occurs, or the default value if given.
|
||||
func (c *ConfigFile) MustValue(section, key string, defaultVal ...string) string {
|
||||
val, err := c.GetValue(section, key)
|
||||
if len(defaultVal) > 0 && (err != nil || len(val) == 0) {
|
||||
return defaultVal[0]
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// MustValue always returns value without error,
|
||||
// It returns empty string if error occurs, or the default value if given,
|
||||
// and a bool value indicates whether default value is returned.
|
||||
func (c *ConfigFile) MustValueSet(section, key string, defaultVal ...string) (string, bool) {
|
||||
val, err := c.GetValue(section, key)
|
||||
if len(defaultVal) > 0 && (err != nil || len(val) == 0) {
|
||||
c.SetValue(section, key, defaultVal[0])
|
||||
return defaultVal[0], true
|
||||
}
|
||||
return val, false
|
||||
}
|
||||
|
||||
// MustValueRange always returns value without error,
|
||||
// it returns default value if error occurs or doesn't fit into range.
|
||||
func (c *ConfigFile) MustValueRange(section, key, defaultVal string, candidates []string) string {
|
||||
val, err := c.GetValue(section, key)
|
||||
if err != nil || len(val) == 0 {
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
for _, cand := range candidates {
|
||||
if val == cand {
|
||||
return val
|
||||
}
|
||||
}
|
||||
return defaultVal
|
||||
}
|
||||
|
||||
// MustValueArray always returns value array without error,
|
||||
// it returns empty array if error occurs, split by delimiter otherwise.
|
||||
func (c *ConfigFile) MustValueArray(section, key, delim string) []string {
|
||||
val, err := c.GetValue(section, key)
|
||||
if err != nil || len(val) == 0 {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
vals := strings.Split(val, delim)
|
||||
for i := range vals {
|
||||
vals[i] = strings.TrimSpace(vals[i])
|
||||
}
|
||||
return vals
|
||||
}
|
||||
|
||||
// MustBool always returns value without error,
|
||||
// it returns false if error occurs.
|
||||
func (c *ConfigFile) MustBool(section, key string, defaultVal ...bool) bool {
|
||||
val, err := c.Bool(section, key)
|
||||
if len(defaultVal) > 0 && err != nil {
|
||||
return defaultVal[0]
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// MustFloat64 always returns value without error,
|
||||
// it returns 0.0 if error occurs.
|
||||
func (c *ConfigFile) MustFloat64(section, key string, defaultVal ...float64) float64 {
|
||||
value, err := c.Float64(section, key)
|
||||
if len(defaultVal) > 0 && err != nil {
|
||||
return defaultVal[0]
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// MustInt always returns value without error,
|
||||
// it returns 0 if error occurs.
|
||||
func (c *ConfigFile) MustInt(section, key string, defaultVal ...int) int {
|
||||
value, err := c.Int(section, key)
|
||||
if len(defaultVal) > 0 && err != nil {
|
||||
return defaultVal[0]
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// MustInt64 always returns value without error,
|
||||
// it returns 0 if error occurs.
|
||||
func (c *ConfigFile) MustInt64(section, key string, defaultVal ...int64) int64 {
|
||||
value, err := c.Int64(section, key)
|
||||
if len(defaultVal) > 0 && err != nil {
|
||||
return defaultVal[0]
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// GetSectionList returns the list of all sections
|
||||
// in the same order in the file.
|
||||
func (c *ConfigFile) GetSectionList() []string {
|
||||
list := make([]string, len(c.sectionList))
|
||||
copy(list, c.sectionList)
|
||||
return list
|
||||
}
|
||||
|
||||
// GetKeyList returns the list of all keys in give section
|
||||
// in the same order in the file.
|
||||
// It returns nil if given section does not exist.
|
||||
func (c *ConfigFile) GetKeyList(section string) []string {
|
||||
// Blank section name represents DEFAULT section.
|
||||
if len(section) == 0 {
|
||||
section = DEFAULT_SECTION
|
||||
}
|
||||
|
||||
if c.BlockMode {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
}
|
||||
|
||||
// Check if section exists.
|
||||
if _, ok := c.data[section]; !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Non-default section has a blank key as section keeper.
|
||||
list := make([]string, 0, len(c.keyList[section]))
|
||||
for _, key := range c.keyList[section] {
|
||||
if key != " " {
|
||||
list = append(list, key)
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// DeleteSection deletes the entire section by given name.
|
||||
// It returns true if the section was deleted, and false if the section didn't exist.
|
||||
func (c *ConfigFile) DeleteSection(section string) bool {
|
||||
// Blank section name represents DEFAULT section.
|
||||
if len(section) == 0 {
|
||||
section = DEFAULT_SECTION
|
||||
}
|
||||
|
||||
if c.BlockMode {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
}
|
||||
|
||||
// Check if section exists.
|
||||
if _, ok := c.data[section]; !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
delete(c.data, section)
|
||||
// Remove comments of section.
|
||||
c.SetSectionComments(section, "")
|
||||
// Get index of section.
|
||||
i := 0
|
||||
for _, secName := range c.sectionList {
|
||||
if secName == section {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
// Remove from section and key list.
|
||||
c.sectionList = append(c.sectionList[:i], c.sectionList[i+1:]...)
|
||||
delete(c.keyList, section)
|
||||
return true
|
||||
}
|
||||
|
||||
// GetSection returns key-value pairs in given section.
|
||||
// If section does not exist, returns nil and error.
|
||||
func (c *ConfigFile) GetSection(section string) (map[string]string, error) {
|
||||
// Blank section name represents DEFAULT section.
|
||||
if len(section) == 0 {
|
||||
section = DEFAULT_SECTION
|
||||
}
|
||||
|
||||
if c.BlockMode {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
}
|
||||
|
||||
// Check if section exists.
|
||||
if _, ok := c.data[section]; !ok {
|
||||
// Section does not exist.
|
||||
return nil, getError{ERR_SECTION_NOT_FOUND, section}
|
||||
}
|
||||
|
||||
// Remove pre-defined key.
|
||||
secMap := c.data[section]
|
||||
delete(c.data[section], " ")
|
||||
|
||||
// Section exists.
|
||||
return secMap, nil
|
||||
}
|
||||
|
||||
// SetSectionComments adds new section comments to the configuration.
|
||||
// If comments are empty(0 length), it will remove its section comments!
|
||||
// It returns true if the comments were inserted or removed,
|
||||
// or returns false if the comments were overwritten.
|
||||
func (c *ConfigFile) SetSectionComments(section, comments string) bool {
|
||||
// Blank section name represents DEFAULT section.
|
||||
if len(section) == 0 {
|
||||
section = DEFAULT_SECTION
|
||||
}
|
||||
|
||||
if len(comments) == 0 {
|
||||
if _, ok := c.sectionComments[section]; ok {
|
||||
delete(c.sectionComments, section)
|
||||
}
|
||||
|
||||
// Not exists can be seen as remove.
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if comments exists.
|
||||
_, ok := c.sectionComments[section]
|
||||
if comments[0] != '#' && comments[0] != ';' {
|
||||
comments = "; " + comments
|
||||
}
|
||||
c.sectionComments[section] = comments
|
||||
return !ok
|
||||
}
|
||||
|
||||
// SetKeyComments adds new section-key comments to the configuration.
|
||||
// If comments are empty(0 length), it will remove its section-key comments!
|
||||
// It returns true if the comments were inserted or removed,
|
||||
// or returns false if the comments were overwritten.
|
||||
// If the section does not exist in advance, it is created.
|
||||
func (c *ConfigFile) SetKeyComments(section, key, comments string) bool {
|
||||
// Blank section name represents DEFAULT section.
|
||||
if len(section) == 0 {
|
||||
section = DEFAULT_SECTION
|
||||
}
|
||||
|
||||
// Check if section exists.
|
||||
if _, ok := c.keyComments[section]; ok {
|
||||
if len(comments) == 0 {
|
||||
if _, ok := c.keyComments[section][key]; ok {
|
||||
delete(c.keyComments[section], key)
|
||||
}
|
||||
|
||||
// Not exists can be seen as remove.
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if len(comments) == 0 {
|
||||
// Not exists can be seen as remove.
|
||||
return true
|
||||
} else {
|
||||
// Execute add operation.
|
||||
c.keyComments[section] = make(map[string]string)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if key exists.
|
||||
_, ok := c.keyComments[section][key]
|
||||
if comments[0] != '#' && comments[0] != ';' {
|
||||
comments = "; " + comments
|
||||
}
|
||||
c.keyComments[section][key] = comments
|
||||
return !ok
|
||||
}
|
||||
|
||||
// GetSectionComments returns the comments in the given section.
|
||||
// It returns an empty string(0 length) if the comments do not exist.
|
||||
func (c *ConfigFile) GetSectionComments(section string) (comments string) {
|
||||
// Blank section name represents DEFAULT section.
|
||||
if len(section) == 0 {
|
||||
section = DEFAULT_SECTION
|
||||
}
|
||||
return c.sectionComments[section]
|
||||
}
|
||||
|
||||
// GetKeyComments returns the comments of key in the given section.
|
||||
// It returns an empty string(0 length) if the comments do not exist.
|
||||
func (c *ConfigFile) GetKeyComments(section, key string) (comments string) {
|
||||
// Blank section name represents DEFAULT section.
|
||||
if len(section) == 0 {
|
||||
section = DEFAULT_SECTION
|
||||
}
|
||||
|
||||
if _, ok := c.keyComments[section]; ok {
|
||||
return c.keyComments[section][key]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// getError occurs when get value in configuration file with invalid parameter.
|
||||
type getError struct {
|
||||
Reason ParseError
|
||||
Name string
|
||||
}
|
||||
|
||||
// Error implements Error interface.
|
||||
func (err getError) Error() string {
|
||||
switch err.Reason {
|
||||
case ERR_SECTION_NOT_FOUND:
|
||||
return fmt.Sprintf("section '%s' not found", err.Name)
|
||||
case ERR_KEY_NOT_FOUND:
|
||||
return fmt.Sprintf("key '%s' not found", err.Name)
|
||||
}
|
||||
return "invalid get error"
|
||||
}
|
||||
294
vendor/github.com/Unknwon/goconfig/read.go
generated
vendored
Executable file
294
vendor/github.com/Unknwon/goconfig/read.go
generated
vendored
Executable file
@@ -0,0 +1,294 @@
|
||||
// Copyright 2013 Unknwon
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package goconfig
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Read reads an io.Reader and returns a configuration representation.
|
||||
// This representation can be queried with GetValue.
|
||||
func (c *ConfigFile) read(reader io.Reader) (err error) {
|
||||
buf := bufio.NewReader(reader)
|
||||
|
||||
// Handle BOM-UTF8.
|
||||
// http://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding
|
||||
mask, err := buf.Peek(3)
|
||||
if err == nil && len(mask) >= 3 &&
|
||||
mask[0] == 239 && mask[1] == 187 && mask[2] == 191 {
|
||||
buf.Read(mask)
|
||||
}
|
||||
|
||||
count := 1 // Counter for auto increment.
|
||||
// Current section name.
|
||||
section := DEFAULT_SECTION
|
||||
var comments string
|
||||
// Parse line-by-line
|
||||
for {
|
||||
line, err := buf.ReadString('\n')
|
||||
line = strings.TrimSpace(line)
|
||||
lineLengh := len(line) //[SWH|+]
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
return err
|
||||
}
|
||||
|
||||
// Reached end of file, if nothing to read then break,
|
||||
// otherwise handle the last line.
|
||||
if lineLengh == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// switch written for readability (not performance)
|
||||
switch {
|
||||
case lineLengh == 0: // Empty line
|
||||
continue
|
||||
case line[0] == '#' || line[0] == ';': // Comment
|
||||
// Append comments
|
||||
if len(comments) == 0 {
|
||||
comments = line
|
||||
} else {
|
||||
comments += LineBreak + line
|
||||
}
|
||||
continue
|
||||
case line[0] == '[' && line[lineLengh-1] == ']': // New sction.
|
||||
// Get section name.
|
||||
section = strings.TrimSpace(line[1 : lineLengh-1])
|
||||
// Set section comments and empty if it has comments.
|
||||
if len(comments) > 0 {
|
||||
c.SetSectionComments(section, comments)
|
||||
comments = ""
|
||||
}
|
||||
// Make section exist even though it does not have any key.
|
||||
c.SetValue(section, " ", " ")
|
||||
// Reset counter.
|
||||
count = 1
|
||||
continue
|
||||
case section == "": // No section defined so far
|
||||
return readError{ERR_BLANK_SECTION_NAME, line}
|
||||
default: // Other alternatives
|
||||
var (
|
||||
i int
|
||||
keyQuote string
|
||||
key string
|
||||
valQuote string
|
||||
value string
|
||||
)
|
||||
//[SWH|+]:支持引号包围起来的字串
|
||||
if line[0] == '"' {
|
||||
if lineLengh >= 6 && line[0:3] == `"""` {
|
||||
keyQuote = `"""`
|
||||
} else {
|
||||
keyQuote = `"`
|
||||
}
|
||||
} else if line[0] == '`' {
|
||||
keyQuote = "`"
|
||||
}
|
||||
if keyQuote != "" {
|
||||
qLen := len(keyQuote)
|
||||
pos := strings.Index(line[qLen:], keyQuote)
|
||||
if pos == -1 {
|
||||
return readError{ERR_COULD_NOT_PARSE, line}
|
||||
}
|
||||
pos = pos + qLen
|
||||
i = strings.IndexAny(line[pos:], "=:")
|
||||
if i <= 0 {
|
||||
return readError{ERR_COULD_NOT_PARSE, line}
|
||||
}
|
||||
i = i + pos
|
||||
key = line[qLen:pos] //保留引号内的两端的空格
|
||||
} else {
|
||||
i = strings.IndexAny(line, "=:")
|
||||
if i <= 0 {
|
||||
return readError{ERR_COULD_NOT_PARSE, line}
|
||||
}
|
||||
key = strings.TrimSpace(line[0:i])
|
||||
}
|
||||
//[SWH|+];
|
||||
|
||||
// Check if it needs auto increment.
|
||||
if key == "-" {
|
||||
key = "#" + fmt.Sprint(count)
|
||||
count++
|
||||
}
|
||||
|
||||
//[SWH|+]:支持引号包围起来的字串
|
||||
lineRight := strings.TrimSpace(line[i+1:])
|
||||
lineRightLength := len(lineRight)
|
||||
firstChar := ""
|
||||
if lineRightLength >= 2 {
|
||||
firstChar = lineRight[0:1]
|
||||
}
|
||||
if firstChar == "`" {
|
||||
valQuote = "`"
|
||||
} else if lineRightLength >= 6 && lineRight[0:3] == `"""` {
|
||||
valQuote = `"""`
|
||||
}
|
||||
if valQuote != "" {
|
||||
qLen := len(valQuote)
|
||||
pos := strings.LastIndex(lineRight[qLen:], valQuote)
|
||||
if pos == -1 {
|
||||
return readError{ERR_COULD_NOT_PARSE, line}
|
||||
}
|
||||
pos = pos + qLen
|
||||
value = lineRight[qLen:pos]
|
||||
} else {
|
||||
value = strings.TrimSpace(lineRight[0:])
|
||||
}
|
||||
//[SWH|+];
|
||||
|
||||
c.SetValue(section, key, value)
|
||||
// Set key comments and empty if it has comments.
|
||||
if len(comments) > 0 {
|
||||
c.SetKeyComments(section, key, comments)
|
||||
comments = ""
|
||||
}
|
||||
}
|
||||
|
||||
// Reached end of file.
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadFromData accepts raw data directly from memory
|
||||
// and returns a new configuration representation.
|
||||
// Note that the configuration is written to the system
|
||||
// temporary folder, so your file should not contain
|
||||
// sensitive information.
|
||||
func LoadFromData(data []byte) (c *ConfigFile, err error) {
|
||||
// Save memory data to temporary file to support further operations.
|
||||
tmpName := path.Join(os.TempDir(), "goconfig", fmt.Sprintf("%d", time.Now().Nanosecond()))
|
||||
if err = os.MkdirAll(path.Dir(tmpName), os.ModePerm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = ioutil.WriteFile(tmpName, data, 0655); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c = newConfigFile([]string{tmpName})
|
||||
err = c.read(bytes.NewBuffer(data))
|
||||
return c, err
|
||||
}
|
||||
|
||||
// LoadFromReader accepts raw data directly from a reader
|
||||
// and returns a new configuration representation.
|
||||
// You must use ReloadData to reload.
|
||||
// You cannot append files a configfile read this way.
|
||||
func LoadFromReader(in io.Reader) (c *ConfigFile, err error) {
|
||||
c = newConfigFile([]string{""})
|
||||
err = c.read(in)
|
||||
return c, err
|
||||
}
|
||||
|
||||
func (c *ConfigFile) loadFile(fileName string) (err error) {
|
||||
f, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return c.read(f)
|
||||
}
|
||||
|
||||
// LoadConfigFile reads a file and returns a new configuration representation.
|
||||
// This representation can be queried with GetValue.
|
||||
func LoadConfigFile(fileName string, moreFiles ...string) (c *ConfigFile, err error) {
|
||||
// Append files' name together.
|
||||
fileNames := make([]string, 1, len(moreFiles)+1)
|
||||
fileNames[0] = fileName
|
||||
if len(moreFiles) > 0 {
|
||||
fileNames = append(fileNames, moreFiles...)
|
||||
}
|
||||
|
||||
c = newConfigFile(fileNames)
|
||||
|
||||
for _, name := range fileNames {
|
||||
if err = c.loadFile(name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Reload reloads configuration file in case it has changes.
|
||||
func (c *ConfigFile) Reload() (err error) {
|
||||
var cfg *ConfigFile
|
||||
if len(c.fileNames) == 1 {
|
||||
if c.fileNames[0] == "" {
|
||||
return fmt.Errorf("file opened from in-memory data, use ReloadData to reload")
|
||||
}
|
||||
cfg, err = LoadConfigFile(c.fileNames[0])
|
||||
} else {
|
||||
cfg, err = LoadConfigFile(c.fileNames[0], c.fileNames[1:]...)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
*c = *cfg
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ReloadData reloads configuration file from memory
|
||||
func (c *ConfigFile) ReloadData(in io.Reader) (err error) {
|
||||
var cfg *ConfigFile
|
||||
if len(c.fileNames) != 1 {
|
||||
return fmt.Errorf("Multiple files loaded, unable to mix in-memory and file data")
|
||||
}
|
||||
|
||||
cfg, err = LoadFromReader(in)
|
||||
if err == nil {
|
||||
*c = *cfg
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// AppendFiles appends more files to ConfigFile and reload automatically.
|
||||
func (c *ConfigFile) AppendFiles(files ...string) error {
|
||||
if len(c.fileNames) == 1 && c.fileNames[0] == "" {
|
||||
return fmt.Errorf("Cannot append file data to in-memory data")
|
||||
}
|
||||
c.fileNames = append(c.fileNames, files...)
|
||||
return c.Reload()
|
||||
}
|
||||
|
||||
// readError occurs when read configuration file with wrong format.
|
||||
type readError struct {
|
||||
Reason ParseError
|
||||
Content string // Line content
|
||||
}
|
||||
|
||||
// Error implement Error interface.
|
||||
func (err readError) Error() string {
|
||||
switch err.Reason {
|
||||
case ERR_BLANK_SECTION_NAME:
|
||||
return "empty section name not allowed"
|
||||
case ERR_COULD_NOT_PARSE:
|
||||
return fmt.Sprintf("could not parse line: %s", string(err.Content))
|
||||
}
|
||||
return "invalid read error"
|
||||
}
|
||||
117
vendor/github.com/Unknwon/goconfig/write.go
generated
vendored
Executable file
117
vendor/github.com/Unknwon/goconfig/write.go
generated
vendored
Executable file
@@ -0,0 +1,117 @@
|
||||
// Copyright 2013 Unknwon
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package goconfig
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Write spaces around "=" to look better.
|
||||
var PrettyFormat = true
|
||||
|
||||
// SaveConfigData writes configuration to a writer
|
||||
func SaveConfigData(c *ConfigFile, out io.Writer) (err error) {
|
||||
equalSign := "="
|
||||
if PrettyFormat {
|
||||
equalSign = " = "
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
for _, section := range c.sectionList {
|
||||
// Write section comments.
|
||||
if len(c.GetSectionComments(section)) > 0 {
|
||||
if _, err = buf.WriteString(c.GetSectionComments(section) + LineBreak); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if section != DEFAULT_SECTION {
|
||||
// Write section name.
|
||||
if _, err = buf.WriteString("[" + section + "]" + LineBreak); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, key := range c.keyList[section] {
|
||||
if key != " " {
|
||||
// Write key comments.
|
||||
if len(c.GetKeyComments(section, key)) > 0 {
|
||||
if _, err = buf.WriteString(c.GetKeyComments(section, key) + LineBreak); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
keyName := key
|
||||
// Check if it's auto increment.
|
||||
if keyName[0] == '#' {
|
||||
keyName = "-"
|
||||
}
|
||||
//[SWH|+]:支持键名包含等号和冒号
|
||||
if strings.Contains(keyName, `=`) || strings.Contains(keyName, `:`) {
|
||||
if strings.Contains(keyName, "`") {
|
||||
if strings.Contains(keyName, `"`) {
|
||||
keyName = `"""` + keyName + `"""`
|
||||
} else {
|
||||
keyName = `"` + keyName + `"`
|
||||
}
|
||||
} else {
|
||||
keyName = "`" + keyName + "`"
|
||||
}
|
||||
}
|
||||
value := c.data[section][key]
|
||||
// In case key value contains "`" or "\"".
|
||||
if strings.Contains(value, "`") {
|
||||
if strings.Contains(value, `"`) {
|
||||
value = `"""` + value + `"""`
|
||||
} else {
|
||||
value = `"` + value + `"`
|
||||
}
|
||||
}
|
||||
|
||||
// Write key and value.
|
||||
if _, err = buf.WriteString(keyName + equalSign + value + LineBreak); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Put a line between sections.
|
||||
if _, err = buf.WriteString(LineBreak); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := buf.WriteTo(out); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SaveConfigFile writes configuration file to local file system
|
||||
func SaveConfigFile(c *ConfigFile, filename string) (err error) {
|
||||
// Write configuration file by filename.
|
||||
var f *os.File
|
||||
if f, err = os.Create(filename); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := SaveConfigData(c, f); err != nil {
|
||||
return err
|
||||
}
|
||||
return f.Close()
|
||||
}
|
||||
178
vendor/github.com/abbot/go-http-auth/LICENSE
generated
vendored
Executable file
178
vendor/github.com/abbot/go-http-auth/LICENSE
generated
vendored
Executable file
@@ -0,0 +1,178 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
12
vendor/github.com/abbot/go-http-auth/Makefile
generated
vendored
Executable file
12
vendor/github.com/abbot/go-http-auth/Makefile
generated
vendored
Executable file
@@ -0,0 +1,12 @@
|
||||
include $(GOROOT)/src/Make.inc
|
||||
|
||||
TARG=auth_digest
|
||||
GOFILES=\
|
||||
auth.go\
|
||||
digest.go\
|
||||
basic.go\
|
||||
misc.go\
|
||||
md5crypt.go\
|
||||
users.go\
|
||||
|
||||
include $(GOROOT)/src/Make.pkg
|
||||
71
vendor/github.com/abbot/go-http-auth/README.md
generated
vendored
Executable file
71
vendor/github.com/abbot/go-http-auth/README.md
generated
vendored
Executable file
@@ -0,0 +1,71 @@
|
||||
HTTP Authentication implementation in Go
|
||||
========================================
|
||||
|
||||
This is an implementation of HTTP Basic and HTTP Digest authentication
|
||||
in Go language. It is designed as a simple wrapper for
|
||||
http.RequestHandler functions.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
* Supports HTTP Basic and HTTP Digest authentication.
|
||||
* Supports htpasswd and htdigest formatted files.
|
||||
* Automatic reloading of password files.
|
||||
* Pluggable interface for user/password storage.
|
||||
* Supports MD5, SHA1 and BCrypt for Basic authentication password storage.
|
||||
* Configurable Digest nonce cache size with expiration.
|
||||
* Wrapper for legacy http handlers (http.HandlerFunc interface)
|
||||
|
||||
Example usage
|
||||
-------------
|
||||
|
||||
This is a complete working example for Basic auth:
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
auth "github.com/abbot/go-http-auth"
|
||||
)
|
||||
|
||||
func Secret(user, realm string) string {
|
||||
if user == "john" {
|
||||
// password is "hello"
|
||||
return "$1$dlPL2MqE$oQmn16q49SqdmhenQuNgs1"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func handle(w http.ResponseWriter, r *auth.AuthenticatedRequest) {
|
||||
fmt.Fprintf(w, "<html><body><h1>Hello, %s!</h1></body></html>", r.Username)
|
||||
}
|
||||
|
||||
func main() {
|
||||
authenticator := auth.NewBasicAuthenticator("example.com", Secret)
|
||||
http.HandleFunc("/", authenticator.Wrap(handle))
|
||||
http.ListenAndServe(":8080", nil)
|
||||
}
|
||||
|
||||
See more examples in the "examples" directory.
|
||||
|
||||
Legal
|
||||
-----
|
||||
|
||||
This module is developed under Apache 2.0 license, and can be used for
|
||||
open and proprietary projects.
|
||||
|
||||
Copyright 2012-2013 Lev Shamardin
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you
|
||||
may not use this file or any other part of this project except in
|
||||
compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied. See the License for the specific language governing
|
||||
permissions and limitations under the License.
|
||||
109
vendor/github.com/abbot/go-http-auth/auth.go
generated
vendored
Executable file
109
vendor/github.com/abbot/go-http-auth/auth.go
generated
vendored
Executable file
@@ -0,0 +1,109 @@
|
||||
// Package auth is an implementation of HTTP Basic and HTTP Digest authentication.
|
||||
package auth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
/*
|
||||
Request handlers must take AuthenticatedRequest instead of http.Request
|
||||
*/
|
||||
type AuthenticatedRequest struct {
|
||||
http.Request
|
||||
/*
|
||||
Authenticated user name. Current API implies that Username is
|
||||
never empty, which means that authentication is always done
|
||||
before calling the request handler.
|
||||
*/
|
||||
Username string
|
||||
}
|
||||
|
||||
/*
|
||||
AuthenticatedHandlerFunc is like http.HandlerFunc, but takes
|
||||
AuthenticatedRequest instead of http.Request
|
||||
*/
|
||||
type AuthenticatedHandlerFunc func(http.ResponseWriter, *AuthenticatedRequest)
|
||||
|
||||
/*
|
||||
Authenticator wraps an AuthenticatedHandlerFunc with
|
||||
authentication-checking code.
|
||||
|
||||
Typical Authenticator usage is something like:
|
||||
|
||||
authenticator := SomeAuthenticator(...)
|
||||
http.HandleFunc("/", authenticator(my_handler))
|
||||
|
||||
Authenticator wrapper checks the user authentication and calls the
|
||||
wrapped function only after authentication has succeeded. Otherwise,
|
||||
it returns a handler which initiates the authentication procedure.
|
||||
*/
|
||||
type Authenticator func(AuthenticatedHandlerFunc) http.HandlerFunc
|
||||
|
||||
// Info contains authentication information for the request.
|
||||
type Info struct {
|
||||
// Authenticated is set to true when request was authenticated
|
||||
// successfully, i.e. username and password passed in request did
|
||||
// pass the check.
|
||||
Authenticated bool
|
||||
|
||||
// Username contains a user name passed in the request when
|
||||
// Authenticated is true. It's value is undefined if Authenticated
|
||||
// is false.
|
||||
Username string
|
||||
|
||||
// ResponseHeaders contains extra headers that must be set by server
|
||||
// when sending back HTTP response.
|
||||
ResponseHeaders http.Header
|
||||
}
|
||||
|
||||
// UpdateHeaders updates headers with this Info's ResponseHeaders. It is
|
||||
// safe to call this function on nil Info.
|
||||
func (i *Info) UpdateHeaders(headers http.Header) {
|
||||
if i == nil {
|
||||
return
|
||||
}
|
||||
for k, values := range i.ResponseHeaders {
|
||||
for _, v := range values {
|
||||
headers.Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type key int // used for context keys
|
||||
|
||||
var infoKey key = 0
|
||||
|
||||
type AuthenticatorInterface interface {
|
||||
// NewContext returns a new context carrying authentication
|
||||
// information extracted from the request.
|
||||
NewContext(ctx context.Context, r *http.Request) context.Context
|
||||
|
||||
// Wrap returns an http.HandlerFunc which wraps
|
||||
// AuthenticatedHandlerFunc with this authenticator's
|
||||
// authentication checks.
|
||||
Wrap(AuthenticatedHandlerFunc) http.HandlerFunc
|
||||
}
|
||||
|
||||
// FromContext returns authentication information from the context or
|
||||
// nil if no such information present.
|
||||
func FromContext(ctx context.Context) *Info {
|
||||
info, ok := ctx.Value(infoKey).(*Info)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
// AuthUsernameHeader is the header set by JustCheck functions. It
|
||||
// contains an authenticated username (if authentication was
|
||||
// successful).
|
||||
const AuthUsernameHeader = "X-Authenticated-Username"
|
||||
|
||||
func JustCheck(auth AuthenticatorInterface, wrapped http.HandlerFunc) http.HandlerFunc {
|
||||
return auth.Wrap(func(w http.ResponseWriter, ar *AuthenticatedRequest) {
|
||||
ar.Header.Set(AuthUsernameHeader, ar.Username)
|
||||
wrapped(w, &ar.Request)
|
||||
})
|
||||
}
|
||||
163
vendor/github.com/abbot/go-http-auth/basic.go
generated
vendored
Executable file
163
vendor/github.com/abbot/go-http-auth/basic.go
generated
vendored
Executable file
@@ -0,0 +1,163 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type compareFunc func(hashedPassword, password []byte) error
|
||||
|
||||
var (
|
||||
errMismatchedHashAndPassword = errors.New("mismatched hash and password")
|
||||
|
||||
compareFuncs = []struct {
|
||||
prefix string
|
||||
compare compareFunc
|
||||
}{
|
||||
{"", compareMD5HashAndPassword}, // default compareFunc
|
||||
{"{SHA}", compareShaHashAndPassword},
|
||||
// Bcrypt is complicated. According to crypt(3) from
|
||||
// crypt_blowfish version 1.3 (fetched from
|
||||
// http://www.openwall.com/crypt/crypt_blowfish-1.3.tar.gz), there
|
||||
// are three different has prefixes: "$2a$", used by versions up
|
||||
// to 1.0.4, and "$2x$" and "$2y$", used in all later
|
||||
// versions. "$2a$" has a known bug, "$2x$" was added as a
|
||||
// migration path for systems with "$2a$" prefix and still has a
|
||||
// bug, and only "$2y$" should be used by modern systems. The bug
|
||||
// has something to do with handling of 8-bit characters. Since
|
||||
// both "$2a$" and "$2x$" are deprecated, we are handling them the
|
||||
// same way as "$2y$", which will yield correct results for 7-bit
|
||||
// character passwords, but is wrong for 8-bit character
|
||||
// passwords. You have to upgrade to "$2y$" if you want sant 8-bit
|
||||
// character password support with bcrypt. To add to the mess,
|
||||
// OpenBSD 5.5. introduced "$2b$" prefix, which behaves exactly
|
||||
// like "$2y$" according to the same source.
|
||||
{"$2a$", bcrypt.CompareHashAndPassword},
|
||||
{"$2b$", bcrypt.CompareHashAndPassword},
|
||||
{"$2x$", bcrypt.CompareHashAndPassword},
|
||||
{"$2y$", bcrypt.CompareHashAndPassword},
|
||||
}
|
||||
)
|
||||
|
||||
type BasicAuth struct {
|
||||
Realm string
|
||||
Secrets SecretProvider
|
||||
// Headers used by authenticator. Set to ProxyHeaders to use with
|
||||
// proxy server. When nil, NormalHeaders are used.
|
||||
Headers *Headers
|
||||
}
|
||||
|
||||
// check that BasicAuth implements AuthenticatorInterface
|
||||
var _ = (AuthenticatorInterface)((*BasicAuth)(nil))
|
||||
|
||||
/*
|
||||
Checks the username/password combination from the request. Returns
|
||||
either an empty string (authentication failed) or the name of the
|
||||
authenticated user.
|
||||
|
||||
Supports MD5 and SHA1 password entries
|
||||
*/
|
||||
func (a *BasicAuth) CheckAuth(r *http.Request) string {
|
||||
s := strings.SplitN(r.Header.Get(a.Headers.V().Authorization), " ", 2)
|
||||
if len(s) != 2 || s[0] != "Basic" {
|
||||
return ""
|
||||
}
|
||||
|
||||
b, err := base64.StdEncoding.DecodeString(s[1])
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
pair := strings.SplitN(string(b), ":", 2)
|
||||
if len(pair) != 2 {
|
||||
return ""
|
||||
}
|
||||
user, password := pair[0], pair[1]
|
||||
secret := a.Secrets(user, a.Realm)
|
||||
if secret == "" {
|
||||
return ""
|
||||
}
|
||||
compare := compareFuncs[0].compare
|
||||
for _, cmp := range compareFuncs[1:] {
|
||||
if strings.HasPrefix(secret, cmp.prefix) {
|
||||
compare = cmp.compare
|
||||
break
|
||||
}
|
||||
}
|
||||
if compare([]byte(secret), []byte(password)) != nil {
|
||||
return ""
|
||||
}
|
||||
return pair[0]
|
||||
}
|
||||
|
||||
func compareShaHashAndPassword(hashedPassword, password []byte) error {
|
||||
d := sha1.New()
|
||||
d.Write(password)
|
||||
if subtle.ConstantTimeCompare(hashedPassword[5:], []byte(base64.StdEncoding.EncodeToString(d.Sum(nil)))) != 1 {
|
||||
return errMismatchedHashAndPassword
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func compareMD5HashAndPassword(hashedPassword, password []byte) error {
|
||||
parts := bytes.SplitN(hashedPassword, []byte("$"), 4)
|
||||
if len(parts) != 4 {
|
||||
return errMismatchedHashAndPassword
|
||||
}
|
||||
magic := []byte("$" + string(parts[1]) + "$")
|
||||
salt := parts[2]
|
||||
if subtle.ConstantTimeCompare(hashedPassword, MD5Crypt(password, salt, magic)) != 1 {
|
||||
return errMismatchedHashAndPassword
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
http.Handler for BasicAuth which initiates the authentication process
|
||||
(or requires reauthentication).
|
||||
*/
|
||||
func (a *BasicAuth) RequireAuth(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set(contentType, a.Headers.V().UnauthContentType)
|
||||
w.Header().Set(a.Headers.V().Authenticate, `Basic realm="`+a.Realm+`"`)
|
||||
w.WriteHeader(a.Headers.V().UnauthCode)
|
||||
w.Write([]byte(a.Headers.V().UnauthResponse))
|
||||
}
|
||||
|
||||
/*
|
||||
BasicAuthenticator returns a function, which wraps an
|
||||
AuthenticatedHandlerFunc converting it to http.HandlerFunc. This
|
||||
wrapper function checks the authentication and either sends back
|
||||
required authentication headers, or calls the wrapped function with
|
||||
authenticated username in the AuthenticatedRequest.
|
||||
*/
|
||||
func (a *BasicAuth) Wrap(wrapped AuthenticatedHandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if username := a.CheckAuth(r); username == "" {
|
||||
a.RequireAuth(w, r)
|
||||
} else {
|
||||
ar := &AuthenticatedRequest{Request: *r, Username: username}
|
||||
wrapped(w, ar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewContext returns a context carrying authentication information for the request.
|
||||
func (a *BasicAuth) NewContext(ctx context.Context, r *http.Request) context.Context {
|
||||
info := &Info{Username: a.CheckAuth(r), ResponseHeaders: make(http.Header)}
|
||||
info.Authenticated = (info.Username != "")
|
||||
if !info.Authenticated {
|
||||
info.ResponseHeaders.Set(a.Headers.V().Authenticate, `Basic realm="`+a.Realm+`"`)
|
||||
}
|
||||
return context.WithValue(ctx, infoKey, info)
|
||||
}
|
||||
|
||||
func NewBasicAuthenticator(realm string, secrets SecretProvider) *BasicAuth {
|
||||
return &BasicAuth{Realm: realm, Secrets: secrets}
|
||||
}
|
||||
274
vendor/github.com/abbot/go-http-auth/digest.go
generated
vendored
Executable file
274
vendor/github.com/abbot/go-http-auth/digest.go
generated
vendored
Executable file
@@ -0,0 +1,274 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type digest_client struct {
|
||||
nc uint64
|
||||
last_seen int64
|
||||
}
|
||||
|
||||
type DigestAuth struct {
|
||||
Realm string
|
||||
Opaque string
|
||||
Secrets SecretProvider
|
||||
PlainTextSecrets bool
|
||||
IgnoreNonceCount bool
|
||||
// Headers used by authenticator. Set to ProxyHeaders to use with
|
||||
// proxy server. When nil, NormalHeaders are used.
|
||||
Headers *Headers
|
||||
|
||||
/*
|
||||
Approximate size of Client's Cache. When actual number of
|
||||
tracked client nonces exceeds
|
||||
ClientCacheSize+ClientCacheTolerance, ClientCacheTolerance*2
|
||||
older entries are purged.
|
||||
*/
|
||||
ClientCacheSize int
|
||||
ClientCacheTolerance int
|
||||
|
||||
clients map[string]*digest_client
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// check that DigestAuth implements AuthenticatorInterface
|
||||
var _ = (AuthenticatorInterface)((*DigestAuth)(nil))
|
||||
|
||||
type digest_cache_entry struct {
|
||||
nonce string
|
||||
last_seen int64
|
||||
}
|
||||
|
||||
type digest_cache []digest_cache_entry
|
||||
|
||||
func (c digest_cache) Less(i, j int) bool {
|
||||
return c[i].last_seen < c[j].last_seen
|
||||
}
|
||||
|
||||
func (c digest_cache) Len() int {
|
||||
return len(c)
|
||||
}
|
||||
|
||||
func (c digest_cache) Swap(i, j int) {
|
||||
c[i], c[j] = c[j], c[i]
|
||||
}
|
||||
|
||||
/*
|
||||
Remove count oldest entries from DigestAuth.clients
|
||||
*/
|
||||
func (a *DigestAuth) Purge(count int) {
|
||||
entries := make([]digest_cache_entry, 0, len(a.clients))
|
||||
for nonce, client := range a.clients {
|
||||
entries = append(entries, digest_cache_entry{nonce, client.last_seen})
|
||||
}
|
||||
cache := digest_cache(entries)
|
||||
sort.Sort(cache)
|
||||
for _, client := range cache[:count] {
|
||||
delete(a.clients, client.nonce)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
http.Handler for DigestAuth which initiates the authentication process
|
||||
(or requires reauthentication).
|
||||
*/
|
||||
func (a *DigestAuth) RequireAuth(w http.ResponseWriter, r *http.Request) {
|
||||
if len(a.clients) > a.ClientCacheSize+a.ClientCacheTolerance {
|
||||
a.Purge(a.ClientCacheTolerance * 2)
|
||||
}
|
||||
nonce := RandomKey()
|
||||
a.clients[nonce] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()}
|
||||
w.Header().Set(contentType, a.Headers.V().UnauthContentType)
|
||||
w.Header().Set(a.Headers.V().Authenticate,
|
||||
fmt.Sprintf(`Digest realm="%s", nonce="%s", opaque="%s", algorithm="MD5", qop="auth"`,
|
||||
a.Realm, nonce, a.Opaque))
|
||||
w.WriteHeader(a.Headers.V().UnauthCode)
|
||||
w.Write([]byte(a.Headers.V().UnauthResponse))
|
||||
}
|
||||
|
||||
/*
|
||||
Parse Authorization header from the http.Request. Returns a map of
|
||||
auth parameters or nil if the header is not a valid parsable Digest
|
||||
auth header.
|
||||
*/
|
||||
func DigestAuthParams(authorization string) map[string]string {
|
||||
s := strings.SplitN(authorization, " ", 2)
|
||||
if len(s) != 2 || s[0] != "Digest" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ParsePairs(s[1])
|
||||
}
|
||||
|
||||
/*
|
||||
Check if request contains valid authentication data. Returns a pair
|
||||
of username, authinfo where username is the name of the authenticated
|
||||
user or an empty string and authinfo is the contents for the optional
|
||||
Authentication-Info response header.
|
||||
*/
|
||||
func (da *DigestAuth) CheckAuth(r *http.Request) (username string, authinfo *string) {
|
||||
da.mutex.Lock()
|
||||
defer da.mutex.Unlock()
|
||||
username = ""
|
||||
authinfo = nil
|
||||
auth := DigestAuthParams(r.Header.Get(da.Headers.V().Authorization))
|
||||
if auth == nil {
|
||||
return "", nil
|
||||
}
|
||||
// RFC2617 Section 3.2.1 specifies that unset value of algorithm in
|
||||
// WWW-Authenticate Response header should be treated as
|
||||
// "MD5". According to section 3.2.2 the "algorithm" value in
|
||||
// subsequent Request Authorization header must be set to whatever
|
||||
// was supplied in the WWW-Authenticate Response header. This
|
||||
// implementation always returns an algorithm in WWW-Authenticate
|
||||
// header, however there seems to be broken clients in the wild
|
||||
// which do not set the algorithm. Assume the unset algorithm in
|
||||
// Authorization header to be equal to MD5.
|
||||
if _, ok := auth["algorithm"]; !ok {
|
||||
auth["algorithm"] = "MD5"
|
||||
}
|
||||
if da.Opaque != auth["opaque"] || auth["algorithm"] != "MD5" || auth["qop"] != "auth" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Check if the requested URI matches auth header
|
||||
if r.RequestURI != auth["uri"] {
|
||||
// We allow auth["uri"] to be a full path prefix of request-uri
|
||||
// for some reason lost in history, which is probably wrong, but
|
||||
// used to be like that for quite some time
|
||||
// (https://tools.ietf.org/html/rfc2617#section-3.2.2 explicitly
|
||||
// says that auth["uri"] is the request-uri).
|
||||
//
|
||||
// TODO: make an option to allow only strict checking.
|
||||
switch u, err := url.Parse(auth["uri"]); {
|
||||
case err != nil:
|
||||
return "", nil
|
||||
case r.URL == nil:
|
||||
return "", nil
|
||||
case len(u.Path) > len(r.URL.Path):
|
||||
return "", nil
|
||||
case !strings.HasPrefix(r.URL.Path, u.Path):
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
HA1 := da.Secrets(auth["username"], da.Realm)
|
||||
if da.PlainTextSecrets {
|
||||
HA1 = H(auth["username"] + ":" + da.Realm + ":" + HA1)
|
||||
}
|
||||
HA2 := H(r.Method + ":" + auth["uri"])
|
||||
KD := H(strings.Join([]string{HA1, auth["nonce"], auth["nc"], auth["cnonce"], auth["qop"], HA2}, ":"))
|
||||
|
||||
if subtle.ConstantTimeCompare([]byte(KD), []byte(auth["response"])) != 1 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// At this point crypto checks are completed and validated.
|
||||
// Now check if the session is valid.
|
||||
|
||||
nc, err := strconv.ParseUint(auth["nc"], 16, 64)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if client, ok := da.clients[auth["nonce"]]; !ok {
|
||||
return "", nil
|
||||
} else {
|
||||
if client.nc != 0 && client.nc >= nc && !da.IgnoreNonceCount {
|
||||
return "", nil
|
||||
}
|
||||
client.nc = nc
|
||||
client.last_seen = time.Now().UnixNano()
|
||||
}
|
||||
|
||||
resp_HA2 := H(":" + auth["uri"])
|
||||
rspauth := H(strings.Join([]string{HA1, auth["nonce"], auth["nc"], auth["cnonce"], auth["qop"], resp_HA2}, ":"))
|
||||
|
||||
info := fmt.Sprintf(`qop="auth", rspauth="%s", cnonce="%s", nc="%s"`, rspauth, auth["cnonce"], auth["nc"])
|
||||
return auth["username"], &info
|
||||
}
|
||||
|
||||
/*
|
||||
Default values for ClientCacheSize and ClientCacheTolerance for DigestAuth
|
||||
*/
|
||||
const DefaultClientCacheSize = 1000
|
||||
const DefaultClientCacheTolerance = 100
|
||||
|
||||
/*
|
||||
Wrap returns an Authenticator which uses HTTP Digest
|
||||
authentication. Arguments:
|
||||
|
||||
realm: The authentication realm.
|
||||
|
||||
secrets: SecretProvider which must return HA1 digests for the same
|
||||
realm as above.
|
||||
*/
|
||||
func (a *DigestAuth) Wrap(wrapped AuthenticatedHandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if username, authinfo := a.CheckAuth(r); username == "" {
|
||||
a.RequireAuth(w, r)
|
||||
} else {
|
||||
ar := &AuthenticatedRequest{Request: *r, Username: username}
|
||||
if authinfo != nil {
|
||||
w.Header().Set(a.Headers.V().AuthInfo, *authinfo)
|
||||
}
|
||||
wrapped(w, ar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
JustCheck returns function which converts an http.HandlerFunc into a
|
||||
http.HandlerFunc which requires authentication. Username is passed as
|
||||
an extra X-Authenticated-Username header.
|
||||
*/
|
||||
func (a *DigestAuth) JustCheck(wrapped http.HandlerFunc) http.HandlerFunc {
|
||||
return a.Wrap(func(w http.ResponseWriter, ar *AuthenticatedRequest) {
|
||||
ar.Header.Set(AuthUsernameHeader, ar.Username)
|
||||
wrapped(w, &ar.Request)
|
||||
})
|
||||
}
|
||||
|
||||
// NewContext returns a context carrying authentication information for the request.
|
||||
func (a *DigestAuth) NewContext(ctx context.Context, r *http.Request) context.Context {
|
||||
username, authinfo := a.CheckAuth(r)
|
||||
info := &Info{Username: username, ResponseHeaders: make(http.Header)}
|
||||
if username != "" {
|
||||
info.Authenticated = true
|
||||
info.ResponseHeaders.Set(a.Headers.V().AuthInfo, *authinfo)
|
||||
} else {
|
||||
// return back digest WWW-Authenticate header
|
||||
if len(a.clients) > a.ClientCacheSize+a.ClientCacheTolerance {
|
||||
a.Purge(a.ClientCacheTolerance * 2)
|
||||
}
|
||||
nonce := RandomKey()
|
||||
a.clients[nonce] = &digest_client{nc: 0, last_seen: time.Now().UnixNano()}
|
||||
info.ResponseHeaders.Set(a.Headers.V().Authenticate,
|
||||
fmt.Sprintf(`Digest realm="%s", nonce="%s", opaque="%s", algorithm="MD5", qop="auth"`,
|
||||
a.Realm, nonce, a.Opaque))
|
||||
}
|
||||
return context.WithValue(ctx, infoKey, info)
|
||||
}
|
||||
|
||||
func NewDigestAuthenticator(realm string, secrets SecretProvider) *DigestAuth {
|
||||
da := &DigestAuth{
|
||||
Opaque: RandomKey(),
|
||||
Realm: realm,
|
||||
Secrets: secrets,
|
||||
PlainTextSecrets: false,
|
||||
ClientCacheSize: DefaultClientCacheSize,
|
||||
ClientCacheTolerance: DefaultClientCacheTolerance,
|
||||
clients: map[string]*digest_client{}}
|
||||
return da
|
||||
}
|
||||
92
vendor/github.com/abbot/go-http-auth/md5crypt.go
generated
vendored
Executable file
92
vendor/github.com/abbot/go-http-auth/md5crypt.go
generated
vendored
Executable file
@@ -0,0 +1,92 @@
|
||||
package auth
|
||||
|
||||
import "crypto/md5"
|
||||
import "strings"
|
||||
|
||||
const itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
var md5_crypt_swaps = [16]int{12, 6, 0, 13, 7, 1, 14, 8, 2, 15, 9, 3, 5, 10, 4, 11}
|
||||
|
||||
type MD5Entry struct {
|
||||
Magic, Salt, Hash []byte
|
||||
}
|
||||
|
||||
func NewMD5Entry(e string) *MD5Entry {
|
||||
parts := strings.SplitN(e, "$", 4)
|
||||
if len(parts) != 4 {
|
||||
return nil
|
||||
}
|
||||
return &MD5Entry{
|
||||
Magic: []byte("$" + parts[1] + "$"),
|
||||
Salt: []byte(parts[2]),
|
||||
Hash: []byte(parts[3]),
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
MD5 password crypt implementation
|
||||
*/
|
||||
func MD5Crypt(password, salt, magic []byte) []byte {
|
||||
d := md5.New()
|
||||
|
||||
d.Write(password)
|
||||
d.Write(magic)
|
||||
d.Write(salt)
|
||||
|
||||
d2 := md5.New()
|
||||
d2.Write(password)
|
||||
d2.Write(salt)
|
||||
d2.Write(password)
|
||||
|
||||
for i, mixin := 0, d2.Sum(nil); i < len(password); i++ {
|
||||
d.Write([]byte{mixin[i%16]})
|
||||
}
|
||||
|
||||
for i := len(password); i != 0; i >>= 1 {
|
||||
if i&1 == 0 {
|
||||
d.Write([]byte{password[0]})
|
||||
} else {
|
||||
d.Write([]byte{0})
|
||||
}
|
||||
}
|
||||
|
||||
final := d.Sum(nil)
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
d2 := md5.New()
|
||||
if i&1 == 0 {
|
||||
d2.Write(final)
|
||||
} else {
|
||||
d2.Write(password)
|
||||
}
|
||||
|
||||
if i%3 != 0 {
|
||||
d2.Write(salt)
|
||||
}
|
||||
|
||||
if i%7 != 0 {
|
||||
d2.Write(password)
|
||||
}
|
||||
|
||||
if i&1 == 0 {
|
||||
d2.Write(password)
|
||||
} else {
|
||||
d2.Write(final)
|
||||
}
|
||||
final = d2.Sum(nil)
|
||||
}
|
||||
|
||||
result := make([]byte, 0, 22)
|
||||
v := uint(0)
|
||||
bits := uint(0)
|
||||
for _, i := range md5_crypt_swaps {
|
||||
v |= (uint(final[i]) << bits)
|
||||
for bits = bits + 8; bits > 6; bits -= 6 {
|
||||
result = append(result, itoa64[v&0x3f])
|
||||
v >>= 6
|
||||
}
|
||||
}
|
||||
result = append(result, itoa64[v&0x3f])
|
||||
|
||||
return append(append(append(magic, salt...), '$'), result...)
|
||||
}
|
||||
141
vendor/github.com/abbot/go-http-auth/misc.go
generated
vendored
Executable file
141
vendor/github.com/abbot/go-http-auth/misc.go
generated
vendored
Executable file
@@ -0,0 +1,141 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// RandomKey returns a random 16-byte base64 alphabet string
|
||||
func RandomKey() string {
|
||||
k := make([]byte, 12)
|
||||
for bytes := 0; bytes < len(k); {
|
||||
n, err := rand.Read(k[bytes:])
|
||||
if err != nil {
|
||||
panic("rand.Read() failed")
|
||||
}
|
||||
bytes += n
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(k)
|
||||
}
|
||||
|
||||
// H function for MD5 algorithm (returns a lower-case hex MD5 digest)
|
||||
func H(data string) string {
|
||||
digest := md5.New()
|
||||
digest.Write([]byte(data))
|
||||
return fmt.Sprintf("%x", digest.Sum(nil))
|
||||
}
|
||||
|
||||
// ParseList parses a comma-separated list of values as described by
|
||||
// RFC 2068 and returns list elements.
|
||||
//
|
||||
// Lifted from https://code.google.com/p/gorilla/source/browse/http/parser/parser.go
|
||||
// which was ported from urllib2.parse_http_list, from the Python
|
||||
// standard library.
|
||||
func ParseList(value string) []string {
|
||||
var list []string
|
||||
var escape, quote bool
|
||||
b := new(bytes.Buffer)
|
||||
for _, r := range value {
|
||||
switch {
|
||||
case escape:
|
||||
b.WriteRune(r)
|
||||
escape = false
|
||||
case quote:
|
||||
if r == '\\' {
|
||||
escape = true
|
||||
} else {
|
||||
if r == '"' {
|
||||
quote = false
|
||||
}
|
||||
b.WriteRune(r)
|
||||
}
|
||||
case r == ',':
|
||||
list = append(list, strings.TrimSpace(b.String()))
|
||||
b.Reset()
|
||||
case r == '"':
|
||||
quote = true
|
||||
b.WriteRune(r)
|
||||
default:
|
||||
b.WriteRune(r)
|
||||
}
|
||||
}
|
||||
// Append last part.
|
||||
if s := b.String(); s != "" {
|
||||
list = append(list, strings.TrimSpace(s))
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// ParsePairs extracts key/value pairs from a comma-separated list of
|
||||
// values as described by RFC 2068 and returns a map[key]value. The
|
||||
// resulting values are unquoted. If a list element doesn't contain a
|
||||
// "=", the key is the element itself and the value is an empty
|
||||
// string.
|
||||
//
|
||||
// Lifted from https://code.google.com/p/gorilla/source/browse/http/parser/parser.go
|
||||
func ParsePairs(value string) map[string]string {
|
||||
m := make(map[string]string)
|
||||
for _, pair := range ParseList(strings.TrimSpace(value)) {
|
||||
if i := strings.Index(pair, "="); i < 0 {
|
||||
m[pair] = ""
|
||||
} else {
|
||||
v := pair[i+1:]
|
||||
if v[0] == '"' && v[len(v)-1] == '"' {
|
||||
// Unquote it.
|
||||
v = v[1 : len(v)-1]
|
||||
}
|
||||
m[pair[:i]] = v
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Headers contains header and error codes used by authenticator.
|
||||
type Headers struct {
|
||||
Authenticate string // WWW-Authenticate
|
||||
Authorization string // Authorization
|
||||
AuthInfo string // Authentication-Info
|
||||
UnauthCode int // 401
|
||||
UnauthContentType string // text/plain
|
||||
UnauthResponse string // Unauthorized.
|
||||
}
|
||||
|
||||
// V returns NormalHeaders when h is nil, or h otherwise. Allows to
|
||||
// use uninitialized *Headers values in structs.
|
||||
func (h *Headers) V() *Headers {
|
||||
if h == nil {
|
||||
return NormalHeaders
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
var (
|
||||
// NormalHeaders are the regular Headers used by an HTTP Server for
|
||||
// request authentication.
|
||||
NormalHeaders = &Headers{
|
||||
Authenticate: "WWW-Authenticate",
|
||||
Authorization: "Authorization",
|
||||
AuthInfo: "Authentication-Info",
|
||||
UnauthCode: http.StatusUnauthorized,
|
||||
UnauthContentType: "text/plain",
|
||||
UnauthResponse: fmt.Sprintf("%d %s\n", http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized)),
|
||||
}
|
||||
|
||||
// ProxyHeaders are Headers used by an HTTP Proxy server for proxy
|
||||
// access authentication.
|
||||
ProxyHeaders = &Headers{
|
||||
Authenticate: "Proxy-Authenticate",
|
||||
Authorization: "Proxy-Authorization",
|
||||
AuthInfo: "Proxy-Authentication-Info",
|
||||
UnauthCode: http.StatusProxyAuthRequired,
|
||||
UnauthContentType: "text/plain",
|
||||
UnauthResponse: fmt.Sprintf("%d %s\n", http.StatusProxyAuthRequired, http.StatusText(http.StatusProxyAuthRequired)),
|
||||
}
|
||||
)
|
||||
|
||||
const contentType = "Content-Type"
|
||||
1
vendor/github.com/abbot/go-http-auth/test.htdigest
generated
vendored
Executable file
1
vendor/github.com/abbot/go-http-auth/test.htdigest
generated
vendored
Executable file
@@ -0,0 +1 @@
|
||||
test:example.com:aa78524fceb0e50fd8ca96dd818b8cf9
|
||||
4
vendor/github.com/abbot/go-http-auth/test.htpasswd
generated
vendored
Executable file
4
vendor/github.com/abbot/go-http-auth/test.htpasswd
generated
vendored
Executable file
@@ -0,0 +1,4 @@
|
||||
test:{SHA}qvTGHdzF6KLavt4PO0gs2a6pQ00=
|
||||
test2:$apr1$a0j62R97$mYqFkloXH0/UOaUnAiV2b0
|
||||
test16:$apr1$JI4wh3am$AmhephVqLTUyAVpFQeHZC0
|
||||
test3:$2y$05$ih3C91zUBSTFcAh2mQnZYuob0UOZVEf16wl/ukgjDhjvj.xgM1WwS
|
||||
154
vendor/github.com/abbot/go-http-auth/users.go
generated
vendored
Executable file
154
vendor/github.com/abbot/go-http-auth/users.go
generated
vendored
Executable file
@@ -0,0 +1,154 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
/*
|
||||
SecretProvider is used by authenticators. Takes user name and realm
|
||||
as an argument, returns secret required for authentication (HA1 for
|
||||
digest authentication, properly encrypted password for basic).
|
||||
|
||||
Returning an empty string means failing the authentication.
|
||||
*/
|
||||
type SecretProvider func(user, realm string) string
|
||||
|
||||
/*
|
||||
Common functions for file auto-reloading
|
||||
*/
|
||||
type File struct {
|
||||
Path string
|
||||
Info os.FileInfo
|
||||
/* must be set in inherited types during initialization */
|
||||
Reload func()
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (f *File) ReloadIfNeeded() {
|
||||
info, err := os.Stat(f.Path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if f.Info == nil || f.Info.ModTime() != info.ModTime() {
|
||||
f.Info = info
|
||||
f.Reload()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Structure used for htdigest file authentication. Users map realms to
|
||||
maps of users to their HA1 digests.
|
||||
*/
|
||||
type HtdigestFile struct {
|
||||
File
|
||||
Users map[string]map[string]string
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func reload_htdigest(hf *HtdigestFile) {
|
||||
r, err := os.Open(hf.Path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
csv_reader := csv.NewReader(r)
|
||||
csv_reader.Comma = ':'
|
||||
csv_reader.Comment = '#'
|
||||
csv_reader.TrimLeadingSpace = true
|
||||
|
||||
records, err := csv_reader.ReadAll()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
hf.mu.Lock()
|
||||
defer hf.mu.Unlock()
|
||||
hf.Users = make(map[string]map[string]string)
|
||||
for _, record := range records {
|
||||
_, exists := hf.Users[record[1]]
|
||||
if !exists {
|
||||
hf.Users[record[1]] = make(map[string]string)
|
||||
}
|
||||
hf.Users[record[1]][record[0]] = record[2]
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
SecretProvider implementation based on htdigest-formated files. Will
|
||||
reload htdigest file on changes. Will panic on syntax errors in
|
||||
htdigest files.
|
||||
*/
|
||||
func HtdigestFileProvider(filename string) SecretProvider {
|
||||
hf := &HtdigestFile{File: File{Path: filename}}
|
||||
hf.Reload = func() { reload_htdigest(hf) }
|
||||
return func(user, realm string) string {
|
||||
hf.ReloadIfNeeded()
|
||||
hf.mu.RLock()
|
||||
defer hf.mu.RUnlock()
|
||||
_, exists := hf.Users[realm]
|
||||
if !exists {
|
||||
return ""
|
||||
}
|
||||
digest, exists := hf.Users[realm][user]
|
||||
if !exists {
|
||||
return ""
|
||||
}
|
||||
return digest
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Structure used for htdigest file authentication. Users map users to
|
||||
their salted encrypted password
|
||||
*/
|
||||
type HtpasswdFile struct {
|
||||
File
|
||||
Users map[string]string
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func reload_htpasswd(h *HtpasswdFile) {
|
||||
r, err := os.Open(h.Path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
csv_reader := csv.NewReader(r)
|
||||
csv_reader.Comma = ':'
|
||||
csv_reader.Comment = '#'
|
||||
csv_reader.TrimLeadingSpace = true
|
||||
|
||||
records, err := csv_reader.ReadAll()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
h.Users = make(map[string]string)
|
||||
for _, record := range records {
|
||||
h.Users[record[0]] = record[1]
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
SecretProvider implementation based on htpasswd-formated files. Will
|
||||
reload htpasswd file on changes. Will panic on syntax errors in
|
||||
htpasswd files. Realm argument of the SecretProvider is ignored.
|
||||
*/
|
||||
func HtpasswdFileProvider(filename string) SecretProvider {
|
||||
h := &HtpasswdFile{File: File{Path: filename}}
|
||||
h.Reload = func() { reload_htpasswd(h) }
|
||||
return func(user, realm string) string {
|
||||
h.ReloadIfNeeded()
|
||||
h.mu.RLock()
|
||||
password, exists := h.Users[user]
|
||||
h.mu.RUnlock()
|
||||
if !exists {
|
||||
return ""
|
||||
}
|
||||
return password
|
||||
}
|
||||
}
|
||||
24
vendor/github.com/andybalholm/cascadia/LICENSE
generated
vendored
Executable file
24
vendor/github.com/andybalholm/cascadia/LICENSE
generated
vendored
Executable file
@@ -0,0 +1,24 @@
|
||||
Copyright (c) 2011 Andy Balholm. 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.
|
||||
|
||||
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.
|
||||
9
vendor/github.com/andybalholm/cascadia/README.md
generated
vendored
Normal file
9
vendor/github.com/andybalholm/cascadia/README.md
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
# cascadia
|
||||
|
||||
[](https://travis-ci.org/andybalholm/cascadia)
|
||||
|
||||
The Cascadia package implements CSS selectors for use with the parse trees produced by the html package.
|
||||
|
||||
To test CSS selectors without writing Go code, check out [cascadia](https://github.com/suntong/cascadia) the command line tool, a thin wrapper around this package.
|
||||
|
||||
[Refer to godoc here](https://godoc.org/github.com/andybalholm/cascadia).
|
||||
5
vendor/github.com/andybalholm/cascadia/go.mod
generated
vendored
Normal file
5
vendor/github.com/andybalholm/cascadia/go.mod
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
module github.com/andybalholm/cascadia
|
||||
|
||||
require golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01
|
||||
|
||||
go 1.13
|
||||
838
vendor/github.com/andybalholm/cascadia/parser.go
generated
vendored
Normal file
838
vendor/github.com/andybalholm/cascadia/parser.go
generated
vendored
Normal file
@@ -0,0 +1,838 @@
|
||||
// Package cascadia is an implementation of CSS selectors.
|
||||
package cascadia
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// a parser for CSS selectors
|
||||
type parser struct {
|
||||
s string // the source text
|
||||
i int // the current position
|
||||
|
||||
// if `false`, parsing a pseudo-element
|
||||
// returns an error.
|
||||
acceptPseudoElements bool
|
||||
}
|
||||
|
||||
// parseEscape parses a backslash escape.
|
||||
func (p *parser) parseEscape() (result string, err error) {
|
||||
if len(p.s) < p.i+2 || p.s[p.i] != '\\' {
|
||||
return "", errors.New("invalid escape sequence")
|
||||
}
|
||||
|
||||
start := p.i + 1
|
||||
c := p.s[start]
|
||||
switch {
|
||||
case c == '\r' || c == '\n' || c == '\f':
|
||||
return "", errors.New("escaped line ending outside string")
|
||||
case hexDigit(c):
|
||||
// unicode escape (hex)
|
||||
var i int
|
||||
for i = start; i < start+6 && i < len(p.s) && hexDigit(p.s[i]); i++ {
|
||||
// empty
|
||||
}
|
||||
v, _ := strconv.ParseUint(p.s[start:i], 16, 21)
|
||||
if len(p.s) > i {
|
||||
switch p.s[i] {
|
||||
case '\r':
|
||||
i++
|
||||
if len(p.s) > i && p.s[i] == '\n' {
|
||||
i++
|
||||
}
|
||||
case ' ', '\t', '\n', '\f':
|
||||
i++
|
||||
}
|
||||
}
|
||||
p.i = i
|
||||
return string(rune(v)), nil
|
||||
}
|
||||
|
||||
// Return the literal character after the backslash.
|
||||
result = p.s[start : start+1]
|
||||
p.i += 2
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// toLowerASCII returns s with all ASCII capital letters lowercased.
|
||||
func toLowerASCII(s string) string {
|
||||
var b []byte
|
||||
for i := 0; i < len(s); i++ {
|
||||
if c := s[i]; 'A' <= c && c <= 'Z' {
|
||||
if b == nil {
|
||||
b = make([]byte, len(s))
|
||||
copy(b, s)
|
||||
}
|
||||
b[i] = s[i] + ('a' - 'A')
|
||||
}
|
||||
}
|
||||
|
||||
if b == nil {
|
||||
return s
|
||||
}
|
||||
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func hexDigit(c byte) bool {
|
||||
return '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F'
|
||||
}
|
||||
|
||||
// nameStart returns whether c can be the first character of an identifier
|
||||
// (not counting an initial hyphen, or an escape sequence).
|
||||
func nameStart(c byte) bool {
|
||||
return 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_' || c > 127
|
||||
}
|
||||
|
||||
// nameChar returns whether c can be a character within an identifier
|
||||
// (not counting an escape sequence).
|
||||
func nameChar(c byte) bool {
|
||||
return 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_' || c > 127 ||
|
||||
c == '-' || '0' <= c && c <= '9'
|
||||
}
|
||||
|
||||
// parseIdentifier parses an identifier.
|
||||
func (p *parser) parseIdentifier() (result string, err error) {
|
||||
startingDash := false
|
||||
if len(p.s) > p.i && p.s[p.i] == '-' {
|
||||
startingDash = true
|
||||
p.i++
|
||||
}
|
||||
|
||||
if len(p.s) <= p.i {
|
||||
return "", errors.New("expected identifier, found EOF instead")
|
||||
}
|
||||
|
||||
if c := p.s[p.i]; !(nameStart(c) || c == '\\') {
|
||||
return "", fmt.Errorf("expected identifier, found %c instead", c)
|
||||
}
|
||||
|
||||
result, err = p.parseName()
|
||||
if startingDash && err == nil {
|
||||
result = "-" + result
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// parseName parses a name (which is like an identifier, but doesn't have
|
||||
// extra restrictions on the first character).
|
||||
func (p *parser) parseName() (result string, err error) {
|
||||
i := p.i
|
||||
loop:
|
||||
for i < len(p.s) {
|
||||
c := p.s[i]
|
||||
switch {
|
||||
case nameChar(c):
|
||||
start := i
|
||||
for i < len(p.s) && nameChar(p.s[i]) {
|
||||
i++
|
||||
}
|
||||
result += p.s[start:i]
|
||||
case c == '\\':
|
||||
p.i = i
|
||||
val, err := p.parseEscape()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
i = p.i
|
||||
result += val
|
||||
default:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
if result == "" {
|
||||
return "", errors.New("expected name, found EOF instead")
|
||||
}
|
||||
|
||||
p.i = i
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// parseString parses a single- or double-quoted string.
|
||||
func (p *parser) parseString() (result string, err error) {
|
||||
i := p.i
|
||||
if len(p.s) < i+2 {
|
||||
return "", errors.New("expected string, found EOF instead")
|
||||
}
|
||||
|
||||
quote := p.s[i]
|
||||
i++
|
||||
|
||||
loop:
|
||||
for i < len(p.s) {
|
||||
switch p.s[i] {
|
||||
case '\\':
|
||||
if len(p.s) > i+1 {
|
||||
switch c := p.s[i+1]; c {
|
||||
case '\r':
|
||||
if len(p.s) > i+2 && p.s[i+2] == '\n' {
|
||||
i += 3
|
||||
continue loop
|
||||
}
|
||||
fallthrough
|
||||
case '\n', '\f':
|
||||
i += 2
|
||||
continue loop
|
||||
}
|
||||
}
|
||||
p.i = i
|
||||
val, err := p.parseEscape()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
i = p.i
|
||||
result += val
|
||||
case quote:
|
||||
break loop
|
||||
case '\r', '\n', '\f':
|
||||
return "", errors.New("unexpected end of line in string")
|
||||
default:
|
||||
start := i
|
||||
for i < len(p.s) {
|
||||
if c := p.s[i]; c == quote || c == '\\' || c == '\r' || c == '\n' || c == '\f' {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
result += p.s[start:i]
|
||||
}
|
||||
}
|
||||
|
||||
if i >= len(p.s) {
|
||||
return "", errors.New("EOF in string")
|
||||
}
|
||||
|
||||
// Consume the final quote.
|
||||
i++
|
||||
|
||||
p.i = i
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// parseRegex parses a regular expression; the end is defined by encountering an
|
||||
// unmatched closing ')' or ']' which is not consumed
|
||||
func (p *parser) parseRegex() (rx *regexp.Regexp, err error) {
|
||||
i := p.i
|
||||
if len(p.s) < i+2 {
|
||||
return nil, errors.New("expected regular expression, found EOF instead")
|
||||
}
|
||||
|
||||
// number of open parens or brackets;
|
||||
// when it becomes negative, finished parsing regex
|
||||
open := 0
|
||||
|
||||
loop:
|
||||
for i < len(p.s) {
|
||||
switch p.s[i] {
|
||||
case '(', '[':
|
||||
open++
|
||||
case ')', ']':
|
||||
open--
|
||||
if open < 0 {
|
||||
break loop
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
if i >= len(p.s) {
|
||||
return nil, errors.New("EOF in regular expression")
|
||||
}
|
||||
rx, err = regexp.Compile(p.s[p.i:i])
|
||||
p.i = i
|
||||
return rx, err
|
||||
}
|
||||
|
||||
// skipWhitespace consumes whitespace characters and comments.
|
||||
// It returns true if there was actually anything to skip.
|
||||
func (p *parser) skipWhitespace() bool {
|
||||
i := p.i
|
||||
for i < len(p.s) {
|
||||
switch p.s[i] {
|
||||
case ' ', '\t', '\r', '\n', '\f':
|
||||
i++
|
||||
continue
|
||||
case '/':
|
||||
if strings.HasPrefix(p.s[i:], "/*") {
|
||||
end := strings.Index(p.s[i+len("/*"):], "*/")
|
||||
if end != -1 {
|
||||
i += end + len("/**/")
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if i > p.i {
|
||||
p.i = i
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// consumeParenthesis consumes an opening parenthesis and any following
|
||||
// whitespace. It returns true if there was actually a parenthesis to skip.
|
||||
func (p *parser) consumeParenthesis() bool {
|
||||
if p.i < len(p.s) && p.s[p.i] == '(' {
|
||||
p.i++
|
||||
p.skipWhitespace()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// consumeClosingParenthesis consumes a closing parenthesis and any preceding
|
||||
// whitespace. It returns true if there was actually a parenthesis to skip.
|
||||
func (p *parser) consumeClosingParenthesis() bool {
|
||||
i := p.i
|
||||
p.skipWhitespace()
|
||||
if p.i < len(p.s) && p.s[p.i] == ')' {
|
||||
p.i++
|
||||
return true
|
||||
}
|
||||
p.i = i
|
||||
return false
|
||||
}
|
||||
|
||||
// parseTypeSelector parses a type selector (one that matches by tag name).
|
||||
func (p *parser) parseTypeSelector() (result tagSelector, err error) {
|
||||
tag, err := p.parseIdentifier()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return tagSelector{tag: toLowerASCII(tag)}, nil
|
||||
}
|
||||
|
||||
// parseIDSelector parses a selector that matches by id attribute.
|
||||
func (p *parser) parseIDSelector() (idSelector, error) {
|
||||
if p.i >= len(p.s) {
|
||||
return idSelector{}, fmt.Errorf("expected id selector (#id), found EOF instead")
|
||||
}
|
||||
if p.s[p.i] != '#' {
|
||||
return idSelector{}, fmt.Errorf("expected id selector (#id), found '%c' instead", p.s[p.i])
|
||||
}
|
||||
|
||||
p.i++
|
||||
id, err := p.parseName()
|
||||
if err != nil {
|
||||
return idSelector{}, err
|
||||
}
|
||||
|
||||
return idSelector{id: id}, nil
|
||||
}
|
||||
|
||||
// parseClassSelector parses a selector that matches by class attribute.
|
||||
func (p *parser) parseClassSelector() (classSelector, error) {
|
||||
if p.i >= len(p.s) {
|
||||
return classSelector{}, fmt.Errorf("expected class selector (.class), found EOF instead")
|
||||
}
|
||||
if p.s[p.i] != '.' {
|
||||
return classSelector{}, fmt.Errorf("expected class selector (.class), found '%c' instead", p.s[p.i])
|
||||
}
|
||||
|
||||
p.i++
|
||||
class, err := p.parseIdentifier()
|
||||
if err != nil {
|
||||
return classSelector{}, err
|
||||
}
|
||||
|
||||
return classSelector{class: class}, nil
|
||||
}
|
||||
|
||||
// parseAttributeSelector parses a selector that matches by attribute value.
|
||||
func (p *parser) parseAttributeSelector() (attrSelector, error) {
|
||||
if p.i >= len(p.s) {
|
||||
return attrSelector{}, fmt.Errorf("expected attribute selector ([attribute]), found EOF instead")
|
||||
}
|
||||
if p.s[p.i] != '[' {
|
||||
return attrSelector{}, fmt.Errorf("expected attribute selector ([attribute]), found '%c' instead", p.s[p.i])
|
||||
}
|
||||
|
||||
p.i++
|
||||
p.skipWhitespace()
|
||||
key, err := p.parseIdentifier()
|
||||
if err != nil {
|
||||
return attrSelector{}, err
|
||||
}
|
||||
key = toLowerASCII(key)
|
||||
|
||||
p.skipWhitespace()
|
||||
if p.i >= len(p.s) {
|
||||
return attrSelector{}, errors.New("unexpected EOF in attribute selector")
|
||||
}
|
||||
|
||||
if p.s[p.i] == ']' {
|
||||
p.i++
|
||||
return attrSelector{key: key, operation: ""}, nil
|
||||
}
|
||||
|
||||
if p.i+2 >= len(p.s) {
|
||||
return attrSelector{}, errors.New("unexpected EOF in attribute selector")
|
||||
}
|
||||
|
||||
op := p.s[p.i : p.i+2]
|
||||
if op[0] == '=' {
|
||||
op = "="
|
||||
} else if op[1] != '=' {
|
||||
return attrSelector{}, fmt.Errorf(`expected equality operator, found "%s" instead`, op)
|
||||
}
|
||||
p.i += len(op)
|
||||
|
||||
p.skipWhitespace()
|
||||
if p.i >= len(p.s) {
|
||||
return attrSelector{}, errors.New("unexpected EOF in attribute selector")
|
||||
}
|
||||
var val string
|
||||
var rx *regexp.Regexp
|
||||
if op == "#=" {
|
||||
rx, err = p.parseRegex()
|
||||
} else {
|
||||
switch p.s[p.i] {
|
||||
case '\'', '"':
|
||||
val, err = p.parseString()
|
||||
default:
|
||||
val, err = p.parseIdentifier()
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return attrSelector{}, err
|
||||
}
|
||||
|
||||
p.skipWhitespace()
|
||||
if p.i >= len(p.s) {
|
||||
return attrSelector{}, errors.New("unexpected EOF in attribute selector")
|
||||
}
|
||||
if p.s[p.i] != ']' {
|
||||
return attrSelector{}, fmt.Errorf("expected ']', found '%c' instead", p.s[p.i])
|
||||
}
|
||||
p.i++
|
||||
|
||||
switch op {
|
||||
case "=", "!=", "~=", "|=", "^=", "$=", "*=", "#=":
|
||||
return attrSelector{key: key, val: val, operation: op, regexp: rx}, nil
|
||||
default:
|
||||
return attrSelector{}, fmt.Errorf("attribute operator %q is not supported", op)
|
||||
}
|
||||
}
|
||||
|
||||
var errExpectedParenthesis = errors.New("expected '(' but didn't find it")
|
||||
var errExpectedClosingParenthesis = errors.New("expected ')' but didn't find it")
|
||||
var errUnmatchedParenthesis = errors.New("unmatched '('")
|
||||
|
||||
// parsePseudoclassSelector parses a pseudoclass selector like :not(p) or a pseudo-element
|
||||
// For backwards compatibility, both ':' and '::' prefix are allowed for pseudo-elements.
|
||||
// https://drafts.csswg.org/selectors-3/#pseudo-elements
|
||||
// Returning a nil `Sel` (and a nil `error`) means we found a pseudo-element.
|
||||
func (p *parser) parsePseudoclassSelector() (out Sel, pseudoElement string, err error) {
|
||||
if p.i >= len(p.s) {
|
||||
return nil, "", fmt.Errorf("expected pseudoclass selector (:pseudoclass), found EOF instead")
|
||||
}
|
||||
if p.s[p.i] != ':' {
|
||||
return nil, "", fmt.Errorf("expected attribute selector (:pseudoclass), found '%c' instead", p.s[p.i])
|
||||
}
|
||||
|
||||
p.i++
|
||||
var mustBePseudoElement bool
|
||||
if p.i >= len(p.s) {
|
||||
return nil, "", fmt.Errorf("got empty pseudoclass (or pseudoelement)")
|
||||
}
|
||||
if p.s[p.i] == ':' { // we found a pseudo-element
|
||||
mustBePseudoElement = true
|
||||
p.i++
|
||||
}
|
||||
|
||||
name, err := p.parseIdentifier()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
name = toLowerASCII(name)
|
||||
if mustBePseudoElement && (name != "after" && name != "backdrop" && name != "before" &&
|
||||
name != "cue" && name != "first-letter" && name != "first-line" && name != "grammar-error" &&
|
||||
name != "marker" && name != "placeholder" && name != "selection" && name != "spelling-error") {
|
||||
return out, "", fmt.Errorf("unknown pseudoelement :%s", name)
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "not", "has", "haschild":
|
||||
if !p.consumeParenthesis() {
|
||||
return out, "", errExpectedParenthesis
|
||||
}
|
||||
sel, parseErr := p.parseSelectorGroup()
|
||||
if parseErr != nil {
|
||||
return out, "", parseErr
|
||||
}
|
||||
if !p.consumeClosingParenthesis() {
|
||||
return out, "", errExpectedClosingParenthesis
|
||||
}
|
||||
|
||||
out = relativePseudoClassSelector{name: name, match: sel}
|
||||
|
||||
case "contains", "containsown":
|
||||
if !p.consumeParenthesis() {
|
||||
return out, "", errExpectedParenthesis
|
||||
}
|
||||
if p.i == len(p.s) {
|
||||
return out, "", errUnmatchedParenthesis
|
||||
}
|
||||
var val string
|
||||
switch p.s[p.i] {
|
||||
case '\'', '"':
|
||||
val, err = p.parseString()
|
||||
default:
|
||||
val, err = p.parseIdentifier()
|
||||
}
|
||||
if err != nil {
|
||||
return out, "", err
|
||||
}
|
||||
val = strings.ToLower(val)
|
||||
p.skipWhitespace()
|
||||
if p.i >= len(p.s) {
|
||||
return out, "", errors.New("unexpected EOF in pseudo selector")
|
||||
}
|
||||
if !p.consumeClosingParenthesis() {
|
||||
return out, "", errExpectedClosingParenthesis
|
||||
}
|
||||
|
||||
out = containsPseudoClassSelector{own: name == "containsown", value: val}
|
||||
|
||||
case "matches", "matchesown":
|
||||
if !p.consumeParenthesis() {
|
||||
return out, "", errExpectedParenthesis
|
||||
}
|
||||
rx, err := p.parseRegex()
|
||||
if err != nil {
|
||||
return out, "", err
|
||||
}
|
||||
if p.i >= len(p.s) {
|
||||
return out, "", errors.New("unexpected EOF in pseudo selector")
|
||||
}
|
||||
if !p.consumeClosingParenthesis() {
|
||||
return out, "", errExpectedClosingParenthesis
|
||||
}
|
||||
|
||||
out = regexpPseudoClassSelector{own: name == "matchesown", regexp: rx}
|
||||
|
||||
case "nth-child", "nth-last-child", "nth-of-type", "nth-last-of-type":
|
||||
if !p.consumeParenthesis() {
|
||||
return out, "", errExpectedParenthesis
|
||||
}
|
||||
a, b, err := p.parseNth()
|
||||
if err != nil {
|
||||
return out, "", err
|
||||
}
|
||||
if !p.consumeClosingParenthesis() {
|
||||
return out, "", errExpectedClosingParenthesis
|
||||
}
|
||||
last := name == "nth-last-child" || name == "nth-last-of-type"
|
||||
ofType := name == "nth-of-type" || name == "nth-last-of-type"
|
||||
out = nthPseudoClassSelector{a: a, b: b, last: last, ofType: ofType}
|
||||
|
||||
case "first-child":
|
||||
out = nthPseudoClassSelector{a: 0, b: 1, ofType: false, last: false}
|
||||
case "last-child":
|
||||
out = nthPseudoClassSelector{a: 0, b: 1, ofType: false, last: true}
|
||||
case "first-of-type":
|
||||
out = nthPseudoClassSelector{a: 0, b: 1, ofType: true, last: false}
|
||||
case "last-of-type":
|
||||
out = nthPseudoClassSelector{a: 0, b: 1, ofType: true, last: true}
|
||||
case "only-child":
|
||||
out = onlyChildPseudoClassSelector{ofType: false}
|
||||
case "only-of-type":
|
||||
out = onlyChildPseudoClassSelector{ofType: true}
|
||||
case "input":
|
||||
out = inputPseudoClassSelector{}
|
||||
case "empty":
|
||||
out = emptyElementPseudoClassSelector{}
|
||||
case "root":
|
||||
out = rootPseudoClassSelector{}
|
||||
case "after", "backdrop", "before", "cue", "first-letter", "first-line", "grammar-error", "marker", "placeholder", "selection", "spelling-error":
|
||||
return nil, name, nil
|
||||
default:
|
||||
return out, "", fmt.Errorf("unknown pseudoclass or pseudoelement :%s", name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// parseInteger parses a decimal integer.
|
||||
func (p *parser) parseInteger() (int, error) {
|
||||
i := p.i
|
||||
start := i
|
||||
for i < len(p.s) && '0' <= p.s[i] && p.s[i] <= '9' {
|
||||
i++
|
||||
}
|
||||
if i == start {
|
||||
return 0, errors.New("expected integer, but didn't find it")
|
||||
}
|
||||
p.i = i
|
||||
|
||||
val, err := strconv.Atoi(p.s[start:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// parseNth parses the argument for :nth-child (normally of the form an+b).
|
||||
func (p *parser) parseNth() (a, b int, err error) {
|
||||
// initial state
|
||||
if p.i >= len(p.s) {
|
||||
goto eof
|
||||
}
|
||||
switch p.s[p.i] {
|
||||
case '-':
|
||||
p.i++
|
||||
goto negativeA
|
||||
case '+':
|
||||
p.i++
|
||||
goto positiveA
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
goto positiveA
|
||||
case 'n', 'N':
|
||||
a = 1
|
||||
p.i++
|
||||
goto readN
|
||||
case 'o', 'O', 'e', 'E':
|
||||
id, nameErr := p.parseName()
|
||||
if nameErr != nil {
|
||||
return 0, 0, nameErr
|
||||
}
|
||||
id = toLowerASCII(id)
|
||||
if id == "odd" {
|
||||
return 2, 1, nil
|
||||
}
|
||||
if id == "even" {
|
||||
return 2, 0, nil
|
||||
}
|
||||
return 0, 0, fmt.Errorf("expected 'odd' or 'even', but found '%s' instead", id)
|
||||
default:
|
||||
goto invalid
|
||||
}
|
||||
|
||||
positiveA:
|
||||
if p.i >= len(p.s) {
|
||||
goto eof
|
||||
}
|
||||
switch p.s[p.i] {
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
a, err = p.parseInteger()
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
goto readA
|
||||
case 'n', 'N':
|
||||
a = 1
|
||||
p.i++
|
||||
goto readN
|
||||
default:
|
||||
goto invalid
|
||||
}
|
||||
|
||||
negativeA:
|
||||
if p.i >= len(p.s) {
|
||||
goto eof
|
||||
}
|
||||
switch p.s[p.i] {
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
a, err = p.parseInteger()
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
a = -a
|
||||
goto readA
|
||||
case 'n', 'N':
|
||||
a = -1
|
||||
p.i++
|
||||
goto readN
|
||||
default:
|
||||
goto invalid
|
||||
}
|
||||
|
||||
readA:
|
||||
if p.i >= len(p.s) {
|
||||
goto eof
|
||||
}
|
||||
switch p.s[p.i] {
|
||||
case 'n', 'N':
|
||||
p.i++
|
||||
goto readN
|
||||
default:
|
||||
// The number we read as a is actually b.
|
||||
return 0, a, nil
|
||||
}
|
||||
|
||||
readN:
|
||||
p.skipWhitespace()
|
||||
if p.i >= len(p.s) {
|
||||
goto eof
|
||||
}
|
||||
switch p.s[p.i] {
|
||||
case '+':
|
||||
p.i++
|
||||
p.skipWhitespace()
|
||||
b, err = p.parseInteger()
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return a, b, nil
|
||||
case '-':
|
||||
p.i++
|
||||
p.skipWhitespace()
|
||||
b, err = p.parseInteger()
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return a, -b, nil
|
||||
default:
|
||||
return a, 0, nil
|
||||
}
|
||||
|
||||
eof:
|
||||
return 0, 0, errors.New("unexpected EOF while attempting to parse expression of form an+b")
|
||||
|
||||
invalid:
|
||||
return 0, 0, errors.New("unexpected character while attempting to parse expression of form an+b")
|
||||
}
|
||||
|
||||
// parseSimpleSelectorSequence parses a selector sequence that applies to
|
||||
// a single element.
|
||||
func (p *parser) parseSimpleSelectorSequence() (Sel, error) {
|
||||
var selectors []Sel
|
||||
|
||||
if p.i >= len(p.s) {
|
||||
return nil, errors.New("expected selector, found EOF instead")
|
||||
}
|
||||
|
||||
switch p.s[p.i] {
|
||||
case '*':
|
||||
// It's the universal selector. Just skip over it, since it doesn't affect the meaning.
|
||||
p.i++
|
||||
case '#', '.', '[', ':':
|
||||
// There's no type selector. Wait to process the other till the main loop.
|
||||
default:
|
||||
r, err := p.parseTypeSelector()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
selectors = append(selectors, r)
|
||||
}
|
||||
|
||||
var pseudoElement string
|
||||
loop:
|
||||
for p.i < len(p.s) {
|
||||
var (
|
||||
ns Sel
|
||||
newPseudoElement string
|
||||
err error
|
||||
)
|
||||
switch p.s[p.i] {
|
||||
case '#':
|
||||
ns, err = p.parseIDSelector()
|
||||
case '.':
|
||||
ns, err = p.parseClassSelector()
|
||||
case '[':
|
||||
ns, err = p.parseAttributeSelector()
|
||||
case ':':
|
||||
ns, newPseudoElement, err = p.parsePseudoclassSelector()
|
||||
default:
|
||||
break loop
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// From https://drafts.csswg.org/selectors-3/#pseudo-elements :
|
||||
// "Only one pseudo-element may appear per selector, and if present
|
||||
// it must appear after the sequence of simple selectors that
|
||||
// represents the subjects of the selector.""
|
||||
if ns == nil { // we found a pseudo-element
|
||||
if pseudoElement != "" {
|
||||
return nil, fmt.Errorf("only one pseudo-element is accepted per selector, got %s and %s", pseudoElement, newPseudoElement)
|
||||
}
|
||||
if !p.acceptPseudoElements {
|
||||
return nil, fmt.Errorf("pseudo-element %s found, but pseudo-elements support is disabled", newPseudoElement)
|
||||
}
|
||||
pseudoElement = newPseudoElement
|
||||
} else {
|
||||
if pseudoElement != "" {
|
||||
return nil, fmt.Errorf("pseudo-element %s must be at the end of selector", pseudoElement)
|
||||
}
|
||||
selectors = append(selectors, ns)
|
||||
}
|
||||
|
||||
}
|
||||
if len(selectors) == 1 && pseudoElement == "" { // no need wrap the selectors in compoundSelector
|
||||
return selectors[0], nil
|
||||
}
|
||||
return compoundSelector{selectors: selectors, pseudoElement: pseudoElement}, nil
|
||||
}
|
||||
|
||||
// parseSelector parses a selector that may include combinators.
|
||||
func (p *parser) parseSelector() (Sel, error) {
|
||||
p.skipWhitespace()
|
||||
result, err := p.parseSimpleSelectorSequence()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for {
|
||||
var (
|
||||
combinator byte
|
||||
c Sel
|
||||
)
|
||||
if p.skipWhitespace() {
|
||||
combinator = ' '
|
||||
}
|
||||
if p.i >= len(p.s) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
switch p.s[p.i] {
|
||||
case '+', '>', '~':
|
||||
combinator = p.s[p.i]
|
||||
p.i++
|
||||
p.skipWhitespace()
|
||||
case ',', ')':
|
||||
// These characters can't begin a selector, but they can legally occur after one.
|
||||
return result, nil
|
||||
}
|
||||
|
||||
if combinator == 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
c, err = p.parseSimpleSelectorSequence()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = combinedSelector{first: result, combinator: combinator, second: c}
|
||||
}
|
||||
}
|
||||
|
||||
// parseSelectorGroup parses a group of selectors, separated by commas.
|
||||
func (p *parser) parseSelectorGroup() (SelectorGroup, error) {
|
||||
current, err := p.parseSelector()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := SelectorGroup{current}
|
||||
|
||||
for p.i < len(p.s) {
|
||||
if p.s[p.i] != ',' {
|
||||
break
|
||||
}
|
||||
p.i++
|
||||
c, err := p.parseSelector()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, c)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
938
vendor/github.com/andybalholm/cascadia/selector.go
generated
vendored
Normal file
938
vendor/github.com/andybalholm/cascadia/selector.go
generated
vendored
Normal file
@@ -0,0 +1,938 @@
|
||||
package cascadia
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
// Matcher is the interface for basic selector functionality.
|
||||
// Match returns whether a selector matches n.
|
||||
type Matcher interface {
|
||||
Match(n *html.Node) bool
|
||||
}
|
||||
|
||||
// Sel is the interface for all the functionality provided by selectors.
|
||||
type Sel interface {
|
||||
Matcher
|
||||
Specificity() Specificity
|
||||
|
||||
// Returns a CSS input compiling to this selector.
|
||||
String() string
|
||||
|
||||
// Returns a pseudo-element, or an empty string.
|
||||
PseudoElement() string
|
||||
}
|
||||
|
||||
// Parse parses a selector. Use `ParseWithPseudoElement`
|
||||
// if you need support for pseudo-elements.
|
||||
func Parse(sel string) (Sel, error) {
|
||||
p := &parser{s: sel}
|
||||
compiled, err := p.parseSelector()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if p.i < len(sel) {
|
||||
return nil, fmt.Errorf("parsing %q: %d bytes left over", sel, len(sel)-p.i)
|
||||
}
|
||||
|
||||
return compiled, nil
|
||||
}
|
||||
|
||||
// ParseWithPseudoElement parses a single selector,
|
||||
// with support for pseudo-element.
|
||||
func ParseWithPseudoElement(sel string) (Sel, error) {
|
||||
p := &parser{s: sel, acceptPseudoElements: true}
|
||||
compiled, err := p.parseSelector()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if p.i < len(sel) {
|
||||
return nil, fmt.Errorf("parsing %q: %d bytes left over", sel, len(sel)-p.i)
|
||||
}
|
||||
|
||||
return compiled, nil
|
||||
}
|
||||
|
||||
// ParseGroup parses a selector, or a group of selectors separated by commas.
|
||||
// Use `ParseGroupWithPseudoElements`
|
||||
// if you need support for pseudo-elements.
|
||||
func ParseGroup(sel string) (SelectorGroup, error) {
|
||||
p := &parser{s: sel}
|
||||
compiled, err := p.parseSelectorGroup()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if p.i < len(sel) {
|
||||
return nil, fmt.Errorf("parsing %q: %d bytes left over", sel, len(sel)-p.i)
|
||||
}
|
||||
|
||||
return compiled, nil
|
||||
}
|
||||
|
||||
// ParseGroupWithPseudoElements parses a selector, or a group of selectors separated by commas.
|
||||
// It supports pseudo-elements.
|
||||
func ParseGroupWithPseudoElements(sel string) (SelectorGroup, error) {
|
||||
p := &parser{s: sel, acceptPseudoElements: true}
|
||||
compiled, err := p.parseSelectorGroup()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if p.i < len(sel) {
|
||||
return nil, fmt.Errorf("parsing %q: %d bytes left over", sel, len(sel)-p.i)
|
||||
}
|
||||
|
||||
return compiled, nil
|
||||
}
|
||||
|
||||
// A Selector is a function which tells whether a node matches or not.
|
||||
//
|
||||
// This type is maintained for compatibility; I recommend using the newer and
|
||||
// more idiomatic interfaces Sel and Matcher.
|
||||
type Selector func(*html.Node) bool
|
||||
|
||||
// Compile parses a selector and returns, if successful, a Selector object
|
||||
// that can be used to match against html.Node objects.
|
||||
func Compile(sel string) (Selector, error) {
|
||||
compiled, err := ParseGroup(sel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return Selector(compiled.Match), nil
|
||||
}
|
||||
|
||||
// MustCompile is like Compile, but panics instead of returning an error.
|
||||
func MustCompile(sel string) Selector {
|
||||
compiled, err := Compile(sel)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return compiled
|
||||
}
|
||||
|
||||
// MatchAll returns a slice of the nodes that match the selector,
|
||||
// from n and its children.
|
||||
func (s Selector) MatchAll(n *html.Node) []*html.Node {
|
||||
return s.matchAllInto(n, nil)
|
||||
}
|
||||
|
||||
func (s Selector) matchAllInto(n *html.Node, storage []*html.Node) []*html.Node {
|
||||
if s(n) {
|
||||
storage = append(storage, n)
|
||||
}
|
||||
|
||||
for child := n.FirstChild; child != nil; child = child.NextSibling {
|
||||
storage = s.matchAllInto(child, storage)
|
||||
}
|
||||
|
||||
return storage
|
||||
}
|
||||
|
||||
func queryInto(n *html.Node, m Matcher, storage []*html.Node) []*html.Node {
|
||||
for child := n.FirstChild; child != nil; child = child.NextSibling {
|
||||
if m.Match(child) {
|
||||
storage = append(storage, child)
|
||||
}
|
||||
storage = queryInto(child, m, storage)
|
||||
}
|
||||
|
||||
return storage
|
||||
}
|
||||
|
||||
// QueryAll returns a slice of all the nodes that match m, from the descendants
|
||||
// of n.
|
||||
func QueryAll(n *html.Node, m Matcher) []*html.Node {
|
||||
return queryInto(n, m, nil)
|
||||
}
|
||||
|
||||
// Match returns true if the node matches the selector.
|
||||
func (s Selector) Match(n *html.Node) bool {
|
||||
return s(n)
|
||||
}
|
||||
|
||||
// MatchFirst returns the first node that matches s, from n and its children.
|
||||
func (s Selector) MatchFirst(n *html.Node) *html.Node {
|
||||
if s.Match(n) {
|
||||
return n
|
||||
}
|
||||
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
m := s.MatchFirst(c)
|
||||
if m != nil {
|
||||
return m
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Query returns the first node that matches m, from the descendants of n.
|
||||
// If none matches, it returns nil.
|
||||
func Query(n *html.Node, m Matcher) *html.Node {
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
if m.Match(c) {
|
||||
return c
|
||||
}
|
||||
if matched := Query(c, m); matched != nil {
|
||||
return matched
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Filter returns the nodes in nodes that match the selector.
|
||||
func (s Selector) Filter(nodes []*html.Node) (result []*html.Node) {
|
||||
for _, n := range nodes {
|
||||
if s(n) {
|
||||
result = append(result, n)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Filter returns the nodes that match m.
|
||||
func Filter(nodes []*html.Node, m Matcher) (result []*html.Node) {
|
||||
for _, n := range nodes {
|
||||
if m.Match(n) {
|
||||
result = append(result, n)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type tagSelector struct {
|
||||
tag string
|
||||
}
|
||||
|
||||
// Matches elements with a given tag name.
|
||||
func (t tagSelector) Match(n *html.Node) bool {
|
||||
return n.Type == html.ElementNode && n.Data == t.tag
|
||||
}
|
||||
|
||||
func (c tagSelector) Specificity() Specificity {
|
||||
return Specificity{0, 0, 1}
|
||||
}
|
||||
|
||||
func (c tagSelector) PseudoElement() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type classSelector struct {
|
||||
class string
|
||||
}
|
||||
|
||||
// Matches elements by class attribute.
|
||||
func (t classSelector) Match(n *html.Node) bool {
|
||||
return matchAttribute(n, "class", func(s string) bool {
|
||||
return matchInclude(t.class, s)
|
||||
})
|
||||
}
|
||||
|
||||
func (c classSelector) Specificity() Specificity {
|
||||
return Specificity{0, 1, 0}
|
||||
}
|
||||
|
||||
func (c classSelector) PseudoElement() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type idSelector struct {
|
||||
id string
|
||||
}
|
||||
|
||||
// Matches elements by id attribute.
|
||||
func (t idSelector) Match(n *html.Node) bool {
|
||||
return matchAttribute(n, "id", func(s string) bool {
|
||||
return s == t.id
|
||||
})
|
||||
}
|
||||
|
||||
func (c idSelector) Specificity() Specificity {
|
||||
return Specificity{1, 0, 0}
|
||||
}
|
||||
|
||||
func (c idSelector) PseudoElement() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type attrSelector struct {
|
||||
key, val, operation string
|
||||
regexp *regexp.Regexp
|
||||
}
|
||||
|
||||
// Matches elements by attribute value.
|
||||
func (t attrSelector) Match(n *html.Node) bool {
|
||||
switch t.operation {
|
||||
case "":
|
||||
return matchAttribute(n, t.key, func(string) bool { return true })
|
||||
case "=":
|
||||
return matchAttribute(n, t.key, func(s string) bool { return s == t.val })
|
||||
case "!=":
|
||||
return attributeNotEqualMatch(t.key, t.val, n)
|
||||
case "~=":
|
||||
// matches elements where the attribute named key is a whitespace-separated list that includes val.
|
||||
return matchAttribute(n, t.key, func(s string) bool { return matchInclude(t.val, s) })
|
||||
case "|=":
|
||||
return attributeDashMatch(t.key, t.val, n)
|
||||
case "^=":
|
||||
return attributePrefixMatch(t.key, t.val, n)
|
||||
case "$=":
|
||||
return attributeSuffixMatch(t.key, t.val, n)
|
||||
case "*=":
|
||||
return attributeSubstringMatch(t.key, t.val, n)
|
||||
case "#=":
|
||||
return attributeRegexMatch(t.key, t.regexp, n)
|
||||
default:
|
||||
panic(fmt.Sprintf("unsuported operation : %s", t.operation))
|
||||
}
|
||||
}
|
||||
|
||||
// matches elements where the attribute named key satisifes the function f.
|
||||
func matchAttribute(n *html.Node, key string, f func(string) bool) bool {
|
||||
if n.Type != html.ElementNode {
|
||||
return false
|
||||
}
|
||||
for _, a := range n.Attr {
|
||||
if a.Key == key && f(a.Val) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// attributeNotEqualMatch matches elements where
|
||||
// the attribute named key does not have the value val.
|
||||
func attributeNotEqualMatch(key, val string, n *html.Node) bool {
|
||||
if n.Type != html.ElementNode {
|
||||
return false
|
||||
}
|
||||
for _, a := range n.Attr {
|
||||
if a.Key == key && a.Val == val {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// returns true if s is a whitespace-separated list that includes val.
|
||||
func matchInclude(val, s string) bool {
|
||||
for s != "" {
|
||||
i := strings.IndexAny(s, " \t\r\n\f")
|
||||
if i == -1 {
|
||||
return s == val
|
||||
}
|
||||
if s[:i] == val {
|
||||
return true
|
||||
}
|
||||
s = s[i+1:]
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// matches elements where the attribute named key equals val or starts with val plus a hyphen.
|
||||
func attributeDashMatch(key, val string, n *html.Node) bool {
|
||||
return matchAttribute(n, key,
|
||||
func(s string) bool {
|
||||
if s == val {
|
||||
return true
|
||||
}
|
||||
if len(s) <= len(val) {
|
||||
return false
|
||||
}
|
||||
if s[:len(val)] == val && s[len(val)] == '-' {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
// attributePrefixMatch returns a Selector that matches elements where
|
||||
// the attribute named key starts with val.
|
||||
func attributePrefixMatch(key, val string, n *html.Node) bool {
|
||||
return matchAttribute(n, key,
|
||||
func(s string) bool {
|
||||
if strings.TrimSpace(s) == "" {
|
||||
return false
|
||||
}
|
||||
return strings.HasPrefix(s, val)
|
||||
})
|
||||
}
|
||||
|
||||
// attributeSuffixMatch matches elements where
|
||||
// the attribute named key ends with val.
|
||||
func attributeSuffixMatch(key, val string, n *html.Node) bool {
|
||||
return matchAttribute(n, key,
|
||||
func(s string) bool {
|
||||
if strings.TrimSpace(s) == "" {
|
||||
return false
|
||||
}
|
||||
return strings.HasSuffix(s, val)
|
||||
})
|
||||
}
|
||||
|
||||
// attributeSubstringMatch matches nodes where
|
||||
// the attribute named key contains val.
|
||||
func attributeSubstringMatch(key, val string, n *html.Node) bool {
|
||||
return matchAttribute(n, key,
|
||||
func(s string) bool {
|
||||
if strings.TrimSpace(s) == "" {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(s, val)
|
||||
})
|
||||
}
|
||||
|
||||
// attributeRegexMatch matches nodes where
|
||||
// the attribute named key matches the regular expression rx
|
||||
func attributeRegexMatch(key string, rx *regexp.Regexp, n *html.Node) bool {
|
||||
return matchAttribute(n, key,
|
||||
func(s string) bool {
|
||||
return rx.MatchString(s)
|
||||
})
|
||||
}
|
||||
|
||||
func (c attrSelector) Specificity() Specificity {
|
||||
return Specificity{0, 1, 0}
|
||||
}
|
||||
|
||||
func (c attrSelector) PseudoElement() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ---------------- Pseudo class selectors ----------------
|
||||
// we use severals concrete types of pseudo-class selectors
|
||||
|
||||
type relativePseudoClassSelector struct {
|
||||
name string // one of "not", "has", "haschild"
|
||||
match SelectorGroup
|
||||
}
|
||||
|
||||
func (s relativePseudoClassSelector) Match(n *html.Node) bool {
|
||||
if n.Type != html.ElementNode {
|
||||
return false
|
||||
}
|
||||
switch s.name {
|
||||
case "not":
|
||||
// matches elements that do not match a.
|
||||
return !s.match.Match(n)
|
||||
case "has":
|
||||
// matches elements with any descendant that matches a.
|
||||
return hasDescendantMatch(n, s.match)
|
||||
case "haschild":
|
||||
// matches elements with a child that matches a.
|
||||
return hasChildMatch(n, s.match)
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported relative pseudo class selector : %s", s.name))
|
||||
}
|
||||
}
|
||||
|
||||
// hasChildMatch returns whether n has any child that matches a.
|
||||
func hasChildMatch(n *html.Node, a Matcher) bool {
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
if a.Match(c) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasDescendantMatch performs a depth-first search of n's descendants,
|
||||
// testing whether any of them match a. It returns true as soon as a match is
|
||||
// found, or false if no match is found.
|
||||
func hasDescendantMatch(n *html.Node, a Matcher) bool {
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
if a.Match(c) || (c.Type == html.ElementNode && hasDescendantMatch(c, a)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Specificity returns the specificity of the most specific selectors
|
||||
// in the pseudo-class arguments.
|
||||
// See https://www.w3.org/TR/selectors/#specificity-rules
|
||||
func (s relativePseudoClassSelector) Specificity() Specificity {
|
||||
var max Specificity
|
||||
for _, sel := range s.match {
|
||||
newSpe := sel.Specificity()
|
||||
if max.Less(newSpe) {
|
||||
max = newSpe
|
||||
}
|
||||
}
|
||||
return max
|
||||
}
|
||||
|
||||
func (c relativePseudoClassSelector) PseudoElement() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type containsPseudoClassSelector struct {
|
||||
own bool
|
||||
value string
|
||||
}
|
||||
|
||||
func (s containsPseudoClassSelector) Match(n *html.Node) bool {
|
||||
var text string
|
||||
if s.own {
|
||||
// matches nodes that directly contain the given text
|
||||
text = strings.ToLower(nodeOwnText(n))
|
||||
} else {
|
||||
// matches nodes that contain the given text.
|
||||
text = strings.ToLower(nodeText(n))
|
||||
}
|
||||
return strings.Contains(text, s.value)
|
||||
}
|
||||
|
||||
func (s containsPseudoClassSelector) Specificity() Specificity {
|
||||
return Specificity{0, 1, 0}
|
||||
}
|
||||
|
||||
func (c containsPseudoClassSelector) PseudoElement() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type regexpPseudoClassSelector struct {
|
||||
own bool
|
||||
regexp *regexp.Regexp
|
||||
}
|
||||
|
||||
func (s regexpPseudoClassSelector) Match(n *html.Node) bool {
|
||||
var text string
|
||||
if s.own {
|
||||
// matches nodes whose text directly matches the specified regular expression
|
||||
text = nodeOwnText(n)
|
||||
} else {
|
||||
// matches nodes whose text matches the specified regular expression
|
||||
text = nodeText(n)
|
||||
}
|
||||
return s.regexp.MatchString(text)
|
||||
}
|
||||
|
||||
// writeNodeText writes the text contained in n and its descendants to b.
|
||||
func writeNodeText(n *html.Node, b *bytes.Buffer) {
|
||||
switch n.Type {
|
||||
case html.TextNode:
|
||||
b.WriteString(n.Data)
|
||||
case html.ElementNode:
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
writeNodeText(c, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// nodeText returns the text contained in n and its descendants.
|
||||
func nodeText(n *html.Node) string {
|
||||
var b bytes.Buffer
|
||||
writeNodeText(n, &b)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// nodeOwnText returns the contents of the text nodes that are direct
|
||||
// children of n.
|
||||
func nodeOwnText(n *html.Node) string {
|
||||
var b bytes.Buffer
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
if c.Type == html.TextNode {
|
||||
b.WriteString(c.Data)
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (s regexpPseudoClassSelector) Specificity() Specificity {
|
||||
return Specificity{0, 1, 0}
|
||||
}
|
||||
|
||||
func (c regexpPseudoClassSelector) PseudoElement() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type nthPseudoClassSelector struct {
|
||||
a, b int
|
||||
last, ofType bool
|
||||
}
|
||||
|
||||
func (s nthPseudoClassSelector) Match(n *html.Node) bool {
|
||||
if s.a == 0 {
|
||||
if s.last {
|
||||
return simpleNthLastChildMatch(s.b, s.ofType, n)
|
||||
} else {
|
||||
return simpleNthChildMatch(s.b, s.ofType, n)
|
||||
}
|
||||
}
|
||||
return nthChildMatch(s.a, s.b, s.last, s.ofType, n)
|
||||
}
|
||||
|
||||
// nthChildMatch implements :nth-child(an+b).
|
||||
// If last is true, implements :nth-last-child instead.
|
||||
// If ofType is true, implements :nth-of-type instead.
|
||||
func nthChildMatch(a, b int, last, ofType bool, n *html.Node) bool {
|
||||
if n.Type != html.ElementNode {
|
||||
return false
|
||||
}
|
||||
|
||||
parent := n.Parent
|
||||
if parent == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if parent.Type == html.DocumentNode {
|
||||
return false
|
||||
}
|
||||
|
||||
i := -1
|
||||
count := 0
|
||||
for c := parent.FirstChild; c != nil; c = c.NextSibling {
|
||||
if (c.Type != html.ElementNode) || (ofType && c.Data != n.Data) {
|
||||
continue
|
||||
}
|
||||
count++
|
||||
if c == n {
|
||||
i = count
|
||||
if !last {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if i == -1 {
|
||||
// This shouldn't happen, since n should always be one of its parent's children.
|
||||
return false
|
||||
}
|
||||
|
||||
if last {
|
||||
i = count - i + 1
|
||||
}
|
||||
|
||||
i -= b
|
||||
if a == 0 {
|
||||
return i == 0
|
||||
}
|
||||
|
||||
return i%a == 0 && i/a >= 0
|
||||
}
|
||||
|
||||
// simpleNthChildMatch implements :nth-child(b).
|
||||
// If ofType is true, implements :nth-of-type instead.
|
||||
func simpleNthChildMatch(b int, ofType bool, n *html.Node) bool {
|
||||
if n.Type != html.ElementNode {
|
||||
return false
|
||||
}
|
||||
|
||||
parent := n.Parent
|
||||
if parent == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if parent.Type == html.DocumentNode {
|
||||
return false
|
||||
}
|
||||
|
||||
count := 0
|
||||
for c := parent.FirstChild; c != nil; c = c.NextSibling {
|
||||
if c.Type != html.ElementNode || (ofType && c.Data != n.Data) {
|
||||
continue
|
||||
}
|
||||
count++
|
||||
if c == n {
|
||||
return count == b
|
||||
}
|
||||
if count >= b {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// simpleNthLastChildMatch implements :nth-last-child(b).
|
||||
// If ofType is true, implements :nth-last-of-type instead.
|
||||
func simpleNthLastChildMatch(b int, ofType bool, n *html.Node) bool {
|
||||
if n.Type != html.ElementNode {
|
||||
return false
|
||||
}
|
||||
|
||||
parent := n.Parent
|
||||
if parent == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if parent.Type == html.DocumentNode {
|
||||
return false
|
||||
}
|
||||
|
||||
count := 0
|
||||
for c := parent.LastChild; c != nil; c = c.PrevSibling {
|
||||
if c.Type != html.ElementNode || (ofType && c.Data != n.Data) {
|
||||
continue
|
||||
}
|
||||
count++
|
||||
if c == n {
|
||||
return count == b
|
||||
}
|
||||
if count >= b {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Specificity for nth-child pseudo-class.
|
||||
// Does not support a list of selectors
|
||||
func (s nthPseudoClassSelector) Specificity() Specificity {
|
||||
return Specificity{0, 1, 0}
|
||||
}
|
||||
|
||||
func (c nthPseudoClassSelector) PseudoElement() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type onlyChildPseudoClassSelector struct {
|
||||
ofType bool
|
||||
}
|
||||
|
||||
// Match implements :only-child.
|
||||
// If `ofType` is true, it implements :only-of-type instead.
|
||||
func (s onlyChildPseudoClassSelector) Match(n *html.Node) bool {
|
||||
if n.Type != html.ElementNode {
|
||||
return false
|
||||
}
|
||||
|
||||
parent := n.Parent
|
||||
if parent == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if parent.Type == html.DocumentNode {
|
||||
return false
|
||||
}
|
||||
|
||||
count := 0
|
||||
for c := parent.FirstChild; c != nil; c = c.NextSibling {
|
||||
if (c.Type != html.ElementNode) || (s.ofType && c.Data != n.Data) {
|
||||
continue
|
||||
}
|
||||
count++
|
||||
if count > 1 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return count == 1
|
||||
}
|
||||
|
||||
func (s onlyChildPseudoClassSelector) Specificity() Specificity {
|
||||
return Specificity{0, 1, 0}
|
||||
}
|
||||
|
||||
func (c onlyChildPseudoClassSelector) PseudoElement() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type inputPseudoClassSelector struct{}
|
||||
|
||||
// Matches input, select, textarea and button elements.
|
||||
func (s inputPseudoClassSelector) Match(n *html.Node) bool {
|
||||
return n.Type == html.ElementNode && (n.Data == "input" || n.Data == "select" || n.Data == "textarea" || n.Data == "button")
|
||||
}
|
||||
|
||||
func (s inputPseudoClassSelector) Specificity() Specificity {
|
||||
return Specificity{0, 1, 0}
|
||||
}
|
||||
|
||||
func (c inputPseudoClassSelector) PseudoElement() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type emptyElementPseudoClassSelector struct{}
|
||||
|
||||
// Matches empty elements.
|
||||
func (s emptyElementPseudoClassSelector) Match(n *html.Node) bool {
|
||||
if n.Type != html.ElementNode {
|
||||
return false
|
||||
}
|
||||
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
switch c.Type {
|
||||
case html.ElementNode, html.TextNode:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (s emptyElementPseudoClassSelector) Specificity() Specificity {
|
||||
return Specificity{0, 1, 0}
|
||||
}
|
||||
|
||||
func (c emptyElementPseudoClassSelector) PseudoElement() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type rootPseudoClassSelector struct{}
|
||||
|
||||
// Match implements :root
|
||||
func (s rootPseudoClassSelector) Match(n *html.Node) bool {
|
||||
if n.Type != html.ElementNode {
|
||||
return false
|
||||
}
|
||||
if n.Parent == nil {
|
||||
return false
|
||||
}
|
||||
return n.Parent.Type == html.DocumentNode
|
||||
}
|
||||
|
||||
func (s rootPseudoClassSelector) Specificity() Specificity {
|
||||
return Specificity{0, 1, 0}
|
||||
}
|
||||
|
||||
func (c rootPseudoClassSelector) PseudoElement() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type compoundSelector struct {
|
||||
selectors []Sel
|
||||
pseudoElement string
|
||||
}
|
||||
|
||||
// Matches elements if each sub-selectors matches.
|
||||
func (t compoundSelector) Match(n *html.Node) bool {
|
||||
if len(t.selectors) == 0 {
|
||||
return n.Type == html.ElementNode
|
||||
}
|
||||
|
||||
for _, sel := range t.selectors {
|
||||
if !sel.Match(n) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s compoundSelector) Specificity() Specificity {
|
||||
var out Specificity
|
||||
for _, sel := range s.selectors {
|
||||
out = out.Add(sel.Specificity())
|
||||
}
|
||||
if s.pseudoElement != "" {
|
||||
// https://drafts.csswg.org/selectors-3/#specificity
|
||||
out = out.Add(Specificity{0, 0, 1})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (c compoundSelector) PseudoElement() string {
|
||||
return c.pseudoElement
|
||||
}
|
||||
|
||||
type combinedSelector struct {
|
||||
first Sel
|
||||
combinator byte
|
||||
second Sel
|
||||
}
|
||||
|
||||
func (t combinedSelector) Match(n *html.Node) bool {
|
||||
if t.first == nil {
|
||||
return false // maybe we should panic
|
||||
}
|
||||
switch t.combinator {
|
||||
case 0:
|
||||
return t.first.Match(n)
|
||||
case ' ':
|
||||
return descendantMatch(t.first, t.second, n)
|
||||
case '>':
|
||||
return childMatch(t.first, t.second, n)
|
||||
case '+':
|
||||
return siblingMatch(t.first, t.second, true, n)
|
||||
case '~':
|
||||
return siblingMatch(t.first, t.second, false, n)
|
||||
default:
|
||||
panic("unknown combinator")
|
||||
}
|
||||
}
|
||||
|
||||
// matches an element if it matches d and has an ancestor that matches a.
|
||||
func descendantMatch(a, d Matcher, n *html.Node) bool {
|
||||
if !d.Match(n) {
|
||||
return false
|
||||
}
|
||||
|
||||
for p := n.Parent; p != nil; p = p.Parent {
|
||||
if a.Match(p) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// matches an element if it matches d and its parent matches a.
|
||||
func childMatch(a, d Matcher, n *html.Node) bool {
|
||||
return d.Match(n) && n.Parent != nil && a.Match(n.Parent)
|
||||
}
|
||||
|
||||
// matches an element if it matches s2 and is preceded by an element that matches s1.
|
||||
// If adjacent is true, the sibling must be immediately before the element.
|
||||
func siblingMatch(s1, s2 Matcher, adjacent bool, n *html.Node) bool {
|
||||
if !s2.Match(n) {
|
||||
return false
|
||||
}
|
||||
|
||||
if adjacent {
|
||||
for n = n.PrevSibling; n != nil; n = n.PrevSibling {
|
||||
if n.Type == html.TextNode || n.Type == html.CommentNode {
|
||||
continue
|
||||
}
|
||||
return s1.Match(n)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Walk backwards looking for element that matches s1
|
||||
for c := n.PrevSibling; c != nil; c = c.PrevSibling {
|
||||
if s1.Match(c) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (s combinedSelector) Specificity() Specificity {
|
||||
spec := s.first.Specificity()
|
||||
if s.second != nil {
|
||||
spec = spec.Add(s.second.Specificity())
|
||||
}
|
||||
return spec
|
||||
}
|
||||
|
||||
// on combinedSelector, a pseudo-element only makes sens on the last
|
||||
// selector, although others increase specificity.
|
||||
func (c combinedSelector) PseudoElement() string {
|
||||
if c.second == nil {
|
||||
return ""
|
||||
}
|
||||
return c.second.PseudoElement()
|
||||
}
|
||||
|
||||
// A SelectorGroup is a list of selectors, which matches if any of the
|
||||
// individual selectors matches.
|
||||
type SelectorGroup []Sel
|
||||
|
||||
// Match returns true if the node matches one of the single selectors.
|
||||
func (s SelectorGroup) Match(n *html.Node) bool {
|
||||
for _, sel := range s {
|
||||
if sel.Match(n) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
120
vendor/github.com/andybalholm/cascadia/serialize.go
generated
vendored
Normal file
120
vendor/github.com/andybalholm/cascadia/serialize.go
generated
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
package cascadia
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// implements the reverse operation Sel -> string
|
||||
|
||||
func (c tagSelector) String() string {
|
||||
return c.tag
|
||||
}
|
||||
|
||||
func (c idSelector) String() string {
|
||||
return "#" + c.id
|
||||
}
|
||||
|
||||
func (c classSelector) String() string {
|
||||
return "." + c.class
|
||||
}
|
||||
|
||||
func (c attrSelector) String() string {
|
||||
val := c.val
|
||||
if c.operation == "#=" {
|
||||
val = c.regexp.String()
|
||||
} else if c.operation != "" {
|
||||
val = fmt.Sprintf(`"%s"`, val)
|
||||
}
|
||||
return fmt.Sprintf(`[%s%s%s]`, c.key, c.operation, val)
|
||||
}
|
||||
|
||||
func (c relativePseudoClassSelector) String() string {
|
||||
return fmt.Sprintf(":%s(%s)", c.name, c.match.String())
|
||||
}
|
||||
func (c containsPseudoClassSelector) String() string {
|
||||
s := "contains"
|
||||
if c.own {
|
||||
s += "Own"
|
||||
}
|
||||
return fmt.Sprintf(`:%s("%s")`, s, c.value)
|
||||
}
|
||||
func (c regexpPseudoClassSelector) String() string {
|
||||
s := "matches"
|
||||
if c.own {
|
||||
s += "Own"
|
||||
}
|
||||
return fmt.Sprintf(":%s(%s)", s, c.regexp.String())
|
||||
}
|
||||
func (c nthPseudoClassSelector) String() string {
|
||||
if c.a == 0 && c.b == 1 { // special cases
|
||||
s := ":first-"
|
||||
if c.last {
|
||||
s = ":last-"
|
||||
}
|
||||
if c.ofType {
|
||||
s += "of-type"
|
||||
} else {
|
||||
s += "child"
|
||||
}
|
||||
return s
|
||||
}
|
||||
var name string
|
||||
switch [2]bool{c.last, c.ofType} {
|
||||
case [2]bool{true, true}:
|
||||
name = "nth-last-of-type"
|
||||
case [2]bool{true, false}:
|
||||
name = "nth-last-child"
|
||||
case [2]bool{false, true}:
|
||||
name = "nth-of-type"
|
||||
case [2]bool{false, false}:
|
||||
name = "nth-child"
|
||||
}
|
||||
return fmt.Sprintf(":%s(%dn+%d)", name, c.a, c.b)
|
||||
}
|
||||
func (c onlyChildPseudoClassSelector) String() string {
|
||||
if c.ofType {
|
||||
return ":only-of-type"
|
||||
}
|
||||
return ":only-child"
|
||||
}
|
||||
func (c inputPseudoClassSelector) String() string {
|
||||
return ":input"
|
||||
}
|
||||
func (c emptyElementPseudoClassSelector) String() string {
|
||||
return ":empty"
|
||||
}
|
||||
func (c rootPseudoClassSelector) String() string {
|
||||
return ":root"
|
||||
}
|
||||
|
||||
func (c compoundSelector) String() string {
|
||||
if len(c.selectors) == 0 && c.pseudoElement == "" {
|
||||
return "*"
|
||||
}
|
||||
chunks := make([]string, len(c.selectors))
|
||||
for i, sel := range c.selectors {
|
||||
chunks[i] = sel.String()
|
||||
}
|
||||
s := strings.Join(chunks, "")
|
||||
if c.pseudoElement != "" {
|
||||
s += "::" + c.pseudoElement
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (c combinedSelector) String() string {
|
||||
start := c.first.String()
|
||||
if c.second != nil {
|
||||
start += fmt.Sprintf(" %s %s", string(c.combinator), c.second.String())
|
||||
}
|
||||
return start
|
||||
}
|
||||
|
||||
func (c SelectorGroup) String() string {
|
||||
ck := make([]string, len(c))
|
||||
for i, s := range c {
|
||||
ck[i] = s.String()
|
||||
}
|
||||
return strings.Join(ck, ", ")
|
||||
}
|
||||
26
vendor/github.com/andybalholm/cascadia/specificity.go
generated
vendored
Normal file
26
vendor/github.com/andybalholm/cascadia/specificity.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
package cascadia
|
||||
|
||||
// Specificity is the CSS specificity as defined in
|
||||
// https://www.w3.org/TR/selectors/#specificity-rules
|
||||
// with the convention Specificity = [A,B,C].
|
||||
type Specificity [3]int
|
||||
|
||||
// returns `true` if s < other (strictly), false otherwise
|
||||
func (s Specificity) Less(other Specificity) bool {
|
||||
for i := range s {
|
||||
if s[i] < other[i] {
|
||||
return true
|
||||
}
|
||||
if s[i] > other[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s Specificity) Add(other Specificity) Specificity {
|
||||
for i, sp := range other {
|
||||
s[i] += sp
|
||||
}
|
||||
return s
|
||||
}
|
||||
202
vendor/github.com/aws/aws-sdk-go/LICENSE.txt
generated
vendored
Executable file
202
vendor/github.com/aws/aws-sdk-go/LICENSE.txt
generated
vendored
Executable file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
3
vendor/github.com/aws/aws-sdk-go/NOTICE.txt
generated
vendored
Executable file
3
vendor/github.com/aws/aws-sdk-go/NOTICE.txt
generated
vendored
Executable file
@@ -0,0 +1,3 @@
|
||||
AWS SDK for Go
|
||||
Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
Copyright 2014-2015 Stripe, Inc.
|
||||
145
vendor/github.com/aws/aws-sdk-go/aws/awserr/error.go
generated
vendored
Executable file
145
vendor/github.com/aws/aws-sdk-go/aws/awserr/error.go
generated
vendored
Executable file
@@ -0,0 +1,145 @@
|
||||
// Package awserr represents API error interface accessors for the SDK.
|
||||
package awserr
|
||||
|
||||
// An Error wraps lower level errors with code, message and an original error.
|
||||
// The underlying concrete error type may also satisfy other interfaces which
|
||||
// can be to used to obtain more specific information about the error.
|
||||
//
|
||||
// Calling Error() or String() will always include the full information about
|
||||
// an error based on its underlying type.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// output, err := s3manage.Upload(svc, input, opts)
|
||||
// if err != nil {
|
||||
// if awsErr, ok := err.(awserr.Error); ok {
|
||||
// // Get error details
|
||||
// log.Println("Error:", awsErr.Code(), awsErr.Message())
|
||||
//
|
||||
// // Prints out full error message, including original error if there was one.
|
||||
// log.Println("Error:", awsErr.Error())
|
||||
//
|
||||
// // Get original error
|
||||
// if origErr := awsErr.OrigErr(); origErr != nil {
|
||||
// // operate on original error.
|
||||
// }
|
||||
// } else {
|
||||
// fmt.Println(err.Error())
|
||||
// }
|
||||
// }
|
||||
//
|
||||
type Error interface {
|
||||
// Satisfy the generic error interface.
|
||||
error
|
||||
|
||||
// Returns the short phrase depicting the classification of the error.
|
||||
Code() string
|
||||
|
||||
// Returns the error details message.
|
||||
Message() string
|
||||
|
||||
// Returns the original error if one was set. Nil is returned if not set.
|
||||
OrigErr() error
|
||||
}
|
||||
|
||||
// BatchError is a batch of errors which also wraps lower level errors with
|
||||
// code, message, and original errors. Calling Error() will include all errors
|
||||
// that occurred in the batch.
|
||||
//
|
||||
// Deprecated: Replaced with BatchedErrors. Only defined for backwards
|
||||
// compatibility.
|
||||
type BatchError interface {
|
||||
// Satisfy the generic error interface.
|
||||
error
|
||||
|
||||
// Returns the short phrase depicting the classification of the error.
|
||||
Code() string
|
||||
|
||||
// Returns the error details message.
|
||||
Message() string
|
||||
|
||||
// Returns the original error if one was set. Nil is returned if not set.
|
||||
OrigErrs() []error
|
||||
}
|
||||
|
||||
// BatchedErrors is a batch of errors which also wraps lower level errors with
|
||||
// code, message, and original errors. Calling Error() will include all errors
|
||||
// that occurred in the batch.
|
||||
//
|
||||
// Replaces BatchError
|
||||
type BatchedErrors interface {
|
||||
// Satisfy the base Error interface.
|
||||
Error
|
||||
|
||||
// Returns the original error if one was set. Nil is returned if not set.
|
||||
OrigErrs() []error
|
||||
}
|
||||
|
||||
// New returns an Error object described by the code, message, and origErr.
|
||||
//
|
||||
// If origErr satisfies the Error interface it will not be wrapped within a new
|
||||
// Error object and will instead be returned.
|
||||
func New(code, message string, origErr error) Error {
|
||||
var errs []error
|
||||
if origErr != nil {
|
||||
errs = append(errs, origErr)
|
||||
}
|
||||
return newBaseError(code, message, errs)
|
||||
}
|
||||
|
||||
// NewBatchError returns an BatchedErrors with a collection of errors as an
|
||||
// array of errors.
|
||||
func NewBatchError(code, message string, errs []error) BatchedErrors {
|
||||
return newBaseError(code, message, errs)
|
||||
}
|
||||
|
||||
// A RequestFailure is an interface to extract request failure information from
|
||||
// an Error such as the request ID of the failed request returned by a service.
|
||||
// RequestFailures may not always have a requestID value if the request failed
|
||||
// prior to reaching the service such as a connection error.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// output, err := s3manage.Upload(svc, input, opts)
|
||||
// if err != nil {
|
||||
// if reqerr, ok := err.(RequestFailure); ok {
|
||||
// log.Println("Request failed", reqerr.Code(), reqerr.Message(), reqerr.RequestID())
|
||||
// } else {
|
||||
// log.Println("Error:", err.Error())
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Combined with awserr.Error:
|
||||
//
|
||||
// output, err := s3manage.Upload(svc, input, opts)
|
||||
// if err != nil {
|
||||
// if awsErr, ok := err.(awserr.Error); ok {
|
||||
// // Generic AWS Error with Code, Message, and original error (if any)
|
||||
// fmt.Println(awsErr.Code(), awsErr.Message(), awsErr.OrigErr())
|
||||
//
|
||||
// if reqErr, ok := err.(awserr.RequestFailure); ok {
|
||||
// // A service error occurred
|
||||
// fmt.Println(reqErr.StatusCode(), reqErr.RequestID())
|
||||
// }
|
||||
// } else {
|
||||
// fmt.Println(err.Error())
|
||||
// }
|
||||
// }
|
||||
//
|
||||
type RequestFailure interface {
|
||||
Error
|
||||
|
||||
// The status code of the HTTP response.
|
||||
StatusCode() int
|
||||
|
||||
// The request ID returned by the service for a request failure. This will
|
||||
// be empty if no request ID is available such as the request failed due
|
||||
// to a connection error.
|
||||
RequestID() string
|
||||
}
|
||||
|
||||
// NewRequestFailure returns a new request error wrapper for the given Error
|
||||
// provided.
|
||||
func NewRequestFailure(err Error, statusCode int, reqID string) RequestFailure {
|
||||
return newRequestError(err, statusCode, reqID)
|
||||
}
|
||||
194
vendor/github.com/aws/aws-sdk-go/aws/awserr/types.go
generated
vendored
Executable file
194
vendor/github.com/aws/aws-sdk-go/aws/awserr/types.go
generated
vendored
Executable file
@@ -0,0 +1,194 @@
|
||||
package awserr
|
||||
|
||||
import "fmt"
|
||||
|
||||
// SprintError returns a string of the formatted error code.
|
||||
//
|
||||
// Both extra and origErr are optional. If they are included their lines
|
||||
// will be added, but if they are not included their lines will be ignored.
|
||||
func SprintError(code, message, extra string, origErr error) string {
|
||||
msg := fmt.Sprintf("%s: %s", code, message)
|
||||
if extra != "" {
|
||||
msg = fmt.Sprintf("%s\n\t%s", msg, extra)
|
||||
}
|
||||
if origErr != nil {
|
||||
msg = fmt.Sprintf("%s\ncaused by: %s", msg, origErr.Error())
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
// A baseError wraps the code and message which defines an error. It also
|
||||
// can be used to wrap an original error object.
|
||||
//
|
||||
// Should be used as the root for errors satisfying the awserr.Error. Also
|
||||
// for any error which does not fit into a specific error wrapper type.
|
||||
type baseError struct {
|
||||
// Classification of error
|
||||
code string
|
||||
|
||||
// Detailed information about error
|
||||
message string
|
||||
|
||||
// Optional original error this error is based off of. Allows building
|
||||
// chained errors.
|
||||
errs []error
|
||||
}
|
||||
|
||||
// newBaseError returns an error object for the code, message, and errors.
|
||||
//
|
||||
// code is a short no whitespace phrase depicting the classification of
|
||||
// the error that is being created.
|
||||
//
|
||||
// message is the free flow string containing detailed information about the
|
||||
// error.
|
||||
//
|
||||
// origErrs is the error objects which will be nested under the new errors to
|
||||
// be returned.
|
||||
func newBaseError(code, message string, origErrs []error) *baseError {
|
||||
b := &baseError{
|
||||
code: code,
|
||||
message: message,
|
||||
errs: origErrs,
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// Error returns the string representation of the error.
|
||||
//
|
||||
// See ErrorWithExtra for formatting.
|
||||
//
|
||||
// Satisfies the error interface.
|
||||
func (b baseError) Error() string {
|
||||
size := len(b.errs)
|
||||
if size > 0 {
|
||||
return SprintError(b.code, b.message, "", errorList(b.errs))
|
||||
}
|
||||
|
||||
return SprintError(b.code, b.message, "", nil)
|
||||
}
|
||||
|
||||
// String returns the string representation of the error.
|
||||
// Alias for Error to satisfy the stringer interface.
|
||||
func (b baseError) String() string {
|
||||
return b.Error()
|
||||
}
|
||||
|
||||
// Code returns the short phrase depicting the classification of the error.
|
||||
func (b baseError) Code() string {
|
||||
return b.code
|
||||
}
|
||||
|
||||
// Message returns the error details message.
|
||||
func (b baseError) Message() string {
|
||||
return b.message
|
||||
}
|
||||
|
||||
// OrigErr returns the original error if one was set. Nil is returned if no
|
||||
// error was set. This only returns the first element in the list. If the full
|
||||
// list is needed, use BatchedErrors.
|
||||
func (b baseError) OrigErr() error {
|
||||
switch len(b.errs) {
|
||||
case 0:
|
||||
return nil
|
||||
case 1:
|
||||
return b.errs[0]
|
||||
default:
|
||||
if err, ok := b.errs[0].(Error); ok {
|
||||
return NewBatchError(err.Code(), err.Message(), b.errs[1:])
|
||||
}
|
||||
return NewBatchError("BatchedErrors",
|
||||
"multiple errors occurred", b.errs)
|
||||
}
|
||||
}
|
||||
|
||||
// OrigErrs returns the original errors if one was set. An empty slice is
|
||||
// returned if no error was set.
|
||||
func (b baseError) OrigErrs() []error {
|
||||
return b.errs
|
||||
}
|
||||
|
||||
// So that the Error interface type can be included as an anonymous field
|
||||
// in the requestError struct and not conflict with the error.Error() method.
|
||||
type awsError Error
|
||||
|
||||
// A requestError wraps a request or service error.
|
||||
//
|
||||
// Composed of baseError for code, message, and original error.
|
||||
type requestError struct {
|
||||
awsError
|
||||
statusCode int
|
||||
requestID string
|
||||
}
|
||||
|
||||
// newRequestError returns a wrapped error with additional information for
|
||||
// request status code, and service requestID.
|
||||
//
|
||||
// Should be used to wrap all request which involve service requests. Even if
|
||||
// the request failed without a service response, but had an HTTP status code
|
||||
// that may be meaningful.
|
||||
//
|
||||
// Also wraps original errors via the baseError.
|
||||
func newRequestError(err Error, statusCode int, requestID string) *requestError {
|
||||
return &requestError{
|
||||
awsError: err,
|
||||
statusCode: statusCode,
|
||||
requestID: requestID,
|
||||
}
|
||||
}
|
||||
|
||||
// Error returns the string representation of the error.
|
||||
// Satisfies the error interface.
|
||||
func (r requestError) Error() string {
|
||||
extra := fmt.Sprintf("status code: %d, request id: %s",
|
||||
r.statusCode, r.requestID)
|
||||
return SprintError(r.Code(), r.Message(), extra, r.OrigErr())
|
||||
}
|
||||
|
||||
// String returns the string representation of the error.
|
||||
// Alias for Error to satisfy the stringer interface.
|
||||
func (r requestError) String() string {
|
||||
return r.Error()
|
||||
}
|
||||
|
||||
// StatusCode returns the wrapped status code for the error
|
||||
func (r requestError) StatusCode() int {
|
||||
return r.statusCode
|
||||
}
|
||||
|
||||
// RequestID returns the wrapped requestID
|
||||
func (r requestError) RequestID() string {
|
||||
return r.requestID
|
||||
}
|
||||
|
||||
// OrigErrs returns the original errors if one was set. An empty slice is
|
||||
// returned if no error was set.
|
||||
func (r requestError) OrigErrs() []error {
|
||||
if b, ok := r.awsError.(BatchedErrors); ok {
|
||||
return b.OrigErrs()
|
||||
}
|
||||
return []error{r.OrigErr()}
|
||||
}
|
||||
|
||||
// An error list that satisfies the golang interface
|
||||
type errorList []error
|
||||
|
||||
// Error returns the string representation of the error.
|
||||
//
|
||||
// Satisfies the error interface.
|
||||
func (e errorList) Error() string {
|
||||
msg := ""
|
||||
// How do we want to handle the array size being zero
|
||||
if size := len(e); size > 0 {
|
||||
for i := 0; i < size; i++ {
|
||||
msg += fmt.Sprintf("%s", e[i].Error())
|
||||
// We check the next index to see if it is within the slice.
|
||||
// If it is, then we append a newline. We do this, because unit tests
|
||||
// could be broken with the additional '\n'
|
||||
if i+1 < size {
|
||||
msg += "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
return msg
|
||||
}
|
||||
108
vendor/github.com/aws/aws-sdk-go/aws/awsutil/copy.go
generated
vendored
Executable file
108
vendor/github.com/aws/aws-sdk-go/aws/awsutil/copy.go
generated
vendored
Executable file
@@ -0,0 +1,108 @@
|
||||
package awsutil
|
||||
|
||||
import (
|
||||
"io"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Copy deeply copies a src structure to dst. Useful for copying request and
|
||||
// response structures.
|
||||
//
|
||||
// Can copy between structs of different type, but will only copy fields which
|
||||
// are assignable, and exist in both structs. Fields which are not assignable,
|
||||
// or do not exist in both structs are ignored.
|
||||
func Copy(dst, src interface{}) {
|
||||
dstval := reflect.ValueOf(dst)
|
||||
if !dstval.IsValid() {
|
||||
panic("Copy dst cannot be nil")
|
||||
}
|
||||
|
||||
rcopy(dstval, reflect.ValueOf(src), true)
|
||||
}
|
||||
|
||||
// CopyOf returns a copy of src while also allocating the memory for dst.
|
||||
// src must be a pointer type or this operation will fail.
|
||||
func CopyOf(src interface{}) (dst interface{}) {
|
||||
dsti := reflect.New(reflect.TypeOf(src).Elem())
|
||||
dst = dsti.Interface()
|
||||
rcopy(dsti, reflect.ValueOf(src), true)
|
||||
return
|
||||
}
|
||||
|
||||
// rcopy performs a recursive copy of values from the source to destination.
|
||||
//
|
||||
// root is used to skip certain aspects of the copy which are not valid
|
||||
// for the root node of a object.
|
||||
func rcopy(dst, src reflect.Value, root bool) {
|
||||
if !src.IsValid() {
|
||||
return
|
||||
}
|
||||
|
||||
switch src.Kind() {
|
||||
case reflect.Ptr:
|
||||
if _, ok := src.Interface().(io.Reader); ok {
|
||||
if dst.Kind() == reflect.Ptr && dst.Elem().CanSet() {
|
||||
dst.Elem().Set(src)
|
||||
} else if dst.CanSet() {
|
||||
dst.Set(src)
|
||||
}
|
||||
} else {
|
||||
e := src.Type().Elem()
|
||||
if dst.CanSet() && !src.IsNil() {
|
||||
if _, ok := src.Interface().(*time.Time); !ok {
|
||||
dst.Set(reflect.New(e))
|
||||
} else {
|
||||
tempValue := reflect.New(e)
|
||||
tempValue.Elem().Set(src.Elem())
|
||||
// Sets time.Time's unexported values
|
||||
dst.Set(tempValue)
|
||||
}
|
||||
}
|
||||
if src.Elem().IsValid() {
|
||||
// Keep the current root state since the depth hasn't changed
|
||||
rcopy(dst.Elem(), src.Elem(), root)
|
||||
}
|
||||
}
|
||||
case reflect.Struct:
|
||||
t := dst.Type()
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
name := t.Field(i).Name
|
||||
srcVal := src.FieldByName(name)
|
||||
dstVal := dst.FieldByName(name)
|
||||
if srcVal.IsValid() && dstVal.CanSet() {
|
||||
rcopy(dstVal, srcVal, false)
|
||||
}
|
||||
}
|
||||
case reflect.Slice:
|
||||
if src.IsNil() {
|
||||
break
|
||||
}
|
||||
|
||||
s := reflect.MakeSlice(src.Type(), src.Len(), src.Cap())
|
||||
dst.Set(s)
|
||||
for i := 0; i < src.Len(); i++ {
|
||||
rcopy(dst.Index(i), src.Index(i), false)
|
||||
}
|
||||
case reflect.Map:
|
||||
if src.IsNil() {
|
||||
break
|
||||
}
|
||||
|
||||
s := reflect.MakeMap(src.Type())
|
||||
dst.Set(s)
|
||||
for _, k := range src.MapKeys() {
|
||||
v := src.MapIndex(k)
|
||||
v2 := reflect.New(v.Type()).Elem()
|
||||
rcopy(v2, v, false)
|
||||
dst.SetMapIndex(k, v2)
|
||||
}
|
||||
default:
|
||||
// Assign the value if possible. If its not assignable, the value would
|
||||
// need to be converted and the impact of that may be unexpected, or is
|
||||
// not compatible with the dst type.
|
||||
if src.Type().AssignableTo(dst.Type()) {
|
||||
dst.Set(src)
|
||||
}
|
||||
}
|
||||
}
|
||||
27
vendor/github.com/aws/aws-sdk-go/aws/awsutil/equal.go
generated
vendored
Executable file
27
vendor/github.com/aws/aws-sdk-go/aws/awsutil/equal.go
generated
vendored
Executable file
@@ -0,0 +1,27 @@
|
||||
package awsutil
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// DeepEqual returns if the two values are deeply equal like reflect.DeepEqual.
|
||||
// In addition to this, this method will also dereference the input values if
|
||||
// possible so the DeepEqual performed will not fail if one parameter is a
|
||||
// pointer and the other is not.
|
||||
//
|
||||
// DeepEqual will not perform indirection of nested values of the input parameters.
|
||||
func DeepEqual(a, b interface{}) bool {
|
||||
ra := reflect.Indirect(reflect.ValueOf(a))
|
||||
rb := reflect.Indirect(reflect.ValueOf(b))
|
||||
|
||||
if raValid, rbValid := ra.IsValid(), rb.IsValid(); !raValid && !rbValid {
|
||||
// If the elements are both nil, and of the same type they are equal
|
||||
// If they are of different types they are not equal
|
||||
return reflect.TypeOf(a) == reflect.TypeOf(b)
|
||||
} else if raValid != rbValid {
|
||||
// Both values must be valid to be equal
|
||||
return false
|
||||
}
|
||||
|
||||
return reflect.DeepEqual(ra.Interface(), rb.Interface())
|
||||
}
|
||||
222
vendor/github.com/aws/aws-sdk-go/aws/awsutil/path_value.go
generated
vendored
Executable file
222
vendor/github.com/aws/aws-sdk-go/aws/awsutil/path_value.go
generated
vendored
Executable file
@@ -0,0 +1,222 @@
|
||||
package awsutil
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/jmespath/go-jmespath"
|
||||
)
|
||||
|
||||
var indexRe = regexp.MustCompile(`(.+)\[(-?\d+)?\]$`)
|
||||
|
||||
// rValuesAtPath returns a slice of values found in value v. The values
|
||||
// in v are explored recursively so all nested values are collected.
|
||||
func rValuesAtPath(v interface{}, path string, createPath, caseSensitive, nilTerm bool) []reflect.Value {
|
||||
pathparts := strings.Split(path, "||")
|
||||
if len(pathparts) > 1 {
|
||||
for _, pathpart := range pathparts {
|
||||
vals := rValuesAtPath(v, pathpart, createPath, caseSensitive, nilTerm)
|
||||
if len(vals) > 0 {
|
||||
return vals
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
values := []reflect.Value{reflect.Indirect(reflect.ValueOf(v))}
|
||||
components := strings.Split(path, ".")
|
||||
for len(values) > 0 && len(components) > 0 {
|
||||
var index *int64
|
||||
var indexStar bool
|
||||
c := strings.TrimSpace(components[0])
|
||||
if c == "" { // no actual component, illegal syntax
|
||||
return nil
|
||||
} else if caseSensitive && c != "*" && strings.ToLower(c[0:1]) == c[0:1] {
|
||||
// TODO normalize case for user
|
||||
return nil // don't support unexported fields
|
||||
}
|
||||
|
||||
// parse this component
|
||||
if m := indexRe.FindStringSubmatch(c); m != nil {
|
||||
c = m[1]
|
||||
if m[2] == "" {
|
||||
index = nil
|
||||
indexStar = true
|
||||
} else {
|
||||
i, _ := strconv.ParseInt(m[2], 10, 32)
|
||||
index = &i
|
||||
indexStar = false
|
||||
}
|
||||
}
|
||||
|
||||
nextvals := []reflect.Value{}
|
||||
for _, value := range values {
|
||||
// pull component name out of struct member
|
||||
if value.Kind() != reflect.Struct {
|
||||
continue
|
||||
}
|
||||
|
||||
if c == "*" { // pull all members
|
||||
for i := 0; i < value.NumField(); i++ {
|
||||
if f := reflect.Indirect(value.Field(i)); f.IsValid() {
|
||||
nextvals = append(nextvals, f)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
value = value.FieldByNameFunc(func(name string) bool {
|
||||
if c == name {
|
||||
return true
|
||||
} else if !caseSensitive && strings.ToLower(name) == strings.ToLower(c) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
if nilTerm && value.Kind() == reflect.Ptr && len(components[1:]) == 0 {
|
||||
if !value.IsNil() {
|
||||
value.Set(reflect.Zero(value.Type()))
|
||||
}
|
||||
return []reflect.Value{value}
|
||||
}
|
||||
|
||||
if createPath && value.Kind() == reflect.Ptr && value.IsNil() {
|
||||
// TODO if the value is the terminus it should not be created
|
||||
// if the value to be set to its position is nil.
|
||||
value.Set(reflect.New(value.Type().Elem()))
|
||||
value = value.Elem()
|
||||
} else {
|
||||
value = reflect.Indirect(value)
|
||||
}
|
||||
|
||||
if value.Kind() == reflect.Slice || value.Kind() == reflect.Map {
|
||||
if !createPath && value.IsNil() {
|
||||
value = reflect.ValueOf(nil)
|
||||
}
|
||||
}
|
||||
|
||||
if value.IsValid() {
|
||||
nextvals = append(nextvals, value)
|
||||
}
|
||||
}
|
||||
values = nextvals
|
||||
|
||||
if indexStar || index != nil {
|
||||
nextvals = []reflect.Value{}
|
||||
for _, valItem := range values {
|
||||
value := reflect.Indirect(valItem)
|
||||
if value.Kind() != reflect.Slice {
|
||||
continue
|
||||
}
|
||||
|
||||
if indexStar { // grab all indices
|
||||
for i := 0; i < value.Len(); i++ {
|
||||
idx := reflect.Indirect(value.Index(i))
|
||||
if idx.IsValid() {
|
||||
nextvals = append(nextvals, idx)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// pull out index
|
||||
i := int(*index)
|
||||
if i >= value.Len() { // check out of bounds
|
||||
if createPath {
|
||||
// TODO resize slice
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
} else if i < 0 { // support negative indexing
|
||||
i = value.Len() + i
|
||||
}
|
||||
value = reflect.Indirect(value.Index(i))
|
||||
|
||||
if value.Kind() == reflect.Slice || value.Kind() == reflect.Map {
|
||||
if !createPath && value.IsNil() {
|
||||
value = reflect.ValueOf(nil)
|
||||
}
|
||||
}
|
||||
|
||||
if value.IsValid() {
|
||||
nextvals = append(nextvals, value)
|
||||
}
|
||||
}
|
||||
values = nextvals
|
||||
}
|
||||
|
||||
components = components[1:]
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
// ValuesAtPath returns a list of values at the case insensitive lexical
|
||||
// path inside of a structure.
|
||||
func ValuesAtPath(i interface{}, path string) ([]interface{}, error) {
|
||||
result, err := jmespath.Search(path, i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := reflect.ValueOf(result)
|
||||
if !v.IsValid() || (v.Kind() == reflect.Ptr && v.IsNil()) {
|
||||
return nil, nil
|
||||
}
|
||||
if s, ok := result.([]interface{}); ok {
|
||||
return s, err
|
||||
}
|
||||
if v.Kind() == reflect.Map && v.Len() == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if v.Kind() == reflect.Slice {
|
||||
out := make([]interface{}, v.Len())
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
out[i] = v.Index(i).Interface()
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
return []interface{}{result}, nil
|
||||
}
|
||||
|
||||
// SetValueAtPath sets a value at the case insensitive lexical path inside
|
||||
// of a structure.
|
||||
func SetValueAtPath(i interface{}, path string, v interface{}) {
|
||||
if rvals := rValuesAtPath(i, path, true, false, v == nil); rvals != nil {
|
||||
for _, rval := range rvals {
|
||||
if rval.Kind() == reflect.Ptr && rval.IsNil() {
|
||||
continue
|
||||
}
|
||||
setValue(rval, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setValue(dstVal reflect.Value, src interface{}) {
|
||||
if dstVal.Kind() == reflect.Ptr {
|
||||
dstVal = reflect.Indirect(dstVal)
|
||||
}
|
||||
srcVal := reflect.ValueOf(src)
|
||||
|
||||
if !srcVal.IsValid() { // src is literal nil
|
||||
if dstVal.CanAddr() {
|
||||
// Convert to pointer so that pointer's value can be nil'ed
|
||||
// dstVal = dstVal.Addr()
|
||||
}
|
||||
dstVal.Set(reflect.Zero(dstVal.Type()))
|
||||
|
||||
} else if srcVal.Kind() == reflect.Ptr {
|
||||
if srcVal.IsNil() {
|
||||
srcVal = reflect.Zero(dstVal.Type())
|
||||
} else {
|
||||
srcVal = reflect.ValueOf(src).Elem()
|
||||
}
|
||||
dstVal.Set(srcVal)
|
||||
} else {
|
||||
dstVal.Set(srcVal)
|
||||
}
|
||||
|
||||
}
|
||||
113
vendor/github.com/aws/aws-sdk-go/aws/awsutil/prettify.go
generated
vendored
Executable file
113
vendor/github.com/aws/aws-sdk-go/aws/awsutil/prettify.go
generated
vendored
Executable file
@@ -0,0 +1,113 @@
|
||||
package awsutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Prettify returns the string representation of a value.
|
||||
func Prettify(i interface{}) string {
|
||||
var buf bytes.Buffer
|
||||
prettify(reflect.ValueOf(i), 0, &buf)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// prettify will recursively walk value v to build a textual
|
||||
// representation of the value.
|
||||
func prettify(v reflect.Value, indent int, buf *bytes.Buffer) {
|
||||
for v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.Struct:
|
||||
strtype := v.Type().String()
|
||||
if strtype == "time.Time" {
|
||||
fmt.Fprintf(buf, "%s", v.Interface())
|
||||
break
|
||||
} else if strings.HasPrefix(strtype, "io.") {
|
||||
buf.WriteString("<buffer>")
|
||||
break
|
||||
}
|
||||
|
||||
buf.WriteString("{\n")
|
||||
|
||||
names := []string{}
|
||||
for i := 0; i < v.Type().NumField(); i++ {
|
||||
name := v.Type().Field(i).Name
|
||||
f := v.Field(i)
|
||||
if name[0:1] == strings.ToLower(name[0:1]) {
|
||||
continue // ignore unexported fields
|
||||
}
|
||||
if (f.Kind() == reflect.Ptr || f.Kind() == reflect.Slice || f.Kind() == reflect.Map) && f.IsNil() {
|
||||
continue // ignore unset fields
|
||||
}
|
||||
names = append(names, name)
|
||||
}
|
||||
|
||||
for i, n := range names {
|
||||
val := v.FieldByName(n)
|
||||
buf.WriteString(strings.Repeat(" ", indent+2))
|
||||
buf.WriteString(n + ": ")
|
||||
prettify(val, indent+2, buf)
|
||||
|
||||
if i < len(names)-1 {
|
||||
buf.WriteString(",\n")
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString("\n" + strings.Repeat(" ", indent) + "}")
|
||||
case reflect.Slice:
|
||||
strtype := v.Type().String()
|
||||
if strtype == "[]uint8" {
|
||||
fmt.Fprintf(buf, "<binary> len %d", v.Len())
|
||||
break
|
||||
}
|
||||
|
||||
nl, id, id2 := "", "", ""
|
||||
if v.Len() > 3 {
|
||||
nl, id, id2 = "\n", strings.Repeat(" ", indent), strings.Repeat(" ", indent+2)
|
||||
}
|
||||
buf.WriteString("[" + nl)
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
buf.WriteString(id2)
|
||||
prettify(v.Index(i), indent+2, buf)
|
||||
|
||||
if i < v.Len()-1 {
|
||||
buf.WriteString("," + nl)
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString(nl + id + "]")
|
||||
case reflect.Map:
|
||||
buf.WriteString("{\n")
|
||||
|
||||
for i, k := range v.MapKeys() {
|
||||
buf.WriteString(strings.Repeat(" ", indent+2))
|
||||
buf.WriteString(k.String() + ": ")
|
||||
prettify(v.MapIndex(k), indent+2, buf)
|
||||
|
||||
if i < v.Len()-1 {
|
||||
buf.WriteString(",\n")
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString("\n" + strings.Repeat(" ", indent) + "}")
|
||||
default:
|
||||
if !v.IsValid() {
|
||||
fmt.Fprint(buf, "<invalid value>")
|
||||
return
|
||||
}
|
||||
format := "%v"
|
||||
switch v.Interface().(type) {
|
||||
case string:
|
||||
format = "%q"
|
||||
case io.ReadSeeker, io.Reader:
|
||||
format = "buffer(%p)"
|
||||
}
|
||||
fmt.Fprintf(buf, format, v.Interface())
|
||||
}
|
||||
}
|
||||
88
vendor/github.com/aws/aws-sdk-go/aws/awsutil/string_value.go
generated
vendored
Executable file
88
vendor/github.com/aws/aws-sdk-go/aws/awsutil/string_value.go
generated
vendored
Executable file
@@ -0,0 +1,88 @@
|
||||
package awsutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// StringValue returns the string representation of a value.
|
||||
func StringValue(i interface{}) string {
|
||||
var buf bytes.Buffer
|
||||
stringValue(reflect.ValueOf(i), 0, &buf)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func stringValue(v reflect.Value, indent int, buf *bytes.Buffer) {
|
||||
for v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.Struct:
|
||||
buf.WriteString("{\n")
|
||||
|
||||
for i := 0; i < v.Type().NumField(); i++ {
|
||||
ft := v.Type().Field(i)
|
||||
fv := v.Field(i)
|
||||
|
||||
if ft.Name[0:1] == strings.ToLower(ft.Name[0:1]) {
|
||||
continue // ignore unexported fields
|
||||
}
|
||||
if (fv.Kind() == reflect.Ptr || fv.Kind() == reflect.Slice) && fv.IsNil() {
|
||||
continue // ignore unset fields
|
||||
}
|
||||
|
||||
buf.WriteString(strings.Repeat(" ", indent+2))
|
||||
buf.WriteString(ft.Name + ": ")
|
||||
|
||||
if tag := ft.Tag.Get("sensitive"); tag == "true" {
|
||||
buf.WriteString("<sensitive>")
|
||||
} else {
|
||||
stringValue(fv, indent+2, buf)
|
||||
}
|
||||
|
||||
buf.WriteString(",\n")
|
||||
}
|
||||
|
||||
buf.WriteString("\n" + strings.Repeat(" ", indent) + "}")
|
||||
case reflect.Slice:
|
||||
nl, id, id2 := "", "", ""
|
||||
if v.Len() > 3 {
|
||||
nl, id, id2 = "\n", strings.Repeat(" ", indent), strings.Repeat(" ", indent+2)
|
||||
}
|
||||
buf.WriteString("[" + nl)
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
buf.WriteString(id2)
|
||||
stringValue(v.Index(i), indent+2, buf)
|
||||
|
||||
if i < v.Len()-1 {
|
||||
buf.WriteString("," + nl)
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString(nl + id + "]")
|
||||
case reflect.Map:
|
||||
buf.WriteString("{\n")
|
||||
|
||||
for i, k := range v.MapKeys() {
|
||||
buf.WriteString(strings.Repeat(" ", indent+2))
|
||||
buf.WriteString(k.String() + ": ")
|
||||
stringValue(v.MapIndex(k), indent+2, buf)
|
||||
|
||||
if i < v.Len()-1 {
|
||||
buf.WriteString(",\n")
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString("\n" + strings.Repeat(" ", indent) + "}")
|
||||
default:
|
||||
format := "%v"
|
||||
switch v.Interface().(type) {
|
||||
case string:
|
||||
format = "%q"
|
||||
}
|
||||
fmt.Fprintf(buf, format, v.Interface())
|
||||
}
|
||||
}
|
||||
96
vendor/github.com/aws/aws-sdk-go/aws/client/client.go
generated
vendored
Executable file
96
vendor/github.com/aws/aws-sdk-go/aws/client/client.go
generated
vendored
Executable file
@@ -0,0 +1,96 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/client/metadata"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
)
|
||||
|
||||
// A Config provides configuration to a service client instance.
|
||||
type Config struct {
|
||||
Config *aws.Config
|
||||
Handlers request.Handlers
|
||||
Endpoint string
|
||||
SigningRegion string
|
||||
SigningName string
|
||||
|
||||
// States that the signing name did not come from a modeled source but
|
||||
// was derived based on other data. Used by service client constructors
|
||||
// to determine if the signin name can be overridden based on metadata the
|
||||
// service has.
|
||||
SigningNameDerived bool
|
||||
}
|
||||
|
||||
// ConfigProvider provides a generic way for a service client to receive
|
||||
// the ClientConfig without circular dependencies.
|
||||
type ConfigProvider interface {
|
||||
ClientConfig(serviceName string, cfgs ...*aws.Config) Config
|
||||
}
|
||||
|
||||
// ConfigNoResolveEndpointProvider same as ConfigProvider except it will not
|
||||
// resolve the endpoint automatically. The service client's endpoint must be
|
||||
// provided via the aws.Config.Endpoint field.
|
||||
type ConfigNoResolveEndpointProvider interface {
|
||||
ClientConfigNoResolveEndpoint(cfgs ...*aws.Config) Config
|
||||
}
|
||||
|
||||
// A Client implements the base client request and response handling
|
||||
// used by all service clients.
|
||||
type Client struct {
|
||||
request.Retryer
|
||||
metadata.ClientInfo
|
||||
|
||||
Config aws.Config
|
||||
Handlers request.Handlers
|
||||
}
|
||||
|
||||
// New will return a pointer to a new initialized service client.
|
||||
func New(cfg aws.Config, info metadata.ClientInfo, handlers request.Handlers, options ...func(*Client)) *Client {
|
||||
svc := &Client{
|
||||
Config: cfg,
|
||||
ClientInfo: info,
|
||||
Handlers: handlers.Copy(),
|
||||
}
|
||||
|
||||
switch retryer, ok := cfg.Retryer.(request.Retryer); {
|
||||
case ok:
|
||||
svc.Retryer = retryer
|
||||
case cfg.Retryer != nil && cfg.Logger != nil:
|
||||
s := fmt.Sprintf("WARNING: %T does not implement request.Retryer; using DefaultRetryer instead", cfg.Retryer)
|
||||
cfg.Logger.Log(s)
|
||||
fallthrough
|
||||
default:
|
||||
maxRetries := aws.IntValue(cfg.MaxRetries)
|
||||
if cfg.MaxRetries == nil || maxRetries == aws.UseServiceDefaultRetries {
|
||||
maxRetries = 3
|
||||
}
|
||||
svc.Retryer = DefaultRetryer{NumMaxRetries: maxRetries}
|
||||
}
|
||||
|
||||
svc.AddDebugHandlers()
|
||||
|
||||
for _, option := range options {
|
||||
option(svc)
|
||||
}
|
||||
|
||||
return svc
|
||||
}
|
||||
|
||||
// NewRequest returns a new Request pointer for the service API
|
||||
// operation and parameters.
|
||||
func (c *Client) NewRequest(operation *request.Operation, params interface{}, data interface{}) *request.Request {
|
||||
return request.New(c.Config, c.ClientInfo, c.Handlers, c.Retryer, operation, params, data)
|
||||
}
|
||||
|
||||
// AddDebugHandlers injects debug logging handlers into the service to log request
|
||||
// debug information.
|
||||
func (c *Client) AddDebugHandlers() {
|
||||
if !c.Config.LogLevel.AtLeast(aws.LogDebug) {
|
||||
return
|
||||
}
|
||||
|
||||
c.Handlers.Send.PushFrontNamed(LogHTTPRequestHandler)
|
||||
c.Handlers.Send.PushBackNamed(LogHTTPResponseHandler)
|
||||
}
|
||||
116
vendor/github.com/aws/aws-sdk-go/aws/client/default_retryer.go
generated
vendored
Executable file
116
vendor/github.com/aws/aws-sdk-go/aws/client/default_retryer.go
generated
vendored
Executable file
@@ -0,0 +1,116 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
"github.com/aws/aws-sdk-go/internal/sdkrand"
|
||||
)
|
||||
|
||||
// DefaultRetryer implements basic retry logic using exponential backoff for
|
||||
// most services. If you want to implement custom retry logic, implement the
|
||||
// request.Retryer interface or create a structure type that composes this
|
||||
// struct and override the specific methods. For example, to override only
|
||||
// the MaxRetries method:
|
||||
//
|
||||
// type retryer struct {
|
||||
// client.DefaultRetryer
|
||||
// }
|
||||
//
|
||||
// // This implementation always has 100 max retries
|
||||
// func (d retryer) MaxRetries() int { return 100 }
|
||||
type DefaultRetryer struct {
|
||||
NumMaxRetries int
|
||||
}
|
||||
|
||||
// MaxRetries returns the number of maximum returns the service will use to make
|
||||
// an individual API request.
|
||||
func (d DefaultRetryer) MaxRetries() int {
|
||||
return d.NumMaxRetries
|
||||
}
|
||||
|
||||
// RetryRules returns the delay duration before retrying this request again
|
||||
func (d DefaultRetryer) RetryRules(r *request.Request) time.Duration {
|
||||
// Set the upper limit of delay in retrying at ~five minutes
|
||||
minTime := 30
|
||||
throttle := d.shouldThrottle(r)
|
||||
if throttle {
|
||||
if delay, ok := getRetryDelay(r); ok {
|
||||
return delay
|
||||
}
|
||||
|
||||
minTime = 500
|
||||
}
|
||||
|
||||
retryCount := r.RetryCount
|
||||
if throttle && retryCount > 8 {
|
||||
retryCount = 8
|
||||
} else if retryCount > 13 {
|
||||
retryCount = 13
|
||||
}
|
||||
|
||||
delay := (1 << uint(retryCount)) * (sdkrand.SeededRand.Intn(minTime) + minTime)
|
||||
return time.Duration(delay) * time.Millisecond
|
||||
}
|
||||
|
||||
// ShouldRetry returns true if the request should be retried.
|
||||
func (d DefaultRetryer) ShouldRetry(r *request.Request) bool {
|
||||
// If one of the other handlers already set the retry state
|
||||
// we don't want to override it based on the service's state
|
||||
if r.Retryable != nil {
|
||||
return *r.Retryable
|
||||
}
|
||||
|
||||
if r.HTTPResponse.StatusCode >= 500 && r.HTTPResponse.StatusCode != 501 {
|
||||
return true
|
||||
}
|
||||
return r.IsErrorRetryable() || d.shouldThrottle(r)
|
||||
}
|
||||
|
||||
// ShouldThrottle returns true if the request should be throttled.
|
||||
func (d DefaultRetryer) shouldThrottle(r *request.Request) bool {
|
||||
switch r.HTTPResponse.StatusCode {
|
||||
case 429:
|
||||
case 502:
|
||||
case 503:
|
||||
case 504:
|
||||
default:
|
||||
return r.IsErrorThrottle()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// This will look in the Retry-After header, RFC 7231, for how long
|
||||
// it will wait before attempting another request
|
||||
func getRetryDelay(r *request.Request) (time.Duration, bool) {
|
||||
if !canUseRetryAfterHeader(r) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
delayStr := r.HTTPResponse.Header.Get("Retry-After")
|
||||
if len(delayStr) == 0 {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
delay, err := strconv.Atoi(delayStr)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return time.Duration(delay) * time.Second, true
|
||||
}
|
||||
|
||||
// Will look at the status code to see if the retry header pertains to
|
||||
// the status code.
|
||||
func canUseRetryAfterHeader(r *request.Request) bool {
|
||||
switch r.HTTPResponse.StatusCode {
|
||||
case 429:
|
||||
case 503:
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
190
vendor/github.com/aws/aws-sdk-go/aws/client/logger.go
generated
vendored
Executable file
190
vendor/github.com/aws/aws-sdk-go/aws/client/logger.go
generated
vendored
Executable file
@@ -0,0 +1,190 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http/httputil"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/request"
|
||||
)
|
||||
|
||||
const logReqMsg = `DEBUG: Request %s/%s Details:
|
||||
---[ REQUEST POST-SIGN ]-----------------------------
|
||||
%s
|
||||
-----------------------------------------------------`
|
||||
|
||||
const logReqErrMsg = `DEBUG ERROR: Request %s/%s:
|
||||
---[ REQUEST DUMP ERROR ]-----------------------------
|
||||
%s
|
||||
------------------------------------------------------`
|
||||
|
||||
type logWriter struct {
|
||||
// Logger is what we will use to log the payload of a response.
|
||||
Logger aws.Logger
|
||||
// buf stores the contents of what has been read
|
||||
buf *bytes.Buffer
|
||||
}
|
||||
|
||||
func (logger *logWriter) Write(b []byte) (int, error) {
|
||||
return logger.buf.Write(b)
|
||||
}
|
||||
|
||||
type teeReaderCloser struct {
|
||||
// io.Reader will be a tee reader that is used during logging.
|
||||
// This structure will read from a body and write the contents to a logger.
|
||||
io.Reader
|
||||
// Source is used just to close when we are done reading.
|
||||
Source io.ReadCloser
|
||||
}
|
||||
|
||||
func (reader *teeReaderCloser) Close() error {
|
||||
return reader.Source.Close()
|
||||
}
|
||||
|
||||
// LogHTTPRequestHandler is a SDK request handler to log the HTTP request sent
|
||||
// to a service. Will include the HTTP request body if the LogLevel of the
|
||||
// request matches LogDebugWithHTTPBody.
|
||||
var LogHTTPRequestHandler = request.NamedHandler{
|
||||
Name: "awssdk.client.LogRequest",
|
||||
Fn: logRequest,
|
||||
}
|
||||
|
||||
func logRequest(r *request.Request) {
|
||||
logBody := r.Config.LogLevel.Matches(aws.LogDebugWithHTTPBody)
|
||||
bodySeekable := aws.IsReaderSeekable(r.Body)
|
||||
|
||||
b, err := httputil.DumpRequestOut(r.HTTPRequest, logBody)
|
||||
if err != nil {
|
||||
r.Config.Logger.Log(fmt.Sprintf(logReqErrMsg,
|
||||
r.ClientInfo.ServiceName, r.Operation.Name, err))
|
||||
return
|
||||
}
|
||||
|
||||
if logBody {
|
||||
if !bodySeekable {
|
||||
r.SetReaderBody(aws.ReadSeekCloser(r.HTTPRequest.Body))
|
||||
}
|
||||
// Reset the request body because dumpRequest will re-wrap the r.HTTPRequest's
|
||||
// Body as a NoOpCloser and will not be reset after read by the HTTP
|
||||
// client reader.
|
||||
r.ResetBody()
|
||||
}
|
||||
|
||||
r.Config.Logger.Log(fmt.Sprintf(logReqMsg,
|
||||
r.ClientInfo.ServiceName, r.Operation.Name, string(b)))
|
||||
}
|
||||
|
||||
// LogHTTPRequestHeaderHandler is a SDK request handler to log the HTTP request sent
|
||||
// to a service. Will only log the HTTP request's headers. The request payload
|
||||
// will not be read.
|
||||
var LogHTTPRequestHeaderHandler = request.NamedHandler{
|
||||
Name: "awssdk.client.LogRequestHeader",
|
||||
Fn: logRequestHeader,
|
||||
}
|
||||
|
||||
func logRequestHeader(r *request.Request) {
|
||||
b, err := httputil.DumpRequestOut(r.HTTPRequest, false)
|
||||
if err != nil {
|
||||
r.Config.Logger.Log(fmt.Sprintf(logReqErrMsg,
|
||||
r.ClientInfo.ServiceName, r.Operation.Name, err))
|
||||
return
|
||||
}
|
||||
|
||||
r.Config.Logger.Log(fmt.Sprintf(logReqMsg,
|
||||
r.ClientInfo.ServiceName, r.Operation.Name, string(b)))
|
||||
}
|
||||
|
||||
const logRespMsg = `DEBUG: Response %s/%s Details:
|
||||
---[ RESPONSE ]--------------------------------------
|
||||
%s
|
||||
-----------------------------------------------------`
|
||||
|
||||
const logRespErrMsg = `DEBUG ERROR: Response %s/%s:
|
||||
---[ RESPONSE DUMP ERROR ]-----------------------------
|
||||
%s
|
||||
-----------------------------------------------------`
|
||||
|
||||
// LogHTTPResponseHandler is a SDK request handler to log the HTTP response
|
||||
// received from a service. Will include the HTTP response body if the LogLevel
|
||||
// of the request matches LogDebugWithHTTPBody.
|
||||
var LogHTTPResponseHandler = request.NamedHandler{
|
||||
Name: "awssdk.client.LogResponse",
|
||||
Fn: logResponse,
|
||||
}
|
||||
|
||||
func logResponse(r *request.Request) {
|
||||
lw := &logWriter{r.Config.Logger, bytes.NewBuffer(nil)}
|
||||
|
||||
if r.HTTPResponse == nil {
|
||||
lw.Logger.Log(fmt.Sprintf(logRespErrMsg,
|
||||
r.ClientInfo.ServiceName, r.Operation.Name, "request's HTTPResponse is nil"))
|
||||
return
|
||||
}
|
||||
|
||||
logBody := r.Config.LogLevel.Matches(aws.LogDebugWithHTTPBody)
|
||||
if logBody {
|
||||
r.HTTPResponse.Body = &teeReaderCloser{
|
||||
Reader: io.TeeReader(r.HTTPResponse.Body, lw),
|
||||
Source: r.HTTPResponse.Body,
|
||||
}
|
||||
}
|
||||
|
||||
handlerFn := func(req *request.Request) {
|
||||
b, err := httputil.DumpResponse(req.HTTPResponse, false)
|
||||
if err != nil {
|
||||
lw.Logger.Log(fmt.Sprintf(logRespErrMsg,
|
||||
req.ClientInfo.ServiceName, req.Operation.Name, err))
|
||||
return
|
||||
}
|
||||
|
||||
lw.Logger.Log(fmt.Sprintf(logRespMsg,
|
||||
req.ClientInfo.ServiceName, req.Operation.Name, string(b)))
|
||||
|
||||
if logBody {
|
||||
b, err := ioutil.ReadAll(lw.buf)
|
||||
if err != nil {
|
||||
lw.Logger.Log(fmt.Sprintf(logRespErrMsg,
|
||||
req.ClientInfo.ServiceName, req.Operation.Name, err))
|
||||
return
|
||||
}
|
||||
|
||||
lw.Logger.Log(string(b))
|
||||
}
|
||||
}
|
||||
|
||||
const handlerName = "awsdk.client.LogResponse.ResponseBody"
|
||||
|
||||
r.Handlers.Unmarshal.SetBackNamed(request.NamedHandler{
|
||||
Name: handlerName, Fn: handlerFn,
|
||||
})
|
||||
r.Handlers.UnmarshalError.SetBackNamed(request.NamedHandler{
|
||||
Name: handlerName, Fn: handlerFn,
|
||||
})
|
||||
}
|
||||
|
||||
// LogHTTPResponseHeaderHandler is a SDK request handler to log the HTTP
|
||||
// response received from a service. Will only log the HTTP response's headers.
|
||||
// The response payload will not be read.
|
||||
var LogHTTPResponseHeaderHandler = request.NamedHandler{
|
||||
Name: "awssdk.client.LogResponseHeader",
|
||||
Fn: logResponseHeader,
|
||||
}
|
||||
|
||||
func logResponseHeader(r *request.Request) {
|
||||
if r.Config.Logger == nil {
|
||||
return
|
||||
}
|
||||
|
||||
b, err := httputil.DumpResponse(r.HTTPResponse, false)
|
||||
if err != nil {
|
||||
r.Config.Logger.Log(fmt.Sprintf(logRespErrMsg,
|
||||
r.ClientInfo.ServiceName, r.Operation.Name, err))
|
||||
return
|
||||
}
|
||||
|
||||
r.Config.Logger.Log(fmt.Sprintf(logRespMsg,
|
||||
r.ClientInfo.ServiceName, r.Operation.Name, string(b)))
|
||||
}
|
||||
13
vendor/github.com/aws/aws-sdk-go/aws/client/metadata/client_info.go
generated
vendored
Executable file
13
vendor/github.com/aws/aws-sdk-go/aws/client/metadata/client_info.go
generated
vendored
Executable file
@@ -0,0 +1,13 @@
|
||||
package metadata
|
||||
|
||||
// ClientInfo wraps immutable data from the client.Client structure.
|
||||
type ClientInfo struct {
|
||||
ServiceName string
|
||||
ServiceID string
|
||||
APIVersion string
|
||||
Endpoint string
|
||||
SigningName string
|
||||
SigningRegion string
|
||||
JSONVersion string
|
||||
TargetPrefix string
|
||||
}
|
||||
536
vendor/github.com/aws/aws-sdk-go/aws/config.go
generated
vendored
Executable file
536
vendor/github.com/aws/aws-sdk-go/aws/config.go
generated
vendored
Executable file
@@ -0,0 +1,536 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
"github.com/aws/aws-sdk-go/aws/endpoints"
|
||||
)
|
||||
|
||||
// UseServiceDefaultRetries instructs the config to use the service's own
|
||||
// default number of retries. This will be the default action if
|
||||
// Config.MaxRetries is nil also.
|
||||
const UseServiceDefaultRetries = -1
|
||||
|
||||
// RequestRetryer is an alias for a type that implements the request.Retryer
|
||||
// interface.
|
||||
type RequestRetryer interface{}
|
||||
|
||||
// A Config provides service configuration for service clients. By default,
|
||||
// all clients will use the defaults.DefaultConfig structure.
|
||||
//
|
||||
// // Create Session with MaxRetry configuration to be shared by multiple
|
||||
// // service clients.
|
||||
// sess := session.Must(session.NewSession(&aws.Config{
|
||||
// MaxRetries: aws.Int(3),
|
||||
// }))
|
||||
//
|
||||
// // Create S3 service client with a specific Region.
|
||||
// svc := s3.New(sess, &aws.Config{
|
||||
// Region: aws.String("us-west-2"),
|
||||
// })
|
||||
type Config struct {
|
||||
// Enables verbose error printing of all credential chain errors.
|
||||
// Should be used when wanting to see all errors while attempting to
|
||||
// retrieve credentials.
|
||||
CredentialsChainVerboseErrors *bool
|
||||
|
||||
// The credentials object to use when signing requests. Defaults to a
|
||||
// chain of credential providers to search for credentials in environment
|
||||
// variables, shared credential file, and EC2 Instance Roles.
|
||||
Credentials *credentials.Credentials
|
||||
|
||||
// An optional endpoint URL (hostname only or fully qualified URI)
|
||||
// that overrides the default generated endpoint for a client. Set this
|
||||
// to `""` to use the default generated endpoint.
|
||||
//
|
||||
// Note: You must still provide a `Region` value when specifying an
|
||||
// endpoint for a client.
|
||||
Endpoint *string
|
||||
|
||||
// The resolver to use for looking up endpoints for AWS service clients
|
||||
// to use based on region.
|
||||
EndpointResolver endpoints.Resolver
|
||||
|
||||
// EnforceShouldRetryCheck is used in the AfterRetryHandler to always call
|
||||
// ShouldRetry regardless of whether or not if request.Retryable is set.
|
||||
// This will utilize ShouldRetry method of custom retryers. If EnforceShouldRetryCheck
|
||||
// is not set, then ShouldRetry will only be called if request.Retryable is nil.
|
||||
// Proper handling of the request.Retryable field is important when setting this field.
|
||||
EnforceShouldRetryCheck *bool
|
||||
|
||||
// The region to send requests to. This parameter is required and must
|
||||
// be configured globally or on a per-client basis unless otherwise
|
||||
// noted. A full list of regions is found in the "Regions and Endpoints"
|
||||
// document.
|
||||
//
|
||||
// See http://docs.aws.amazon.com/general/latest/gr/rande.html for AWS
|
||||
// Regions and Endpoints.
|
||||
Region *string
|
||||
|
||||
// Set this to `true` to disable SSL when sending requests. Defaults
|
||||
// to `false`.
|
||||
DisableSSL *bool
|
||||
|
||||
// The HTTP client to use when sending requests. Defaults to
|
||||
// `http.DefaultClient`.
|
||||
HTTPClient *http.Client
|
||||
|
||||
// An integer value representing the logging level. The default log level
|
||||
// is zero (LogOff), which represents no logging. To enable logging set
|
||||
// to a LogLevel Value.
|
||||
LogLevel *LogLevelType
|
||||
|
||||
// The logger writer interface to write logging messages to. Defaults to
|
||||
// standard out.
|
||||
Logger Logger
|
||||
|
||||
// The maximum number of times that a request will be retried for failures.
|
||||
// Defaults to -1, which defers the max retry setting to the service
|
||||
// specific configuration.
|
||||
MaxRetries *int
|
||||
|
||||
// Retryer guides how HTTP requests should be retried in case of
|
||||
// recoverable failures.
|
||||
//
|
||||
// When nil or the value does not implement the request.Retryer interface,
|
||||
// the client.DefaultRetryer will be used.
|
||||
//
|
||||
// When both Retryer and MaxRetries are non-nil, the former is used and
|
||||
// the latter ignored.
|
||||
//
|
||||
// To set the Retryer field in a type-safe manner and with chaining, use
|
||||
// the request.WithRetryer helper function:
|
||||
//
|
||||
// cfg := request.WithRetryer(aws.NewConfig(), myRetryer)
|
||||
//
|
||||
Retryer RequestRetryer
|
||||
|
||||
// Disables semantic parameter validation, which validates input for
|
||||
// missing required fields and/or other semantic request input errors.
|
||||
DisableParamValidation *bool
|
||||
|
||||
// Disables the computation of request and response checksums, e.g.,
|
||||
// CRC32 checksums in Amazon DynamoDB.
|
||||
DisableComputeChecksums *bool
|
||||
|
||||
// Set this to `true` to force the request to use path-style addressing,
|
||||
// i.e., `http://s3.amazonaws.com/BUCKET/KEY`. By default, the S3 client
|
||||
// will use virtual hosted bucket addressing when possible
|
||||
// (`http://BUCKET.s3.amazonaws.com/KEY`).
|
||||
//
|
||||
// Note: This configuration option is specific to the Amazon S3 service.
|
||||
//
|
||||
// See http://docs.aws.amazon.com/AmazonS3/latest/dev/VirtualHosting.html
|
||||
// for Amazon S3: Virtual Hosting of Buckets
|
||||
S3ForcePathStyle *bool
|
||||
|
||||
// Set this to `true` to disable the SDK adding the `Expect: 100-Continue`
|
||||
// header to PUT requests over 2MB of content. 100-Continue instructs the
|
||||
// HTTP client not to send the body until the service responds with a
|
||||
// `continue` status. This is useful to prevent sending the request body
|
||||
// until after the request is authenticated, and validated.
|
||||
//
|
||||
// http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPUT.html
|
||||
//
|
||||
// 100-Continue is only enabled for Go 1.6 and above. See `http.Transport`'s
|
||||
// `ExpectContinueTimeout` for information on adjusting the continue wait
|
||||
// timeout. https://golang.org/pkg/net/http/#Transport
|
||||
//
|
||||
// You should use this flag to disble 100-Continue if you experience issues
|
||||
// with proxies or third party S3 compatible services.
|
||||
S3Disable100Continue *bool
|
||||
|
||||
// Set this to `true` to enable S3 Accelerate feature. For all operations
|
||||
// compatible with S3 Accelerate will use the accelerate endpoint for
|
||||
// requests. Requests not compatible will fall back to normal S3 requests.
|
||||
//
|
||||
// The bucket must be enable for accelerate to be used with S3 client with
|
||||
// accelerate enabled. If the bucket is not enabled for accelerate an error
|
||||
// will be returned. The bucket name must be DNS compatible to also work
|
||||
// with accelerate.
|
||||
S3UseAccelerate *bool
|
||||
|
||||
// S3DisableContentMD5Validation config option is temporarily disabled,
|
||||
// For S3 GetObject API calls, #1837.
|
||||
//
|
||||
// Set this to `true` to disable the S3 service client from automatically
|
||||
// adding the ContentMD5 to S3 Object Put and Upload API calls. This option
|
||||
// will also disable the SDK from performing object ContentMD5 validation
|
||||
// on GetObject API calls.
|
||||
S3DisableContentMD5Validation *bool
|
||||
|
||||
// Set this to `true` to disable the EC2Metadata client from overriding the
|
||||
// default http.Client's Timeout. This is helpful if you do not want the
|
||||
// EC2Metadata client to create a new http.Client. This options is only
|
||||
// meaningful if you're not already using a custom HTTP client with the
|
||||
// SDK. Enabled by default.
|
||||
//
|
||||
// Must be set and provided to the session.NewSession() in order to disable
|
||||
// the EC2Metadata overriding the timeout for default credentials chain.
|
||||
//
|
||||
// Example:
|
||||
// sess := session.Must(session.NewSession(aws.NewConfig()
|
||||
// .WithEC2MetadataDiableTimeoutOverride(true)))
|
||||
//
|
||||
// svc := s3.New(sess)
|
||||
//
|
||||
EC2MetadataDisableTimeoutOverride *bool
|
||||
|
||||
// Instructs the endpoint to be generated for a service client to
|
||||
// be the dual stack endpoint. The dual stack endpoint will support
|
||||
// both IPv4 and IPv6 addressing.
|
||||
//
|
||||
// Setting this for a service which does not support dual stack will fail
|
||||
// to make requets. It is not recommended to set this value on the session
|
||||
// as it will apply to all service clients created with the session. Even
|
||||
// services which don't support dual stack endpoints.
|
||||
//
|
||||
// If the Endpoint config value is also provided the UseDualStack flag
|
||||
// will be ignored.
|
||||
//
|
||||
// Only supported with.
|
||||
//
|
||||
// sess := session.Must(session.NewSession())
|
||||
//
|
||||
// svc := s3.New(sess, &aws.Config{
|
||||
// UseDualStack: aws.Bool(true),
|
||||
// })
|
||||
UseDualStack *bool
|
||||
|
||||
// SleepDelay is an override for the func the SDK will call when sleeping
|
||||
// during the lifecycle of a request. Specifically this will be used for
|
||||
// request delays. This value should only be used for testing. To adjust
|
||||
// the delay of a request see the aws/client.DefaultRetryer and
|
||||
// aws/request.Retryer.
|
||||
//
|
||||
// SleepDelay will prevent any Context from being used for canceling retry
|
||||
// delay of an API operation. It is recommended to not use SleepDelay at all
|
||||
// and specify a Retryer instead.
|
||||
SleepDelay func(time.Duration)
|
||||
|
||||
// DisableRestProtocolURICleaning will not clean the URL path when making rest protocol requests.
|
||||
// Will default to false. This would only be used for empty directory names in s3 requests.
|
||||
//
|
||||
// Example:
|
||||
// sess := session.Must(session.NewSession(&aws.Config{
|
||||
// DisableRestProtocolURICleaning: aws.Bool(true),
|
||||
// }))
|
||||
//
|
||||
// svc := s3.New(sess)
|
||||
// out, err := svc.GetObject(&s3.GetObjectInput {
|
||||
// Bucket: aws.String("bucketname"),
|
||||
// Key: aws.String("//foo//bar//moo"),
|
||||
// })
|
||||
DisableRestProtocolURICleaning *bool
|
||||
|
||||
// EnableEndpointDiscovery will allow for endpoint discovery on operations that
|
||||
// have the definition in its model. By default, endpoint discovery is off.
|
||||
//
|
||||
// Example:
|
||||
// sess := session.Must(session.NewSession(&aws.Config{
|
||||
// EnableEndpointDiscovery: aws.Bool(true),
|
||||
// }))
|
||||
//
|
||||
// svc := s3.New(sess)
|
||||
// out, err := svc.GetObject(&s3.GetObjectInput {
|
||||
// Bucket: aws.String("bucketname"),
|
||||
// Key: aws.String("/foo/bar/moo"),
|
||||
// })
|
||||
EnableEndpointDiscovery *bool
|
||||
|
||||
// DisableEndpointHostPrefix will disable the SDK's behavior of prefixing
|
||||
// request endpoint hosts with modeled information.
|
||||
//
|
||||
// Disabling this feature is useful when you want to use local endpoints
|
||||
// for testing that do not support the modeled host prefix pattern.
|
||||
DisableEndpointHostPrefix *bool
|
||||
}
|
||||
|
||||
// NewConfig returns a new Config pointer that can be chained with builder
|
||||
// methods to set multiple configuration values inline without using pointers.
|
||||
//
|
||||
// // Create Session with MaxRetry configuration to be shared by multiple
|
||||
// // service clients.
|
||||
// sess := session.Must(session.NewSession(aws.NewConfig().
|
||||
// WithMaxRetries(3),
|
||||
// ))
|
||||
//
|
||||
// // Create S3 service client with a specific Region.
|
||||
// svc := s3.New(sess, aws.NewConfig().
|
||||
// WithRegion("us-west-2"),
|
||||
// )
|
||||
func NewConfig() *Config {
|
||||
return &Config{}
|
||||
}
|
||||
|
||||
// WithCredentialsChainVerboseErrors sets a config verbose errors boolean and returning
|
||||
// a Config pointer.
|
||||
func (c *Config) WithCredentialsChainVerboseErrors(verboseErrs bool) *Config {
|
||||
c.CredentialsChainVerboseErrors = &verboseErrs
|
||||
return c
|
||||
}
|
||||
|
||||
// WithCredentials sets a config Credentials value returning a Config pointer
|
||||
// for chaining.
|
||||
func (c *Config) WithCredentials(creds *credentials.Credentials) *Config {
|
||||
c.Credentials = creds
|
||||
return c
|
||||
}
|
||||
|
||||
// WithEndpoint sets a config Endpoint value returning a Config pointer for
|
||||
// chaining.
|
||||
func (c *Config) WithEndpoint(endpoint string) *Config {
|
||||
c.Endpoint = &endpoint
|
||||
return c
|
||||
}
|
||||
|
||||
// WithEndpointResolver sets a config EndpointResolver value returning a
|
||||
// Config pointer for chaining.
|
||||
func (c *Config) WithEndpointResolver(resolver endpoints.Resolver) *Config {
|
||||
c.EndpointResolver = resolver
|
||||
return c
|
||||
}
|
||||
|
||||
// WithRegion sets a config Region value returning a Config pointer for
|
||||
// chaining.
|
||||
func (c *Config) WithRegion(region string) *Config {
|
||||
c.Region = ®ion
|
||||
return c
|
||||
}
|
||||
|
||||
// WithDisableSSL sets a config DisableSSL value returning a Config pointer
|
||||
// for chaining.
|
||||
func (c *Config) WithDisableSSL(disable bool) *Config {
|
||||
c.DisableSSL = &disable
|
||||
return c
|
||||
}
|
||||
|
||||
// WithHTTPClient sets a config HTTPClient value returning a Config pointer
|
||||
// for chaining.
|
||||
func (c *Config) WithHTTPClient(client *http.Client) *Config {
|
||||
c.HTTPClient = client
|
||||
return c
|
||||
}
|
||||
|
||||
// WithMaxRetries sets a config MaxRetries value returning a Config pointer
|
||||
// for chaining.
|
||||
func (c *Config) WithMaxRetries(max int) *Config {
|
||||
c.MaxRetries = &max
|
||||
return c
|
||||
}
|
||||
|
||||
// WithDisableParamValidation sets a config DisableParamValidation value
|
||||
// returning a Config pointer for chaining.
|
||||
func (c *Config) WithDisableParamValidation(disable bool) *Config {
|
||||
c.DisableParamValidation = &disable
|
||||
return c
|
||||
}
|
||||
|
||||
// WithDisableComputeChecksums sets a config DisableComputeChecksums value
|
||||
// returning a Config pointer for chaining.
|
||||
func (c *Config) WithDisableComputeChecksums(disable bool) *Config {
|
||||
c.DisableComputeChecksums = &disable
|
||||
return c
|
||||
}
|
||||
|
||||
// WithLogLevel sets a config LogLevel value returning a Config pointer for
|
||||
// chaining.
|
||||
func (c *Config) WithLogLevel(level LogLevelType) *Config {
|
||||
c.LogLevel = &level
|
||||
return c
|
||||
}
|
||||
|
||||
// WithLogger sets a config Logger value returning a Config pointer for
|
||||
// chaining.
|
||||
func (c *Config) WithLogger(logger Logger) *Config {
|
||||
c.Logger = logger
|
||||
return c
|
||||
}
|
||||
|
||||
// WithS3ForcePathStyle sets a config S3ForcePathStyle value returning a Config
|
||||
// pointer for chaining.
|
||||
func (c *Config) WithS3ForcePathStyle(force bool) *Config {
|
||||
c.S3ForcePathStyle = &force
|
||||
return c
|
||||
}
|
||||
|
||||
// WithS3Disable100Continue sets a config S3Disable100Continue value returning
|
||||
// a Config pointer for chaining.
|
||||
func (c *Config) WithS3Disable100Continue(disable bool) *Config {
|
||||
c.S3Disable100Continue = &disable
|
||||
return c
|
||||
}
|
||||
|
||||
// WithS3UseAccelerate sets a config S3UseAccelerate value returning a Config
|
||||
// pointer for chaining.
|
||||
func (c *Config) WithS3UseAccelerate(enable bool) *Config {
|
||||
c.S3UseAccelerate = &enable
|
||||
return c
|
||||
|
||||
}
|
||||
|
||||
// WithS3DisableContentMD5Validation sets a config
|
||||
// S3DisableContentMD5Validation value returning a Config pointer for chaining.
|
||||
func (c *Config) WithS3DisableContentMD5Validation(enable bool) *Config {
|
||||
c.S3DisableContentMD5Validation = &enable
|
||||
return c
|
||||
|
||||
}
|
||||
|
||||
// WithUseDualStack sets a config UseDualStack value returning a Config
|
||||
// pointer for chaining.
|
||||
func (c *Config) WithUseDualStack(enable bool) *Config {
|
||||
c.UseDualStack = &enable
|
||||
return c
|
||||
}
|
||||
|
||||
// WithEC2MetadataDisableTimeoutOverride sets a config EC2MetadataDisableTimeoutOverride value
|
||||
// returning a Config pointer for chaining.
|
||||
func (c *Config) WithEC2MetadataDisableTimeoutOverride(enable bool) *Config {
|
||||
c.EC2MetadataDisableTimeoutOverride = &enable
|
||||
return c
|
||||
}
|
||||
|
||||
// WithSleepDelay overrides the function used to sleep while waiting for the
|
||||
// next retry. Defaults to time.Sleep.
|
||||
func (c *Config) WithSleepDelay(fn func(time.Duration)) *Config {
|
||||
c.SleepDelay = fn
|
||||
return c
|
||||
}
|
||||
|
||||
// WithEndpointDiscovery will set whether or not to use endpoint discovery.
|
||||
func (c *Config) WithEndpointDiscovery(t bool) *Config {
|
||||
c.EnableEndpointDiscovery = &t
|
||||
return c
|
||||
}
|
||||
|
||||
// WithDisableEndpointHostPrefix will set whether or not to use modeled host prefix
|
||||
// when making requests.
|
||||
func (c *Config) WithDisableEndpointHostPrefix(t bool) *Config {
|
||||
c.DisableEndpointHostPrefix = &t
|
||||
return c
|
||||
}
|
||||
|
||||
// MergeIn merges the passed in configs into the existing config object.
|
||||
func (c *Config) MergeIn(cfgs ...*Config) {
|
||||
for _, other := range cfgs {
|
||||
mergeInConfig(c, other)
|
||||
}
|
||||
}
|
||||
|
||||
func mergeInConfig(dst *Config, other *Config) {
|
||||
if other == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if other.CredentialsChainVerboseErrors != nil {
|
||||
dst.CredentialsChainVerboseErrors = other.CredentialsChainVerboseErrors
|
||||
}
|
||||
|
||||
if other.Credentials != nil {
|
||||
dst.Credentials = other.Credentials
|
||||
}
|
||||
|
||||
if other.Endpoint != nil {
|
||||
dst.Endpoint = other.Endpoint
|
||||
}
|
||||
|
||||
if other.EndpointResolver != nil {
|
||||
dst.EndpointResolver = other.EndpointResolver
|
||||
}
|
||||
|
||||
if other.Region != nil {
|
||||
dst.Region = other.Region
|
||||
}
|
||||
|
||||
if other.DisableSSL != nil {
|
||||
dst.DisableSSL = other.DisableSSL
|
||||
}
|
||||
|
||||
if other.HTTPClient != nil {
|
||||
dst.HTTPClient = other.HTTPClient
|
||||
}
|
||||
|
||||
if other.LogLevel != nil {
|
||||
dst.LogLevel = other.LogLevel
|
||||
}
|
||||
|
||||
if other.Logger != nil {
|
||||
dst.Logger = other.Logger
|
||||
}
|
||||
|
||||
if other.MaxRetries != nil {
|
||||
dst.MaxRetries = other.MaxRetries
|
||||
}
|
||||
|
||||
if other.Retryer != nil {
|
||||
dst.Retryer = other.Retryer
|
||||
}
|
||||
|
||||
if other.DisableParamValidation != nil {
|
||||
dst.DisableParamValidation = other.DisableParamValidation
|
||||
}
|
||||
|
||||
if other.DisableComputeChecksums != nil {
|
||||
dst.DisableComputeChecksums = other.DisableComputeChecksums
|
||||
}
|
||||
|
||||
if other.S3ForcePathStyle != nil {
|
||||
dst.S3ForcePathStyle = other.S3ForcePathStyle
|
||||
}
|
||||
|
||||
if other.S3Disable100Continue != nil {
|
||||
dst.S3Disable100Continue = other.S3Disable100Continue
|
||||
}
|
||||
|
||||
if other.S3UseAccelerate != nil {
|
||||
dst.S3UseAccelerate = other.S3UseAccelerate
|
||||
}
|
||||
|
||||
if other.S3DisableContentMD5Validation != nil {
|
||||
dst.S3DisableContentMD5Validation = other.S3DisableContentMD5Validation
|
||||
}
|
||||
|
||||
if other.UseDualStack != nil {
|
||||
dst.UseDualStack = other.UseDualStack
|
||||
}
|
||||
|
||||
if other.EC2MetadataDisableTimeoutOverride != nil {
|
||||
dst.EC2MetadataDisableTimeoutOverride = other.EC2MetadataDisableTimeoutOverride
|
||||
}
|
||||
|
||||
if other.SleepDelay != nil {
|
||||
dst.SleepDelay = other.SleepDelay
|
||||
}
|
||||
|
||||
if other.DisableRestProtocolURICleaning != nil {
|
||||
dst.DisableRestProtocolURICleaning = other.DisableRestProtocolURICleaning
|
||||
}
|
||||
|
||||
if other.EnforceShouldRetryCheck != nil {
|
||||
dst.EnforceShouldRetryCheck = other.EnforceShouldRetryCheck
|
||||
}
|
||||
|
||||
if other.EnableEndpointDiscovery != nil {
|
||||
dst.EnableEndpointDiscovery = other.EnableEndpointDiscovery
|
||||
}
|
||||
|
||||
if other.DisableEndpointHostPrefix != nil {
|
||||
dst.DisableEndpointHostPrefix = other.DisableEndpointHostPrefix
|
||||
}
|
||||
}
|
||||
|
||||
// Copy will return a shallow copy of the Config object. If any additional
|
||||
// configurations are provided they will be merged into the new config returned.
|
||||
func (c *Config) Copy(cfgs ...*Config) *Config {
|
||||
dst := &Config{}
|
||||
dst.MergeIn(c)
|
||||
|
||||
for _, cfg := range cfgs {
|
||||
dst.MergeIn(cfg)
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
37
vendor/github.com/aws/aws-sdk-go/aws/context_1_5.go
generated
vendored
Executable file
37
vendor/github.com/aws/aws-sdk-go/aws/context_1_5.go
generated
vendored
Executable file
@@ -0,0 +1,37 @@
|
||||
// +build !go1.9
|
||||
|
||||
package aws
|
||||
|
||||
import "time"
|
||||
|
||||
// Context is an copy of the Go v1.7 stdlib's context.Context interface.
|
||||
// It is represented as a SDK interface to enable you to use the "WithContext"
|
||||
// API methods with Go v1.6 and a Context type such as golang.org/x/net/context.
|
||||
//
|
||||
// See https://golang.org/pkg/context on how to use contexts.
|
||||
type Context interface {
|
||||
// Deadline returns the time when work done on behalf of this context
|
||||
// should be canceled. Deadline returns ok==false when no deadline is
|
||||
// set. Successive calls to Deadline return the same results.
|
||||
Deadline() (deadline time.Time, ok bool)
|
||||
|
||||
// Done returns a channel that's closed when work done on behalf of this
|
||||
// context should be canceled. Done may return nil if this context can
|
||||
// never be canceled. Successive calls to Done return the same value.
|
||||
Done() <-chan struct{}
|
||||
|
||||
// Err returns a non-nil error value after Done is closed. Err returns
|
||||
// Canceled if the context was canceled or DeadlineExceeded if the
|
||||
// context's deadline passed. No other values for Err are defined.
|
||||
// After Done is closed, successive calls to Err return the same value.
|
||||
Err() error
|
||||
|
||||
// Value returns the value associated with this context for key, or nil
|
||||
// if no value is associated with key. Successive calls to Value with
|
||||
// the same key returns the same result.
|
||||
//
|
||||
// Use context values only for request-scoped data that transits
|
||||
// processes and API boundaries, not for passing optional parameters to
|
||||
// functions.
|
||||
Value(key interface{}) interface{}
|
||||
}
|
||||
11
vendor/github.com/aws/aws-sdk-go/aws/context_1_9.go
generated
vendored
Executable file
11
vendor/github.com/aws/aws-sdk-go/aws/context_1_9.go
generated
vendored
Executable file
@@ -0,0 +1,11 @@
|
||||
// +build go1.9
|
||||
|
||||
package aws
|
||||
|
||||
import "context"
|
||||
|
||||
// Context is an alias of the Go stdlib's context.Context interface.
|
||||
// It can be used within the SDK's API operation "WithContext" methods.
|
||||
//
|
||||
// See https://golang.org/pkg/context on how to use contexts.
|
||||
type Context = context.Context
|
||||
56
vendor/github.com/aws/aws-sdk-go/aws/context_background_1_5.go
generated
vendored
Executable file
56
vendor/github.com/aws/aws-sdk-go/aws/context_background_1_5.go
generated
vendored
Executable file
@@ -0,0 +1,56 @@
|
||||
// +build !go1.7
|
||||
|
||||
package aws
|
||||
|
||||
import "time"
|
||||
|
||||
// An emptyCtx is a copy of the Go 1.7 context.emptyCtx type. This is copied to
|
||||
// provide a 1.6 and 1.5 safe version of context that is compatible with Go
|
||||
// 1.7's Context.
|
||||
//
|
||||
// An emptyCtx is never canceled, has no values, and has no deadline. It is not
|
||||
// struct{}, since vars of this type must have distinct addresses.
|
||||
type emptyCtx int
|
||||
|
||||
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
|
||||
return
|
||||
}
|
||||
|
||||
func (*emptyCtx) Done() <-chan struct{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*emptyCtx) Err() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*emptyCtx) Value(key interface{}) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *emptyCtx) String() string {
|
||||
switch e {
|
||||
case backgroundCtx:
|
||||
return "aws.BackgroundContext"
|
||||
}
|
||||
return "unknown empty Context"
|
||||
}
|
||||
|
||||
var (
|
||||
backgroundCtx = new(emptyCtx)
|
||||
)
|
||||
|
||||
// BackgroundContext returns a context that will never be canceled, has no
|
||||
// values, and no deadline. This context is used by the SDK to provide
|
||||
// backwards compatibility with non-context API operations and functionality.
|
||||
//
|
||||
// Go 1.6 and before:
|
||||
// This context function is equivalent to context.Background in the Go stdlib.
|
||||
//
|
||||
// Go 1.7 and later:
|
||||
// The context returned will be the value returned by context.Background()
|
||||
//
|
||||
// See https://golang.org/pkg/context for more information on Contexts.
|
||||
func BackgroundContext() Context {
|
||||
return backgroundCtx
|
||||
}
|
||||
20
vendor/github.com/aws/aws-sdk-go/aws/context_background_1_7.go
generated
vendored
Executable file
20
vendor/github.com/aws/aws-sdk-go/aws/context_background_1_7.go
generated
vendored
Executable file
@@ -0,0 +1,20 @@
|
||||
// +build go1.7
|
||||
|
||||
package aws
|
||||
|
||||
import "context"
|
||||
|
||||
// BackgroundContext returns a context that will never be canceled, has no
|
||||
// values, and no deadline. This context is used by the SDK to provide
|
||||
// backwards compatibility with non-context API operations and functionality.
|
||||
//
|
||||
// Go 1.6 and before:
|
||||
// This context function is equivalent to context.Background in the Go stdlib.
|
||||
//
|
||||
// Go 1.7 and later:
|
||||
// The context returned will be the value returned by context.Background()
|
||||
//
|
||||
// See https://golang.org/pkg/context for more information on Contexts.
|
||||
func BackgroundContext() Context {
|
||||
return context.Background()
|
||||
}
|
||||
24
vendor/github.com/aws/aws-sdk-go/aws/context_sleep.go
generated
vendored
Executable file
24
vendor/github.com/aws/aws-sdk-go/aws/context_sleep.go
generated
vendored
Executable file
@@ -0,0 +1,24 @@
|
||||
package aws
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// SleepWithContext will wait for the timer duration to expire, or the context
|
||||
// is canceled. Which ever happens first. If the context is canceled the Context's
|
||||
// error will be returned.
|
||||
//
|
||||
// Expects Context to always return a non-nil error if the Done channel is closed.
|
||||
func SleepWithContext(ctx Context, dur time.Duration) error {
|
||||
t := time.NewTimer(dur)
|
||||
defer t.Stop()
|
||||
|
||||
select {
|
||||
case <-t.C:
|
||||
break
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
387
vendor/github.com/aws/aws-sdk-go/aws/convert_types.go
generated
vendored
Executable file
387
vendor/github.com/aws/aws-sdk-go/aws/convert_types.go
generated
vendored
Executable file
@@ -0,0 +1,387 @@
|
||||
package aws
|
||||
|
||||
import "time"
|
||||
|
||||
// String returns a pointer to the string value passed in.
|
||||
func String(v string) *string {
|
||||
return &v
|
||||
}
|
||||
|
||||
// StringValue returns the value of the string pointer passed in or
|
||||
// "" if the pointer is nil.
|
||||
func StringValue(v *string) string {
|
||||
if v != nil {
|
||||
return *v
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// StringSlice converts a slice of string values into a slice of
|
||||
// string pointers
|
||||
func StringSlice(src []string) []*string {
|
||||
dst := make([]*string, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
dst[i] = &(src[i])
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// StringValueSlice converts a slice of string pointers into a slice of
|
||||
// string values
|
||||
func StringValueSlice(src []*string) []string {
|
||||
dst := make([]string, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
if src[i] != nil {
|
||||
dst[i] = *(src[i])
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// StringMap converts a string map of string values into a string
|
||||
// map of string pointers
|
||||
func StringMap(src map[string]string) map[string]*string {
|
||||
dst := make(map[string]*string)
|
||||
for k, val := range src {
|
||||
v := val
|
||||
dst[k] = &v
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// StringValueMap converts a string map of string pointers into a string
|
||||
// map of string values
|
||||
func StringValueMap(src map[string]*string) map[string]string {
|
||||
dst := make(map[string]string)
|
||||
for k, val := range src {
|
||||
if val != nil {
|
||||
dst[k] = *val
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Bool returns a pointer to the bool value passed in.
|
||||
func Bool(v bool) *bool {
|
||||
return &v
|
||||
}
|
||||
|
||||
// BoolValue returns the value of the bool pointer passed in or
|
||||
// false if the pointer is nil.
|
||||
func BoolValue(v *bool) bool {
|
||||
if v != nil {
|
||||
return *v
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// BoolSlice converts a slice of bool values into a slice of
|
||||
// bool pointers
|
||||
func BoolSlice(src []bool) []*bool {
|
||||
dst := make([]*bool, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
dst[i] = &(src[i])
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// BoolValueSlice converts a slice of bool pointers into a slice of
|
||||
// bool values
|
||||
func BoolValueSlice(src []*bool) []bool {
|
||||
dst := make([]bool, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
if src[i] != nil {
|
||||
dst[i] = *(src[i])
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// BoolMap converts a string map of bool values into a string
|
||||
// map of bool pointers
|
||||
func BoolMap(src map[string]bool) map[string]*bool {
|
||||
dst := make(map[string]*bool)
|
||||
for k, val := range src {
|
||||
v := val
|
||||
dst[k] = &v
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// BoolValueMap converts a string map of bool pointers into a string
|
||||
// map of bool values
|
||||
func BoolValueMap(src map[string]*bool) map[string]bool {
|
||||
dst := make(map[string]bool)
|
||||
for k, val := range src {
|
||||
if val != nil {
|
||||
dst[k] = *val
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Int returns a pointer to the int value passed in.
|
||||
func Int(v int) *int {
|
||||
return &v
|
||||
}
|
||||
|
||||
// IntValue returns the value of the int pointer passed in or
|
||||
// 0 if the pointer is nil.
|
||||
func IntValue(v *int) int {
|
||||
if v != nil {
|
||||
return *v
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// IntSlice converts a slice of int values into a slice of
|
||||
// int pointers
|
||||
func IntSlice(src []int) []*int {
|
||||
dst := make([]*int, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
dst[i] = &(src[i])
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// IntValueSlice converts a slice of int pointers into a slice of
|
||||
// int values
|
||||
func IntValueSlice(src []*int) []int {
|
||||
dst := make([]int, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
if src[i] != nil {
|
||||
dst[i] = *(src[i])
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// IntMap converts a string map of int values into a string
|
||||
// map of int pointers
|
||||
func IntMap(src map[string]int) map[string]*int {
|
||||
dst := make(map[string]*int)
|
||||
for k, val := range src {
|
||||
v := val
|
||||
dst[k] = &v
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// IntValueMap converts a string map of int pointers into a string
|
||||
// map of int values
|
||||
func IntValueMap(src map[string]*int) map[string]int {
|
||||
dst := make(map[string]int)
|
||||
for k, val := range src {
|
||||
if val != nil {
|
||||
dst[k] = *val
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Int64 returns a pointer to the int64 value passed in.
|
||||
func Int64(v int64) *int64 {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Int64Value returns the value of the int64 pointer passed in or
|
||||
// 0 if the pointer is nil.
|
||||
func Int64Value(v *int64) int64 {
|
||||
if v != nil {
|
||||
return *v
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Int64Slice converts a slice of int64 values into a slice of
|
||||
// int64 pointers
|
||||
func Int64Slice(src []int64) []*int64 {
|
||||
dst := make([]*int64, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
dst[i] = &(src[i])
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Int64ValueSlice converts a slice of int64 pointers into a slice of
|
||||
// int64 values
|
||||
func Int64ValueSlice(src []*int64) []int64 {
|
||||
dst := make([]int64, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
if src[i] != nil {
|
||||
dst[i] = *(src[i])
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Int64Map converts a string map of int64 values into a string
|
||||
// map of int64 pointers
|
||||
func Int64Map(src map[string]int64) map[string]*int64 {
|
||||
dst := make(map[string]*int64)
|
||||
for k, val := range src {
|
||||
v := val
|
||||
dst[k] = &v
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Int64ValueMap converts a string map of int64 pointers into a string
|
||||
// map of int64 values
|
||||
func Int64ValueMap(src map[string]*int64) map[string]int64 {
|
||||
dst := make(map[string]int64)
|
||||
for k, val := range src {
|
||||
if val != nil {
|
||||
dst[k] = *val
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Float64 returns a pointer to the float64 value passed in.
|
||||
func Float64(v float64) *float64 {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Float64Value returns the value of the float64 pointer passed in or
|
||||
// 0 if the pointer is nil.
|
||||
func Float64Value(v *float64) float64 {
|
||||
if v != nil {
|
||||
return *v
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Float64Slice converts a slice of float64 values into a slice of
|
||||
// float64 pointers
|
||||
func Float64Slice(src []float64) []*float64 {
|
||||
dst := make([]*float64, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
dst[i] = &(src[i])
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Float64ValueSlice converts a slice of float64 pointers into a slice of
|
||||
// float64 values
|
||||
func Float64ValueSlice(src []*float64) []float64 {
|
||||
dst := make([]float64, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
if src[i] != nil {
|
||||
dst[i] = *(src[i])
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Float64Map converts a string map of float64 values into a string
|
||||
// map of float64 pointers
|
||||
func Float64Map(src map[string]float64) map[string]*float64 {
|
||||
dst := make(map[string]*float64)
|
||||
for k, val := range src {
|
||||
v := val
|
||||
dst[k] = &v
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Float64ValueMap converts a string map of float64 pointers into a string
|
||||
// map of float64 values
|
||||
func Float64ValueMap(src map[string]*float64) map[string]float64 {
|
||||
dst := make(map[string]float64)
|
||||
for k, val := range src {
|
||||
if val != nil {
|
||||
dst[k] = *val
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Time returns a pointer to the time.Time value passed in.
|
||||
func Time(v time.Time) *time.Time {
|
||||
return &v
|
||||
}
|
||||
|
||||
// TimeValue returns the value of the time.Time pointer passed in or
|
||||
// time.Time{} if the pointer is nil.
|
||||
func TimeValue(v *time.Time) time.Time {
|
||||
if v != nil {
|
||||
return *v
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// SecondsTimeValue converts an int64 pointer to a time.Time value
|
||||
// representing seconds since Epoch or time.Time{} if the pointer is nil.
|
||||
func SecondsTimeValue(v *int64) time.Time {
|
||||
if v != nil {
|
||||
return time.Unix((*v / 1000), 0)
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// MillisecondsTimeValue converts an int64 pointer to a time.Time value
|
||||
// representing milliseconds sinch Epoch or time.Time{} if the pointer is nil.
|
||||
func MillisecondsTimeValue(v *int64) time.Time {
|
||||
if v != nil {
|
||||
return time.Unix(0, (*v * 1000000))
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// TimeUnixMilli returns a Unix timestamp in milliseconds from "January 1, 1970 UTC".
|
||||
// The result is undefined if the Unix time cannot be represented by an int64.
|
||||
// Which includes calling TimeUnixMilli on a zero Time is undefined.
|
||||
//
|
||||
// This utility is useful for service API's such as CloudWatch Logs which require
|
||||
// their unix time values to be in milliseconds.
|
||||
//
|
||||
// See Go stdlib https://golang.org/pkg/time/#Time.UnixNano for more information.
|
||||
func TimeUnixMilli(t time.Time) int64 {
|
||||
return t.UnixNano() / int64(time.Millisecond/time.Nanosecond)
|
||||
}
|
||||
|
||||
// TimeSlice converts a slice of time.Time values into a slice of
|
||||
// time.Time pointers
|
||||
func TimeSlice(src []time.Time) []*time.Time {
|
||||
dst := make([]*time.Time, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
dst[i] = &(src[i])
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// TimeValueSlice converts a slice of time.Time pointers into a slice of
|
||||
// time.Time values
|
||||
func TimeValueSlice(src []*time.Time) []time.Time {
|
||||
dst := make([]time.Time, len(src))
|
||||
for i := 0; i < len(src); i++ {
|
||||
if src[i] != nil {
|
||||
dst[i] = *(src[i])
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// TimeMap converts a string map of time.Time values into a string
|
||||
// map of time.Time pointers
|
||||
func TimeMap(src map[string]time.Time) map[string]*time.Time {
|
||||
dst := make(map[string]*time.Time)
|
||||
for k, val := range src {
|
||||
v := val
|
||||
dst[k] = &v
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// TimeValueMap converts a string map of time.Time pointers into a string
|
||||
// map of time.Time values
|
||||
func TimeValueMap(src map[string]*time.Time) map[string]time.Time {
|
||||
dst := make(map[string]time.Time)
|
||||
for k, val := range src {
|
||||
if val != nil {
|
||||
dst[k] = *val
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user