├── .gitignore
├── demo
├── html
│ ├── logo.png
│ ├── index.html
│ ├── about.html
│ └── folder
│ │ ├── about.html
│ │ └── index.html
├── data-fragments
│ ├── site1.json
│ └── site2.json
├── template
│ ├── todos.tmpl
│ ├── user.tmpl
│ └── users.tmpl
└── site.json
├── website
├── themes
│ └── juice
│ │ ├── screenshot.png
│ │ ├── content
│ │ ├── go-search-extension.png
│ │ ├── cpp-search-extension.png
│ │ ├── rust-search-extension.png
│ │ ├── about.md
│ │ ├── changelog.md
│ │ ├── showcases.md
│ │ ├── _index.md
│ │ └── juice.svg
│ │ ├── vercel.json
│ │ ├── sass
│ │ ├── _ultility.scss
│ │ ├── _markdown.scss
│ │ ├── _text.scss
│ │ └── juice.scss
│ │ ├── theme.toml
│ │ ├── templates
│ │ ├── page.html
│ │ ├── _variables.html
│ │ ├── _macros.html
│ │ └── index.html
│ │ ├── config.toml
│ │ ├── LICENSE
│ │ ├── README.md
│ │ └── static
│ │ └── normalize.css
├── content
│ ├── message.md
│ ├── news.md
│ ├── community.md
│ └── _index.md
├── message.tmpl
├── message_list.tmpl
├── templates
│ ├── _variables.html
│ └── index.html
├── config.toml
├── stitcher.hcl
└── public
│ ├── juice.css
│ └── normalize.css
├── main.go
├── default.nix
├── shell.nix
├── stitcher
├── fragmented_page.go
├── config.go
├── signal.go
├── transform.go
├── cache.go
├── host.go
├── fragement_fetcher.go
├── server.go
├── fragment.go
└── route.go
├── flake.nix
├── go.mod
├── cmd
└── root.go
├── flake.lock
├── README.md
├── LICENSE
├── gomod2nix.toml
└── go.sum
/.gitignore:
--------------------------------------------------------------------------------
1 | /gomod2nix-template
2 |
--------------------------------------------------------------------------------
/demo/html/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhodges/stitcherd/HEAD/demo/html/logo.png
--------------------------------------------------------------------------------
/demo/data-fragments/site1.json:
--------------------------------------------------------------------------------
1 | {
2 | "site_name": "Site One From site1.json",
3 | "site_key": "foobarbaz"
4 | }
--------------------------------------------------------------------------------
/website/themes/juice/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhodges/stitcherd/HEAD/website/themes/juice/screenshot.png
--------------------------------------------------------------------------------
/demo/data-fragments/site2.json:
--------------------------------------------------------------------------------
1 | {
2 | "site_name": "Site Two From site2.json",
3 | "site_key": "otherfoobarbaz"
4 | }
5 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/vhodges/stitcherd/cmd"
5 | )
6 |
7 | func main() {
8 | cmd.Execute()
9 | }
10 |
--------------------------------------------------------------------------------
/website/themes/juice/content/go-search-extension.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhodges/stitcherd/HEAD/website/themes/juice/content/go-search-extension.png
--------------------------------------------------------------------------------
/website/themes/juice/content/cpp-search-extension.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhodges/stitcherd/HEAD/website/themes/juice/content/cpp-search-extension.png
--------------------------------------------------------------------------------
/website/themes/juice/content/rust-search-extension.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vhodges/stitcherd/HEAD/website/themes/juice/content/rust-search-extension.png
--------------------------------------------------------------------------------
/website/themes/juice/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "build": {
3 | "env": {
4 | "ZOLA_VERSION": "0.11.0"
5 | }
6 | },
7 | "github": {
8 | "silent": true
9 | }
10 | }
--------------------------------------------------------------------------------
/website/content/message.md:
--------------------------------------------------------------------------------
1 | +++
2 | title = "Message"
3 | description = "Message"
4 | [extra]
5 | no_nav = true
6 | +++
7 |
8 |
9 | Content gets replaced at runtime
10 |
11 |
--------------------------------------------------------------------------------
/website/content/news.md:
--------------------------------------------------------------------------------
1 | +++
2 | title = "News"
3 | description = "Latest News"
4 | weight = 1
5 | +++
6 | Archives/Subscribe: stitcherd-announce
7 |
8 |
9 |
--------------------------------------------------------------------------------
/demo/template/todos.tmpl:
--------------------------------------------------------------------------------
1 |
2 | {{range .json}}
3 |
7 | {{.title}}
8 | {{end}}
9 |
--------------------------------------------------------------------------------
/demo/template/user.tmpl:
--------------------------------------------------------------------------------
1 |
2 |
{{.json.name}}
3 |
{{.json.email}}
4 |
8 |
9 |
--------------------------------------------------------------------------------
/website/themes/juice/sass/_ultility.scss:
--------------------------------------------------------------------------------
1 | .text-center {
2 | text-align: center;
3 | }
4 |
5 | .pos-absolute {
6 | right: 0;
7 | left: 0;
8 | position: absolute;
9 | }
10 |
11 | .box-shadow {
12 | box-shadow: 0 2px 10px 2px #ddd;
13 | }
--------------------------------------------------------------------------------
/website/message.tmpl:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ (.document.Find ".container .row:first-of-type div h3").Html }}
4 |
5 |
6 |
7 | {{ unescape (.document.Find "pre.message-body").Html }}
8 |
9 |
10 |
--------------------------------------------------------------------------------
/demo/template/users.tmpl:
--------------------------------------------------------------------------------
1 |
2 | {{range .json}}
3 |
7 | {{end}}
8 |
--------------------------------------------------------------------------------
/website/content/community.md:
--------------------------------------------------------------------------------
1 | +++
2 | title = "Mailing List"
3 | description = "Mailing list"
4 | weight = 2
5 | [extra]
6 | no_nav = true
7 | +++
8 | Archives/Subscribe: stitcherd-general
9 |
10 | Recent Threads
11 | Content Goes Here
--------------------------------------------------------------------------------
/website/themes/juice/theme.toml:
--------------------------------------------------------------------------------
1 | name = "juice"
2 | description = "An intuitive, elegant, and lightweight Zola theme for product sites."
3 | license = "MIT"
4 | homepage = "https://github.com/huhu/juice"
5 | min_version = "0.11.0"
6 | demo = "https://juice.huhu.io"
7 |
8 | [extra]
9 |
10 | [author]
11 | name = "Huhu teams"
12 | homepage = "https://huhu.io"
13 |
--------------------------------------------------------------------------------
/website/message_list.tmpl:
--------------------------------------------------------------------------------
1 |
2 | {{$elements := (.document.Find ".event-list").Children }}
3 | {{$el := $elements.First}}
4 | {{range $i, $_ := N $elements.Length }}
5 | {{ $a := $el.Find "h4 a"}}
6 | {{ $href := $a.AttrOr "href" "" }}
7 | {{ $url := urlParse $href }}
8 | {{ $label := $a.Text }}
9 |
10 |
11 |
12 | {{ $el = $el.Next}}
13 |
14 | {{end}}
15 |
16 |
--------------------------------------------------------------------------------
/website/themes/juice/templates/page.html:
--------------------------------------------------------------------------------
1 | {% import "_macros.html" as macros %}
2 | {% extends "index.html" %}
3 |
4 | {% block title %}{{ page.title }} | {{ super() }} {% endblock title %}
5 |
6 | {% block header %}
7 |
8 | {{ macros::render_header() }}
9 |
10 | {% endblock header %}
11 |
12 | {% block content %}
13 | {{ page.description }}
14 | {{ page.content | safe }}
15 | {% endblock content %}
--------------------------------------------------------------------------------
/default.nix:
--------------------------------------------------------------------------------
1 | { pkgs ? (
2 | let
3 | inherit (builtins) fetchTree fromJSON readFile;
4 | inherit ((fromJSON (readFile ./flake.lock)).nodes) nixpkgs gomod2nix;
5 | in
6 | import (fetchTree nixpkgs.locked) {
7 | overlays = [
8 | (import "${fetchTree gomod2nix.locked}/overlay.nix")
9 | ];
10 | }
11 | )
12 | }:
13 |
14 | pkgs.buildGoApplication {
15 | pname = "myapp";
16 | version = "0.1";
17 | pwd = ./.;
18 | src = ./.;
19 | modules = ./gomod2nix.toml;
20 | }
21 |
--------------------------------------------------------------------------------
/shell.nix:
--------------------------------------------------------------------------------
1 | { pkgs ? (
2 | let
3 | inherit (builtins) fetchTree fromJSON readFile;
4 | inherit ((fromJSON (readFile ./flake.lock)).nodes) nixpkgs gomod2nix;
5 | in
6 | import (fetchTree nixpkgs.locked) {
7 | overlays = [
8 | (import "${fetchTree gomod2nix.locked}/overlay.nix")
9 | ];
10 | }
11 | )
12 | }:
13 |
14 | let
15 | goEnv = pkgs.mkGoEnv { pwd = ./.; };
16 | in
17 | pkgs.mkShell {
18 | packages = [
19 | goEnv
20 | pkgs.gomod2nix
21 | ];
22 | }
23 |
--------------------------------------------------------------------------------
/website/templates/_variables.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/themes/juice/templates/_variables.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/stitcher/fragmented_page.go:
--------------------------------------------------------------------------------
1 | package stitcher
2 |
3 | import (
4 | )
5 |
6 | // Yes, this duplicates Fragment but simplifies marshalling
7 | type FragmentedPage struct {
8 | Fragment Fragment
9 | }
10 |
11 | func (page *FragmentedPage) Render(site *Host, contextdata map[string]interface{}) string {
12 | var err error
13 | var content string = ""
14 |
15 | if page.Fragment.Cachable() {
16 | content, err = page.Fragment.FromCache(site, contextdata)
17 | if err != nil {
18 | return "" // TODO Make this better
19 | }
20 | } else {
21 | content = page.Fragment.Render(site, contextdata)
22 | }
23 |
24 | return content
25 | }
26 |
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | description = "A basic gomod2nix flake";
3 |
4 | inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
5 | inputs.flake-utils.url = "github:numtide/flake-utils";
6 | inputs.gomod2nix.url = "github:nix-community/gomod2nix";
7 |
8 | outputs = { self, nixpkgs, flake-utils, gomod2nix }:
9 | (flake-utils.lib.eachDefaultSystem
10 | (system:
11 | let
12 | pkgs = import nixpkgs {
13 | inherit system;
14 | overlays = [ gomod2nix.overlays.default ];
15 | };
16 |
17 | in
18 | {
19 | packages.default = pkgs.callPackage ./. { };
20 | devShells.default = import ./shell.nix { inherit pkgs; };
21 | })
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/stitcher/config.go:
--------------------------------------------------------------------------------
1 | package stitcher
2 |
3 | import (
4 | "encoding/json"
5 | "io/ioutil"
6 | "log"
7 | )
8 |
9 | func ReadHostConfigFile(filename string) (c *Host, err error) {
10 |
11 | content, err := ioutil.ReadFile(filename)
12 | if err != nil {
13 | return nil, err
14 | }
15 |
16 | // TODO Unmarshall JSON into Host struct pointer
17 | var host Host
18 | json.Unmarshal([]byte(content), &host)
19 |
20 | return &host, nil
21 | }
22 |
23 | func NewHostFromFile(file string) (*Host, error) {
24 |
25 | host, err := ReadHostConfigFile(file)
26 |
27 | if err != nil {
28 | log.Printf("Error reading config file '%s': %v", file, err)
29 | return nil, err
30 | }
31 |
32 | host.Init()
33 |
34 | return host, nil
35 | }
36 |
--------------------------------------------------------------------------------
/website/themes/juice/templates/_macros.html:
--------------------------------------------------------------------------------
1 | {% macro render_header() %}
2 | {% set section = get_section(path="_index.md") %}
3 |
4 |
5 | {{ config.extra.juice_logo_name }}
6 |
7 |
8 |
9 |
10 | {% for p in section.pages %}
11 | {% if p.extra.no_nav is undefined %}
12 | {{ p.title }}
13 | {% endif %}
14 | {% endfor %}
15 | {% if config.extra.juice_extra_menu %}
16 | {% for menu in config.extra.juice_extra_menu %}
17 | {{ menu.title }}
18 | {% endfor %}
19 | {% endif %}
20 |
21 | {% endmacro render_header %}
--------------------------------------------------------------------------------
/website/themes/juice/config.toml:
--------------------------------------------------------------------------------
1 | # The URL the site will be built for
2 | base_url = "/"
3 |
4 | title = "Juice - An intuitive, elegant, and lightweight Zola theme for product sites."
5 |
6 | # Whether to automatically compile all Sass files in the sass directory
7 | compile_sass = true
8 |
9 | # Whether to do syntax highlighting
10 | # Theme can be customised by setting the `highlight_theme` variable to a theme supported by Zola
11 | highlight_code = true
12 | highlight_theme = "inspired-github"
13 |
14 | # Whether to build a search index to be used later on by a JavaScript library
15 | build_search_index = false
16 |
17 | [extra]
18 | juice_logo_name = "Stitcherd"
19 | juice_logo_path = "logo.png"
20 | juice_extra_menu = [
21 | { title = "Github", link = "https://github.com/huhu/juice" }
22 | ]
--------------------------------------------------------------------------------
/website/themes/juice/content/about.md:
--------------------------------------------------------------------------------
1 | +++
2 | title = "About"
3 | description = "About"
4 | weight = 3
5 | +++
6 |
7 | # Juice
8 |
9 | **Juice** is an intuitive, elegant, and responsive Zola theme for product sites.
10 | Built by [Huhu.io](https://huhu.io), adopted by a several product sites.
11 |
12 | # Logo
13 |
14 | 
15 |
16 | # Zola
17 |
18 | [Zola](https://www.getzola.org) is a fast static site generator in a single binary with everything built-in.
19 |
20 |
21 | # Huhu.io
22 |
23 | [Huhu.io](https://huhu.io) is a global community of coders dedicated to making cool stuff coders need and want.
24 | We focus on enabling the developer community by curating, incubating, and launching tools based on great ideas,
25 | providing support and funding that allows our engineers to develop what they want, the way they want.
26 |
--------------------------------------------------------------------------------
/website/config.toml:
--------------------------------------------------------------------------------
1 | # The URL the site will be built for
2 | base_url = "https://stitcherd.vhodges.dev"
3 |
4 | # Whether to automatically compile all Sass files in the sass directory
5 | compile_sass = true
6 |
7 | # Whether to do syntax highlighting
8 | # Theme can be customised by setting the `highlight_theme` variable to a theme supported by Zola
9 | highlight_code = false
10 |
11 | # Whether to build a search index to be used later on by a JavaScript library
12 | build_search_index = false
13 |
14 | theme = "juice"
15 |
16 | [extra]
17 | # Put all your custom variables here
18 | juice_logo_name = "Stitcherd"
19 | juice_logo_path = "logo.png"
20 | juice_extra_menu = [
21 | { title = "Mailing List", link = "https://lists.sr.ht/~vhodges/stitcherd-general/" },
22 | { title = "Github", link = "https://github.com/vhodges/stitcherd" },
23 | ]
24 |
--------------------------------------------------------------------------------
/website/themes/juice/sass/_markdown.scss:
--------------------------------------------------------------------------------
1 | .content {
2 | padding: 0 40px;
3 | display: flex;
4 | flex-direction: column;
5 | justify-content: center;
6 | overflow-x: auto;
7 | }
8 |
9 | .content pre {
10 | overflow-x: auto;
11 | padding: 1.25em 1.5em;
12 | white-space: pre;
13 | word-wrap: normal;
14 | background-color: white;
15 | color: #4a4a4a;
16 | font-size: .875em;
17 | font-family: monospace;
18 | }
19 |
20 | .content code {
21 | background-color: white;
22 | color: #4a4a4a;
23 | font-size: .875em;
24 | font-weight: normal;
25 | padding: 0.25em 0.5em 0.25em;
26 | font-family: monospace;
27 | }
28 |
29 | .content a {
30 | color: var(--primary-link-color);
31 |
32 | &:hover {
33 | text-decoration: underline;
34 | }
35 | }
36 |
37 | .content blockquote {
38 | border-left: #e2dede 8px solid;
39 | margin: 0;
40 | background-color: #ececec;
41 | padding: 0 20px;
42 | }
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/vhodges/stitcherd
2 |
3 | go 1.12
4 |
5 | require (
6 | github.com/Masterminds/goutils v1.1.0 // indirect
7 | github.com/Masterminds/semver v1.5.0 // indirect
8 | github.com/Masterminds/sprig v2.22.0+incompatible
9 | github.com/PuerkitoBio/goquery v1.5.1
10 | github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8
11 | github.com/google/uuid v1.1.2
12 | github.com/gorilla/handlers v1.5.1
13 | github.com/gorilla/mux v1.8.0
14 | github.com/huandu/xstrings v1.3.2 // indirect
15 | github.com/imdario/mergo v0.3.11 // indirect
16 | github.com/mailgun/groupcache/v2 v2.2.0
17 | github.com/mitchellh/copystructure v1.0.0 // indirect
18 | github.com/mitchellh/go-homedir v1.1.0
19 | github.com/spf13/cobra v1.1.1
20 | github.com/spf13/viper v1.7.1
21 | github.com/valyala/fasttemplate v1.2.1
22 | github.com/x-way/crawlerdetect v0.2.7
23 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4
24 | )
25 |
--------------------------------------------------------------------------------
/demo/html/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Simple Sticherd Demo Site
5 |
6 |
7 |
8 | Simple Stitcherd Demo Site
9 | Home -
10 | Folder -
11 | About -
12 | Users
13 |
14 |
15 |
Demo Home Page
16 |
This is a small demo of what stitcherd does and how it does it.
17 |
18 | Home - Static content
19 | Folder - A rewritten index page in a subfolder
20 | About - A rewritten html page in a subfolder
21 | Users - Richer Go template Demo with multiple JSON data requests
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/demo/html/about.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Ye Olde Careersite Demo
5 |
6 |
7 |
8 | Simple Stictherd Demo Site
9 | Home -
10 | Folder -
11 | About -
12 | Users
13 |
14 |
15 | About Page (in a sub-folder)
16 |
17 |
18 |
19 |
This is a small demo of what stitcherd does and how it does it.
20 |
21 | Home - Static content
22 | Jobs - Scaped content injection
23 | Todos - Simple Go Template Demo with JSON data
24 | Users - Richer Go template Demo with multiple JSON data requests
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/website/themes/juice/sass/_text.scss:
--------------------------------------------------------------------------------
1 | .heading-text {
2 | font-family: "Fira Sans", sans-serif;
3 | font-size: 32px;
4 | font-weight: 600;
5 | padding: 10px 0 25px 0;
6 | color: var(--primary-text-color);
7 | }
8 |
9 | h1, .title-text {
10 | font-family: "Fira Sans", sans-serif;
11 | font-size: 25px;
12 | font-weight: 500;
13 | color: var(--primary-text-color);
14 | border-left: var(--primary-color) 8px solid;
15 | padding-left: 10px;
16 | }
17 |
18 | h2, .subtitle-text {
19 | font-family: "Fira Sans", sans-serif;
20 | font-size: 20px;
21 | font-weight: 500;
22 | color: var(--primary-text-color);
23 | }
24 |
25 | .text {
26 | font-family: "Fira Sans", sans-serif;
27 | font-size: 18px;
28 | font-weight: 400;
29 | line-height: 26px;
30 | letter-spacing: 0.2px;
31 | color: var(--primary-text-color);
32 | }
33 |
34 | .subtext {
35 | font-family: "Fira Sans", sans-serif;
36 | font-size: 16px;
37 | font-weight: 400;
38 | letter-spacing: 0.1px;
39 | }
--------------------------------------------------------------------------------
/demo/html/folder/about.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Simple Stitcher Demo Site
5 |
6 |
7 |
8 | Simple Stitcherd Demo Site
9 | Home -
10 | Folder -
11 | About -
12 | Users
13 |
14 |
15 | About Page (in a sub-folder)
16 |
17 |
18 |
19 |
This is a small demo of what stitcherd does and how it does it.
20 |
21 | Home - Static content
22 | Jobs - Scaped content injection
23 | Todos - Simple Go Template Demo with JSON data
24 | Users - Richer Go template Demo with multiple JSON data requests
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/demo/html/folder/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Simple Stitcher Demo Site
5 |
6 |
7 |
8 | Simple Stitcherd Demo Site
9 | Home -
10 | Folder -
11 | About -
12 | Users
13 |
14 |
15 | An index page in a sub-folder
16 |
17 |
18 |
19 |
This is a small demo of what stitcherd does and how it does it.
20 |
21 | Home - Static content
22 | Jobs - Scaped content injection
23 | Todos - Simple Go Template Demo with JSON data
24 | Users - Richer Go template Demo with multiple JSON data requests
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/stitcher/signal.go:
--------------------------------------------------------------------------------
1 | package stitcher
2 |
3 | import (
4 | "context"
5 | "log"
6 | "net/http"
7 | "os"
8 | "os/signal"
9 | "time"
10 | )
11 |
12 | // WaitForSignal blocks until SIGINT arrives.
13 | func WaitForSignal(srv *http.Server) {
14 | c := make(chan os.Signal, 1)
15 |
16 | // We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C)
17 | // SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught.
18 | signal.Notify(c, os.Interrupt)
19 |
20 | // Block until we receive our signal.
21 | <-c
22 |
23 | var wait time.Duration
24 | wait = time.Second * 15
25 |
26 | // Create a deadline to wait for.
27 | ctx, cancel := context.WithTimeout(context.Background(), wait)
28 | defer cancel()
29 |
30 | // Doesn't block if no connections, but will otherwise wait
31 | // until the timeout deadline.
32 | srv.Shutdown(ctx)
33 |
34 | // Optionally, you could run srv.Shutdown in a goroutine and block on
35 | // <-ctx.Done() if your application should wait for other services
36 | // to finalize based on context cancellation.
37 | log.Println("shutting down")
38 | os.Exit(0)
39 | }
40 |
--------------------------------------------------------------------------------
/website/themes/juice/content/changelog.md:
--------------------------------------------------------------------------------
1 | +++
2 | title = "Changelog"
3 | description = "Changelog"
4 | weight = 2
5 | +++
6 |
7 | # v0.7 - 2020-07-01
8 |
9 | - Introduction 1
10 | - New Features:
11 | - feature 1
12 | - Bugfix:
13 | - Fix bug #10
14 | - Fix bug #11
15 |
16 | # v0.6 - 2020-06-01
17 |
18 | - Introduction 1
19 | - New Features:
20 | - feature 1
21 | - Bugfix:
22 | - Fix bug #8
23 | - Fix bug #9
24 |
25 | # v0.5 - 2020-05-01
26 |
27 | - Introduction 1
28 | - New Features:
29 | - feature 1
30 | - Bugfix:
31 | - Fix bug #6
32 | - Fix bug #7
33 |
34 | # v0.4 - 2020-04-01
35 |
36 | - Introduction 1
37 | - New Features:
38 | - feature 1
39 | - Bugfix:
40 | - Fix bug #4
41 | - Fix bug #5
42 |
43 | # v0.3 - 2020-03-01
44 |
45 | - Introduction 1
46 | - New Features:
47 | - feature 1
48 | - Bugfix:
49 | - Fix bug #2
50 | - Fix bug #3
51 |
52 | # v0.2 - 2020-02-04
53 |
54 | - Introduction 1
55 | - New Features:
56 | - feature 1
57 | - Bugfix:
58 | - Fix bug #1
59 |
60 | # v0.1 - 2020-01-01
61 |
62 | - First release!
--------------------------------------------------------------------------------
/website/themes/juice/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Huhu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/stitcher/transform.go:
--------------------------------------------------------------------------------
1 |
2 | package stitcher
3 |
4 | import (
5 | "log"
6 | "github.com/PuerkitoBio/goquery"
7 | )
8 |
9 | type DocumentTransform struct {
10 | Type string
11 | ParentSelector string
12 | ChildSelector string
13 |
14 | Classname string
15 | }
16 |
17 | func (transform *DocumentTransform) Transform(parent_doc *goquery.Document, child_doc *goquery.Document) {
18 |
19 | switch transform.Type {
20 | case "replace":
21 |
22 | if parent_doc == nil || transform.ParentSelector == "" || child_doc == nil {
23 | return
24 | }
25 |
26 | replaceAt := parent_doc.Find(transform.ParentSelector)
27 |
28 | if transform.ChildSelector != "" {
29 | replaceWith := child_doc.Find(transform.ChildSelector)
30 | html, err := replaceWith.Html()
31 | if err != nil {
32 | log.Printf("err: '%v'\n", err)
33 | return
34 | }
35 | replaceAt.ReplaceWithHtml(html)
36 | } else {
37 | html, err := child_doc.Html()
38 | if err != nil {
39 | log.Printf("err: '%v'\n", err)
40 | return
41 | }
42 | replaceAt.ReplaceWithHtml(html)
43 | }
44 | case "set_class":
45 | if parent_doc == nil || transform.ParentSelector == "" || transform.Classname == "" {
46 | return
47 | }
48 | // TODO
49 | default:
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/stitcher/cache.go:
--------------------------------------------------------------------------------
1 | package stitcher
2 |
3 | import (
4 | "log"
5 | "net/http"
6 |
7 | "github.com/mailgun/groupcache/v2"
8 | )
9 |
10 | // RenderContext is passed via Context.WithValue() to the endpoint Getter Func
11 | type FragmentRenderContext struct {
12 | Site *Host
13 | Fragment *Fragment
14 | ContextData map[string]interface{}
15 | }
16 |
17 | type requestContextKey string
18 |
19 | // InitCache sets up the cache service
20 | func InitCache() *http.Server {
21 |
22 | // Keep track of peers in our cluster and add our instance to the pool `http://localhost:8080`
23 | // TODO pool/service config
24 | pool := groupcache.NewHTTPPoolOpts("http://localhost:8080", &groupcache.HTTPPoolOptions{})
25 |
26 | // TODO Config and Add more peers to the cluster
27 | //pool.Set("http://peer1:8080", "http://peer2:8080")
28 | // TODO Dynamim peer addition/removal
29 |
30 | server := http.Server{
31 | Addr: "localhost:8080", // TODO Cache service address
32 | Handler: pool,
33 | }
34 |
35 | // Start a HTTP server to listen for peer requests from the groupcache
36 | go func() {
37 | log.Printf("Cache Server Running....\n")
38 | if err := server.ListenAndServe(); err != nil {
39 | log.Fatal(err)
40 | }
41 | }()
42 |
43 | return &server
44 | }
45 |
--------------------------------------------------------------------------------
/stitcher/host.go:
--------------------------------------------------------------------------------
1 | package stitcher
2 |
3 | import (
4 | "regexp"
5 |
6 | "github.com/gorilla/mux"
7 |
8 | "github.com/mailgun/groupcache/v2"
9 | )
10 |
11 | // Host represents a single VHOSTed site
12 | type Host struct {
13 | Hostname string
14 |
15 | Routes []Route
16 |
17 | Cache *groupcache.Group
18 | MaxCache int64
19 |
20 | Router *mux.Router
21 |
22 | hostPattern *regexp.Regexp
23 | }
24 |
25 | // Init handles host specific initialization
26 | func (host *Host) Init() {
27 |
28 | // Treat HostName as a regular expression
29 | host.hostPattern = regexp.MustCompile(host.Hostname)
30 |
31 | maxCache := host.MaxCache
32 | if maxCache == 0 {
33 | maxCache = 1 << 24
34 | }
35 |
36 | host.Router = mux.NewRouter()
37 |
38 | // Use a previously created group ie if reloading
39 | host.Cache = groupcache.GetGroup(host.Hostname)
40 |
41 | // or Create a new group cache with a max cache size of 3MB
42 | if host.Cache == nil {
43 | host.Cache = groupcache.NewGroup(host.Hostname,
44 | maxCache, groupcache.GetterFunc(FillFragmentCache))
45 | }
46 |
47 | for _, r := range host.Routes {
48 | r.Init(host)
49 | }
50 | }
51 |
52 | func (host *Host) Match(hostname string) bool {
53 | return host.hostPattern.MatchString(hostname)
54 | }
55 |
56 |
--------------------------------------------------------------------------------
/website/themes/juice/content/showcases.md:
--------------------------------------------------------------------------------
1 | +++
2 | title = "Showcases"
3 | description = "Showcases"
4 | weight = 1
5 | +++
6 |
7 | # Pull request
8 |
9 | If you use **Juice** as your theme, feel free to make Pull request.
10 |
11 | Here are some steps to help you get started:
12 |
13 | - Change the [content/showcases.md](https://github.com/huhu/juice/blob/master/content/showcases.md) file, add your product site. Make sure the lexicographical order.
14 | - Add your screenshots to [content](https://github.com/huhu/juice/tree/master/static/showcases) directory, then link the screenshot in the markdown file.
15 | - Add a link to your product site.
16 |
17 | # Gallery
18 |
19 | Here are some product websites which use **Juice** as the theme.
20 | Sort by lexicographical order.
21 |
22 | ## C/C++ Search Extension
23 |
24 | 
25 |
26 | Link: [https://cpp.extension.sh/](https://cpp.extension.sh/)
27 |
28 | ## Go Search Extension
29 |
30 | 
31 |
32 | Link: [https://go.extension.sh/](https://go.extension.sh/)
33 |
34 | ## JS Search Extension
35 |
36 | 
37 |
38 | Link: [https://js.extension.sh/](https://js.extension.sh/)
39 |
40 | ## Rust Search Extension
41 |
42 | 
43 |
44 | Link: [https://rust.extension.sh/](https://rust.extension.sh/)
45 |
--------------------------------------------------------------------------------
/website/stitcher.hcl:
--------------------------------------------------------------------------------
1 | hostname = "stitcherd.vhodges.dev"
2 |
3 | route "/news/" {
4 | content {
5 | source = "website/public/news/index.html"
6 |
7 | replacement "#news" {
8 | content {
9 | template = "website/message_list.tmpl"
10 | source = "https://lists.sr.ht/~vhodges/stitcherd-announce/"
11 | cache = "/news/list/template/"
12 | ttl = "30m"
13 | }
14 | }
15 |
16 | cache = "/news/"
17 | ttl = "10m"
18 | }
19 | }
20 |
21 | route "/news/{messageId}" {
22 | content {
23 | source = "website/public/message/index.html"
24 |
25 | replacement ".content" {
26 | content {
27 | template = "website/message.tmpl"
28 | source = "https://lists.sr.ht/~vhodges/stitcherd-announce/{{messageId}}"
29 | cache = "/news/message/template/{{messageId}}"
30 | ttl = "30m"
31 | }
32 | }
33 |
34 | cache = "/news/{{messageId}}/"
35 | ttl = "10m"
36 | }
37 | }
38 |
39 | route "/" {
40 | content {
41 | source = "website/public/index.html"
42 |
43 | replacement "#homepage" {
44 | content {
45 | source = "https://github.com/vhodges/stitcherd/blob/main/README.md"
46 | select = "article:first-of-type"
47 | cache = "github/homepage"
48 | ttl = "30m"
49 | }
50 | }
51 |
52 | cache = "homepage"
53 | ttl = "10m"
54 | }
55 | }
56 |
57 | route "/" {
58 | static {
59 | directory = "website/public"
60 | }
61 | }
62 |
63 |
--------------------------------------------------------------------------------
/website/templates/index.html:
--------------------------------------------------------------------------------
1 | {% extends "juice/templates/index.html" %}
2 |
3 | {% block hero %}
4 |
5 |
6 |
7 | Dynamic content for mostly static sites
8 |
9 |
10 | Stitcherd can automatically modify the element tree of a page before serving it to users. You can tell it where to insert new content using CSS selectors. For example, if you want to be evil, inject a huge animated banner just before the <main> element of every page you serve.
11 |
12 |
18 |
19 |
20 |
21 |
23 | Explore More ⇩
24 |
25 |
39 |
40 | {% endblock hero %}
41 |
42 | {% block footer %}
43 |
48 | {% endblock footer %}
49 |
--------------------------------------------------------------------------------
/cmd/root.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 | "os"
6 |
7 | "github.com/google/uuid"
8 |
9 | homedir "github.com/mitchellh/go-homedir"
10 | "github.com/spf13/cobra"
11 | "github.com/spf13/viper"
12 | "github.com/vhodges/stitcherd/stitcher"
13 | )
14 |
15 | var (
16 | // Used for flags.
17 | cfgFile string
18 | hostConfigFiles []string
19 | listenAddress string
20 | adminHostname string
21 | workingDirectory string
22 | adminEnabled bool
23 |
24 | rootCmd = &cobra.Command{
25 | Use: "stitcherd",
26 | Short: "Site composition server",
27 | Long: `Stitcherd uses css selectors to retrieve and replace
28 | elements in an HTML document allowing site architects to compose
29 | their site from a number of different and disparate parts.`,
30 | }
31 |
32 | serverCmd = &cobra.Command{
33 | Use: "serve",
34 | Short: "Serve the one or more websites",
35 | Long: `Server one or more websites configured with a --host.hcl`,
36 | Run: func(cmd *cobra.Command, args []string) {
37 | if adminHostname == "" {
38 | adminHostname = uuid.New().String()
39 | }
40 | server := &stitcher.Stitcherd{
41 | ListenAddress: listenAddress,
42 | WorkingDirectory: workingDirectory,
43 | AdminHostName: adminHostname,
44 | AdminEnabled: adminEnabled,
45 | }
46 |
47 | server.Init().Run(hostConfigFiles)
48 | },
49 | }
50 | )
51 |
52 | // Execute executes the root command.
53 | func Execute() error {
54 | return rootCmd.Execute()
55 | }
56 |
57 | func init() {
58 | cobra.OnInitialize(initConfig)
59 |
60 | rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)")
61 | rootCmd.PersistentFlags().StringSliceVarP(&hostConfigFiles, "host", "", []string{}, "")
62 |
63 | serverCmd.Flags().StringVar(&listenAddress, "listen", "0.0.0.0:3000", "Address the server should listen on")
64 | serverCmd.Flags().StringVar(&adminHostname, "adminhost", "", "Hostname to use for the admin end point. If blank will use a secure UUID")
65 | serverCmd.Flags().StringVar(&workingDirectory, "workingdir", ".", "Workding directory for site files. Defaults to .")
66 | serverCmd.Flags().BoolVar(&adminEnabled, "enable-admin", false, "Enable Admin/API enpoint(s)")
67 |
68 | rootCmd.AddCommand(serverCmd)
69 | }
70 |
71 | func er(msg interface{}) {
72 | fmt.Println("Error:", msg)
73 | os.Exit(1)
74 | }
75 |
76 | func initConfig() {
77 | if cfgFile != "" {
78 | // Use config file from the flag.
79 | viper.SetConfigFile(cfgFile)
80 | } else {
81 | // Find home directory.
82 | home, err := homedir.Dir()
83 | if err != nil {
84 | er(err)
85 | }
86 |
87 | // Search config in home directory with name ".cobra" (without extension).
88 | viper.AddConfigPath(home)
89 | viper.SetConfigName(".cobra")
90 | }
91 |
92 | viper.AutomaticEnv()
93 |
94 | if err := viper.ReadInConfig(); err == nil {
95 | fmt.Println("Using config file:", viper.ConfigFileUsed())
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/website/themes/juice/README.md:
--------------------------------------------------------------------------------
1 | # Juice
2 |
3 |
4 |
5 | **Juice** is an intuitive, elegant, and responsive Zola theme for product sites.
6 |
7 | - Build for product sites
8 | - Simple and intuitive structure
9 | - Clean and elegant design
10 | - Responsive and mobile device compatible
11 | - Customize and extend friendly
12 |
13 | https://juice.huhu.io
14 |
15 | # Installation
16 |
17 | First download this theme to your `themes` directory:
18 |
19 | ```bash
20 | $ cd themes
21 | $ git clone https://github.com/huhu/juice.git
22 | ```
23 |
24 | or add as a submodule
25 | ```bash
26 | $ git submodule add https://github.com/huhu/juice themes/juice
27 | ```
28 |
29 | and then enable it in your `config.toml`:
30 |
31 | ```toml
32 | theme = "juice"
33 | ```
34 |
35 | # Structure
36 |
37 | ### Hero
38 |
39 | **Juice** is designed for product websites, hence we let **hero** part fills whole of screen.
40 | You can customize your **hero** by using `hero` block in the `index.html`.
41 |
42 | ```html
43 | {% block hero %}
44 |
45 | Your cool hero html...
46 |
47 | {% endblock hero %}
48 | ```
49 |
50 | ### Page
51 |
52 | Every markdown file located in `content` directory will become a **Page**. There also will display as
53 | a navigate link on the top-right corner.
54 | You can change the frontmatter's `weight` value to sort the order (ascending order).
55 |
56 | ```
57 | +++
58 | title = "Changelog"
59 | description = "Changelog"
60 | weight = 2
61 | +++
62 |
63 | ```
64 |
65 | ### CSS variables
66 |
67 | You can override theme variable by creating a file named `_variables.html` in your `templates` directory.
68 |
69 | ```html
70 |
85 | ```
86 |
87 | # Configuration
88 |
89 | You can customize some builtin property in `config.toml` file:
90 |
91 | ```toml
92 | [extra]
93 | juice_logo_name = "Juice"
94 | juice_logo_path = "juice.svg"
95 | juice_extra_menu = [
96 | { title = "Github", link = "https://github.com/huhu/juice"}
97 | ]
98 | ```
99 |
100 | # Showcases
101 |
102 | Please see the [showcases page](/showcases).
103 |
104 | # Contributing
105 |
106 | Thank you very much for considering contributing to this project!
107 |
108 | We appreciate any form of contribution:
109 |
110 | - New issues (feature requests, bug reports, questions, ideas, ...)
111 | - Pull requests (documentation improvements, code improvements, new features, ...)
--------------------------------------------------------------------------------
/website/themes/juice/content/_index.md:
--------------------------------------------------------------------------------
1 | +++
2 | title = "Juice"
3 | sort_by = "weight"
4 | +++
5 |
6 | # Juice
7 |
8 | **Juice** is an intuitive, elegant, and responsive Zola theme for product sites.
9 |
10 | - Build for product sites
11 | - Simple and intuitive structure
12 | - Clean and elegant design
13 | - Responsive and mobile device compatible
14 | - Customize and extend friendly
15 |
16 | # Installation
17 |
18 | > **Zola** is a prerequisite. Please refer to the [Zola installation](https://www.getzola.org/documentation/getting-started/installation/) docs.
19 |
20 | First download this theme to your `themes` directory:
21 |
22 | ```bash
23 | $ cd themes
24 | $ git clone https://github.com/huhu/juice.git
25 | ```
26 |
27 | or add as a submodule
28 | ```bash
29 | $ git submodule add https://github.com/huhu/juice themes/juice
30 | ```
31 |
32 | and then enable it in your `config.toml`:
33 |
34 | ```toml
35 | theme = "juice"
36 | ```
37 |
38 | # Structure
39 |
40 | ### Hero
41 |
42 | **Juice** is designed for product websites, hence we let **hero** part fills whole of screen.
43 | You can customize your **hero** by using `hero` block in the `index.html`.
44 |
45 | ```html
46 | {% block hero %}
47 |
48 | Your cool hero html...
49 |
50 | {% endblock hero %}
51 | ```
52 |
53 | ### Page
54 |
55 | Every markdown file located in `content` directory will become a **Page**. There also will display as
56 | a navigate link on the top-right corner.
57 | You can change the frontmatter's `weight` value to sort the order (ascending order).
58 |
59 | ```
60 | +++
61 | title = "Changelog"
62 | description = "Changelog"
63 | weight = 2
64 | +++
65 |
66 | ```
67 |
68 | ### CSS variables
69 |
70 | You can override theme variable by creating a file named `_variables.html` in your `templates` directory.
71 |
72 | ```html
73 |
88 | ```
89 |
90 | # Configuration
91 |
92 | You can customize some builtin property in `config.toml` file:
93 |
94 | ```toml
95 | [extra]
96 | juice_logo_name = "Juice"
97 | juice_logo_path = "juice.svg"
98 | juice_extra_menu = [
99 | { title = "Github", link = "https://github.com/huhu/juice"}
100 | ]
101 | ```
102 |
103 | # Showcases
104 |
105 | Please see the [showcases page](/showcases).
106 |
107 | # Contributing
108 |
109 | Thank you very much for considering contributing to this project!
110 |
111 | We appreciate any form of contribution:
112 |
113 | - New issues (feature requests, bug reports, questions, ideas, ...)
114 | - Pull requests (documentation improvements, code improvements, new features, ...)
115 |
--------------------------------------------------------------------------------
/website/themes/juice/templates/index.html:
--------------------------------------------------------------------------------
1 | {% import "_macros.html" as macros %}
2 |
3 |
4 |
5 |
6 |
7 | {% block title %}{{ config.title }}{% endblock title %}
8 |
9 | {% include "_variables.html" %}
10 |
11 |
12 |
13 |
14 | {% block head %}
15 | {% endblock head %}
16 |
17 |
18 |
19 | {% block header %}
20 |
21 |
22 | {{ macros::render_header() }}
23 |
24 |
25 |
26 | {% block hero %}
27 |
28 |
29 |
30 | Build your static website
31 |
32 |
33 | Juice is an intuitive, elegant, and lightweight Zola theme for product websites.
34 |
35 |
41 |
42 |
43 |
44 |
46 | Explore More ⇩
47 |
48 |
62 | {% endblock hero %}
63 |
64 |
65 | {% endblock header %}
66 |
67 |
68 |
69 | {% block content %}
70 |
Overview
71 | {{ section.content | safe }}
72 | {% endblock content %}
73 |
74 |
75 |
76 |
77 | {% block footer %}
78 |
83 | {% endblock footer %}
84 |
85 |
--------------------------------------------------------------------------------
/website/public/juice.css:
--------------------------------------------------------------------------------
1 | .text-center{text-align:center}.pos-absolute{right:0;left:0;position:absolute}.box-shadow{box-shadow:0 2px 10px 2px #ddd}.heading-text{font-family:"Fira Sans", sans-serif;font-size:32px;font-weight:600;padding:10px 0 25px 0;color:var(--primary-text-color)}h1,.title-text{font-family:"Fira Sans", sans-serif;font-size:25px;font-weight:500;color:var(--primary-text-color);border-left:var(--primary-color) 8px solid;padding-left:10px}h2,.subtitle-text{font-family:"Fira Sans", sans-serif;font-size:20px;font-weight:500;color:var(--primary-text-color)}.text{font-family:"Fira Sans", sans-serif;font-size:18px;font-weight:400;line-height:26px;letter-spacing:0.2px;color:var(--primary-text-color)}.subtext{font-family:"Fira Sans", sans-serif;font-size:16px;font-weight:400;letter-spacing:0.1px}.content{padding:0 40px;display:flex;flex-direction:column;justify-content:center;overflow-x:auto}.content pre{overflow-x:auto;padding:1.25em 1.5em;white-space:pre;word-wrap:normal;background-color:white;color:#4a4a4a;font-size:.875em;font-family:monospace}.content code{background-color:white;color:#4a4a4a;font-size:.875em;font-weight:normal;padding:0.25em 0.5em 0.25em;font-family:monospace}.content a{color:var(--primary-link-color)}.content a:hover{text-decoration:underline}.content blockquote{border-left:#e2dede 8px solid;margin:0;background-color:#ececec;padding:0 20px}body{padding:0;margin:0;box-sizing:border-box;background-color:var(--secondary-color)}a{text-decoration:none}ul{margin-top:0.5rem}ul>li{padding:0.3rem 0}p>img{width:100%;height:auto}header{background-color:var(--primary-color);color:black;padding:20px 50px;display:flex;align-items:center;justify-content:space-between}.logo{font-family:"Alfa Slab One", serif;font-size:32px;color:var(--primary-text-color);display:flex;align-items:center;margin:0 40px}.logo img{width:60px;margin:0 25px}.nav-item{margin:0 10px;text-decoration:none;font-size:18px;font-weight:bold}.nav-item:hover{color:#000;text-decoration:underline}.hero{display:flex;align-items:center;justify-content:space-evenly;height:100vh;background-color:var(--primary-color);overflow-x:hidden;padding:0 40px}.hero .explore-more{position:absolute;bottom:20px;left:45%;cursor:pointer}main{display:flex;padding:50px 100px}main .toc{max-width:260px;min-width:240px}main .toc-item{padding:10px 20px;color:#424242}main .toc-item a,main .toc-item-child a{color:var(--secondary-text-color)}main .toc-item a:hover,main .toc-item-child a:hover{cursor:pointer;text-decoration:underline}main .toc-item a.active,main .toc-item-child a.active{color:var(--toc-highlight-text-color)}main .toc-item-child{padding:0 30px 5px;color:#424242}.toc-sticky{border-radius:3px;border-top:5px solid var(--primary-color);background-color:white;position:sticky;position:-webkit-sticky;position:-moz-sticky;position:-ms-sticky;position:-o-sticky;top:10px;padding:10px 0}footer{padding:50px;display:flex;flex-direction:column;justify-content:center;align-items:center;background-color:#202020;color:#fcfcfc}footer a{color:#fcfcfc;text-decoration:underline}@media screen and (max-width: 768px){header{padding:10px 30px;flex-direction:column;align-items:center;justify-content:center}.logo{font-size:28px;margin:10px}.logo img{width:45px;margin:0 10px 0 0}.nav-item{margin:0 5px;font-size:14px}.hero{padding:40px 30px}main{padding:30px}.content{padding:0}.explore-more,.toc{display:none}}
2 |
--------------------------------------------------------------------------------
/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "flake-utils": {
4 | "inputs": {
5 | "systems": "systems"
6 | },
7 | "locked": {
8 | "lastModified": 1681202837,
9 | "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
10 | "owner": "numtide",
11 | "repo": "flake-utils",
12 | "rev": "cfacdce06f30d2b68473a46042957675eebb3401",
13 | "type": "github"
14 | },
15 | "original": {
16 | "owner": "numtide",
17 | "repo": "flake-utils",
18 | "type": "github"
19 | }
20 | },
21 | "gomod2nix": {
22 | "inputs": {
23 | "nixpkgs": "nixpkgs",
24 | "utils": "utils"
25 | },
26 | "locked": {
27 | "lastModified": 1677459247,
28 | "narHash": "sha256-JbakfAiPYmCCV224yAMq/XO0udN5coWv/oazblMKdoY=",
29 | "owner": "nix-community",
30 | "repo": "gomod2nix",
31 | "rev": "3cbf3a51fe32e2f57af4c52744e7228bab22983d",
32 | "type": "github"
33 | },
34 | "original": {
35 | "owner": "nix-community",
36 | "repo": "gomod2nix",
37 | "type": "github"
38 | }
39 | },
40 | "nixpkgs": {
41 | "locked": {
42 | "lastModified": 1658285632,
43 | "narHash": "sha256-zRS5S/hoeDGUbO+L95wXG9vJNwsSYcl93XiD0HQBXLk=",
44 | "owner": "NixOS",
45 | "repo": "nixpkgs",
46 | "rev": "5342fc6fb59d0595d26883c3cadff16ce58e44f3",
47 | "type": "github"
48 | },
49 | "original": {
50 | "owner": "NixOS",
51 | "ref": "master",
52 | "repo": "nixpkgs",
53 | "type": "github"
54 | }
55 | },
56 | "nixpkgs_2": {
57 | "locked": {
58 | "lastModified": 1682526928,
59 | "narHash": "sha256-2cKh4O6t1rQ8Ok+v16URynmb0rV7oZPEbXkU0owNLQs=",
60 | "owner": "NixOS",
61 | "repo": "nixpkgs",
62 | "rev": "d6b863fd9b7bb962e6f9fdf292419a775e772891",
63 | "type": "github"
64 | },
65 | "original": {
66 | "owner": "NixOS",
67 | "ref": "nixos-unstable",
68 | "repo": "nixpkgs",
69 | "type": "github"
70 | }
71 | },
72 | "root": {
73 | "inputs": {
74 | "flake-utils": "flake-utils",
75 | "gomod2nix": "gomod2nix",
76 | "nixpkgs": "nixpkgs_2"
77 | }
78 | },
79 | "systems": {
80 | "locked": {
81 | "lastModified": 1681028828,
82 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
83 | "owner": "nix-systems",
84 | "repo": "default",
85 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
86 | "type": "github"
87 | },
88 | "original": {
89 | "owner": "nix-systems",
90 | "repo": "default",
91 | "type": "github"
92 | }
93 | },
94 | "utils": {
95 | "locked": {
96 | "lastModified": 1653893745,
97 | "narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=",
98 | "owner": "numtide",
99 | "repo": "flake-utils",
100 | "rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1",
101 | "type": "github"
102 | },
103 | "original": {
104 | "owner": "numtide",
105 | "repo": "flake-utils",
106 | "type": "github"
107 | }
108 | }
109 | },
110 | "root": "root",
111 | "version": 7
112 | }
113 |
--------------------------------------------------------------------------------
/stitcher/fragement_fetcher.go:
--------------------------------------------------------------------------------
1 | package stitcher
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "html/template"
8 | "io/ioutil"
9 |
10 | // "log"
11 | "net/http"
12 | "strings"
13 |
14 | "github.com/Masterminds/sprig"
15 | "github.com/PuerkitoBio/goquery"
16 | "github.com/bradfitz/iter"
17 | "github.com/valyala/fasttemplate"
18 | )
19 |
20 | type FragmentFetcher struct {
21 | Type string
22 |
23 | Source string // Filepath, URI, static/interpolated string, Cachekey name (For rendered)
24 |
25 | Template string // Go template name/path
26 | IsJson bool // Parse fetched fragment as JSON, Only useful if Template is present.
27 |
28 | // Values suitable for URI sources
29 | URIVerb string // GET POST PATCH DELETE(?) etc
30 | URIParams map[string]string // Post and Get will be different
31 | Headers map[string]string // Makes sense for remote fragments
32 | }
33 |
34 | func (fetcher *FragmentFetcher) Fetch(contextdata map[string]interface{}) (string, error) {
35 |
36 | t := fasttemplate.New(fetcher.Source, "{{", "}}")
37 | src := t.ExecuteString(contextdata)
38 |
39 | var fetched_fragment string = src // Default to String source
40 | var err error = nil
41 |
42 | switch fetcher.Type {
43 | case "uri":
44 | fetched_fragment, err = fetcher.FetchURI(src)
45 | case "file":
46 | fetched_fragment, err = fetcher.FetchFile(src)
47 | }
48 |
49 | if fetcher.Template != "" {
50 | templateBytes, _ := ioutil.ReadFile(fetcher.Template)
51 | templateContents := string(templateBytes)
52 |
53 | funcs := sprig.GenericFuncMap()
54 | funcs["N"] = iter.N
55 | funcs["unescape"] = unescape
56 |
57 | parsedTemplate := template.Must(template.New(fetcher.Template).Funcs(template.FuncMap(funcs)).Parse(templateContents))
58 |
59 | var buffer bytes.Buffer
60 | var data = make(map[string]interface{})
61 |
62 | if fetcher.IsJson {
63 | var jsonData interface{}
64 |
65 | json.Unmarshal([]byte(fetched_fragment), &jsonData)
66 |
67 | data["json"] = jsonData
68 | } else {
69 | var doc *goquery.Document
70 | doc, err = goquery.NewDocumentFromReader(strings.NewReader(fetched_fragment))
71 | data["document"] = doc // Add the Dom tree to the data for the template
72 | }
73 |
74 | err = parsedTemplate.Execute(&buffer, data)
75 |
76 | if err != nil {
77 | return "", err
78 | }
79 |
80 | return buffer.String(), nil
81 | }
82 |
83 | return fetched_fragment, nil
84 | }
85 |
86 | func (fetcher *FragmentFetcher) FetchURI(src string) (string, error) {
87 |
88 | // TODO At somepoint we'll probably need finer grained control over the client/request
89 | // not to mention cookie/session handling
90 | res, err := http.Get(src)
91 |
92 | if err != nil {
93 | return "", err
94 | }
95 |
96 | defer res.Body.Close()
97 |
98 | if res.StatusCode != 200 {
99 | return "", fmt.Errorf("status code error: %d %s", res.StatusCode, res.Status)
100 | }
101 |
102 | // Will need to handle/follow redirects - might be an option on the http client
103 |
104 | b, err2 := ioutil.ReadAll(res.Body)
105 | return string(b), err2
106 | }
107 |
108 | func (fetcher *FragmentFetcher) FetchFile(src string) (string, error) {
109 | b, err := ioutil.ReadFile(src)
110 | return string(b), err
111 | }
112 |
113 | func unescape(s string) template.HTML {
114 | return template.HTML(s)
115 | }
116 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | Static sites are great. They're fast to serve, there's less to to go wrong and are hostable
4 | pretty much anywhere and any how you would like to. But sometimes, just sometimes there is
5 | some page or some part of every page that has dynamic or even personalized content on it.
6 | Stitcherd is for those times.
7 |
8 | It is a web server that reads some source content (typically a local static file, but could come from a remote source), fetches one or more pieces of remote “dynamic” content and injects them into the source document using a css selector to find the insertion point. These can be nested. The remote dynamic content can be remote html or output of a Go template that can also process remote html and/or remote json data.
9 |
10 | The resulting content is then served to the client. You can have multiple routes with different sets of content to be replaced (and indeed source document). In other words it does server side includes, but with css/dom manipulation and so doesn’t require special directives in the source documents.
11 |
12 | A couple of Use cases:
13 |
14 | * Fast e-commerce site with static product pages, but dynamic pricing/availability/promotions. Plus server side carts
15 | * Commenting system for static blogs. JS Free
16 | * Micro services/frontends
17 |
18 | Yes, it’s server that has to be hosted somewhere and you need to decide if that’s okay for your use case or not, but then so are most of the alternatives, except JS of course, but same-origin, et al leads it to be harder (imo) to use than this.
19 |
20 | ## Features
21 |
22 | ### Current
23 |
24 | * Multiple vhosts
25 | * CSS Selector page assembly
26 | * Static Content catch all (but... will allow proxy fallback soon)
27 | * Simple cache controls per endpoint/route (more types coming soon - ie etag, last modified etc.)
28 | * Go templates (with HTML and JSON Data retrieval) for endpoints
29 | * Bot detection (>800 known bots)
30 | * Both General and (Bot == true) rate limiting (per route)
31 | * Static content routes
32 |
33 | ### Coming Soon
34 |
35 | * (Optional) Sessions
36 | * Site Authentication (OAUTH/SAML end point config, basic auth? Builtin user/password (agencies?)). Authenticate routes?
37 | * HMAC auth support for backend ends
38 | * More control over endpoint request (ACTION/Verb, protocol, headers, cookies, form vars, etc )
39 | * Proxy for fallback (eg / to some CMS) and for routes (eg /blog/ proxied to Wordpress)
40 |
41 |
42 | # Building
43 |
44 | Requires Go > 1.12
45 |
46 | Clone the repo and then a simple
47 |
48 | ```
49 | go build
50 | ```
51 |
52 | Docker image at some point
53 |
54 | # Examples
55 |
56 | There is a 'demo' folder that serves as an example/testbed
57 |
58 | ```
59 | ./stitcherd --host demo/site.json
60 | http://localhost:3000/
61 | ```
62 |
63 | # Prior Art and Inspiration
64 |
65 | * Edge side includes (ESI) using Varnish https://varnish-cache.org/
66 | * shtml files and virtual paths in nginx
67 | * Greasemonkey: https://addons.mozilla.org/en-CA/firefox/addon/greasemonkey/ (Client side)
68 | * Mousehole: https://github.com/whymirror/mousehole
69 | * Jigsaw: https://www.w3.org/Jigsaw/Overview.html (I vaguely recall it could do some of this kind of thing - I could be wrong though)
70 | * Netlify: most recently has the same idea with their Edge (Handlers) https://www.netlify.com/products/edge/edge-handlers/
71 | * Soupault: Is the main inspiration for how stitcherd works. https://soupault.neocities.org/
72 |
73 | # License
74 |
75 | Apache 2.0
76 |
77 |
--------------------------------------------------------------------------------
/website/themes/juice/sass/juice.scss:
--------------------------------------------------------------------------------
1 | @import "_ultility.scss";
2 | @import "_text.scss";
3 | @import "_markdown.scss";
4 |
5 | body {
6 | padding: 0;
7 | margin: 0;
8 | box-sizing: border-box;
9 | background-color: var(--secondary-color);
10 | }
11 |
12 | a {
13 | text-decoration: none;
14 | }
15 |
16 | ul {
17 | margin-top: 0.5rem;
18 | }
19 |
20 | ul > li {
21 | padding: 0.3rem 0;
22 | }
23 |
24 | p > img {
25 | width: 100%;
26 | height: auto;
27 | }
28 |
29 | header {
30 | background-color: var(--primary-color);
31 | color: black;
32 | padding: 20px 50px;
33 | display: flex;
34 | align-items: center;
35 | justify-content: space-between;
36 | }
37 |
38 | .logo {
39 | font-family: "Alfa Slab One", serif;
40 | font-size: 32px;
41 | color: var(--primary-text-color);
42 | display: flex;
43 | align-items: center;
44 | margin: 0 40px;
45 |
46 | img {
47 | width: 60px;
48 | margin: 0 25px;
49 | }
50 | }
51 |
52 | .nav-item {
53 | margin: 0 10px;
54 | text-decoration: none;
55 | font-size: 18px;
56 | font-weight: bold;
57 |
58 | &:hover {
59 | color: #000;
60 | text-decoration: underline;
61 | }
62 | }
63 |
64 | .hero {
65 | display: flex;
66 | align-items: center;
67 | justify-content: space-evenly;
68 | height: 100vh;
69 | background-color: var(--primary-color);
70 | overflow-x: hidden;
71 | padding: 0 40px;
72 |
73 | .explore-more {
74 | position: absolute;
75 | bottom: 20px;
76 | left: 45%;
77 | cursor: pointer;
78 | }
79 | }
80 |
81 | main {
82 | display: flex;
83 | padding: 50px 100px;
84 |
85 | .toc {
86 | max-width: 260px;
87 | min-width: 240px;
88 | }
89 |
90 | .toc-item {
91 | padding: 10px 20px;
92 | color: #424242;
93 | }
94 |
95 | .toc-item a, .toc-item-child a {
96 | color: var(--secondary-text-color);
97 |
98 | &:hover {
99 | cursor: pointer;
100 | text-decoration: underline;
101 | }
102 | }
103 |
104 | .toc-item a.active, .toc-item-child a.active {
105 | color: var(--toc-highlight-text-color);
106 | }
107 |
108 | .toc-item-child {
109 | padding: 0 30px 5px;
110 | color: #424242;
111 | }
112 |
113 | }
114 |
115 | .toc-sticky {
116 | border-radius: 3px;
117 | border-top: 5px solid var(--primary-color);
118 | background-color: white;
119 | position: sticky;
120 | position: -webkit-sticky;
121 | position: -moz-sticky;
122 | position: -ms-sticky;
123 | position: -o-sticky;
124 | top: 10px;
125 | padding: 10px 0;
126 | }
127 |
128 | footer {
129 | padding: 50px;
130 | display: flex;
131 | flex-direction: column;
132 | justify-content: center;
133 | align-items: center;
134 | background-color: #202020;
135 | color: #fcfcfc;
136 |
137 | a {
138 | color: #fcfcfc;
139 | text-decoration: underline;
140 | }
141 | }
142 |
143 | @media screen and (max-width: 768px) {
144 | header {
145 | padding: 10px 30px;
146 | flex-direction: column;
147 | align-items: center;
148 | justify-content: center;
149 | }
150 |
151 | .logo {
152 | font-size: 28px;
153 | margin: 10px;
154 |
155 | img {
156 | width: 45px;
157 | margin: 0 10px 0 0;
158 | }
159 | }
160 |
161 | .nav-item {
162 | margin: 0 5px;
163 | font-size: 14px;
164 | }
165 |
166 | .hero {
167 | padding: 40px 30px;
168 | }
169 |
170 | main {
171 | padding: 30px;
172 | }
173 |
174 | .content {
175 | padding: 0;
176 | }
177 |
178 | .explore-more, .toc {
179 | display: none;
180 | }
181 |
182 | }
--------------------------------------------------------------------------------
/stitcher/server.go:
--------------------------------------------------------------------------------
1 | package stitcher
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "log"
7 | "net/http"
8 | "time"
9 |
10 | "github.com/gorilla/handlers"
11 | "github.com/gorilla/mux"
12 | )
13 |
14 | type Stitcherd struct {
15 |
16 | ListenAddress string
17 | WorkingDirectory string
18 |
19 | AdminHostName string
20 | AdminEnabled bool
21 |
22 | hosts map[string]*Host
23 | adminRouter *mux.Router
24 | }
25 |
26 | // Performs initialization
27 | func (stitcherd *Stitcherd) Init() *Stitcherd {
28 |
29 | stitcherd.hosts = make(map[string]*Host)
30 |
31 | if stitcherd.AdminEnabled {
32 | stitcherd.adminRouter = mux.NewRouter().Host(stitcherd.AdminHostName).Subrouter()
33 | stitcherd.adminRouter.HandleFunc("/hosts/load/{filename:.*}", stitcherd.AdminHandler())
34 | }
35 |
36 | return stitcherd
37 | }
38 |
39 | func (stitcherd *Stitcherd) ServeHTTP(w http.ResponseWriter, request *http.Request) {
40 |
41 | var host *Host = nil
42 |
43 | log.Printf("Request for Host: '%s'\n", request.Host)
44 |
45 | // Find a valid host matching request.host
46 | for _, h := range stitcherd.hosts {
47 | if h.Match(request.Host) {
48 | host = h
49 | break
50 | }
51 | }
52 |
53 | if host != nil {
54 | log.Printf("Processing...\n")
55 | host.Router.ServeHTTP(w, request)
56 | log.Printf("Done\n")
57 | } else if stitcherd.adminRouter != nil {
58 | stitcherd.adminRouter.ServeHTTP(w, request)
59 | } else {
60 | log.Printf("No handler, 404\n")
61 | w.WriteHeader(http.StatusNotFound)
62 | w.Write([]byte("404 - Not found\n"))
63 | }
64 | }
65 |
66 | // RunStictcherd serves up sites specified in hosts
67 | func (stitcherd *Stitcherd) Run(hostConfigFiles []string) {
68 | log.Printf("Start - admin hostname: '%s', working directory: '%s'\n",
69 | stitcherd.AdminHostName, stitcherd.WorkingDirectory)
70 |
71 | for _, file := range hostConfigFiles {
72 | host, err := NewHostFromFile(file)
73 | if err == nil {
74 | stitcherd.SetHost(host)
75 | }
76 | }
77 |
78 | cacheService := InitCache()
79 | defer cacheService.Shutdown(context.Background())
80 |
81 | srv := &http.Server{
82 | Addr: stitcherd.ListenAddress,
83 |
84 | // Good practice to set timeouts to avoid Slowloris attacks. TODO Timeout config
85 | WriteTimeout: time.Second * 15,
86 | ReadTimeout: time.Second * 15,
87 | IdleTimeout: time.Second * 60,
88 |
89 | // Stictcherd: Stictcherds.CombinedLoggingStictcherd(os.Stderr, Stictcherds.RecoveryStictcherd()(r)), // Pass our instance of gorilla/mux in.
90 | Handler: handlers.RecoveryHandler()(stitcherd),
91 | }
92 |
93 | // Run our Stictcherd in a goroutine so that it doesn't block.
94 | go func() {
95 | if err := srv.ListenAndServe(); err != nil {
96 | log.Println(err)
97 | }
98 | }()
99 |
100 | WaitForSignal(srv)
101 | }
102 |
103 | // Add or replace a host for the Stictcherd
104 | func (stitcherd *Stitcherd) SetHost(host *Host) {
105 | stitcherd.hosts[host.Hostname] = host
106 | }
107 |
108 | // Returns an AdminHandler func
109 | func (stitcherd *Stitcherd) AdminHandler() func(http.ResponseWriter, *http.Request) {
110 | return func(w http.ResponseWriter, r *http.Request) {
111 | vars := mux.Vars(r)
112 |
113 | filename := vars["filename"]
114 |
115 | log.Printf("AdminHandler: New Host File?: '%s'\n", filename)
116 |
117 | host, err := NewHostFromFile(filename)
118 |
119 | if err == nil {
120 | stitcherd.SetHost(host)
121 | w.WriteHeader(http.StatusOK)
122 | fmt.Fprintf(w, "Filename: %v OK\n", vars["filename"])
123 | log.Printf("AdminHandler: (re)Loaded %s\n", filename)
124 | } else {
125 | log.Printf("AdminHandler: Error Loading %s, %v\n", filename, err)
126 | w.WriteHeader(http.StatusInternalServerError)
127 | fmt.Fprintf(w, "filename: %v ERROR '%v'\n", vars["filename"], err)
128 | }
129 | }
130 | }
131 |
132 |
--------------------------------------------------------------------------------
/website/content/_index.md:
--------------------------------------------------------------------------------
1 | +++
2 | title = "Stitcherd"
3 | sort_by = "weight"
4 | +++
5 |
6 |
7 |
8 | Static sites are great. They're fast to serve, there's less to to go wrong and are hostable
9 | pretty much anywhere and any how you would like to. But sometimes, just sometimes there is
10 | some page or some part of every page that has dynamic or even personalized content on it.
11 | Stitcherd is for those times.
12 |
13 | It is a web server that reads some source content (typically a local static file, but could come from a remote source), fetches one or more pieces of remote “dynamic” content and injects them into the source document using a css selector to find the insertion point. These can be nested. The remote dynamic content can be remote html or output of a Go template that can also process remote html and/or remote json data.
14 |
15 | The resulting content is then served to the client. You can have multiple routes with different sets of content to be replaced (and indeed source document). In other words it does server side includes, but with css/dom manipulation and so doesn’t require special directives in the source documents.
16 |
17 | A couple of Use cases:
18 |
19 | * Fast e-commerce site with static product pages, but dynamic pricing/availability/promotions. Plus server side carts
20 | * Commenting system for static blogs. JS Free
21 | * Micro services/frontends
22 |
23 | Yes, it’s server that has to be hosted somewhere and you need to decide if that’s okay for your use case or not, but then so are most of the alternatives, except JS of course, but same-origin, et al leads it to be harder (imo) to use than this.
24 |
25 | ## Features
26 |
27 | ### Current
28 |
29 | * Multiple vhosts
30 | * CSS Selector page assembly
31 | * Static Content catch all (but... will allow proxy fallback soon)
32 | * Simple cache controls per endpoint/route (more types coming soon - ie etag, last modified etc.)
33 | * Go templates (with HTML and JSON Data retrieval) for endpoints
34 |
35 | ### Coming Soon
36 |
37 | * Bot detection (>800 known bots)
38 | * Both General and (Bot == true) rate limiting (Site wide and per route)
39 | * (Optional) Sessions
40 | * Proxy for fallback (eg / to some CMS) and for routes (eg /blog/ proxied to Wordpress)
41 | * Static content routes
42 | * Site Authentication (OAUTH/SAML end point config, basic auth? Builtin user/password (agencies?)). Authenticate routes?
43 | * HMAC auth support for backend ends
44 | * More control over endpoint request (ACTION/Verb, protocol, headers, cookies, form vars, etc )
45 |
46 | # Building
47 |
48 | Requires Go > 1.12
49 |
50 | Clone the repo and then a simple
51 |
52 | ```
53 | go build
54 | ```
55 |
56 | Docker image at some point
57 |
58 | # Examples
59 |
60 | There is a 'demo' folder that serves as an example/testbed
61 |
62 | ```
63 | ./stitcherd --host demo/site.hcl
64 | http://localhost:3000/
65 | ```
66 |
67 | The home page at https://stitcherd.vhodges.dev/ will also serve as an example (soon).
68 |
69 | * It pulls the index page content from the Github README.md (ie this file)
70 | * It pulls a list of recent posts to the Announcment mailing list from sourcehut
71 | * Source (and generated static site - via zola - are checked into source control under website)
72 |
73 | # Prior Art and Inspiration
74 |
75 | * Edge side includes (ESI) using Varnish https://varnish-cache.org/
76 | * shtml files and virtual paths in nginx
77 | * Greasemonkey: https://addons.mozilla.org/en-CA/firefox/addon/greasemonkey/ (Client side)
78 | * Mousehole: https://github.com/whymirror/mousehole
79 | * Jigsaw: https://www.w3.org/Jigsaw/Overview.html (I vaguely recall it could do some of this kind of thing - I could be wrong though)
80 | * Netlify: most recently has the same idea with their Edge (Handlers) https://www.netlify.com/products/edge/edge-handlers/
81 | * Soupault: Is the main inspiration for how stitcherd works. https://soupault.neocities.org/
82 |
83 | # License
84 |
85 | Apache 2.0
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/stitcher/fragment.go:
--------------------------------------------------------------------------------
1 | package stitcher
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 |
7 | "log"
8 | "strings"
9 | "time"
10 |
11 | "github.com/PuerkitoBio/goquery"
12 | "github.com/mailgun/groupcache/v2"
13 | "github.com/valyala/fasttemplate"
14 | )
15 |
16 | // Fragments are renderable pieces of markup with one at the top level
17 | // representing the page being sent to the browser.
18 | type Fragment struct {
19 |
20 | Fetcher FragmentFetcher
21 |
22 | Fragments []Fragment // Can be nested 0 or more
23 | DocumentTransforms []DocumentTransform
24 | TransformSelfTransforms []DocumentTransform
25 |
26 | CacheKey string `json:"cache,optional"`
27 | CacheTTL string `json:"ttl,optional"`
28 | }
29 |
30 | // Caching returns true if we are to use the endpoint
31 | func (fragment *Fragment) Cachable() bool {
32 | return fragment.CacheKey != ""
33 | }
34 |
35 | // InterpolatedCacheKey returns the interpolated endpoint key
36 | func (fragment *Fragment) InterpolatedCacheKey(contextData map[string]interface{}) string {
37 | t := fasttemplate.New(fragment.CacheKey, "{{", "}}")
38 | return t.ExecuteString(contextData)
39 |
40 | }
41 |
42 | func (fragment *Fragment) Render(site *Host, contextdata map[string]interface{}) string {
43 |
44 | var this_content string
45 | var this_doc *goquery.Document
46 | var err error
47 |
48 | this_content, err= fragment.Fetcher.Fetch(contextdata)
49 |
50 | if err != nil {
51 | return "" // TODO Handle error better.
52 | }
53 |
54 | this_doc, err = goquery.NewDocumentFromReader(strings.NewReader(this_content))
55 | if err != nil {
56 | return "" // TODO Handle error better.
57 | }
58 |
59 |
60 | for _, frag := range fragment.Fragments {
61 | var child_content string
62 | var child_doc *goquery.Document
63 |
64 | if frag.Cachable() {
65 | child_content, err = frag.FromCache(site, contextdata)
66 | if err != nil {
67 | continue; // Skip on error... TODO Log the error
68 | }
69 | } else {
70 | child_content = frag.Render(site, contextdata)
71 | }
72 |
73 | child_doc, err = goquery.NewDocumentFromReader(strings.NewReader(child_content))
74 | if err != nil {
75 | continue; // Skip on error, TODO log the error
76 | }
77 |
78 | for _, transformation := range frag.DocumentTransforms {
79 | transformation.Transform(this_doc, child_doc)
80 | }
81 | }
82 |
83 | for _, transformation := range fragment.TransformSelfTransforms {
84 | transformation.Transform(this_doc, nil) // Only makes sense for add_class
85 | }
86 |
87 |
88 | html, err2 := this_doc.Html()
89 |
90 | if err2 != nil {
91 | return "" // TODO Handle error better
92 | }
93 |
94 | return html
95 | }
96 |
97 | func (fragment *Fragment) GetData(contextdata map[string]interface{}) map[string]interface{} {
98 | var jsonData map[string]interface{}
99 |
100 | fetched_fragment, err := fragment.Fetcher.Fetch(contextdata)
101 |
102 | if err != nil {
103 | return nil // TODO Handle error better.
104 | }
105 |
106 | json.Unmarshal([]byte(fetched_fragment), &jsonData)
107 |
108 | return jsonData
109 | }
110 |
111 | func (fragment *Fragment) FromCache(site *Host, contextdata map[string]interface{}) (string, error) {
112 |
113 | var content string
114 |
115 | var contextvalue = FragmentRenderContext{Site: site, Fragment: fragment, ContextData: contextdata}
116 | ctx, cancel := context.WithTimeout(context.WithValue(context.Background(), requestContextKey("request"), contextvalue),
117 | time.Millisecond*2000) // TODO Make this configurable
118 |
119 | defer cancel()
120 |
121 | if err := site.Cache.Get(ctx, fragment.InterpolatedCacheKey(contextdata), groupcache.StringSink(&content)); err != nil {
122 | log.Printf("Error getting from cache: %v\n", err)
123 | return "", err
124 | }
125 |
126 | return content, nil
127 | }
128 |
129 | func FillFragmentCache(ctx context.Context, id string, dest groupcache.Sink) error {
130 | var err error
131 |
132 | v := ctx.Value(requestContextKey("request"))
133 |
134 | r, ok := v.(FragmentRenderContext)
135 |
136 | if ok {
137 |
138 | content := r.Fragment.Render(r.Site, r.ContextData)
139 |
140 | if err != nil {
141 | log.Printf("Error: %v - '%+v'\n", err, r.Fragment)
142 | return err
143 | }
144 |
145 | ttl, err := time.ParseDuration(r.Fragment.CacheTTL)
146 | if err != nil {
147 | log.Printf("Error parsing TTL duration: '%v' for key '%s' defaulting to a TTL of one minute\n", err, id)
148 | ttl = time.Minute * 1
149 | }
150 |
151 | if err := dest.SetString(content, time.Now().Add(ttl)); err != nil {
152 | log.Println("SetString", err)
153 | return err
154 | }
155 | }
156 |
157 | return nil
158 | }
159 |
--------------------------------------------------------------------------------
/stitcher/route.go:
--------------------------------------------------------------------------------
1 | package stitcher
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "os"
7 | "net/http"
8 | "strings"
9 | "time"
10 |
11 | "github.com/gorilla/mux"
12 | "github.com/x-way/crawlerdetect"
13 | "golang.org/x/time/rate"
14 | )
15 |
16 | // Route returns content for a given path
17 | type Route struct {
18 | Path string // Respond to requests at this path
19 |
20 | RouteDataFragment *Fragment
21 |
22 | RespondWith string
23 |
24 | // RespondWith "fragemented_page' renders this
25 | Page *FragmentedPage
26 |
27 | // ResponeWith 'static_files' servers this local directory to server
28 | StaticPath string
29 |
30 | // TODO
31 | // RedirectTo string // TODO interpolate
32 | // RedirectStatus // Eg 302 (Found) or 302 (Moved permanently)
33 |
34 | // ResponseString string // TODO Interpolate
35 | // ResponseCode string
36 |
37 | // ProxyHost string
38 | // ProxyString??? string
39 |
40 | // Rate limiter
41 | MaxRate float64
42 | AllowBurst int
43 | BotMaxRate float64
44 | BotAllowBurst int
45 |
46 | normalLimiter *rate.Limiter // Really only makes sense/applies to Route
47 | botLimiter *rate.Limiter
48 | }
49 |
50 | // Init creates runtime objects for the end point
51 | func (route *Route) Init(host *Host) {
52 |
53 | if route.MaxRate > 0 && route.AllowBurst > 0 {
54 | route.normalLimiter = rate.NewLimiter(rate.Limit(route.MaxRate),
55 | route.AllowBurst)
56 |
57 | log.Printf("Added rate limiter for '%s' Rate: %f Burst: %d\n",
58 | route.Path, rate.Limit(route.MaxRate), route.AllowBurst)
59 | }
60 |
61 | if route.BotMaxRate > 0 && route.BotAllowBurst > 0 {
62 | route.botLimiter = rate.NewLimiter(rate.Limit(route.BotMaxRate),
63 | route.BotAllowBurst)
64 |
65 | log.Printf("Added bot limiter for '%s' Rate: %f Burst: %d\n",
66 | route.Path, rate.Limit(route.BotMaxRate), route.BotAllowBurst)
67 | }
68 |
69 | // Add the handler for the route
70 | switch route.RespondWith {
71 | case "fragmented_page":
72 | host.Router.HandleFunc(route.Path, FragmentedPageHandler(host, *route))
73 | case "static_content":
74 | host.Router.PathPrefix(route.Path).Handler(http.StripPrefix(route.Path, http.FileServer(http.Dir(route.StaticPath))))
75 | case "redirect":
76 | case "proxy":
77 | }
78 |
79 | }
80 |
81 | // Throttling returns true if the current request is to be rate limited.
82 | func (route *Route) Throttling(r *http.Request) bool {
83 |
84 | var limiter = route.normalLimiter
85 |
86 | if route.botLimiter != nil && crawlerdetect.IsCrawler(r.UserAgent()) {
87 | log.Println("Crawler detected")
88 | limiter = route.botLimiter
89 | }
90 |
91 | if limiter != nil {
92 | return !limiter.Allow()
93 | }
94 |
95 | return false
96 | }
97 |
98 | func (route *Route) nextRequestID() string {
99 | return fmt.Sprintf("%d", time.Now().UnixNano())
100 | }
101 |
102 | // FragmentedPageHandler Renders the route.Page
103 | func (route *Route) FragmentedPageHandler(site *Host, w http.ResponseWriter, r *http.Request) {
104 | var err error
105 |
106 | start := time.Now()
107 |
108 | var fetchContext map[string]interface{} = make(map[string]interface{})
109 |
110 | if route.Throttling(r) {
111 | http.Error(w, http.StatusText(http.StatusTooManyRequests), http.StatusTooManyRequests)
112 | return
113 | }
114 |
115 | // TODO Better request tracing... (context.Context too?)
116 | fetchContext["_requestId"] = route.nextRequestID()
117 |
118 | // Any params passed in, make available to the request.
119 | for key, element := range mux.Vars(r) {
120 | fetchContext[key] = element
121 | }
122 |
123 | // Same for any env vars
124 | for _, e := range os.Environ() {
125 | pair := strings.SplitN(e, "=", 2)
126 | fetchContext[pair[0]] = pair[1]
127 | }
128 |
129 | // TODO these probably need to be escaped
130 | fetchContext["requestPath"] = r.URL.Path
131 | fetchContext["queryString"] = r.URL.RawQuery
132 |
133 | fetchContext["host"] = r.Host
134 |
135 | for key, element := range r.URL.Query() {
136 | if len(element) == 0 {
137 | fetchContext[key] = ""
138 | } else {
139 | fetchContext[key] = element[0]
140 | }
141 | }
142 |
143 | if route.RouteDataFragment != nil {
144 | log.Printf("RouteDataFragment was not nil\n")
145 |
146 | // Fetch a data blob Fragement to use to augment fetchContext
147 | routeData := route.RouteDataFragment.GetData(fetchContext)
148 |
149 | // Add the key,value pairs to fetchContext
150 | for k,v := range routeData {
151 | fetchContext[k] = v
152 | }
153 | }
154 |
155 |
156 | // TODO Add Headers? Cookies? to fetchContext
157 |
158 | content := route.Page.Render(site, fetchContext)
159 |
160 | if err != nil {
161 | log.Printf("Error from endpoint '%s': %v", route.Path, err)
162 | fmt.Fprintln(w, "")
163 | } else {
164 | fmt.Fprintln(w, content)
165 | }
166 |
167 | elapsed := time.Since(start)
168 | log.Println(fetchContext["_requestId"], r.Host, r.Method, r.URL.Path, r.Proto, elapsed)
169 | }
170 |
171 | // FragmentedPageHandler uses the Source to render content
172 | func FragmentedPageHandler(site *Host, route Route) func(http.ResponseWriter, *http.Request) {
173 | return func(w http.ResponseWriter, r *http.Request) {
174 | route.FragmentedPageHandler(site, w, r)
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/demo/site.json:
--------------------------------------------------------------------------------
1 | {
2 | "Hostname": ".*",
3 | "Routes": [
4 | {
5 | "Path": "/users",
6 | "RespondWith": "fragmented_page",
7 |
8 | "MaxRate": 10.0,
9 | "Burst": 50,
10 | "BotMaxRate": 2.0,
11 | "BotBurst": 5,
12 |
13 | "RouteDataFragment": {
14 | "CacheKey": "/users-data-fragement/{{host}}",
15 | "CacheTTL": "30s",
16 |
17 | "Fetcher": {
18 | "Type": "file",
19 | "Source": "demo/data-fragments/{{host}}.json"
20 | },
21 | "Fragments": []
22 | },
23 | "Page": {
24 | "Fragment": {
25 | "CacheKey": "/users",
26 | "CacheTTL": "30s",
27 |
28 | "Fetcher": {
29 | "Type": "file",
30 | "Source": "demo/html/index.html"
31 | },
32 | "Fragments": [
33 | {
34 | "Fetcher": {
35 | "Type": "uri",
36 | "Source": "https://jsonplaceholder.typicode.com/users",
37 | "IsJson": true,
38 | "Template": "demo/template/users.tmpl"
39 | },
40 | "DocumentTransforms": [
41 | {
42 | "Type": "replace",
43 | "ParentSelector": "#replaceme"
44 | }
45 | ]
46 | }
47 | ]
48 | }
49 | }
50 | },
51 | {
52 | "Path": "/users/{userid}",
53 | "RespondWith": "fragmented_page",
54 |
55 | "Page": {
56 | "Fragment": {
57 | "CacheKey": "/users/{{userid}}",
58 | "CacheTTL": "10s",
59 |
60 | "Fetcher": {
61 | "Type": "file",
62 | "Source": "demo/html/index.html"
63 | },
64 | "Fragments": [
65 | {
66 | "CacheKey": "JSON:/users/{{userid}}",
67 | "CacheTTL": "5m",
68 | "Fetcher": {
69 | "Type": "uri",
70 | "Source": "https://jsonplaceholder.typicode.com/users/{{userid}}",
71 | "IsJson": true,
72 | "Template": "demo/template/user.tmpl"
73 | },
74 | "DocumentTransforms": [
75 | {
76 | "Type": "replace",
77 | "ParentSelector": "#replaceme"
78 | }
79 | ]
80 | },
81 | {
82 | "CacheKey": "JSON:/todos/{{userid}}",
83 | "CacheTTL": "1m",
84 | "Fetcher": {
85 | "Type": "uri",
86 | "Source": "https://jsonplaceholder.typicode.com/todos?userId={{userid}}",
87 | "IsJson": true,
88 | "Template": "demo/template/todos.tmpl"
89 | },
90 | "DocumentTransforms": [
91 | {
92 | "Type": "replace",
93 | "ParentSelector": "#todo_list"
94 | }
95 | ]
96 | }
97 | ]
98 | }
99 | }
100 | },
101 | {
102 | "Path": "/{folderPath:.*\\/$}",
103 | "RespondWith": "fragmented_page",
104 |
105 | "Page": {
106 | "Fragment": {
107 | "Fetcher":{
108 | "Type": "file",
109 | "Source": "demo/html/{{folderPath}}index.html"
110 | },
111 | "Fragments":[
112 | {
113 | "Fetcher":{
114 | "Source": "This is the other replacement string (one)
"
115 | },
116 | "DocumentTransforms": [
117 | {
118 | "Type": "replace",
119 | "ParentSelector": "#replaceme"
120 | }
121 | ]
122 | }
123 | ]
124 | }
125 | }
126 | },
127 | {
128 | "Path": "/{rest:.*html$}",
129 | "RespondWith": "fragmented_page",
130 |
131 | "Page": {
132 | "Fragment": {
133 | "Fetcher":{
134 | "Type": "file",
135 | "Source": "demo/html/{{rest}}"
136 | },
137 | "Fragments":[
138 | {
139 | "Fetcher":{
140 | "Source": "This is another replacement string (two)
"
141 | },
142 | "DocumentTransforms": [
143 | {
144 | "Type": "replace",
145 | "ParentSelector": "#replaceme"
146 | }
147 | ]
148 | }
149 | ]
150 | }
151 | }
152 | },
153 | {
154 | "Path": "/",
155 | "RespondWith": "static_content",
156 | "StaticPath": "demo/html"
157 | }
158 | ]
159 | }
160 |
--------------------------------------------------------------------------------
/website/public/normalize.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
2 |
3 | /* Document
4 | ========================================================================== */
5 |
6 | /**
7 | * 1. Correct the line height in all browsers.
8 | * 2. Prevent adjustments of font size after orientation changes in iOS.
9 | */
10 |
11 | html {
12 | line-height: 1.15; /* 1 */
13 | -webkit-text-size-adjust: 100%; /* 2 */
14 | }
15 |
16 | /* Sections
17 | ========================================================================== */
18 |
19 | /**
20 | * Remove the margin in all browsers.
21 | */
22 |
23 | body {
24 | margin: 0;
25 | }
26 |
27 | /**
28 | * Render the `main` element consistently in IE.
29 | */
30 |
31 | main {
32 | display: block;
33 | }
34 |
35 | /**
36 | * Correct the font size and margin on `h1` elements within `section` and
37 | * `article` contexts in Chrome, Firefox, and Safari.
38 | */
39 |
40 | h1 {
41 | font-size: 2em;
42 | margin: 0.67em 0;
43 | }
44 |
45 | /* Grouping content
46 | ========================================================================== */
47 |
48 | /**
49 | * 1. Add the correct box sizing in Firefox.
50 | * 2. Show the overflow in Edge and IE.
51 | */
52 |
53 | hr {
54 | box-sizing: content-box; /* 1 */
55 | height: 0; /* 1 */
56 | overflow: visible; /* 2 */
57 | }
58 |
59 | /**
60 | * 1. Correct the inheritance and scaling of font size in all browsers.
61 | * 2. Correct the odd `em` font sizing in all browsers.
62 | */
63 |
64 | pre {
65 | font-family: monospace, monospace; /* 1 */
66 | font-size: 1em; /* 2 */
67 | }
68 |
69 | /* Text-level semantics
70 | ========================================================================== */
71 |
72 | /**
73 | * Remove the gray background on active links in IE 10.
74 | */
75 |
76 | a {
77 | background-color: transparent;
78 | }
79 |
80 | /**
81 | * 1. Remove the bottom border in Chrome 57-
82 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
83 | */
84 |
85 | abbr[title] {
86 | border-bottom: none; /* 1 */
87 | text-decoration: underline; /* 2 */
88 | text-decoration: underline dotted; /* 2 */
89 | }
90 |
91 | /**
92 | * Add the correct font weight in Chrome, Edge, and Safari.
93 | */
94 |
95 | b,
96 | strong {
97 | font-weight: bolder;
98 | }
99 |
100 | /**
101 | * 1. Correct the inheritance and scaling of font size in all browsers.
102 | * 2. Correct the odd `em` font sizing in all browsers.
103 | */
104 |
105 | code,
106 | kbd,
107 | samp {
108 | font-family: monospace, monospace; /* 1 */
109 | font-size: 1em; /* 2 */
110 | }
111 |
112 | /**
113 | * Add the correct font size in all browsers.
114 | */
115 |
116 | small {
117 | font-size: 80%;
118 | }
119 |
120 | /**
121 | * Prevent `sub` and `sup` elements from affecting the line height in
122 | * all browsers.
123 | */
124 |
125 | sub,
126 | sup {
127 | font-size: 75%;
128 | line-height: 0;
129 | position: relative;
130 | vertical-align: baseline;
131 | }
132 |
133 | sub {
134 | bottom: -0.25em;
135 | }
136 |
137 | sup {
138 | top: -0.5em;
139 | }
140 |
141 | /* Embedded content
142 | ========================================================================== */
143 |
144 | /**
145 | * Remove the border on images inside links in IE 10.
146 | */
147 |
148 | img {
149 | border-style: none;
150 | }
151 |
152 | /* Forms
153 | ========================================================================== */
154 |
155 | /**
156 | * 1. Change the font styles in all browsers.
157 | * 2. Remove the margin in Firefox and Safari.
158 | */
159 |
160 | button,
161 | input,
162 | optgroup,
163 | select,
164 | textarea {
165 | font-family: inherit; /* 1 */
166 | font-size: 100%; /* 1 */
167 | line-height: 1.15; /* 1 */
168 | margin: 0; /* 2 */
169 | }
170 |
171 | /**
172 | * Show the overflow in IE.
173 | * 1. Show the overflow in Edge.
174 | */
175 |
176 | button,
177 | input { /* 1 */
178 | overflow: visible;
179 | }
180 |
181 | /**
182 | * Remove the inheritance of text transform in Edge, Firefox, and IE.
183 | * 1. Remove the inheritance of text transform in Firefox.
184 | */
185 |
186 | button,
187 | select { /* 1 */
188 | text-transform: none;
189 | }
190 |
191 | /**
192 | * Correct the inability to style clickable types in iOS and Safari.
193 | */
194 |
195 | button,
196 | [type="button"],
197 | [type="reset"],
198 | [type="submit"] {
199 | -webkit-appearance: button;
200 | }
201 |
202 | /**
203 | * Remove the inner border and padding in Firefox.
204 | */
205 |
206 | button::-moz-focus-inner,
207 | [type="button"]::-moz-focus-inner,
208 | [type="reset"]::-moz-focus-inner,
209 | [type="submit"]::-moz-focus-inner {
210 | border-style: none;
211 | padding: 0;
212 | }
213 |
214 | /**
215 | * Restore the focus styles unset by the previous rule.
216 | */
217 |
218 | button:-moz-focusring,
219 | [type="button"]:-moz-focusring,
220 | [type="reset"]:-moz-focusring,
221 | [type="submit"]:-moz-focusring {
222 | outline: 1px dotted ButtonText;
223 | }
224 |
225 | /**
226 | * Correct the padding in Firefox.
227 | */
228 |
229 | fieldset {
230 | padding: 0.35em 0.75em 0.625em;
231 | }
232 |
233 | /**
234 | * 1. Correct the text wrapping in Edge and IE.
235 | * 2. Correct the color inheritance from `fieldset` elements in IE.
236 | * 3. Remove the padding so developers are not caught out when they zero out
237 | * `fieldset` elements in all browsers.
238 | */
239 |
240 | legend {
241 | box-sizing: border-box; /* 1 */
242 | color: inherit; /* 2 */
243 | display: table; /* 1 */
244 | max-width: 100%; /* 1 */
245 | padding: 0; /* 3 */
246 | white-space: normal; /* 1 */
247 | }
248 |
249 | /**
250 | * Add the correct vertical alignment in Chrome, Firefox, and Opera.
251 | */
252 |
253 | progress {
254 | vertical-align: baseline;
255 | }
256 |
257 | /**
258 | * Remove the default vertical scrollbar in IE 10+.
259 | */
260 |
261 | textarea {
262 | overflow: auto;
263 | }
264 |
265 | /**
266 | * 1. Add the correct box sizing in IE 10.
267 | * 2. Remove the padding in IE 10.
268 | */
269 |
270 | [type="checkbox"],
271 | [type="radio"] {
272 | box-sizing: border-box; /* 1 */
273 | padding: 0; /* 2 */
274 | }
275 |
276 | /**
277 | * Correct the cursor style of increment and decrement buttons in Chrome.
278 | */
279 |
280 | [type="number"]::-webkit-inner-spin-button,
281 | [type="number"]::-webkit-outer-spin-button {
282 | height: auto;
283 | }
284 |
285 | /**
286 | * 1. Correct the odd appearance in Chrome and Safari.
287 | * 2. Correct the outline style in Safari.
288 | */
289 |
290 | [type="search"] {
291 | -webkit-appearance: textfield; /* 1 */
292 | outline-offset: -2px; /* 2 */
293 | }
294 |
295 | /**
296 | * Remove the inner padding in Chrome and Safari on macOS.
297 | */
298 |
299 | [type="search"]::-webkit-search-decoration {
300 | -webkit-appearance: none;
301 | }
302 |
303 | /**
304 | * 1. Correct the inability to style clickable types in iOS and Safari.
305 | * 2. Change font properties to `inherit` in Safari.
306 | */
307 |
308 | ::-webkit-file-upload-button {
309 | -webkit-appearance: button; /* 1 */
310 | font: inherit; /* 2 */
311 | }
312 |
313 | /* Interactive
314 | ========================================================================== */
315 |
316 | /*
317 | * Add the correct display in Edge, IE 10+, and Firefox.
318 | */
319 |
320 | details {
321 | display: block;
322 | }
323 |
324 | /*
325 | * Add the correct display in all browsers.
326 | */
327 |
328 | summary {
329 | display: list-item;
330 | }
331 |
332 | /* Misc
333 | ========================================================================== */
334 |
335 | /**
336 | * Add the correct display in IE 10+.
337 | */
338 |
339 | template {
340 | display: none;
341 | }
342 |
343 | /**
344 | * Add the correct display in IE 10.
345 | */
346 |
347 | [hidden] {
348 | display: none;
349 | }
350 |
--------------------------------------------------------------------------------
/website/themes/juice/static/normalize.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
2 |
3 | /* Document
4 | ========================================================================== */
5 |
6 | /**
7 | * 1. Correct the line height in all browsers.
8 | * 2. Prevent adjustments of font size after orientation changes in iOS.
9 | */
10 |
11 | html {
12 | line-height: 1.15; /* 1 */
13 | -webkit-text-size-adjust: 100%; /* 2 */
14 | }
15 |
16 | /* Sections
17 | ========================================================================== */
18 |
19 | /**
20 | * Remove the margin in all browsers.
21 | */
22 |
23 | body {
24 | margin: 0;
25 | }
26 |
27 | /**
28 | * Render the `main` element consistently in IE.
29 | */
30 |
31 | main {
32 | display: block;
33 | }
34 |
35 | /**
36 | * Correct the font size and margin on `h1` elements within `section` and
37 | * `article` contexts in Chrome, Firefox, and Safari.
38 | */
39 |
40 | h1 {
41 | font-size: 2em;
42 | margin: 0.67em 0;
43 | }
44 |
45 | /* Grouping content
46 | ========================================================================== */
47 |
48 | /**
49 | * 1. Add the correct box sizing in Firefox.
50 | * 2. Show the overflow in Edge and IE.
51 | */
52 |
53 | hr {
54 | box-sizing: content-box; /* 1 */
55 | height: 0; /* 1 */
56 | overflow: visible; /* 2 */
57 | }
58 |
59 | /**
60 | * 1. Correct the inheritance and scaling of font size in all browsers.
61 | * 2. Correct the odd `em` font sizing in all browsers.
62 | */
63 |
64 | pre {
65 | font-family: monospace, monospace; /* 1 */
66 | font-size: 1em; /* 2 */
67 | }
68 |
69 | /* Text-level semantics
70 | ========================================================================== */
71 |
72 | /**
73 | * Remove the gray background on active links in IE 10.
74 | */
75 |
76 | a {
77 | background-color: transparent;
78 | }
79 |
80 | /**
81 | * 1. Remove the bottom border in Chrome 57-
82 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
83 | */
84 |
85 | abbr[title] {
86 | border-bottom: none; /* 1 */
87 | text-decoration: underline; /* 2 */
88 | text-decoration: underline dotted; /* 2 */
89 | }
90 |
91 | /**
92 | * Add the correct font weight in Chrome, Edge, and Safari.
93 | */
94 |
95 | b,
96 | strong {
97 | font-weight: bolder;
98 | }
99 |
100 | /**
101 | * 1. Correct the inheritance and scaling of font size in all browsers.
102 | * 2. Correct the odd `em` font sizing in all browsers.
103 | */
104 |
105 | code,
106 | kbd,
107 | samp {
108 | font-family: monospace, monospace; /* 1 */
109 | font-size: 1em; /* 2 */
110 | }
111 |
112 | /**
113 | * Add the correct font size in all browsers.
114 | */
115 |
116 | small {
117 | font-size: 80%;
118 | }
119 |
120 | /**
121 | * Prevent `sub` and `sup` elements from affecting the line height in
122 | * all browsers.
123 | */
124 |
125 | sub,
126 | sup {
127 | font-size: 75%;
128 | line-height: 0;
129 | position: relative;
130 | vertical-align: baseline;
131 | }
132 |
133 | sub {
134 | bottom: -0.25em;
135 | }
136 |
137 | sup {
138 | top: -0.5em;
139 | }
140 |
141 | /* Embedded content
142 | ========================================================================== */
143 |
144 | /**
145 | * Remove the border on images inside links in IE 10.
146 | */
147 |
148 | img {
149 | border-style: none;
150 | }
151 |
152 | /* Forms
153 | ========================================================================== */
154 |
155 | /**
156 | * 1. Change the font styles in all browsers.
157 | * 2. Remove the margin in Firefox and Safari.
158 | */
159 |
160 | button,
161 | input,
162 | optgroup,
163 | select,
164 | textarea {
165 | font-family: inherit; /* 1 */
166 | font-size: 100%; /* 1 */
167 | line-height: 1.15; /* 1 */
168 | margin: 0; /* 2 */
169 | }
170 |
171 | /**
172 | * Show the overflow in IE.
173 | * 1. Show the overflow in Edge.
174 | */
175 |
176 | button,
177 | input { /* 1 */
178 | overflow: visible;
179 | }
180 |
181 | /**
182 | * Remove the inheritance of text transform in Edge, Firefox, and IE.
183 | * 1. Remove the inheritance of text transform in Firefox.
184 | */
185 |
186 | button,
187 | select { /* 1 */
188 | text-transform: none;
189 | }
190 |
191 | /**
192 | * Correct the inability to style clickable types in iOS and Safari.
193 | */
194 |
195 | button,
196 | [type="button"],
197 | [type="reset"],
198 | [type="submit"] {
199 | -webkit-appearance: button;
200 | }
201 |
202 | /**
203 | * Remove the inner border and padding in Firefox.
204 | */
205 |
206 | button::-moz-focus-inner,
207 | [type="button"]::-moz-focus-inner,
208 | [type="reset"]::-moz-focus-inner,
209 | [type="submit"]::-moz-focus-inner {
210 | border-style: none;
211 | padding: 0;
212 | }
213 |
214 | /**
215 | * Restore the focus styles unset by the previous rule.
216 | */
217 |
218 | button:-moz-focusring,
219 | [type="button"]:-moz-focusring,
220 | [type="reset"]:-moz-focusring,
221 | [type="submit"]:-moz-focusring {
222 | outline: 1px dotted ButtonText;
223 | }
224 |
225 | /**
226 | * Correct the padding in Firefox.
227 | */
228 |
229 | fieldset {
230 | padding: 0.35em 0.75em 0.625em;
231 | }
232 |
233 | /**
234 | * 1. Correct the text wrapping in Edge and IE.
235 | * 2. Correct the color inheritance from `fieldset` elements in IE.
236 | * 3. Remove the padding so developers are not caught out when they zero out
237 | * `fieldset` elements in all browsers.
238 | */
239 |
240 | legend {
241 | box-sizing: border-box; /* 1 */
242 | color: inherit; /* 2 */
243 | display: table; /* 1 */
244 | max-width: 100%; /* 1 */
245 | padding: 0; /* 3 */
246 | white-space: normal; /* 1 */
247 | }
248 |
249 | /**
250 | * Add the correct vertical alignment in Chrome, Firefox, and Opera.
251 | */
252 |
253 | progress {
254 | vertical-align: baseline;
255 | }
256 |
257 | /**
258 | * Remove the default vertical scrollbar in IE 10+.
259 | */
260 |
261 | textarea {
262 | overflow: auto;
263 | }
264 |
265 | /**
266 | * 1. Add the correct box sizing in IE 10.
267 | * 2. Remove the padding in IE 10.
268 | */
269 |
270 | [type="checkbox"],
271 | [type="radio"] {
272 | box-sizing: border-box; /* 1 */
273 | padding: 0; /* 2 */
274 | }
275 |
276 | /**
277 | * Correct the cursor style of increment and decrement buttons in Chrome.
278 | */
279 |
280 | [type="number"]::-webkit-inner-spin-button,
281 | [type="number"]::-webkit-outer-spin-button {
282 | height: auto;
283 | }
284 |
285 | /**
286 | * 1. Correct the odd appearance in Chrome and Safari.
287 | * 2. Correct the outline style in Safari.
288 | */
289 |
290 | [type="search"] {
291 | -webkit-appearance: textfield; /* 1 */
292 | outline-offset: -2px; /* 2 */
293 | }
294 |
295 | /**
296 | * Remove the inner padding in Chrome and Safari on macOS.
297 | */
298 |
299 | [type="search"]::-webkit-search-decoration {
300 | -webkit-appearance: none;
301 | }
302 |
303 | /**
304 | * 1. Correct the inability to style clickable types in iOS and Safari.
305 | * 2. Change font properties to `inherit` in Safari.
306 | */
307 |
308 | ::-webkit-file-upload-button {
309 | -webkit-appearance: button; /* 1 */
310 | font: inherit; /* 2 */
311 | }
312 |
313 | /* Interactive
314 | ========================================================================== */
315 |
316 | /*
317 | * Add the correct display in Edge, IE 10+, and Firefox.
318 | */
319 |
320 | details {
321 | display: block;
322 | }
323 |
324 | /*
325 | * Add the correct display in all browsers.
326 | */
327 |
328 | summary {
329 | display: list-item;
330 | }
331 |
332 | /* Misc
333 | ========================================================================== */
334 |
335 | /**
336 | * Add the correct display in IE 10+.
337 | */
338 |
339 | template {
340 | display: none;
341 | }
342 |
343 | /**
344 | * Add the correct display in IE 10.
345 | */
346 |
347 | [hidden] {
348 | display: none;
349 | }
350 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/website/themes/juice/content/juice.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/gomod2nix.toml:
--------------------------------------------------------------------------------
1 | schema = 3
2 |
3 | [mod]
4 | [mod."cloud.google.com/go"]
5 | version = "v0.46.3"
6 | hash = "sha256-EBHCL2O/O+alb/ot4x5jIngTH+9DqXTVWlsNpyDulbY="
7 | [mod."cloud.google.com/go/bigquery"]
8 | version = "v1.0.1"
9 | hash = "sha256-MgQGLYK7oBLu1AV++KBsg1iasPKqCJsBvHZGAcySyvc="
10 | [mod."cloud.google.com/go/datastore"]
11 | version = "v1.0.0"
12 | hash = "sha256-zsrEec4SFRHfYXd/1RyNg5FVjBlLNh8Jc5IH4hZejL8="
13 | [mod."cloud.google.com/go/firestore"]
14 | version = "v1.1.0"
15 | hash = "sha256-sxPVdUINjmpjTGXfKqkc5u7MxQe4As35kT/TYeizvII="
16 | [mod."cloud.google.com/go/pubsub"]
17 | version = "v1.0.1"
18 | hash = "sha256-YT2rFA1wMVnBTwuy057CN5DLKlrW6SfR9Fi273pqQtQ="
19 | [mod."cloud.google.com/go/storage"]
20 | version = "v1.0.0"
21 | hash = "sha256-SFGPH2VU53v0xdp8dBs1WgthpcWYY2AcS14Ume2HWco="
22 | [mod."dmitri.shuralyov.com/gpu/mtl"]
23 | version = "v0.0.0-20190408044501-666a987793e9"
24 | hash = "sha256-H+xcbVdCNDahWZPgwk4k+XxnM73g0hwaFY7x+OAATcc="
25 | [mod."github.com/BurntSushi/toml"]
26 | version = "v0.3.1"
27 | hash = "sha256-Rqak1dE/Aj/+Kx1/pl3Hifgt+Q3OzuZ5fJR+/x3nTbo="
28 | [mod."github.com/BurntSushi/xgb"]
29 | version = "v0.0.0-20160522181843-27f122750802"
30 | hash = "sha256-ck+gNOSXNYy/ji6mpSX3OTHgCDm2nww+3ZKu4lAXl6I="
31 | [mod."github.com/Masterminds/goutils"]
32 | version = "v1.1.0"
33 | hash = "sha256-ODpWU/QbHKyPm3waLNtSicb41qplz1Wmx2Yk+Q7pF6A="
34 | [mod."github.com/Masterminds/semver"]
35 | version = "v1.5.0"
36 | hash = "sha256-3fEInOXFdzCiGdDZ1s9otEes7VXiL8Q1RVB3zXRPJsQ="
37 | [mod."github.com/Masterminds/sprig"]
38 | version = "v2.22.0+incompatible"
39 | hash = "sha256-7Q2DnQyL1Mu8S9PD86u62zZMrAkseiiDM3fATyTkvyU="
40 | [mod."github.com/OneOfOne/xxhash"]
41 | version = "v1.2.2"
42 | hash = "sha256-JvJnJFr9NFh5u+b7BgNEIwZR6scXW8l8RkT1DXmGTtY="
43 | [mod."github.com/PuerkitoBio/goquery"]
44 | version = "v1.5.1"
45 | hash = "sha256-GqxIdhuN1L3enT8pNlWm+rmkdN5uMfF8TfpxhAHhEDQ="
46 | [mod."github.com/alecthomas/template"]
47 | version = "v0.0.0-20160405071501-a0175ee3bccc"
48 | hash = "sha256-sjuHfqM34m9uL7muq2JwZsj2HPRNuYSdZ2FuI+DeT2I="
49 | [mod."github.com/alecthomas/units"]
50 | version = "v0.0.0-20151022065526-2efee857e7cf"
51 | hash = "sha256-dJWY/6oZSgkTQIdm+P5/lR/gWHZLXZZozUunhUNaxcg="
52 | [mod."github.com/andybalholm/cascadia"]
53 | version = "v1.1.0"
54 | hash = "sha256-mL40/Q9iXBzzMHwMomqTOpp9rnI/MRsufb5cIU1MMPg="
55 | [mod."github.com/armon/circbuf"]
56 | version = "v0.0.0-20150827004946-bbbad097214e"
57 | hash = "sha256-klQjllsJZqZ2KPNx1mZT9XP+UAJkuBhmTnZdNlAflEM="
58 | [mod."github.com/armon/go-metrics"]
59 | version = "v0.0.0-20180917152333-f0300d1749da"
60 | hash = "sha256-+zqX1hlJgc+IrXRzBQDMhR8GYQdc0Oj6PiIDfctgh44="
61 | [mod."github.com/armon/go-radix"]
62 | version = "v0.0.0-20180808171621-7fddfc383310"
63 | hash = "sha256-ZHU4pyBqHHRuQJuYr2K+LqeAnLX9peX07cmSYK+GDHk="
64 | [mod."github.com/beorn7/perks"]
65 | version = "v1.0.0"
66 | hash = "sha256-Pf2w+tFkeW9J18AR6lZWdu7n8CxfRTW8iL2UZ1z4NsQ="
67 | [mod."github.com/bgentry/speakeasy"]
68 | version = "v0.1.0"
69 | hash = "sha256-Gt1vj6CFovLnO6wX5u2O4UfecY9V2J9WGw1ez4HMrgk="
70 | [mod."github.com/bketelsen/crypt"]
71 | version = "v0.0.3-0.20200106085610-5cbc8cc4026c"
72 | hash = "sha256-d44/dm431lvmGZQBJjDOWfIa/GX/jNE9y4t49WO1a/U="
73 | [mod."github.com/bradfitz/iter"]
74 | version = "v0.0.0-20191230175014-e8f45d346db8"
75 | hash = "sha256-CZQf2C0EmqsokHGoIEECGgzp7qxdZxWo+U5nWqELHuM="
76 | [mod."github.com/cespare/xxhash"]
77 | version = "v1.1.0"
78 | hash = "sha256-nVDTtXH9PC3yJ0THaQZEN243UP9xgLi/clt5xRqj3+M="
79 | [mod."github.com/client9/misspell"]
80 | version = "v0.3.4"
81 | hash = "sha256-MIKnt4va/nPl+5cCgOvCyRGIORTnguieQhlj8ery4BU="
82 | [mod."github.com/coreos/bbolt"]
83 | version = "v1.3.2"
84 | hash = "sha256-otoFfHibSdPIg6A/d6yLeKTC0ocTJrtNnpsXZq6hpY0="
85 | [mod."github.com/coreos/etcd"]
86 | version = "v3.3.13+incompatible"
87 | hash = "sha256-Hz8x4xE3ku/xSVlUW/LrKv2Tc9fo/PsIcy5LcE8OD4Q="
88 | [mod."github.com/coreos/go-semver"]
89 | version = "v0.3.0"
90 | hash = "sha256-ielBK5+kGscOuygfFNNr5iKuuF1qKBiXLlK8eGuA4Bw="
91 | [mod."github.com/coreos/go-systemd"]
92 | version = "v0.0.0-20190321100706-95778dfbb74e"
93 | hash = "sha256-1WiFUSLDPxsSVafwCkzz0xjpC0W7bNX/sJ0wRBVrvn4="
94 | [mod."github.com/coreos/pkg"]
95 | version = "v0.0.0-20180928190104-399ea9e2e55f"
96 | hash = "sha256-R4EcMkhMPi5fSE5SU8Oa1FlvP5VEysXPaX9GYqnW15w="
97 | [mod."github.com/cpuguy83/go-md2man/v2"]
98 | version = "v2.0.0"
99 | hash = "sha256-Pi84FPmTnz+Oq8tV9Lx7cMopiMculHkUtUmtWCuaX1s="
100 | [mod."github.com/davecgh/go-spew"]
101 | version = "v1.1.1"
102 | hash = "sha256-nhzSUrE1fCkN0+RL04N4h8jWmRFPPPWbCuDc7Ss0akI="
103 | [mod."github.com/dgrijalva/jwt-go"]
104 | version = "v3.2.0+incompatible"
105 | hash = "sha256-t5rhczm+60rYmMg0mZTp86dJkzuGp/OLd5ccXek+oiI="
106 | [mod."github.com/dgryski/go-sip13"]
107 | version = "v0.0.0-20181026042036-e10d5fee7954"
108 | hash = "sha256-pgVia6npFluwOrbY1DyEgy5X2zzgHTe+pAkIrdyK3pU="
109 | [mod."github.com/fatih/color"]
110 | version = "v1.7.0"
111 | hash = "sha256-4In7ef7it7d+6oGUJ3pkD0V+lsL40hVtYdy2KD2ovn0="
112 | [mod."github.com/felixge/httpsnoop"]
113 | version = "v1.0.1"
114 | hash = "sha256-TNXnnC/ZGNY9lInAcES1cBGqIdEljKuh5LH/khVFjVk="
115 | [mod."github.com/fsnotify/fsnotify"]
116 | version = "v1.4.7"
117 | hash = "sha256-j/Ts92oXa3k1MFU7Yd8/AqafRTsFn7V2pDKCyDJLah8="
118 | [mod."github.com/ghodss/yaml"]
119 | version = "v1.0.0"
120 | hash = "sha256-D+2i+EwF2YptR0m/OG4WIVVLL7tUC7XvgRQef2usfGo="
121 | [mod."github.com/go-gl/glfw"]
122 | version = "v0.0.0-20190409004039-e6da0acd62b1"
123 | hash = "sha256-tqPStzM1xOuEWqAv4pBbzB+lNIxEqqyCCP0wWCbrlyY="
124 | [mod."github.com/go-kit/kit"]
125 | version = "v0.8.0"
126 | hash = "sha256-EWRJSQWDNhbzr9EvEzFg76Kn7G/2VBgsiAADHZvqU4s="
127 | [mod."github.com/go-logfmt/logfmt"]
128 | version = "v0.4.0"
129 | hash = "sha256-qX6aMMNTmN+D7LtQLZxW/LAKxRpze4vO77F2EQLrVRs="
130 | [mod."github.com/go-stack/stack"]
131 | version = "v1.8.0"
132 | hash = "sha256-26RlTEcAkbewMUtmirKrDGQ1WJlNousp69v7HMopYnI="
133 | [mod."github.com/gogo/protobuf"]
134 | version = "v1.2.1"
135 | hash = "sha256-4pf13ClcrrzGFFQ16729mQr2Na8s4ZBHfAj8mgoGrR4="
136 | [mod."github.com/golang/glog"]
137 | version = "v0.0.0-20160126235308-23def4e6c14b"
138 | hash = "sha256-YDyL9TRikSXHSrYtITVA/ovYIYrdnZGym14XnslAYkk="
139 | [mod."github.com/golang/groupcache"]
140 | version = "v0.0.0-20190129154638-5b532d6fd5ef"
141 | hash = "sha256-0DT8oGJVLiVsy3b27V4ZS75IaZWFHWlKEZk03yOJQlM="
142 | [mod."github.com/golang/mock"]
143 | version = "v1.3.1"
144 | hash = "sha256-Xos7FPK9IcvtpRrpZ9p9Q0W9RMhiXk2Q8lAWeMVUNWo="
145 | [mod."github.com/golang/protobuf"]
146 | version = "v1.3.2"
147 | hash = "sha256-4fGAPuXMGpohqcqHeoIHwzCvkiWtIOAs0ewIhZ8JeU8="
148 | [mod."github.com/google/btree"]
149 | version = "v1.0.0"
150 | hash = "sha256-5gr0RMnlvrzCke3kwpkf92WvW3x5nnKZesoulyoYRC0="
151 | [mod."github.com/google/go-cmp"]
152 | version = "v0.3.0"
153 | hash = "sha256-Uq5Hevk9jCVtOwrHZqhPPYc3KX/oKJmqoFB/Msjo3cM="
154 | [mod."github.com/google/martian"]
155 | version = "v2.1.0+incompatible"
156 | hash = "sha256-N3tPu89U5MQqmtFIqSEfqEXNgnHf883TAmXKvA2N8KQ="
157 | [mod."github.com/google/pprof"]
158 | version = "v0.0.0-20190515194954-54271f7e092f"
159 | hash = "sha256-guAhd83wFhqNpaeb1gXhsIHjH5kXWTlJq/v1WWceGC8="
160 | [mod."github.com/google/renameio"]
161 | version = "v0.1.0"
162 | hash = "sha256-XQ5yI+LMfFQuK7+T3Xx5jiaRP7GmiQSsPkFmm1TpIs4="
163 | [mod."github.com/google/uuid"]
164 | version = "v1.1.2"
165 | hash = "sha256-DXttjObhEiMn5/OH+mYkJU6u03Gwsx5t08lTsIFyd+U="
166 | [mod."github.com/googleapis/gax-go/v2"]
167 | version = "v2.0.5"
168 | hash = "sha256-2ibpBbDxLVeYHd8gdszHb3w8rgKrChbUNlkaxW9lIhU="
169 | [mod."github.com/gopherjs/gopherjs"]
170 | version = "v0.0.0-20181017120253-0766667cb4d1"
171 | hash = "sha256-AuXnjjoLbFZ85Oi8sldH117MBh+yCUB9HU5Y5syJ7Lg="
172 | [mod."github.com/gorilla/handlers"]
173 | version = "v1.5.1"
174 | hash = "sha256-GnBAARgOx1E+hDMQ63SI17hdhGtLQxb31lZOmn5j/pU="
175 | [mod."github.com/gorilla/mux"]
176 | version = "v1.8.0"
177 | hash = "sha256-s905hpzMH9bOLue09E2JmzPXfIS4HhAlgT7g13HCwKE="
178 | [mod."github.com/gorilla/websocket"]
179 | version = "v1.4.2"
180 | hash = "sha256-GhBLM/XTm2lFCyDvJbnCLAI2OyYXQV6W+jRPOQ1PdVY="
181 | [mod."github.com/grpc-ecosystem/go-grpc-middleware"]
182 | version = "v1.0.0"
183 | hash = "sha256-tpSN7ECXK1UGn0OISMom7ZDbSvm+dlFEF0K8ylcYQTg="
184 | [mod."github.com/grpc-ecosystem/go-grpc-prometheus"]
185 | version = "v1.2.0"
186 | hash = "sha256-XtdBJuUYTXEokPrUetjD6iEqxFTBgyrm1M0X7r+1Uys="
187 | [mod."github.com/grpc-ecosystem/grpc-gateway"]
188 | version = "v1.9.0"
189 | hash = "sha256-sq5aQKVK8lHfmWehs7uFeSYVaOpQ21p+dTIC/oCZhMY="
190 | [mod."github.com/hashicorp/consul/api"]
191 | version = "v1.1.0"
192 | hash = "sha256-w2m1ObnPFNFUc8PTXABizzkVVd05g9cUKzMbkNghPoQ="
193 | [mod."github.com/hashicorp/consul/sdk"]
194 | version = "v0.1.1"
195 | hash = "sha256-0UFJskter51L0yMXbr59hyOmCsW3rSZYOix9Nk/WuTc="
196 | [mod."github.com/hashicorp/errwrap"]
197 | version = "v1.0.0"
198 | hash = "sha256-LGSLrefkABG1kH1i+GUWiD2/ggJxiZEJ+D2YNbhZjmo="
199 | [mod."github.com/hashicorp/go-cleanhttp"]
200 | version = "v0.5.1"
201 | hash = "sha256-c54zcHr9THj3MQk7hrDQcpjOcQi1MvXZ4Kpin6EbfR4="
202 | [mod."github.com/hashicorp/go-immutable-radix"]
203 | version = "v1.0.0"
204 | hash = "sha256-JmNxdGaJG63Ty/sVnPjqvTyA4/k5wkzZ/QvpMK2uduw="
205 | [mod."github.com/hashicorp/go-msgpack"]
206 | version = "v0.5.3"
207 | hash = "sha256-2OUYjD/Jt12TFBrtH0wRqg+lzRljDxSIhk2CqBLUczo="
208 | [mod."github.com/hashicorp/go-multierror"]
209 | version = "v1.0.0"
210 | hash = "sha256-iXzjerl96o7QDiSwQjbak8R/t+YzZeoUqm59TCmy3gI="
211 | [mod."github.com/hashicorp/go-rootcerts"]
212 | version = "v1.0.0"
213 | hash = "sha256-4NZJAT5/vocyto+dv6FmW4kFiYldmNvewowsYK/LiTI="
214 | [mod."github.com/hashicorp/go-sockaddr"]
215 | version = "v1.0.0"
216 | hash = "sha256-orG+SHVsp5lgNRCErmhMLABVFQ3ZWfYIJ/4LTFzlvao="
217 | [mod."github.com/hashicorp/go-syslog"]
218 | version = "v1.0.0"
219 | hash = "sha256-YRuq6oPMwAFVY7mvwpMDvZqGwNnb5CjBYyKI/x5mbCc="
220 | [mod."github.com/hashicorp/go-uuid"]
221 | version = "v1.0.1"
222 | hash = "sha256-s1wIvBu37z4U3qK9sdUR1CtbD39N6RwfX4HgDCpCa0s="
223 | [mod."github.com/hashicorp/go.net"]
224 | version = "v0.0.1"
225 | hash = "sha256-JKal3E+wPO+Hk838opKV4HHKB4O72Xy+77ncXlLkWRk="
226 | [mod."github.com/hashicorp/golang-lru"]
227 | version = "v0.5.1"
228 | hash = "sha256-/tr/EXgTmXPqrrx6kdMPMfWSC6GBdCPlX8GEuRk4yI0="
229 | [mod."github.com/hashicorp/hcl"]
230 | version = "v1.0.0"
231 | hash = "sha256-xsRCmYyBfglMxeWUvTZqkaRLSW+V2FvNodEDjTGg1WA="
232 | [mod."github.com/hashicorp/logutils"]
233 | version = "v1.0.0"
234 | hash = "sha256-e8t8Dm8sp/PzKClN1TOmFcrTAWNh4mZHSW7cAjVx3Bw="
235 | [mod."github.com/hashicorp/mdns"]
236 | version = "v1.0.0"
237 | hash = "sha256-ravx4tklQG43OEjPiJn68iJM9ODZ6hgU0idFCEOiJGM="
238 | [mod."github.com/hashicorp/memberlist"]
239 | version = "v0.1.3"
240 | hash = "sha256-IsxqevYulPt+2VGtlq068Jyq1YfIk4Ohh9TgakIGxnc="
241 | [mod."github.com/hashicorp/serf"]
242 | version = "v0.8.2"
243 | hash = "sha256-diRxWOouFLTO75f2E9NlrKgie/qsT+gOOrrbf4tACHw="
244 | [mod."github.com/huandu/xstrings"]
245 | version = "v1.3.2"
246 | hash = "sha256-ueAZrYRXMdRpeTKct3Yxa5YXkCZEoUHpNQs7wLLJil8="
247 | [mod."github.com/imdario/mergo"]
248 | version = "v0.3.11"
249 | hash = "sha256-E8E7mwcChKvrsizYGHSgTPH9nvdvJbupy/j4PGTyKh4="
250 | [mod."github.com/inconshreveable/mousetrap"]
251 | version = "v1.0.0"
252 | hash = "sha256-ogTuLrV40FwS4ueo4hh6hi1wPywOI+LyIqfNjsibwNY="
253 | [mod."github.com/jonboulle/clockwork"]
254 | version = "v0.1.0"
255 | hash = "sha256-dEV9aGzJRIrYfPpuJux3guJNvZGi+5dfseGurZqGHd8="
256 | [mod."github.com/json-iterator/go"]
257 | version = "v1.1.6"
258 | hash = "sha256-AQvfLt0tm22yphiZPktC7Y+YZVzq0Jjd8hQa8MSnyJc="
259 | [mod."github.com/jstemmer/go-junit-report"]
260 | version = "v0.0.0-20190106144839-af01ea7f8024"
261 | hash = "sha256-GrRw03OKGVnUIK89/jOjCGnIhwsjcgGYgiLomEmy49I="
262 | [mod."github.com/jtolds/gls"]
263 | version = "v4.20.0+incompatible"
264 | hash = "sha256-Zu5naRjnwOYBzRv0CYhIZTizA0AajzOg7mJrL7Bo/cw="
265 | [mod."github.com/julienschmidt/httprouter"]
266 | version = "v1.2.0"
267 | hash = "sha256-xhj9PYKNIfQ/CQb8/3O3OtBmiIAJYtiL23cTnRj1C80="
268 | [mod."github.com/kisielk/errcheck"]
269 | version = "v1.1.0"
270 | hash = "sha256-3njoI0rJFSwfsDV13gAFn0o/XDvujq3ipWUuXHsmbac="
271 | [mod."github.com/kisielk/gotool"]
272 | version = "v1.0.0"
273 | hash = "sha256-lsdQkue8gFz754PGqczUnvGiCQq87SruQtdrDdQVTpE="
274 | [mod."github.com/konsorten/go-windows-terminal-sequences"]
275 | version = "v1.0.3"
276 | hash = "sha256-9HojTXKv7b3HiEhYaKXDxraEfvU5vrg47FbCjTRpOvs="
277 | [mod."github.com/kr/logfmt"]
278 | version = "v0.0.0-20140226030751-b84e30acd515"
279 | hash = "sha256-CePQbqWGtS8qP/Av9pkLiqZwH6RaZQff/s1l+1//jQo="
280 | [mod."github.com/kr/pretty"]
281 | version = "v0.1.0"
282 | hash = "sha256-Fx+TaNrxW0VfzonT2jnd5MU09RRz7GJZkqAtJR6/pKI="
283 | [mod."github.com/kr/pty"]
284 | version = "v1.1.1"
285 | hash = "sha256-AVeS+ivwNzIrgWQaLtsmO2f2MYGpxIVqdac/EzaYI1Q="
286 | [mod."github.com/kr/text"]
287 | version = "v0.1.0"
288 | hash = "sha256-QT65kTrNypS5GPWGvgnCpGLPlVbQAL4IYvuqAKhepb4="
289 | [mod."github.com/magiconair/properties"]
290 | version = "v1.8.1"
291 | hash = "sha256-y9tzLVKluie7cCruJ86XvjA2rUDeE8Q+gpkBDnrg+Kc="
292 | [mod."github.com/mailgun/groupcache/v2"]
293 | version = "v2.2.0"
294 | hash = "sha256-j125kHuG+EOPN6GwW3nP9pq4fr7lmlyY7TRV9uV5l0s="
295 | [mod."github.com/mattn/go-colorable"]
296 | version = "v0.0.9"
297 | hash = "sha256-fVPF8VxbdggLAZnaexMl6Id1WjXKImzVySxKfa+ukts="
298 | [mod."github.com/mattn/go-isatty"]
299 | version = "v0.0.3"
300 | hash = "sha256-9ogEEd8/kl/dImldzdBmTFdepvB0dVXEzN4o8bEqhBs="
301 | [mod."github.com/matttproud/golang_protobuf_extensions"]
302 | version = "v1.0.1"
303 | hash = "sha256-ystDNStxR90j4CK+AMcEQ5oyYFRgWoGdvWlS0XQMDLQ="
304 | [mod."github.com/miekg/dns"]
305 | version = "v1.0.14"
306 | hash = "sha256-OeijUgBaEmDapclTxfvjIqrjh4qZu3+DQpHelGGI4aA="
307 | [mod."github.com/mitchellh/cli"]
308 | version = "v1.0.0"
309 | hash = "sha256-4nG7AhRcjTRCwUCdnaNaFrAKDxEEoiihaCA4lk+uM8U="
310 | [mod."github.com/mitchellh/copystructure"]
311 | version = "v1.0.0"
312 | hash = "sha256-Oeecmi8XpDvy7JUEmeIZ02B3c/tAS0A92QiBwEV60hY="
313 | [mod."github.com/mitchellh/go-homedir"]
314 | version = "v1.1.0"
315 | hash = "sha256-oduBKXHAQG8X6aqLEpqZHs5DOKe84u6WkBwi4W6cv3k="
316 | [mod."github.com/mitchellh/go-testing-interface"]
317 | version = "v1.0.0"
318 | hash = "sha256-/Dpv/4i5xuK8hDH+q8YTdF6Jg6NNtfO4Wqig2JCWgrY="
319 | [mod."github.com/mitchellh/gox"]
320 | version = "v0.4.0"
321 | hash = "sha256-GV3LYxzJt8YVbnSac2orlj2QR3MX/YIDrLkSkPhsjuA="
322 | [mod."github.com/mitchellh/iochan"]
323 | version = "v1.0.0"
324 | hash = "sha256-b5Tp7cw/e8mL++IjsebbmKWXtb9Hrzu4Fc6M4tZKFhU="
325 | [mod."github.com/mitchellh/mapstructure"]
326 | version = "v1.1.2"
327 | hash = "sha256-OU9HZYHtl0qaqMFd84w7snkkRuRY6UMSsfCnL5HYdw0="
328 | [mod."github.com/mitchellh/reflectwalk"]
329 | version = "v1.0.0"
330 | hash = "sha256-gB5v7dJY59MXQ2Q1+dPWjmZq62tn+OSPQ1aI3hy483M="
331 | [mod."github.com/modern-go/concurrent"]
332 | version = "v0.0.0-20180306012644-bacd9c7ef1dd"
333 | hash = "sha256-OTySieAgPWR4oJnlohaFTeK1tRaVp/b0d1rYY8xKMzo="
334 | [mod."github.com/modern-go/reflect2"]
335 | version = "v1.0.1"
336 | hash = "sha256-5D1HGVBc/REwPVdlPYcXsbZM80OIh7V5uiyKAbMA5qo="
337 | [mod."github.com/mwitkow/go-conntrack"]
338 | version = "v0.0.0-20161129095857-cc309e4a2223"
339 | hash = "sha256-rgZ2fm0Vi04xGDhkeFPDSZ+KPKi0a/5rerjOdea1eVk="
340 | [mod."github.com/oklog/ulid"]
341 | version = "v1.3.1"
342 | hash = "sha256-LNn883rYNiaoY9sGEPIzlMRx5UwGThdYTjXqfzeGc9k="
343 | [mod."github.com/pascaldekloe/goe"]
344 | version = "v0.0.0-20180627143212-57f6aae5913c"
345 | hash = "sha256-2KUjqrEC/BwkTZRxImazcI/C3H7QmXfNrlt8slwdDbc="
346 | [mod."github.com/pelletier/go-toml"]
347 | version = "v1.2.0"
348 | hash = "sha256-Yt9MGTbIaU/1FhE7SO5UCQbTLxe+2vsypTdf38i3kFY="
349 | [mod."github.com/pkg/errors"]
350 | version = "v0.8.1"
351 | hash = "sha256-oe3iddfoLRwpC3ki5fifHf2ZFprtg99iNak50shiuDw="
352 | [mod."github.com/pmezard/go-difflib"]
353 | version = "v1.0.0"
354 | hash = "sha256-/FtmHnaGjdvEIKAJtrUfEhV7EVo5A/eYrtdnUkuxLDA="
355 | [mod."github.com/posener/complete"]
356 | version = "v1.1.1"
357 | hash = "sha256-heyPMSBzVlx7ZKgTyzl/xmUfZw3EZCcvGFGrRMIbIr8="
358 | [mod."github.com/prometheus/client_golang"]
359 | version = "v0.9.3"
360 | hash = "sha256-N7zxgq5eg4M68PCcKfZ0626ooWGeny4m2nFc4UPNCJg="
361 | [mod."github.com/prometheus/client_model"]
362 | version = "v0.0.0-20190129233127-fd36f4220a90"
363 | hash = "sha256-RSktK1tyVTRf1QRBpzMYchQOrBCMJPqoozSYMcVpRa8="
364 | [mod."github.com/prometheus/common"]
365 | version = "v0.4.0"
366 | hash = "sha256-udJZ6nhdlMuYBIQJtvIjLpBrbxgy2FSqQMk79dlFAAA="
367 | [mod."github.com/prometheus/procfs"]
368 | version = "v0.0.0-20190507164030-5867b95ac084"
369 | hash = "sha256-SWSQFoTPskJvm5KW0p3OyNBXz+EskiyZeZ6DZZmYhxw="
370 | [mod."github.com/prometheus/tsdb"]
371 | version = "v0.7.1"
372 | hash = "sha256-BPz7YJbfMZgeR+u9YaeWeipVzHIS73EdgXD7VSJSLbA="
373 | [mod."github.com/rogpeppe/fastuuid"]
374 | version = "v0.0.0-20150106093220-6724a57986af"
375 | hash = "sha256-n4HjQqPQwAH49y6AoG6vxa38pkJylgU2kR2a7uAtRos="
376 | [mod."github.com/rogpeppe/go-internal"]
377 | version = "v1.3.0"
378 | hash = "sha256-JgiasZeXDy10syy7wmXtqRffDY7CJ1o5VNY+FmmAjVU="
379 | [mod."github.com/russross/blackfriday/v2"]
380 | version = "v2.0.1"
381 | hash = "sha256-smS2RGP+eOAlWkCJKSQZv7PIKUyJIKM/ty+T1nQ8n1o="
382 | [mod."github.com/ryanuber/columnize"]
383 | version = "v0.0.0-20160712163229-9b3edd62028f"
384 | hash = "sha256-RLUQcU6Z03upKe08v6rjn9/tkyrQsgmpdEmBtWaLQfk="
385 | [mod."github.com/sean-/seed"]
386 | version = "v0.0.0-20170313163322-e2103e2c3529"
387 | hash = "sha256-RQQTjvf8Y91jP5FGOyEnGMFw7zCrcSnUU4eH2CXKkT4="
388 | [mod."github.com/shurcooL/sanitized_anchor_name"]
389 | version = "v1.0.0"
390 | hash = "sha256-DtFSzeLmD1fAl103ncgwab7Vv2F0aulsA+gbkq24ab8="
391 | [mod."github.com/sirupsen/logrus"]
392 | version = "v1.6.0"
393 | hash = "sha256-4v27X4yyl52BtZcZEnDe0tfvOaEq+TCcp7R8HBzreDM="
394 | [mod."github.com/smartystreets/assertions"]
395 | version = "v0.0.0-20180927180507-b2de0cb4f26d"
396 | hash = "sha256-PoE+VQEnzJogI/mDBJ6dTCCR217nFjHfYWXQt9Vr9MQ="
397 | [mod."github.com/smartystreets/goconvey"]
398 | version = "v1.6.4"
399 | hash = "sha256-gDEvwEBgCVYi6daVRlQ2DUXFFlpybM1h4HyvvHphmM4="
400 | [mod."github.com/soheilhy/cmux"]
401 | version = "v0.1.4"
402 | hash = "sha256-EGyOVbQFq4k+A2M61ZMZ5aAM8uwOPLOcp3ynhswz47g="
403 | [mod."github.com/spaolacci/murmur3"]
404 | version = "v0.0.0-20180118202830-f09979ecbc72"
405 | hash = "sha256-RWD4PPrlAsZZ8Xy356MBxpj+/NZI7w2XOU14Ob7/Y9M="
406 | [mod."github.com/spf13/afero"]
407 | version = "v1.1.2"
408 | hash = "sha256-00yWOvw9GosWm6QkogM8MxpnVFRm/7BcdBLG4pQjO1Y="
409 | [mod."github.com/spf13/cast"]
410 | version = "v1.3.0"
411 | hash = "sha256-hbVF7F0YsgSybYEJa7W+Rz0As6OpgmpZOxB5JLFzAXc="
412 | [mod."github.com/spf13/cobra"]
413 | version = "v1.1.1"
414 | hash = "sha256-YdKaCAvr6wAMOQSGzNnNG9LO6Q60T6Z6VSJVTUblomM="
415 | [mod."github.com/spf13/jwalterweatherman"]
416 | version = "v1.0.0"
417 | hash = "sha256-KLftz+gaA5wSkvLqvQ7CuboB79kKEoTJvgTtrXatbiQ="
418 | [mod."github.com/spf13/pflag"]
419 | version = "v1.0.5"
420 | hash = "sha256-w9LLYzxxP74WHT4ouBspH/iQZXjuAh2WQCHsuvyEjAw="
421 | [mod."github.com/spf13/viper"]
422 | version = "v1.7.1"
423 | hash = "sha256-AcR7S/bHMJftBqhQdrsjos71P6aNH2n5Z/AhiRlPka0="
424 | [mod."github.com/stretchr/objx"]
425 | version = "v0.1.1"
426 | hash = "sha256-HdGVZCuy7VprC5W9UxGbDmXqsKADMjpEDht7ilGVLco="
427 | [mod."github.com/stretchr/testify"]
428 | version = "v1.3.0"
429 | hash = "sha256-+mSebBNccNcxbY462iKTNTWmd5ZuUkUqFebccn3EtIA="
430 | [mod."github.com/subosito/gotenv"]
431 | version = "v1.2.0"
432 | hash = "sha256-RUsfBl9xvHk8H6SPwiLi/BpHjkyO/YLvlFmRfGRIW1U="
433 | [mod."github.com/tmc/grpc-websocket-proxy"]
434 | version = "v0.0.0-20190109142713-0ad062ec5ee5"
435 | hash = "sha256-5OJsX5qqW/MeL4gCapWnU/KvNeAply9cT9xezdMm3Ko="
436 | [mod."github.com/valyala/bytebufferpool"]
437 | version = "v1.0.0"
438 | hash = "sha256-I9FPZ3kCNRB+o0dpMwBnwZ35Fj9+ThvITn8a3Jr8mAY="
439 | [mod."github.com/valyala/fasttemplate"]
440 | version = "v1.2.1"
441 | hash = "sha256-+VtRavE4b+C8CSzQUQoN5QnVwfeepQUGfBI+8VasjLg="
442 | [mod."github.com/x-way/crawlerdetect"]
443 | version = "v0.2.7"
444 | hash = "sha256-graGzcfvZdmWaH7o7bicKYbzMW+65nU/5QMUF7AOPU0="
445 | [mod."github.com/xiang90/probing"]
446 | version = "v0.0.0-20190116061207-43a291ad63a2"
447 | hash = "sha256-sXyLzdjys2YAQBxz1ELmV3RulY5huFrOEUQWaYKuQvw="
448 | [mod."go.etcd.io/bbolt"]
449 | version = "v1.3.2"
450 | hash = "sha256-otoFfHibSdPIg6A/d6yLeKTC0ocTJrtNnpsXZq6hpY0="
451 | [mod."go.opencensus.io"]
452 | version = "v0.22.0"
453 | hash = "sha256-mFhAHSmpGBFt+BD05NE+zyHKDApZthH07yL3hBzIR1A="
454 | [mod."go.uber.org/atomic"]
455 | version = "v1.4.0"
456 | hash = "sha256-gBTPaM4TReaxVxzJQnUJewpykKzB0yTNfyv5HtzPYAE="
457 | [mod."go.uber.org/multierr"]
458 | version = "v1.1.0"
459 | hash = "sha256-ktOZp7gmmUJ1uZ8pZiWyzlAabxj3ttA9F/B+rKOj0x4="
460 | [mod."go.uber.org/zap"]
461 | version = "v1.10.0"
462 | hash = "sha256-MXBF5M2xKqncmcTA4143LnK2VwkZXmMPPrdc0VRoNWw="
463 | [mod."golang.org/x/crypto"]
464 | version = "v0.0.0-20190605123033-f99c8df09eb5"
465 | hash = "sha256-DiLhbgWenQw6vJE7fFJv+8OTp1Cj839tTSkZMwwzkUs="
466 | [mod."golang.org/x/exp"]
467 | version = "v0.0.0-20191030013958-a1ab85dbe136"
468 | hash = "sha256-b/S+nBYaiuf5JD3xFHlA2/xt+QOQwXw0BI8iKpC451c="
469 | [mod."golang.org/x/image"]
470 | version = "v0.0.0-20190802002840-cff245a6509b"
471 | hash = "sha256-BP2l1VUXd5afv4fsZ9g6WYy6zEPY782ZAsMrFSe1P0I="
472 | [mod."golang.org/x/lint"]
473 | version = "v0.0.0-20190930215403-16217165b5de"
474 | hash = "sha256-8LRtePuV2+lVH+6aRVA4+yHalL1AWWKCq2z6DRivHEo="
475 | [mod."golang.org/x/mobile"]
476 | version = "v0.0.0-20190719004257-d2bd2a29d028"
477 | hash = "sha256-At0uE2mTr/GHCyF4U8Z+AiU2jlvBVQuX25tooo2ll6M="
478 | [mod."golang.org/x/mod"]
479 | version = "v0.1.0"
480 | hash = "sha256-khYEpNxR6oDmxjXk4TJjwwx6IBcMu/iXxeyzCGf9kVY="
481 | [mod."golang.org/x/net"]
482 | version = "v0.0.0-20200202094626-16171245cfb2"
483 | hash = "sha256-VFY1SxRHKXGKdfGLICaHPCS5HtiZcYjODMcRDUyz/e0="
484 | [mod."golang.org/x/oauth2"]
485 | version = "v0.0.0-20190604053449-0f29369cfe45"
486 | hash = "sha256-Iv78f8vZzKVMECGDedDbp82SI1u5pZK8sPKJDvq+XBo="
487 | [mod."golang.org/x/sync"]
488 | version = "v0.0.0-20190423024810-112230192c58"
489 | hash = "sha256-1lGQ6frW6gwuLzwomu530IsFh7vlQWQOphQ0IQeZIhY="
490 | [mod."golang.org/x/sys"]
491 | version = "v0.0.0-20190624142023-c5567b49c5d0"
492 | hash = "sha256-RnADIWiKq0JljSjVQfIvlekzB7RRPyleKRh+lv3Wb4g="
493 | [mod."golang.org/x/text"]
494 | version = "v0.3.2"
495 | hash = "sha256-XCq76CoE5+BMqkzKbjV6l3RS6E0TrNANaOfs6faJEWo="
496 | [mod."golang.org/x/time"]
497 | version = "v0.0.0-20190308202827-9d24e82272b4"
498 | hash = "sha256-azbksMSLQf1CK0jF2i+ESjFenMDR88xRW1tov0metrg="
499 | [mod."golang.org/x/tools"]
500 | version = "v0.0.0-20191112195655-aa38f8e97acc"
501 | hash = "sha256-37LZ+Xy/+l52kr+h1n1p/w/HdOMi5N/Q9h3gL3eVpqA="
502 | [mod."golang.org/x/xerrors"]
503 | version = "v0.0.0-20190717185122-a985d3407aa7"
504 | hash = "sha256-kj2qs47n+a4gtKXHJN3U9gcSQ3BozjzYu7EphXjJnwM="
505 | [mod."google.golang.org/api"]
506 | version = "v0.13.0"
507 | hash = "sha256-hqj8AfRC4isI1Lrmxu/UykJhycYtXTXBX8Knfc7VwjE="
508 | [mod."google.golang.org/appengine"]
509 | version = "v1.6.1"
510 | hash = "sha256-0vZ1jkVY4tCl9ZNc227oY2gphcJmHy4q7iEUUEtmfS8="
511 | [mod."google.golang.org/genproto"]
512 | version = "v0.0.0-20191108220845-16a3f7862a1a"
513 | hash = "sha256-c0PDxmBggwlzoJ99YF9zExH1rgh7Wq0v0OPJIL7ecIk="
514 | [mod."google.golang.org/grpc"]
515 | version = "v1.21.1"
516 | hash = "sha256-WrgMlGplEB1E5NR8Ugi0MI5NPet2vXYxyi18wYc1VNg="
517 | [mod."gopkg.in/alecthomas/kingpin.v2"]
518 | version = "v2.2.6"
519 | hash = "sha256-uViE2kPj7tMrGYVjjdLOl2jFDmmu+3P7GvnZBse2zVY="
520 | [mod."gopkg.in/check.v1"]
521 | version = "v1.0.0-20180628173108-788fd7840127"
522 | hash = "sha256-KsRJNTprd1UijnJusbHwQGM7Bdm45Jt/QL+cIUGNa2w="
523 | [mod."gopkg.in/errgo.v2"]
524 | version = "v2.1.0"
525 | hash = "sha256-Ir/MuxQFxvVJEciovGOZbM8ZfKJ/AYotPwYfH2FctRg="
526 | [mod."gopkg.in/ini.v1"]
527 | version = "v1.51.0"
528 | hash = "sha256-gQ77WlSLs7+1ACOxnaSp5qb8KZd1tGsoNL9O4uq/jPE="
529 | [mod."gopkg.in/resty.v1"]
530 | version = "v1.12.0"
531 | hash = "sha256-t9KTjlm1K1WdPAZ0L6rLv0ME/iP/gKeKgvDjXMaxVRg="
532 | [mod."gopkg.in/yaml.v2"]
533 | version = "v2.3.0"
534 | hash = "sha256-8tPC5nMGvUFs97W6+JXsxJLjU6EpDmPG9tXo1DyFoNU="
535 | [mod."honnef.co/go/tools"]
536 | version = "v0.0.1-2019.2.3"
537 | hash = "sha256-jpCzv1UG8xGB44IA+kcAXDULYQlJWqa816GYP+BQoMs="
538 | [mod."rsc.io/binaryregexp"]
539 | version = "v0.2.0"
540 | hash = "sha256-izALTmzybQe67BNXliqQ3xCEOo+b6o8C4YoX5H0FWc0="
541 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
8 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
9 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
10 | cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
11 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
12 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
13 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
14 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
15 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
16 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
17 | github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg=
18 | github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
19 | github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
20 | github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
21 | github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
22 | github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
23 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
24 | github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE=
25 | github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
26 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
27 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
28 | github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
29 | github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
30 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
31 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
32 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
33 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
34 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
35 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
36 | github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
37 | github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaqKnf+7Qs6GbEPfd4iMOitWzXJx8=
38 | github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8/go.mod h1:spo1JLcs67NmW1aVLEgtA8Yy1elc+X8y5SRW1sFW4Og=
39 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
40 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
41 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
42 | github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
43 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
44 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
45 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
46 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
47 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
48 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
49 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
50 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
51 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
52 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
53 | github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
54 | github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
55 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
56 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
57 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
58 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
59 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
60 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
61 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
62 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
63 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
64 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
65 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
66 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
67 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
68 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
69 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
70 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
71 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
72 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
73 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
74 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
75 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
76 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
77 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
78 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
79 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
80 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
81 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
82 | github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
83 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
84 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
85 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
86 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
87 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
88 | github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
89 | github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
90 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
91 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
92 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
93 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
94 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
95 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
96 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
97 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
98 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
99 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
100 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
101 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
102 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
103 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
104 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
105 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
106 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
107 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
108 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
109 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
110 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
111 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
112 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
113 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
114 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
115 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
116 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
117 | github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
118 | github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
119 | github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
120 | github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
121 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
122 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
123 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
124 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
125 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
126 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
127 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
128 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
129 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
130 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
131 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
132 | github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
133 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
134 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
135 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
136 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
137 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
138 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
139 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
140 | github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
141 | github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
142 | github.com/mailgun/groupcache/v2 v2.2.0 h1:vWi1ROSiYZcstf5ZRtZ+iD6sVcWxOM4dwg52XkxwBLc=
143 | github.com/mailgun/groupcache/v2 v2.2.0/go.mod h1:E28iTa7lFjf5/t1sSJwnCtERqmOgSRcWocJTYUPT2BA=
144 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
145 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
146 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
147 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
148 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
149 | github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
150 | github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
151 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
152 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
153 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
154 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
155 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
156 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
157 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
158 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
159 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
160 | github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
161 | github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
162 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
163 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
164 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
165 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
166 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
167 | github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
168 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
169 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
170 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
171 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
172 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
173 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
174 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
175 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
176 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
177 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
178 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
179 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
180 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
181 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
182 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
183 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
184 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
185 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
186 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
187 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
188 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
189 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
190 | github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
191 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
192 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
193 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
194 | github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
195 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
196 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
197 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
198 | github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
199 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
200 | github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
201 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
202 | github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=
203 | github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
204 | github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
205 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
206 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
207 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
208 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
209 | github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
210 | github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
211 | github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
212 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
213 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
214 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
215 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
216 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
217 | github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
218 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
219 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
220 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
221 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
222 | github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
223 | github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
224 | github.com/x-way/crawlerdetect v0.2.7 h1:DLgB2bFUz9eww/zhQJEgq2SqMEpXxAlH+DuY2+2Ij2Y=
225 | github.com/x-way/crawlerdetect v0.2.7/go.mod h1:S8CHanGLTMMTzqgKAvkDxlLKSXfBSEJyTSx6YGS8Y48=
226 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
227 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
228 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
229 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
230 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
231 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
232 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
233 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
234 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
235 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
236 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
237 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU=
238 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
239 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
240 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
241 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
242 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
243 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
244 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
245 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
246 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
247 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
248 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
249 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
250 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
251 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
252 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
253 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
254 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
255 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
256 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
257 | golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
258 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
259 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
260 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
261 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
262 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
263 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
264 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
265 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
266 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
267 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
268 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
269 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
270 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
271 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
272 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
273 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
274 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
275 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
276 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
277 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
278 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
279 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
280 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
281 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
282 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
283 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
284 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
285 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
286 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
287 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
288 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
289 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
290 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
291 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
292 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
293 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
294 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
295 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc=
296 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
297 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
298 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
299 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
300 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
301 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
302 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
303 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
304 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
305 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
306 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
307 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
308 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
309 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
310 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
311 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
312 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
313 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
314 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
315 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
316 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
317 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
318 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
319 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
320 | golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
321 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
322 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
323 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
324 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
325 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
326 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
327 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
328 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
329 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
330 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
331 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
332 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
333 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
334 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
335 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
336 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
337 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
338 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
339 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
340 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
341 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
342 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
343 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
344 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
345 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
346 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
347 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
348 | gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
349 | gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
350 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
351 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
352 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
353 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
354 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
355 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
356 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
357 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
358 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
359 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
360 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
361 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
362 |
--------------------------------------------------------------------------------