├── .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 |
5 |

TODO:

6 |
7 |
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 |
4 |

{{.name}}

5 | {{.email}} 6 |
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 |

{{$label}}

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 | 7 | 8 | 9 | 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 | ![](/juice.svg) 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 | ![](/cpp-search-extension.png) 25 | 26 | Link: [https://cpp.extension.sh/](https://cpp.extension.sh/) 27 | 28 | ## Go Search Extension 29 | 30 | ![](/go-search-extension.png) 31 | 32 | Link: [https://go.extension.sh/](https://go.extension.sh/) 33 | 34 | ## JS Search Extension 35 | 36 | ![](/js-search-extension.png) 37 | 38 | Link: [https://js.extension.sh/](https://js.extension.sh/) 39 | 40 | ## Rust Search Extension 41 | 42 | ![](/rust-search-extension.png) 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 |
13 | Star 15 | Fork 17 |
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 |
36 | Star 38 | Fork 40 |
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 | --------------------------------------------------------------------------------