├── .gitattributes ├── .gitignore ├── LICENSE.md ├── README.md ├── cmd.go ├── go.mod ├── go.sum ├── server └── server.go └── slate ├── _slate ├── CHANGELOG.md ├── LICENSE ├── README.md ├── fonts │ ├── slate.eot │ ├── slate.svg │ ├── slate.ttf │ ├── slate.woff │ └── slate.woff2 ├── images │ ├── logo.png │ └── navbar.png ├── includes │ └── _errors.md ├── index.html.md ├── javascripts │ ├── all.js │ ├── all_nosearch.js │ ├── app │ │ ├── _lang.js │ │ ├── _search.js │ │ └── _toc.js │ └── lib │ │ ├── _energize.js │ │ ├── _imagesloaded.min.js │ │ ├── _jquery.highlight.js │ │ ├── _jquery.js │ │ └── _lunr.js ├── layouts │ └── layout.tmpl └── stylesheets │ ├── _icon-font.scss │ ├── _normalize.scss │ ├── _rtl.scss │ ├── _variables.scss │ ├── print.css.scss │ └── screen.css.scss ├── content.go ├── internal └── slate │ ├── data.s │ ├── index.go │ ├── index_386.s │ ├── index_amd64.s │ ├── index_arm.s │ ├── index_arm64.s │ ├── index_mips64x.s │ ├── index_mipsx.s │ ├── index_ppc64x.s │ ├── index_s390x.s │ └── index_test.go ├── slate.go └── slateficate.go /.gitattributes: -------------------------------------------------------------------------------- 1 | slate/_slate/* linguist-vendored 2 | slate/internal/* linguist-generated=true 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright © 2018 Alexey Naidyonov `` 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the “Software”), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-slate 2 | 3 | go-slate is a CLI tool to generate API documentation using brilliant [Slate](https://github.com/lord/slate) layout 4 | by [Robert Lord](https://github.com/lord). go-slate contains bundled Slate (including Kittn API Example Documentation) 5 | and requires no additional software to install. 6 | 7 | go-slate can also generate a Go package with embedded documentation to include into Go binary. 8 | A simple HTTP(S) server to serve rendered documentation is also provided. 9 | 10 | ## Why 11 | 12 | Slate is arguably the best tool around to quickly hack an API documentation which 13 | will look really good. However it uses Ruby `bundler` and brings loads of software 14 | which did not go well with my Mac. Besides, I really got used to Go _all you need is 15 | a single binary_ way of things. 16 | 17 | There were also [Hugo DocuAPI theme](https://github.com/bep/docuapi) (based on Slate, too), 18 | but it has its own drawbacks, mostly because of different ways middleman and backfriday render 19 | contents. 20 | 21 | So I quickly hacked go-slate. 22 | 23 | ## Installation 24 | 25 | ```text 26 | $ go get -u github.com/growler/go-slate 27 | ``` 28 | 29 | ## How to start 30 | 31 | Assuming following source tree 32 | 33 | src 34 | └── service 35 | ├── main.go 36 | └── apidoc 37 | 38 | the typical usage would be: 39 | 40 | 1. Extract template content 41 | 42 | ```bash 43 | go-slate extract src/service/apidoc contents 44 | ``` 45 | 46 | 2. Now edit `apidoc/index.html.md` and add following lines to your `main.go`: 47 | 48 | ```go 49 | //go:generate go-slate package apidoc internal/apidoc 50 | 51 | package main 52 | 53 | import ( 54 | "net/http" 55 | "fmt" 56 | "service/internal/apidoc" 57 | ) 58 | 59 | func main() { 60 | http.HandleFunc("/help/", apidoc.HTTPHandlerWithPrefix("/help")) 61 | //... 62 | if err := http.ListenAndServe(":8080", nil); err != nil{ 63 | fmt.Println(err) 64 | } 65 | } 66 | ``` 67 | 68 | 3. Generate package with embedded documentation 69 | 70 | ```bash 71 | go generate service 72 | go build service 73 | ``` 74 | 75 | 3. Enjoy beautiful documentation at `http://service:8080/help/` 76 | 77 | ## Usage 78 | 79 | ```bash 80 | go-slate [command] 81 | ``` 82 | 83 | Available commands: 84 | 85 | help Help about any command 86 | extract extracts slate files bundled with go-slate to specified directory 87 | package produces an embeddable package with rendered documentation content and HTTP handler 88 | site renders documentation from source directory to output directory 89 | server serves rendered API documentation over HTTP(S) 90 | version prints version 91 | 92 | ## Extact 93 | 94 | ```bash 95 | go-slate extract [directory] [components to extract...] 96 | ``` 97 | 98 | Extracts Slate files bundled with go-slate to a specified directory. 99 | With none components supplied, extracts only slate example documentation 100 | project content (Markdown files). Otherwise, a specified list of components will be 101 | extracted. The component name might be either file name or one of 102 | 103 | - all 104 | - contents 105 | - layouts 106 | - stylesheets 107 | - javascripts 108 | - images 109 | 110 | To list bundled Slate contents, use `go-slate extract -l` 111 | 112 | ## Rendering commands options 113 | 114 | Commands `site`, `package` and `server` render content and share a set of options 115 | 116 | `--no-minify css|jss|html|all` 117 | 118 | Disables compact output for a specific type of content. Multiple options can be listed with comma. 119 | 120 | `--logo` 121 | 122 | Defines a logo file to use, overriding default or setting in [document preamble](#slate-preamble-options). 123 | Use word `none` to disable logo block completely. 124 | 125 | `--rtl` and `--no-rtl` 126 | 127 | Enables or disable Right-to-left scripts support, overriding default (no) or setting in [document preamble](#slate-preamble-options). 128 | 129 | `--search` and `--no-search` 130 | 131 | Enables or disable Slate search block, overriding setting in [document preamble](#slate-preamble-options). 132 | 133 | `--style file` 134 | 135 | Loads an SCSS style to adjust Slate styles. Note that this adds to, and not override [document preamble](#slate-preamble-options) 136 | option `style`. A list of available variables can be obtained by `go-slate extract . stylesheets/_variables.scss` 137 | 138 | ## Site 139 | 140 | ```bash 141 | go-slate site [source directory] [output directory] [flags] 142 | ``` 143 | 144 | Renders documentation to a destination. 145 | 146 | ## Package 147 | 148 | ```bash 149 | go-slate package [source directory] [resulting package directory] [flags] 150 | ``` 151 | 152 | Produces an embeddable Go package using [go-imbed](https://github.com/growler/go-imbed) 153 | 154 | `--pkg name` 155 | 156 | By default, `package` takes package name from the last path item of resulting package directory. This option 157 | sets package name explicitly. 158 | 159 | `--work-dir directory` 160 | 161 | By default, `package` renders content in a temporary directory and removes it afterwards. This option tells 162 | `go-slate` to use specified directory and keep content afterwards. 163 | 164 | ## Server 165 | 166 | ```bash 167 | go-slate server [source directory] [address to listen at] [flags] 168 | ``` 169 | 170 | Starts an HTTP server listening at the specified address and serving rendered content. 171 | 172 | `--tls-cert file` and `--tls-key file` 173 | 174 | If both options are specified, server will serve HTTPS. 175 | 176 | `--monitor-changes` 177 | 178 | Monitor changes of source content and re-render if necessary. Any rendering error will be printed 179 | to stdout. 180 | 181 | (A neat trick: `go-slate server :8080` will serve the Slate example Kittn API Documentation) 182 | 183 | ## Slate preamble options 184 | 185 | `go-slate` supports Slate preamble options: 186 | 187 | ```yaml 188 | # document title 189 | title: API Reference 190 | 191 | # must be one of supported by https://github.com/alecthomas/chroma 192 | language_tabs: 193 | - shell 194 | - ruby 195 | - python 196 | - javascript 197 | 198 | # a list of footers to place under TOC navigation menu 199 | toc_footers: 200 | - Sign Up for a Developer Key 201 | - Documentation Powered by Slate 202 | 203 | # a list of files to include from includes directory. 204 | # files must be named as includes/_.md 205 | includes: 206 | - errors 207 | 208 | # enable search block 209 | search: true 210 | ``` 211 | 212 | In addition, `go-slate` defines a few others: 213 | 214 | ```yaml 215 | # a logo file to use. file must be located 216 | # in directory images. Use `none` to disable logo 217 | logo: logo.png 218 | 219 | # enable RTL support 220 | enable_rtl: true 221 | 222 | # use code highlight style, must be supported by https://github.com/alecthomas/chroma 223 | highlight_style: monokai 224 | 225 | # plain html to add to html _before_ all Slate stylesheets and javascripts references 226 | html_premble: | 227 | 228 | 229 | # An SCSS header to adjust Slate CSS 230 | # A list of available variables can be obtained by go-slate extract . stylesheets/_variables.scss 231 | style: | 232 | %default-font { 233 | font-family: "Fira Code", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 234 | font-size: 14px; 235 | } 236 | 237 | ``` 238 | 239 | ## What go-slate is not 240 | 241 | `go-slate` solves a very basic task and tries to be as simple and unobtrusive as possible. It is not, by any 242 | mean, a generic site generator. Use [Hugo](https://gohugo.io/) for that. 243 | 244 | ## License 245 | 246 | The MIT License, see [LICENSE.md](LICENSE.md). 247 | 248 | The embedded [Slate](https://github.com/lord/slate) is licensed under Apache 2.0 License, which can be 249 | extracted with `go-slate extract . LICENSE` command. 250 | 251 | # Special thanks 252 | 253 | - [Slate](https://github.com/lord/slate) for the great API documentation layout 254 | - [Hugo DocuAPI theme](https://github.com/bep/docuapi) for inspiration 255 | - [Blackfriday](https://github.com/russross/blackfriday) Mardown engine for Go 256 | - [Chroma](https://github.com/alecthomas/chroma) the syntax highlighter for Go 257 | - [go-libsass](https://github.com/wellington/go-libsass) the [LibSass](http://sass-lang.com/libsass) Go bindings 258 | - [Steve Francia](https://github.com/spf13) for cobra, pflags and afero 259 | -------------------------------------------------------------------------------- /cmd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexey Naidyonov. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE.md file. 4 | 5 | package main 6 | 7 | import ( 8 | "os" 9 | "fmt" 10 | "github.com/spf13/cobra" 11 | "github.com/growler/go-slate/slate" 12 | "time" 13 | "github.com/spf13/afero" 14 | "path/filepath" 15 | "io/ioutil" 16 | "github.com/growler/go-imbed/imbed" 17 | "github.com/growler/go-slate/server" 18 | "errors" 19 | ) 20 | 21 | var ( 22 | cmd *cobra.Command 23 | ) 24 | 25 | func setParams(params *slate.Params, opts generateOptions) error { 26 | params.MinifyHTML = true 27 | params.MinifyCSS = true 28 | params.MinifyJS = true 29 | if opts.search && opts.noSearch { 30 | return errors.New("both --search and --no-search set") 31 | } else if opts.search { 32 | params.Search = new(bool) 33 | *params.Search = true 34 | } else if opts.noSearch { 35 | params.Search = new(bool) 36 | } 37 | if opts.rtl && opts.noRtl { 38 | return errors.New("both --rtl and --no-rtl set") 39 | } else if opts.rtl { 40 | params.RTL = new(bool) 41 | *params.RTL = true 42 | } else if opts.noRtl { 43 | params.RTL = new(bool) 44 | } 45 | params.LogoFile = opts.logoFile 46 | params.StyleFile = opts.styleFile 47 | for _, s := range opts.noMinify { 48 | switch s { 49 | case "all": 50 | params.MinifyCSS = false 51 | params.MinifyJS = false 52 | params.MinifyHTML = false 53 | return nil 54 | case "css": 55 | params.MinifyCSS = false 56 | case "js": 57 | params.MinifyJS = false 58 | case "html": 59 | params.MinifyHTML = false 60 | default: 61 | return fmt.Errorf("unknown parameter %s to --no-minify", s) 62 | } 63 | } 64 | return nil 65 | } 66 | 67 | func cmdVersion() *cobra.Command { 68 | cmd := &cobra.Command{ 69 | Use: "version", 70 | Short: "prints version", 71 | Long: `Prints versions of both go-slate and embedded Slate`, 72 | Args: cobra.ExactArgs(0), 73 | RunE: func(cmd *cobra.Command, args []string) error { 74 | fmt.Printf("go-slate version: %s\n", slate.GoSlateVersion) 75 | fmt.Printf("slate version: %s\n", slate.SlateVersion) 76 | return nil 77 | }, 78 | } 79 | return cmd 80 | } 81 | 82 | type generateOptions struct { 83 | noMinify []string 84 | search bool 85 | noSearch bool 86 | rtl bool 87 | noRtl bool 88 | styleFile string 89 | logoFile string 90 | } 91 | 92 | func genOpts(cmd *cobra.Command, opts *generateOptions) { 93 | cmd.Flags().BoolVar(&opts.rtl, "rtl", false, "enable right-to-left scripts support (overrides option in source file)") 94 | cmd.Flags().BoolVar(&opts.noRtl, "no-rtl", false, "disable right-to-left scripts support (overrides option in source file)") 95 | cmd.Flags().StringVarP(&opts.styleFile, "style", "s", "", "supply an SCSS `file` to adjust documentation styles (overrides option in source file)") 96 | cmd.Flags().StringVarP(&opts.logoFile, "logo", "l", "", "supply a logo image `file` to use (overrides option in source file)") 97 | cmd.Flags().BoolVar(&opts.noSearch, "search", false, "enable Slate search block (overrides option in source file)") 98 | cmd.Flags().BoolVar(&opts.noSearch, "no-search", false, "disable Slate search block (overrides option in source file)") 99 | cmd.Flags().StringSliceVar(&opts.noMinify, "no-minify", []string{}, "disable compaction, comma-separated list of `css|js|html|all`") 100 | } 101 | 102 | func cmdSite() *cobra.Command { 103 | var opts generateOptions 104 | cmd := &cobra.Command{ 105 | Use: "site [source directory] [output directory]", 106 | Short: "renders documentation from source directory to output directory", 107 | Args: cobra.ExactArgs(2), 108 | RunE: func(cmd *cobra.Command, args []string) error { 109 | var params slate.Params 110 | if err := setParams(¶ms, opts); err != nil { 111 | return err 112 | } 113 | p, err := filepath.Abs(args[1]) 114 | if err != nil { 115 | return err 116 | } 117 | fs := &afero.Afero{Fs: afero.NewBasePathFs(afero.NewOsFs(), p)} 118 | if err := slate.Slateficate(args[0], fs, params); err != nil { 119 | return err 120 | } 121 | return nil 122 | }, 123 | } 124 | genOpts(cmd, &opts) 125 | return cmd 126 | } 127 | 128 | func rmtree(name string) { 129 | var files []string 130 | var dirs []string 131 | filepath.Walk(name, func(path string, info os.FileInfo, err error) error { 132 | if err != nil { 133 | return nil 134 | } 135 | if info.IsDir() { 136 | dirs = append(dirs, path) 137 | } else { 138 | files = append(files, path) 139 | } 140 | return nil 141 | }) 142 | for j := len(files) - 1; j >= 0; j-- { 143 | os.Remove(files[j]) 144 | } 145 | for j := len(dirs) - 1; j >= 0; j-- { 146 | os.Remove(dirs[j]) 147 | } 148 | } 149 | 150 | func cmdPackage() *cobra.Command { 151 | var opts generateOptions 152 | var workDir string 153 | var pkgName string 154 | cmd := &cobra.Command{ 155 | Use: "package [source directory] [resulting package directory]", 156 | Short: "produces an embeddable package with rendered documentation content and HTTP handler", 157 | Long: ` 158 | Produces an embeddable package with rendered documentation content using github.com/growler/go-imbed tool. 159 | The package will also contain HTTP handler to serve content with standard Go http server. 160 | `, 161 | Args: cobra.ExactArgs(2), 162 | RunE: func(cmd *cobra.Command, args []string) error { 163 | var params slate.Params 164 | if err := setParams(¶ms, opts); err != nil { 165 | return err 166 | } 167 | if workDir == "" { 168 | tmpDir, err := ioutil.TempDir(os.TempDir(), ".go-slate") 169 | if err != nil { 170 | return fmt.Errorf("error creating temp directory: %s", err) 171 | } 172 | defer rmtree(tmpDir) 173 | workDir = tmpDir 174 | } 175 | fs := &afero.Afero{Fs: afero.NewBasePathFs(afero.NewOsFs(), workDir)} 176 | if err := slate.Slateficate(args[0], fs, params); err != nil { 177 | return err 178 | } 179 | if pkgName == "" { 180 | pkgName = filepath.Base(args[1]) 181 | } 182 | if err := imbed.Imbed(workDir, args[1], pkgName, imbed.CompressAssets | imbed.BuildHttpHandlerAPI); err != nil { 183 | return err 184 | } 185 | return nil 186 | }, 187 | } 188 | cmd.Flags().StringVarP(&pkgName, "pkg", "n", "", "use this package `name` (otherwise the base name of resulting package directory will be used)") 189 | cmd.Flags().StringVarP(&workDir, "work-dir", "d", "", "use specified working `directory` to render documentation results") 190 | genOpts(cmd, &opts) 191 | return cmd 192 | } 193 | 194 | func cmdServer() *cobra.Command { 195 | var opts generateOptions 196 | var tlsCert string 197 | var tlsKey string 198 | var monitor bool 199 | cmd := &cobra.Command{ 200 | Use: "server [source directory] [address to listen at]", 201 | Short: "serves rendered API documentation over HTTP(S)", 202 | Long: ` 203 | Serves API documentation over HTTP(S) at specified address. Monitors changes if requested and 204 | updates documentation in real time. 205 | `, 206 | Args: cobra.ExactArgs(2), 207 | RunE: func(cmd *cobra.Command, args []string) error { 208 | var params slate.Params 209 | if err := setParams(¶ms, opts); err != nil { 210 | return err 211 | } 212 | return server.Serve(args[0], params, args[1], tlsCert, tlsKey, monitor) 213 | }, 214 | } 215 | cmd.Flags().StringVarP(&tlsCert, "tls-cert", "c", "", "TLS certificate `file` to use") 216 | cmd.Flags().StringVarP(&tlsKey, "tls-key", "k", "", "TLS key `file` to use") 217 | cmd.Flags().BoolVarP(&monitor, "monitor-changes", "m", false, "monitor changes and re-generate documentation if necessary") 218 | genOpts(cmd, &opts) 219 | return cmd 220 | } 221 | 222 | func cmdExtract() *cobra.Command { 223 | var ( 224 | list bool 225 | overwrite bool 226 | ) 227 | cmd := &cobra.Command{ 228 | Use: "extract [directory] [components to extract...]", 229 | 230 | Short: "extracts slate files bundled with go-slate to specified directory", 231 | 232 | Long: ` 233 | Extracts Slate files bundled with go-slate to a specified directory. 234 | By default, extracts only slate example documentation project content (Markdown files). 235 | Otherwise, a specified list of components will be extracted. Specify word "all" to extract 236 | all files, any of "contents", "layouts", "stylesheets", "javascripts", "images" to extract 237 | specific kinds, or a single file name: 238 | 239 | $ go-slate extract -l 240 | ... 241 | $ go-slate extract . contents stylesheets 242 | ... 243 | edit index.html.md 244 | edit stylesheets/_variables.scss 245 | ... 246 | $ go-slate package . internal/apidoc 247 | $ 248 | `, 249 | RunE: func(cmd *cobra.Command, args []string) error { 250 | if list { 251 | slate.ListBundled(os.Stdout) 252 | return nil 253 | } else if len(args) == 0 { 254 | return fmt.Errorf("") 255 | } else if len(args) == 1 { 256 | return slate.Extract(args[0], overwrite,"contents") 257 | } else { 258 | return slate.Extract(args[0], overwrite, args[1:]...) 259 | } 260 | }, 261 | } 262 | cmd.Flags().BoolVarP(&list, "list", "l", false, "lists slate components available to extract") 263 | cmd.Flags().BoolVarP(&overwrite, "overwrite", "w", false, "overwrite existing files") 264 | return cmd 265 | } 266 | 267 | func init() { 268 | var timings bool 269 | var startTs time.Time 270 | cmd = &cobra.Command{ 271 | Use: "go-slate", 272 | Short: "a simple Go tool to generate API documentation using brilliant github.com/lord/slate layout", 273 | Long: ` 274 | go-slate is a CLI tool to generate API documentation using brilliant Slate layout 275 | by Robert Lord. go-slate contains bundled Slate ` + slate.SlateVersion + ` and requires no additional 276 | software to install. go-slate can also generate a go package with embedded 277 | documentation to include into Go binary. go-slate uses bundled source files or files from 278 | the filesystem if present. A simple HTTP(s) server to serve rendered documentation is 279 | also provided. 280 | 281 | Assuming following source tree 282 | 283 | src 284 | └── service 285 | ├── main.go 286 | └── apidoc 287 | 288 | the typical usage would be: 289 | 290 | 1. Extract template content 291 | 292 | $ cd src/service 293 | $ go-slate extract apidoc contents 294 | 295 | 2. Now edit apidoc/index.html.md and add following lines to 296 | 297 | // go:generate go-slate package apidoc internal/apidoc 298 | package main 299 | 300 | import ( 301 | "net/http" 302 | "fmt" 303 | "service/internal/apidoc" 304 | ) 305 | 306 | func main() { 307 | http.HandleFunc("/help/", apidoc.HTTPHandlerWithPrefix("/help")) 308 | //... 309 | if err := http.ListenAndServe(":8080", nil); err != nil{ 310 | fmt.Println(err) 311 | } 312 | } 313 | 314 | 3. Generate package with embedded documentation 315 | 316 | $ go generate service 317 | $ go build service 318 | 319 | 3. Enjoy beautiful documentation at http://service:8080/help/ 320 | `, 321 | PersistentPreRun: func(cmd *cobra.Command, args []string) { 322 | if timings { 323 | startTs = time.Now() 324 | } 325 | }, 326 | PersistentPostRun: func(cmd *cobra.Command, args []string) { 327 | if timings { 328 | executionTime := time.Now().Sub(startTs) 329 | fmt.Printf("%0.3fs\n", executionTime.Seconds()) 330 | } 331 | }, 332 | SilenceUsage: true, 333 | } 334 | cmd.AddCommand( 335 | cmdVersion(), 336 | cmdSite(), 337 | cmdPackage(), 338 | cmdExtract(), 339 | cmdServer(), 340 | ) 341 | cmd.PersistentFlags().BoolVarP(&timings, "time", "t", false, "prints command execution time") 342 | } 343 | 344 | func main() { 345 | if err := cmd.Execute(); err != nil { 346 | os.Exit(1) 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/growler/go-slate 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/alecthomas/chroma v0.8.2 7 | github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1 // indirect 8 | github.com/dlclark/regexp2 v1.4.0 // indirect 9 | github.com/fsnotify/fsnotify v1.4.9 10 | github.com/growler/go-imbed v1.1.2 11 | github.com/russross/blackfriday/v2 v2.1.0 12 | github.com/spf13/afero v1.5.1 13 | github.com/spf13/cobra v1.1.3 14 | github.com/tdewolff/minify v2.3.6+incompatible 15 | github.com/tdewolff/parse v2.3.4+incompatible // indirect 16 | github.com/tdewolff/test v1.0.0 // indirect 17 | github.com/wellington/go-libsass v0.9.2 18 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 // indirect 19 | golang.org/x/sys v0.0.0-20210309074719-68d13333faf2 // indirect 20 | golang.org/x/text v0.3.5 // indirect 21 | gopkg.in/yaml.v2 v2.4.0 22 | ) 23 | -------------------------------------------------------------------------------- /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/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 15 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 16 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 17 | github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U= 18 | github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= 19 | github.com/alecthomas/chroma v0.8.2 h1:x3zkuE2lUk/RIekyAJ3XRqSCP4zwWDfcw/YJCuCAACg= 20 | github.com/alecthomas/chroma v0.8.2/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM= 21 | github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo= 22 | github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= 23 | github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE= 24 | github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= 25 | github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1 h1:GDQdwm/gAcJcLAKQQZGOJ4knlw+7rfEQQcmwTbt4p5E= 26 | github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= 27 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 28 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 29 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 30 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 31 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 32 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 33 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 34 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 35 | github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= 36 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 37 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 38 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 39 | github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 40 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 41 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 42 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 43 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 44 | github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= 45 | github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= 46 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 47 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 48 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 49 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 50 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 51 | github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= 52 | github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= 53 | github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= 54 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 55 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 56 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 57 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 58 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 59 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 60 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 61 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 62 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 63 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 64 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 65 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 66 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 67 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 68 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 69 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 70 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 71 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 72 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 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/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 83 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 84 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 85 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 86 | github.com/growler/go-imbed v1.1.2 h1:r6GNggOcQwjU+IOHKruKQMOn6N2Xqn9Yk558t2y7m7M= 87 | github.com/growler/go-imbed v1.1.2/go.mod h1:GZdI6xWrP5ofNpNeSUfrFMl4u6FBd7GaXAy/RS+TRZ8= 88 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 89 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 90 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 91 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= 92 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= 93 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 94 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 95 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 96 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 97 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 98 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 99 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 100 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 101 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 102 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 103 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 104 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 105 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 106 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 107 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 108 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= 109 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= 110 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 111 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 112 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 113 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 114 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 115 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 116 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 117 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 118 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 119 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 120 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 121 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 122 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 123 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 124 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 125 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 126 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 127 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 128 | github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 129 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 130 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 131 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 132 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 133 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 134 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 135 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 136 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 137 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 138 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 139 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 140 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= 141 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= 142 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 143 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 144 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 145 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 146 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 147 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 148 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 149 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 150 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 151 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 152 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 153 | github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= 154 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 155 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 156 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 157 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 158 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 159 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 160 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 161 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 162 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 163 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 164 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 165 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 166 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 167 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 168 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 169 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 170 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 171 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 172 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 173 | github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= 174 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 175 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 176 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 177 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 178 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 179 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 180 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 181 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 182 | github.com/spf13/afero v1.5.1 h1:VHu76Lk0LSP1x254maIu2bplkWpfBWI+B+6fdoZprcg= 183 | github.com/spf13/afero v1.5.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= 184 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 185 | github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= 186 | github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= 187 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 188 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 189 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 190 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 191 | github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= 192 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 193 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 194 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 195 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 196 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 197 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 198 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 199 | github.com/tdewolff/minify v2.3.6+incompatible h1:2hw5/9ZvxhWLvBUnHE06gElGYz+Jv9R4Eys0XUzItYo= 200 | github.com/tdewolff/minify v2.3.6+incompatible/go.mod h1:9Ov578KJUmAWpS6NeZwRZyT56Uf6o3Mcz9CEsg8USYs= 201 | github.com/tdewolff/parse v2.3.4+incompatible h1:x05/cnGwIMf4ceLuDMBOdQ1qGniMoxpP46ghf0Qzh38= 202 | github.com/tdewolff/parse v2.3.4+incompatible/go.mod h1:8oBwCsVmUkgHO8M5iCzSIDtpzXOT0WXX9cWhz+bIzJQ= 203 | github.com/tdewolff/test v1.0.0 h1:jOwzqCXr5ePXEPGJaq2ivoR6HOCi+D5TPfpoyg8yvmU= 204 | github.com/tdewolff/test v1.0.0/go.mod h1:DiQUlutnqlEvdvhSn2LPGy4TFwRauAaYDsL+683RNX4= 205 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 206 | github.com/wellington/go-libsass v0.9.2 h1:6Ims04UDdBs6/CGSVK5JC8FNikR5ssrsMMKE/uaO5Q8= 207 | github.com/wellington/go-libsass v0.9.2/go.mod h1:mxgxgam0N0E+NAUMHLcu20Ccfc3mVpDkyrLDayqfiTs= 208 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 209 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 210 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 211 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 212 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 213 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 214 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 215 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 216 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 217 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 218 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 219 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 220 | golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 221 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 222 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 223 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 224 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 225 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 226 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 227 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 228 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 229 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 230 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 231 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 232 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 233 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 234 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 235 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 236 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 237 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 238 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 239 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 240 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 241 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 242 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 243 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 244 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 245 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 246 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 247 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 248 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 249 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 250 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 251 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 252 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 253 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= 254 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 255 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 256 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 257 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 258 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 259 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 260 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 261 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 262 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 263 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 264 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 265 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 266 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 267 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 268 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 269 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 270 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 271 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 272 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 273 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 274 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 275 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 276 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 277 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 278 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 279 | golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 280 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 281 | golang.org/x/sys v0.0.0-20210309074719-68d13333faf2 h1:46ULzRKLh1CwgRq2dC5SlBzEqqNCi8rreOZnNrbqcIY= 282 | golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 283 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 284 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 285 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 286 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 287 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 288 | golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= 289 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 290 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 291 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 292 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 293 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 294 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 295 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 296 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 297 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 298 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 299 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 300 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 301 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 302 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 303 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 304 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 305 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 306 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 307 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 308 | golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 309 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 310 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 311 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 312 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 313 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 314 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 315 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 316 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 317 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 318 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 319 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 320 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 321 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 322 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 323 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 324 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 325 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 326 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 327 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 328 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 329 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 330 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 331 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 332 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 333 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 334 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 335 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 336 | gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 337 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 338 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 339 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 340 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 341 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 342 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 343 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 344 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 345 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 346 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 347 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 348 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 349 | -------------------------------------------------------------------------------- /server/server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexey Naidyonov. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE.md file. 4 | 5 | package server 6 | 7 | import ( 8 | "errors" 9 | "log" 10 | "net/http" 11 | "os" 12 | "path/filepath" 13 | "sync" 14 | "time" 15 | 16 | "github.com/fsnotify/fsnotify" 17 | "github.com/growler/go-slate/slate" 18 | "github.com/spf13/afero" 19 | ) 20 | 21 | func monitor(watcher *fsnotify.Watcher, src string, params slate.Params, lock *sync.RWMutex, httpFs **afero.HttpFs) { 22 | ts := time.Now() 23 | tm := time.NewTimer(0) 24 | tm.Stop() 25 | for { 26 | select { 27 | case <-tm.C: 28 | case event := <-watcher.Events: 29 | if event.Op&fsnotify.Remove == fsnotify.Remove { 30 | watcher.Remove(event.Name) 31 | } else if event.Op&fsnotify.Create == fsnotify.Create { 32 | watcher.Add(event.Name) 33 | } 34 | } 35 | now := time.Now() 36 | // totally arbitrary interval to skip bursts of changes 37 | // (Intellij Goland creates a lot of temporary files for 38 | // a single save) 39 | if now.After(ts) { 40 | ts = now.Add(2 * time.Second) 41 | fs := afero.NewMemMapFs() 42 | if err := slate.Slateficate(src, &afero.Afero{Fs: fs}, params); err != nil { 43 | log.Printf("error processing source: %s", err) 44 | } else { 45 | log.Print("documentation sucessfuly updated") 46 | lock.Lock() 47 | *httpFs = afero.NewHttpFs(fs) 48 | lock.Unlock() 49 | } 50 | tm.Stop() 51 | } else { 52 | tm.Reset(ts.Sub(now)) 53 | } 54 | } 55 | } 56 | 57 | // Serve API documentation from the src location. Monitors changes if requested and 58 | // updates rendered documentation. 59 | func Serve(src string, params slate.Params, addr, tlsCert, tlsKey string, mon bool) error { 60 | var ( 61 | err error 62 | tls bool 63 | lock sync.RWMutex 64 | httpFs *afero.HttpFs 65 | ) 66 | log.SetOutput(os.Stderr) 67 | fs := afero.NewMemMapFs() 68 | if err := slate.Slateficate(src, &afero.Afero{Fs: fs}, params); err != nil { 69 | return err 70 | } 71 | if tlsCert != "" && tlsKey != "" { 72 | tls = true 73 | } else if tlsCert != "" || tlsKey != "" { 74 | return errors.New("both cert and key must be supplied for HTTPS") 75 | } 76 | httpFs = afero.NewHttpFs(fs) 77 | if mon { 78 | watcher, err := fsnotify.NewWatcher() 79 | if err != nil { 80 | return err 81 | } 82 | filepath.Walk(src, func(path string, info os.FileInfo, err error) error { 83 | if err == nil { 84 | watcher.Add(path) 85 | } 86 | return nil 87 | }) 88 | defer watcher.Close() 89 | go monitor(watcher, src, params, &lock, &httpFs) 90 | http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { 91 | lock.RLock() 92 | fs := httpFs 93 | lock.RUnlock() 94 | if fs == nil { 95 | http.NotFound(writer, request) 96 | } 97 | http.FileServer(fs.Dir("")).ServeHTTP(writer, request) 98 | }) 99 | } else { 100 | http.Handle("/", http.FileServer(httpFs.Dir(""))) 101 | } 102 | if tls { 103 | err = http.ListenAndServeTLS(addr, tlsCert, tlsKey, nil) 104 | } else { 105 | err = http.ListenAndServe(addr, nil) 106 | } 107 | return err 108 | } 109 | -------------------------------------------------------------------------------- /slate/_slate/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Version 2.3.1 4 | 5 | *July 5, 2018* 6 | 7 | - Update `sprockets` in `Gemfile.lock` to fix security warnings 8 | 9 | ## Version 2.3 10 | 11 | *July 5, 2018* 12 | 13 | - Allows strikethrough in markdown by default. 14 | - Upgrades jQuery to 3.2.1, thanks to [Tomi Takussaari](https://github.com/TomiTakussaari) 15 | - Fixes invalid HTML in `layout.erb`, thanks to [Eric Scouten](https://github.com/scouten) for pointing out 16 | - Hopefully fixes Vagrant memory issues, thanks to [Petter Blomberg](https://github.com/p-blomberg) for the suggestion 17 | - Cleans HTML in headers before setting `document.title`, thanks to [Dan Levy](https://github.com/justsml) 18 | - Allows trailing whitespace in markdown files, thanks to [Samuel Cousin](https://github.com/kuzyn) 19 | - Fixes pushState/replaceState problems with scrolling not changing the document hash, thanks to [Andrey Fedorov](https://github.com/anfedorov) 20 | - Removes some outdated examples, thanks [@al-tr](https://github.com/al-tr), [Jerome Dahdah](https://github.com/jdahdah), and [Ricardo Castro](https://github.com/mccricardo) 21 | - Fixes `nav-padding` bug, thanks [Jerome Dahdah](https://github.com/jdahdah) 22 | - Code style fixes thanks to [Sebastian Zaremba](https://github.com/vassyz) 23 | - Nokogiri version bump thanks to [Grey Baker](https://github.com/greysteil) 24 | - Fix to default `index.md` text thanks to [Nick Busey](https://github.com/NickBusey) 25 | 26 | Thanks to everyone who contributed to this release! 27 | 28 | ## Version 2.2 29 | 30 | *January 19, 2018* 31 | 32 | - Fixes bugs with some non-roman languages not generating unique headers 33 | - Adds editorconfig, thanks to [Jay Thomas](https://github.com/jaythomas) 34 | - Adds optional `NestingUniqueHeadCounter`, thanks to [Vladimir Morozov](https://github.com/greenhost87) 35 | - Small fixes to typos and language, thx [Emir Ribić](https://github.com/ribice), [Gregor Martynus](https://github.com/gr2m), and [Martius](https://github.com/martiuslim)! 36 | - Adds links to Spectrum chat for questions in README and ISSUE_TEMPLATE 37 | 38 | ## Version 2.1 39 | 40 | *October 30, 2017* 41 | 42 | - Right-to-left text stylesheet option, thanks to [Mohammad Hossein Rabiee](https://github.com/mhrabiee) 43 | - Fix for HTML5 history state bug, thanks to [Zach Toolson](https://github.com/ztoolson) 44 | - Small styling changes, typo fixes, small bug fixes from [Marian Friedmann](https://github.com/rnarian), [Ben Wilhelm](https://github.com/benwilhelm), [Fouad Matin](https://github.com/fouad), [Nicolas Bonduel](https://github.com/NicolasBonduel), [Christian Oliff](https://github.com/coliff) 45 | 46 | Thanks to everyone who submitted PRs for this version! 47 | 48 | ## Version 2.0 49 | 50 | *July 17, 2017* 51 | 52 | - All-new statically generated table of contents 53 | - Should be much faster loading and scrolling for large pages 54 | - Smaller Javascript file sizes 55 | - Avoids the problem with the last link in the ToC not ever highlighting if the section was shorter than the page 56 | - Fixes control-click not opening in a new page 57 | - Automatically updates the HTML title as you scroll 58 | - Updated design 59 | - New default colors! 60 | - New spacings and sizes! 61 | - System-default typefaces, just like GitHub 62 | - Added search input delay on large corpuses to reduce lag 63 | - We even bumped the major version cause hey, why not? 64 | - Various small bug fixes 65 | 66 | Thanks to everyone who helped debug or wrote code for this version! It was a serious community effort, and I couldn't have done it alone. 67 | 68 | ## Version 1.5 69 | 70 | *February 23, 2017* 71 | 72 | - Add [multiple tabs per programming language](https://github.com/lord/slate/wiki/Multiple-language-tabs-per-programming-language) feature 73 | - Upgrade Middleman to add Ruby 1.4.0 compatibility 74 | - Switch default code highlighting color scheme to better highlight JSON 75 | - Various small typo and bug fixes 76 | 77 | ## Version 1.4 78 | 79 | *November 24, 2016* 80 | 81 | - Upgrade Middleman and Rouge gems, should hopefully solve a number of bugs 82 | - Update some links in README 83 | - Fix broken Vagrant startup script 84 | - Fix some problems with deploy.sh help message 85 | - Fix bug with language tabs not hiding properly if no error 86 | - Add `!default` to SASS variables 87 | - Fix bug with logo margin 88 | - Bump tested Ruby versions in .travis.yml 89 | 90 | ## Version 1.3.3 91 | 92 | *June 11, 2016* 93 | 94 | Documentation and example changes. 95 | 96 | ## Version 1.3.2 97 | 98 | *February 3, 2016* 99 | 100 | A small bugfix for slightly incorrect background colors on code samples in some cases. 101 | 102 | ## Version 1.3.1 103 | 104 | *January 31, 2016* 105 | 106 | A small bugfix for incorrect whitespace in code blocks. 107 | 108 | ## Version 1.3 109 | 110 | *January 27, 2016* 111 | 112 | We've upgraded Middleman and a number of other dependencies, which should fix quite a few bugs. 113 | 114 | Instead of `rake build` and `rake deploy`, you should now run `bundle exec middleman build --clean` to build your server, and `./deploy.sh` to deploy it to Github Pages. 115 | 116 | ## Version 1.2 117 | 118 | *June 20, 2015* 119 | 120 | **Fixes:** 121 | 122 | - Remove crash on invalid languages 123 | - Update Tocify to scroll to the highlighted header in the Table of Contents 124 | - Fix variable leak and update search algorithms 125 | - Update Python examples to be valid Python 126 | - Update gems 127 | - More misc. bugfixes of Javascript errors 128 | - Add Dockerfile 129 | - Remove unused gems 130 | - Optimize images, fonts, and generated asset files 131 | - Add chinese font support 132 | - Remove RedCarpet header ID patch 133 | - Update language tabs to not disturb existing query strings 134 | 135 | ## Version 1.1 136 | 137 | *July 27, 2014* 138 | 139 | **Fixes:** 140 | 141 | - Finally, a fix for the redcarpet upgrade bug 142 | 143 | ## Version 1.0 144 | 145 | *July 2, 2014* 146 | 147 | [View Issues](https://github.com/tripit/slate/issues?milestone=1&state=closed) 148 | 149 | **Features:** 150 | 151 | - Responsive designs for phones and tablets 152 | - Started tagging versions 153 | 154 | **Fixes:** 155 | 156 | - Fixed 'unrecognized expression' error 157 | - Fixed #undefined hash bug 158 | - Fixed bug where the current language tab would be unselected 159 | - Fixed bug where tocify wouldn't highlight the current section while searching 160 | - Fixed bug where ids of header tags would have special characters that caused problems 161 | - Updated layout so that pages with disabled search wouldn't load search.js 162 | - Cleaned up Javascript 163 | -------------------------------------------------------------------------------- /slate/_slate/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2008-2013 Concur Technologies, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | not use this file except in compliance with the License. You may obtain 5 | a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | License for the specific language governing permissions and limitations 13 | under the License. -------------------------------------------------------------------------------- /slate/_slate/README.md: -------------------------------------------------------------------------------- 1 |

2 | Slate: API Documentation Generator 3 |
4 | Build Status 5 |

6 | 7 |

Slate helps you create beautiful, intelligent, responsive API documentation.

8 | 9 |

Screenshot of Example Documentation created with Slate

10 | 11 |

The example above was created with Slate. Check it out at lord.github.io/slate.

12 | 13 | Features 14 | ------------ 15 | 16 | * **Clean, intuitive design** — With Slate, the description of your API is on the left side of your documentation, and all the code examples are on the right side. Inspired by [Stripe's](https://stripe.com/docs/api) and [PayPal's](https://developer.paypal.com/webapps/developer/docs/api/) API docs. Slate is responsive, so it looks great on tablets, phones, and even in print. 17 | 18 | * **Everything on a single page** — Gone are the days when your users had to search through a million pages to find what they wanted. Slate puts the entire documentation on a single page. We haven't sacrificed linkability, though. As you scroll, your browser's hash will update to the nearest header, so linking to a particular point in the documentation is still natural and easy. 19 | 20 | * **Slate is just Markdown** — When you write docs with Slate, you're just writing Markdown, which makes it simple to edit and understand. Everything is written in Markdown — even the code samples are just Markdown code blocks. 21 | 22 | * **Write code samples in multiple languages** — If your API has bindings in multiple programming languages, you can easily put in tabs to switch between them. In your document, you'll distinguish different languages by specifying the language name at the top of each code block, just like with GitHub Flavored Markdown. 23 | 24 | * **Out-of-the-box syntax highlighting** for [over 100 languages](https://github.com/jneen/rouge/wiki/List-of-supported-languages-and-lexers), no configuration required. 25 | 26 | * **Automatic, smoothly scrolling table of contents** on the far left of the page. As you scroll, it displays your current position in the document. It's fast, too. We're using Slate at TripIt to build documentation for our new API, where our table of contents has over 180 entries. We've made sure that the performance remains excellent, even for larger documents. 27 | 28 | * **Let your users update your documentation for you** — By default, your Slate-generated documentation is hosted in a public GitHub repository. Not only does this mean you get free hosting for your docs with GitHub Pages, but it also makes it simple for other developers to make pull requests to your docs if they find typos or other problems. Of course, if you don't want to use GitHub, you're also welcome to host your docs elsewhere. 29 | 30 | * **RTL Support** Full right-to-left layout for RTL languages such as Arabic, Persian (Farsi), Hebrew etc. 31 | 32 | Getting started with Slate is super easy! Simply fork this repository and follow the instructions below. Or, if you'd like to check out what Slate is capable of, take a look at the [sample docs](http://lord.github.io/slate). 33 | 34 | Getting Started with Slate 35 | ------------------------------ 36 | 37 | ### Prerequisites 38 | 39 | You're going to need: 40 | 41 | - **Linux or macOS** — Windows may work, but is unsupported. 42 | - **Ruby, version 2.3.1 or newer** 43 | - **Bundler** — If Ruby is already installed, but the `bundle` command doesn't work, just run `gem install bundler` in a terminal. 44 | 45 | ### Getting Set Up 46 | 47 | 1. Fork this repository on GitHub. 48 | 2. Clone *your forked repository* (not our original one) to your hard drive with `git clone https://github.com/YOURUSERNAME/slate.git` 49 | 3. `cd slate` 50 | 4. Initialize and start Slate. You can either do this locally, or with Vagrant: 51 | 52 | ```shell 53 | # either run this to run locally 54 | bundle install 55 | bundle exec middleman server 56 | 57 | # OR run this to run with vagrant 58 | vagrant up 59 | ``` 60 | 61 | You can now see the docs at http://localhost:4567. Whoa! That was fast! 62 | 63 | Now that Slate is all set up on your machine, you'll probably want to learn more about [editing Slate markdown](https://github.com/lord/slate/wiki/Markdown-Syntax), or [how to publish your docs](https://github.com/lord/slate/wiki/Deploying-Slate). 64 | 65 | If you'd prefer to use Docker, instructions are available [in the wiki](https://github.com/lord/slate/wiki/Docker). 66 | 67 | ### Note on JavaScript Runtime 68 | 69 | For those who don't have JavaScript runtime or are experiencing JavaScript runtime issues with ExecJS, it is recommended to add the [rubyracer gem](https://github.com/cowboyd/therubyracer) to your gemfile and run `bundle` again. 70 | 71 | Companies Using Slate 72 | --------------------------------- 73 | 74 | * [NASA](https://api.nasa.gov) 75 | * [Sony](http://developers.cimediacloud.com) 76 | * [Best Buy](https://bestbuyapis.github.io/api-documentation/) 77 | * [Travis-CI](https://docs.travis-ci.com/api/) 78 | * [Greenhouse](https://developers.greenhouse.io/harvest.html) 79 | * [Woocommerce](http://woocommerce.github.io/woocommerce-rest-api-docs/) 80 | * [Dwolla](https://docs.dwolla.com/) 81 | * [Clearbit](https://clearbit.com/docs) 82 | * [Coinbase](https://developers.coinbase.com/api) 83 | * [Parrot Drones](http://developer.parrot.com/docs/bebop/) 84 | * [Scale](https://docs.scaleapi.com/) 85 | 86 | You can view more in [the list on the wiki](https://github.com/lord/slate/wiki/Slate-in-the-Wild). 87 | 88 | Questions? Need Help? Found a bug? 89 | -------------------- 90 | 91 | If you've got questions about setup, deploying, special feature implementation in your fork, or just want to chat with the developer, please feel free to [start a thread in our Spectrum community](https://spectrum.chat/slate)! 92 | 93 | Found a bug with upstream Slate? Go ahead and [submit an issue](https://github.com/lord/slate/issues). And, of course, feel free to submit pull requests with bug fixes or changes to the `dev` branch. 94 | 95 | Contributors 96 | -------------------- 97 | 98 | Slate was built by [Robert Lord](https://lord.io) while interning at [TripIt](https://www.tripit.com/). 99 | 100 | Thanks to the following people who have submitted major pull requests: 101 | 102 | - [@chrissrogers](https://github.com/chrissrogers) 103 | - [@bootstraponline](https://github.com/bootstraponline) 104 | - [@realityking](https://github.com/realityking) 105 | - [@cvkef](https://github.com/cvkef) 106 | 107 | Also, thanks to [Sauce Labs](http://saucelabs.com) for sponsoring the development of the responsive styles. 108 | 109 | Special Thanks 110 | -------------------- 111 | - [Middleman](https://github.com/middleman/middleman) 112 | - [jquery.tocify.js](https://github.com/gfranko/jquery.tocify.js) 113 | - [middleman-syntax](https://github.com/middleman/middleman-syntax) 114 | - [middleman-gh-pages](https://github.com/edgecase/middleman-gh-pages) 115 | - [Font Awesome](http://fortawesome.github.io/Font-Awesome/) 116 | -------------------------------------------------------------------------------- /slate/_slate/fonts/slate.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/growler/go-slate/2bec607d2672ca5b86680d7169b497c579657392/slate/_slate/fonts/slate.eot -------------------------------------------------------------------------------- /slate/_slate/fonts/slate.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /slate/_slate/fonts/slate.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/growler/go-slate/2bec607d2672ca5b86680d7169b497c579657392/slate/_slate/fonts/slate.ttf -------------------------------------------------------------------------------- /slate/_slate/fonts/slate.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/growler/go-slate/2bec607d2672ca5b86680d7169b497c579657392/slate/_slate/fonts/slate.woff -------------------------------------------------------------------------------- /slate/_slate/fonts/slate.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/growler/go-slate/2bec607d2672ca5b86680d7169b497c579657392/slate/_slate/fonts/slate.woff2 -------------------------------------------------------------------------------- /slate/_slate/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/growler/go-slate/2bec607d2672ca5b86680d7169b497c579657392/slate/_slate/images/logo.png -------------------------------------------------------------------------------- /slate/_slate/images/navbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/growler/go-slate/2bec607d2672ca5b86680d7169b497c579657392/slate/_slate/images/navbar.png -------------------------------------------------------------------------------- /slate/_slate/includes/_errors.md: -------------------------------------------------------------------------------- 1 | # Errors 2 | 3 | 6 | 7 | The Kittn API uses the following error codes: 8 | 9 | 10 | Error Code | Meaning 11 | ---------- | ------- 12 | 400 | Bad Request -- Your request is invalid. 13 | 401 | Unauthorized -- Your API key is wrong. 14 | 403 | Forbidden -- The kitten requested is hidden for administrators only. 15 | 404 | Not Found -- The specified kitten could not be found. 16 | 405 | Method Not Allowed -- You tried to access a kitten with an invalid method. 17 | 406 | Not Acceptable -- You requested a format that isn't json. 18 | 410 | Gone -- The kitten requested has been removed from our servers. 19 | 418 | I'm a teapot. 20 | 429 | Too Many Requests -- You're requesting too many kittens! Slow down! 21 | 500 | Internal Server Error -- We had a problem with our server. Try again later. 22 | 503 | Service Unavailable -- We're temporarily offline for maintenance. Please try again later. 23 | -------------------------------------------------------------------------------- /slate/_slate/index.html.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: API Reference 3 | 4 | language_tabs: # must be one of https://git.io/vQNgJ 5 | - shell 6 | - ruby 7 | - python 8 | - javascript 9 | 10 | toc_footers: 11 | - Sign Up for a Developer Key 12 | - Documentation Powered by Slate 13 | 14 | includes: 15 | - errors 16 | 17 | search: true 18 | --- 19 | 20 | # Introduction 21 | 22 | Welcome to the Kittn API! You can use our API to access Kittn API endpoints, which can get information on various cats, kittens, and breeds in our database. 23 | 24 | We have language bindings in Shell, Ruby, Python, and JavaScript! You can view code examples in the dark area to the right, and you can switch the programming language of the examples with the tabs in the top right. 25 | 26 | This example API documentation page was created with [Slate](https://github.com/lord/slate). Feel free to edit it and use it as a base for your own API's documentation. 27 | 28 | # Authentication 29 | 30 | > To authorize, use this code: 31 | 32 | ```ruby 33 | require 'kittn' 34 | 35 | api = Kittn::APIClient.authorize!('meowmeowmeow') 36 | ``` 37 | 38 | ```python 39 | import kittn 40 | 41 | api = kittn.authorize('meowmeowmeow') 42 | ``` 43 | 44 | ```shell 45 | # With shell, you can just pass the correct header with each request 46 | curl "api_endpoint_here" 47 | -H "Authorization: meowmeowmeow" 48 | ``` 49 | 50 | ```javascript 51 | const kittn = require('kittn'); 52 | 53 | let api = kittn.authorize('meowmeowmeow'); 54 | ``` 55 | 56 | > Make sure to replace `meowmeowmeow` with your API key. 57 | 58 | Kittn uses API keys to allow access to the API. You can register a new Kittn API key at our [developer portal](http://example.com/developers). 59 | 60 | Kittn expects for the API key to be included in all API requests to the server in a header that looks like the following: 61 | 62 | `Authorization: meowmeowmeow` 63 | 64 | 67 | 68 | # Kittens 69 | 70 | ## Get All Kittens 71 | 72 | ```ruby 73 | require 'kittn' 74 | 75 | api = Kittn::APIClient.authorize!('meowmeowmeow') 76 | api.kittens.get 77 | ``` 78 | 79 | ```python 80 | import kittn 81 | 82 | api = kittn.authorize('meowmeowmeow') 83 | api.kittens.get() 84 | ``` 85 | 86 | ```shell 87 | curl "http://example.com/api/kittens" 88 | -H "Authorization: meowmeowmeow" 89 | ``` 90 | 91 | ```javascript 92 | const kittn = require('kittn'); 93 | 94 | let api = kittn.authorize('meowmeowmeow'); 95 | let kittens = api.kittens.get(); 96 | ``` 97 | 98 | > The above command returns JSON structured like this: 99 | 100 | ```json 101 | [ 102 | { 103 | "id": 1, 104 | "name": "Fluffums", 105 | "breed": "calico", 106 | "fluffiness": 6, 107 | "cuteness": 7 108 | }, 109 | { 110 | "id": 2, 111 | "name": "Max", 112 | "breed": "unknown", 113 | "fluffiness": 5, 114 | "cuteness": 10 115 | } 116 | ] 117 | ``` 118 | 119 | This endpoint retrieves all kittens. 120 | 121 | ### HTTP Request 122 | 123 | `GET http://example.com/api/kittens` 124 | 125 | ### Query Parameters 126 | 127 | Parameter | Default | Description 128 | --------- | ------- | ----------- 129 | include_cats | false | If set to true, the result will also include cats. 130 | available | true | If set to false, the result will include kittens that have already been adopted. 131 | 132 | 135 | 136 | ## Get a Specific Kitten 137 | 138 | ```ruby 139 | require 'kittn' 140 | 141 | api = Kittn::APIClient.authorize!('meowmeowmeow') 142 | api.kittens.get(2) 143 | ``` 144 | 145 | ```python 146 | import kittn 147 | 148 | api = kittn.authorize('meowmeowmeow') 149 | api.kittens.get(2) 150 | ``` 151 | 152 | ```shell 153 | curl "http://example.com/api/kittens/2" 154 | -H "Authorization: meowmeowmeow" 155 | ``` 156 | 157 | ```javascript 158 | const kittn = require('kittn'); 159 | 160 | let api = kittn.authorize('meowmeowmeow'); 161 | let max = api.kittens.get(2); 162 | ``` 163 | 164 | > The above command returns JSON structured like this: 165 | 166 | ```json 167 | { 168 | "id": 2, 169 | "name": "Max", 170 | "breed": "unknown", 171 | "fluffiness": 5, 172 | "cuteness": 10 173 | } 174 | ``` 175 | 176 | This endpoint retrieves a specific kitten. 177 | 178 | 179 | 180 | ### HTTP Request 181 | 182 | `GET http://example.com/kittens/` 183 | 184 | ### URL Parameters 185 | 186 | Parameter | Description 187 | --------- | ----------- 188 | ID | The ID of the kitten to retrieve 189 | 190 | ## Delete a Specific Kitten 191 | 192 | ```ruby 193 | require 'kittn' 194 | 195 | api = Kittn::APIClient.authorize!('meowmeowmeow') 196 | api.kittens.delete(2) 197 | ``` 198 | 199 | ```python 200 | import kittn 201 | 202 | api = kittn.authorize('meowmeowmeow') 203 | api.kittens.delete(2) 204 | ``` 205 | 206 | ```shell 207 | curl "http://example.com/api/kittens/2" 208 | -X DELETE 209 | -H "Authorization: meowmeowmeow" 210 | ``` 211 | 212 | ```javascript 213 | const kittn = require('kittn'); 214 | 215 | let api = kittn.authorize('meowmeowmeow'); 216 | let max = api.kittens.delete(2); 217 | ``` 218 | 219 | > The above command returns JSON structured like this: 220 | 221 | ```json 222 | { 223 | "id": 2, 224 | "deleted" : ":(" 225 | } 226 | ``` 227 | 228 | This endpoint deletes a specific kitten. 229 | 230 | ### HTTP Request 231 | 232 | `DELETE http://example.com/kittens/` 233 | 234 | ### URL Parameters 235 | 236 | Parameter | Description 237 | --------- | ----------- 238 | ID | The ID of the kitten to delete 239 | 240 | -------------------------------------------------------------------------------- /slate/_slate/javascripts/all.js: -------------------------------------------------------------------------------- 1 | //= require ./all_nosearch 2 | //= require ./app/_search 3 | -------------------------------------------------------------------------------- /slate/_slate/javascripts/all_nosearch.js: -------------------------------------------------------------------------------- 1 | //= require ./lib/_energize 2 | //= require ./app/_toc 3 | //= require ./app/_lang 4 | 5 | $(function() { 6 | loadToc($('#toc'), '.toc-link', '.toc-list-h2', 10); 7 | setupLanguages($('body').data('languages')); 8 | $('.content').imagesLoaded( function() { 9 | window.recacheHeights(); 10 | window.refreshToc(); 11 | }); 12 | }); 13 | 14 | window.onpopstate = function() { 15 | activateLanguage(getLanguageFromQueryString()); 16 | }; 17 | -------------------------------------------------------------------------------- /slate/_slate/javascripts/app/_lang.js: -------------------------------------------------------------------------------- 1 | //= require ../lib/_jquery 2 | 3 | /* 4 | Copyright 2008-2013 Concur Technologies, Inc. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | not use this file except in compliance with the License. You may obtain 8 | a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | License for the specific language governing permissions and limitations 16 | under the License. 17 | */ 18 | ;(function () { 19 | 'use strict'; 20 | 21 | var languages = []; 22 | 23 | window.setupLanguages = setupLanguages; 24 | window.activateLanguage = activateLanguage; 25 | window.getLanguageFromQueryString = getLanguageFromQueryString; 26 | 27 | function activateLanguage(language) { 28 | if (!language) return; 29 | if (language === "") return; 30 | 31 | $(".lang-selector a").removeClass('active'); 32 | $(".lang-selector a[data-language-name='" + language + "']").addClass('active'); 33 | for (var i=0; i < languages.length; i++) { 34 | $(".highlight.tab-" + languages[i]).hide(); 35 | $(".lang-specific." + languages[i]).hide(); 36 | } 37 | $(".highlight.tab-" + language).show(); 38 | $(".lang-specific." + language).show(); 39 | 40 | window.recacheHeights(); 41 | 42 | // scroll to the new location of the position 43 | if ($(window.location.hash).get(0)) { 44 | $(window.location.hash).get(0).scrollIntoView(true); 45 | } 46 | } 47 | 48 | // parseURL and stringifyURL are from https://github.com/sindresorhus/query-string 49 | // MIT licensed 50 | // https://github.com/sindresorhus/query-string/blob/7bee64c16f2da1a326579e96977b9227bf6da9e6/license 51 | function parseURL(str) { 52 | if (typeof str !== 'string') { 53 | return {}; 54 | } 55 | 56 | str = str.trim().replace(/^(\?|#|&)/, ''); 57 | 58 | if (!str) { 59 | return {}; 60 | } 61 | 62 | return str.split('&').reduce(function (ret, param) { 63 | var parts = param.replace(/\+/g, ' ').split('='); 64 | var key = parts[0]; 65 | var val = parts[1]; 66 | 67 | key = decodeURIComponent(key); 68 | // missing `=` should be `null`: 69 | // http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters 70 | val = val === undefined ? null : decodeURIComponent(val); 71 | 72 | if (!ret.hasOwnProperty(key)) { 73 | ret[key] = val; 74 | } else if (Array.isArray(ret[key])) { 75 | ret[key].push(val); 76 | } else { 77 | ret[key] = [ret[key], val]; 78 | } 79 | 80 | return ret; 81 | }, {}); 82 | }; 83 | 84 | function stringifyURL(obj) { 85 | return obj ? Object.keys(obj).sort().map(function (key) { 86 | var val = obj[key]; 87 | 88 | if (Array.isArray(val)) { 89 | return val.sort().map(function (val2) { 90 | return encodeURIComponent(key) + '=' + encodeURIComponent(val2); 91 | }).join('&'); 92 | } 93 | 94 | return encodeURIComponent(key) + '=' + encodeURIComponent(val); 95 | }).join('&') : ''; 96 | }; 97 | 98 | // gets the language set in the query string 99 | function getLanguageFromQueryString() { 100 | if (location.search.length >= 1) { 101 | var language = parseURL(location.search).language; 102 | if (language) { 103 | return language; 104 | } else if (jQuery.inArray(location.search.substr(1), languages) != -1) { 105 | return location.search.substr(1); 106 | } 107 | } 108 | 109 | return false; 110 | } 111 | 112 | // returns a new query string with the new language in it 113 | function generateNewQueryString(language) { 114 | var url = parseURL(location.search); 115 | if (url.language) { 116 | url.language = language; 117 | return stringifyURL(url); 118 | } 119 | return language; 120 | } 121 | 122 | // if a button is clicked, add the state to the history 123 | function pushURL(language) { 124 | if (!history) { return; } 125 | var hash = window.location.hash; 126 | if (hash) { 127 | hash = hash.replace(/^#+/, ''); 128 | } 129 | history.pushState({}, '', '?' + generateNewQueryString(language) + '#' + hash); 130 | 131 | // save language as next default 132 | localStorage.setItem("language", language); 133 | } 134 | 135 | function setupLanguages(l) { 136 | var defaultLanguage = localStorage.getItem("language"); 137 | 138 | languages = l; 139 | 140 | var presetLanguage = getLanguageFromQueryString(); 141 | if (presetLanguage) { 142 | // the language is in the URL, so use that language! 143 | activateLanguage(presetLanguage); 144 | 145 | localStorage.setItem("language", presetLanguage); 146 | } else if ((defaultLanguage !== null) && (jQuery.inArray(defaultLanguage, languages) != -1)) { 147 | // the language was the last selected one saved in localstorage, so use that language! 148 | activateLanguage(defaultLanguage); 149 | } else { 150 | // no language selected, so use the default 151 | activateLanguage(languages[0]); 152 | } 153 | } 154 | 155 | // if we click on a language tab, activate that language 156 | $(function() { 157 | $(".lang-selector a").on("click", function() { 158 | var language = $(this).data("language-name"); 159 | pushURL(language); 160 | activateLanguage(language); 161 | return false; 162 | }); 163 | }); 164 | })(); 165 | -------------------------------------------------------------------------------- /slate/_slate/javascripts/app/_search.js: -------------------------------------------------------------------------------- 1 | //= require ../lib/_lunr 2 | //= require ../lib/_jquery 3 | //= require ../lib/_jquery.highlight 4 | ;(function () { 5 | 'use strict'; 6 | 7 | var content, searchResults; 8 | var highlightOpts = { element: 'span', className: 'search-highlight' }; 9 | var searchDelay = 0; 10 | var timeoutHandle = 0; 11 | 12 | var index = new lunr.Index(); 13 | 14 | index.ref('id'); 15 | index.field('title', { boost: 10 }); 16 | index.field('body'); 17 | index.pipeline.add(lunr.trimmer, lunr.stopWordFilter); 18 | 19 | $(populate); 20 | $(bind); 21 | 22 | function populate() { 23 | $('h1, h2').each(function() { 24 | var title = $(this); 25 | var body = title.nextUntil('h1, h2'); 26 | index.add({ 27 | id: title.prop('id'), 28 | title: title.text(), 29 | body: body.text() 30 | }); 31 | }); 32 | 33 | determineSearchDelay(); 34 | } 35 | function determineSearchDelay() { 36 | if(index.tokenStore.length>5000) { 37 | searchDelay = 300; 38 | } 39 | } 40 | 41 | function bind() { 42 | content = $('.content'); 43 | searchResults = $('.search-results'); 44 | 45 | $('#input-search').on('keyup',function(e) { 46 | var wait = function() { 47 | return function(executingFunction, waitTime){ 48 | clearTimeout(timeoutHandle); 49 | timeoutHandle = setTimeout(executingFunction, waitTime); 50 | }; 51 | }(); 52 | wait(function(){ 53 | search(e); 54 | }, searchDelay ); 55 | }); 56 | } 57 | 58 | function search(event) { 59 | 60 | var searchInput = $('#input-search')[0]; 61 | 62 | unhighlight(); 63 | searchResults.addClass('visible'); 64 | 65 | // ESC clears the field 66 | if (event.keyCode === 27) searchInput.value = ''; 67 | 68 | if (searchInput.value) { 69 | var results = index.search(searchInput.value).filter(function(r) { 70 | return r.score > 0.0001; 71 | }); 72 | 73 | if (results.length) { 74 | searchResults.empty(); 75 | $.each(results, function (index, result) { 76 | var elem = document.getElementById(result.ref); 77 | searchResults.append("
  • " + $(elem).text() + "
  • "); 78 | }); 79 | highlight.call(searchInput); 80 | } else { 81 | searchResults.html('
  • '); 82 | $('.search-results li').text('No Results Found for "' + searchInput.value + '"'); 83 | } 84 | } else { 85 | unhighlight(); 86 | searchResults.removeClass('visible'); 87 | } 88 | } 89 | 90 | function highlight() { 91 | if (this.value) content.highlight(this.value, highlightOpts); 92 | } 93 | 94 | function unhighlight() { 95 | content.unhighlight(highlightOpts); 96 | } 97 | })(); 98 | 99 | -------------------------------------------------------------------------------- /slate/_slate/javascripts/app/_toc.js: -------------------------------------------------------------------------------- 1 | //= require ../lib/_jquery 2 | //= require ../lib/_imagesloaded.min 3 | ;(function () { 4 | 'use strict'; 5 | 6 | var htmlPattern = /<[^>]*>/g; 7 | var loaded = false; 8 | 9 | var debounce = function(func, waitTime) { 10 | var timeout = false; 11 | return function() { 12 | if (timeout === false) { 13 | setTimeout(function() { 14 | func(); 15 | timeout = false; 16 | }, waitTime); 17 | timeout = true; 18 | } 19 | }; 20 | }; 21 | 22 | var closeToc = function() { 23 | $(".toc-wrapper").removeClass('open'); 24 | $("#nav-button").removeClass('open'); 25 | }; 26 | 27 | function loadToc($toc, tocLinkSelector, tocListSelector, scrollOffset) { 28 | var headerHeights = {}; 29 | var pageHeight = 0; 30 | var windowHeight = 0; 31 | var originalTitle = document.title; 32 | 33 | var recacheHeights = function() { 34 | headerHeights = {}; 35 | pageHeight = $(document).height(); 36 | windowHeight = $(window).height(); 37 | 38 | $toc.find(tocLinkSelector).each(function() { 39 | var targetId = $(this).attr('href'); 40 | if (targetId[0] === "#") { 41 | headerHeights[targetId] = $(targetId).offset().top; 42 | } 43 | }); 44 | }; 45 | 46 | var refreshToc = function() { 47 | var currentTop = $(document).scrollTop() + scrollOffset; 48 | 49 | if (currentTop + windowHeight >= pageHeight) { 50 | // at bottom of page, so just select last header by making currentTop very large 51 | // this fixes the problem where the last header won't ever show as active if its content 52 | // is shorter than the window height 53 | currentTop = pageHeight + 1000; 54 | } 55 | 56 | var best = null; 57 | for (var name in headerHeights) { 58 | if ((headerHeights[name] < currentTop && headerHeights[name] > headerHeights[best]) || best === null) { 59 | best = name; 60 | } 61 | } 62 | 63 | // Catch the initial load case 64 | if (currentTop == scrollOffset && !loaded) { 65 | best = window.location.hash; 66 | loaded = true; 67 | } 68 | 69 | var $best = $toc.find("[href='" + best + "']").first(); 70 | if (!$best.hasClass("active")) { 71 | // .active is applied to the ToC link we're currently on, and its parent
      s selected by tocListSelector 72 | // .active-expanded is applied to the ToC links that are parents of this one 73 | $toc.find(".active").removeClass("active"); 74 | $toc.find(".active-parent").removeClass("active-parent"); 75 | $best.addClass("active"); 76 | $best.parents(tocListSelector).addClass("active").siblings(tocLinkSelector).addClass('active-parent'); 77 | $best.siblings(tocListSelector).addClass("active"); 78 | $toc.find(tocListSelector).filter(":not(.active)").slideUp(150); 79 | $toc.find(tocListSelector).filter(".active").slideDown(150); 80 | if (window.history.replaceState) { 81 | window.history.replaceState(null, "", best); 82 | } 83 | var thisTitle = $best.data("title") 84 | if (thisTitle !== undefined && thisTitle.length > 0) { 85 | document.title = thisTitle + " – " + originalTitle; 86 | } else { 87 | document.title = originalTitle; 88 | } 89 | } 90 | }; 91 | 92 | var makeToc = function() { 93 | recacheHeights(); 94 | refreshToc(); 95 | 96 | $("#nav-button").click(function() { 97 | $(".toc-wrapper").toggleClass('open'); 98 | $("#nav-button").toggleClass('open'); 99 | return false; 100 | }); 101 | $(".page-wrapper").click(closeToc); 102 | $(".toc-link").click(closeToc); 103 | 104 | // reload immediately after scrolling on toc click 105 | $toc.find(tocLinkSelector).click(function() { 106 | setTimeout(function() { 107 | refreshToc(); 108 | }, 0); 109 | }); 110 | 111 | $(window).scroll(debounce(refreshToc, 200)); 112 | $(window).resize(debounce(recacheHeights, 200)); 113 | }; 114 | 115 | makeToc(); 116 | 117 | window.recacheHeights = recacheHeights; 118 | window.refreshToc = refreshToc; 119 | } 120 | 121 | window.loadToc = loadToc; 122 | })(); 123 | -------------------------------------------------------------------------------- /slate/_slate/javascripts/lib/_energize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * energize.js v0.1.0 3 | * 4 | * Speeds up click events on mobile devices. 5 | * https://github.com/davidcalhoun/energize.js 6 | */ 7 | 8 | (function() { // Sandbox 9 | /** 10 | * Don't add to non-touch devices, which don't need to be sped up 11 | */ 12 | if(!('ontouchstart' in window)) return; 13 | 14 | var lastClick = {}, 15 | isThresholdReached, touchstart, touchmove, touchend, 16 | click, closest; 17 | 18 | /** 19 | * isThresholdReached 20 | * 21 | * Compare touchstart with touchend xy coordinates, 22 | * and only fire simulated click event if the coordinates 23 | * are nearby. (don't want clicking to be confused with a swipe) 24 | */ 25 | isThresholdReached = function(startXY, xy) { 26 | return Math.abs(startXY[0] - xy[0]) > 5 || Math.abs(startXY[1] - xy[1]) > 5; 27 | }; 28 | 29 | /** 30 | * touchstart 31 | * 32 | * Save xy coordinates when the user starts touching the screen 33 | */ 34 | touchstart = function(e) { 35 | this.startXY = [e.touches[0].clientX, e.touches[0].clientY]; 36 | this.threshold = false; 37 | }; 38 | 39 | /** 40 | * touchmove 41 | * 42 | * Check if the user is scrolling past the threshold. 43 | * Have to check here because touchend will not always fire 44 | * on some tested devices (Kindle Fire?) 45 | */ 46 | touchmove = function(e) { 47 | // NOOP if the threshold has already been reached 48 | if(this.threshold) return false; 49 | 50 | this.threshold = isThresholdReached(this.startXY, [e.touches[0].clientX, e.touches[0].clientY]); 51 | }; 52 | 53 | /** 54 | * touchend 55 | * 56 | * If the user didn't scroll past the threshold between 57 | * touchstart and touchend, fire a simulated click. 58 | * 59 | * (This will fire before a native click) 60 | */ 61 | touchend = function(e) { 62 | // Don't fire a click if the user scrolled past the threshold 63 | if(this.threshold || isThresholdReached(this.startXY, [e.changedTouches[0].clientX, e.changedTouches[0].clientY])) { 64 | return; 65 | } 66 | 67 | /** 68 | * Create and fire a click event on the target element 69 | * https://developer.mozilla.org/en/DOM/event.initMouseEvent 70 | */ 71 | var touch = e.changedTouches[0], 72 | evt = document.createEvent('MouseEvents'); 73 | evt.initMouseEvent('click', true, true, window, 0, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null); 74 | evt.simulated = true; // distinguish from a normal (nonsimulated) click 75 | e.target.dispatchEvent(evt); 76 | }; 77 | 78 | /** 79 | * click 80 | * 81 | * Because we've already fired a click event in touchend, 82 | * we need to listed for all native click events here 83 | * and suppress them as necessary. 84 | */ 85 | click = function(e) { 86 | /** 87 | * Prevent ghost clicks by only allowing clicks we created 88 | * in the click event we fired (look for e.simulated) 89 | */ 90 | var time = Date.now(), 91 | timeDiff = time - lastClick.time, 92 | x = e.clientX, 93 | y = e.clientY, 94 | xyDiff = [Math.abs(lastClick.x - x), Math.abs(lastClick.y - y)], 95 | target = closest(e.target, 'A') || e.target, // needed for standalone apps 96 | nodeName = target.nodeName, 97 | isLink = nodeName === 'A', 98 | standAlone = window.navigator.standalone && isLink && e.target.getAttribute("href"); 99 | 100 | lastClick.time = time; 101 | lastClick.x = x; 102 | lastClick.y = y; 103 | 104 | /** 105 | * Unfortunately Android sometimes fires click events without touch events (seen on Kindle Fire), 106 | * so we have to add more logic to determine the time of the last click. Not perfect... 107 | * 108 | * Older, simpler check: if((!e.simulated) || standAlone) 109 | */ 110 | if((!e.simulated && (timeDiff < 500 || (timeDiff < 1500 && xyDiff[0] < 50 && xyDiff[1] < 50))) || standAlone) { 111 | e.preventDefault(); 112 | e.stopPropagation(); 113 | if(!standAlone) return false; 114 | } 115 | 116 | /** 117 | * Special logic for standalone web apps 118 | * See http://stackoverflow.com/questions/2898740/iphone-safari-web-app-opens-links-in-new-window 119 | */ 120 | if(standAlone) { 121 | window.location = target.getAttribute("href"); 122 | } 123 | 124 | /** 125 | * Add an energize-focus class to the targeted link (mimics :focus behavior) 126 | * TODO: test and/or remove? Does this work? 127 | */ 128 | if(!target || !target.classList) return; 129 | target.classList.add("energize-focus"); 130 | window.setTimeout(function(){ 131 | target.classList.remove("energize-focus"); 132 | }, 150); 133 | }; 134 | 135 | /** 136 | * closest 137 | * @param {HTMLElement} node current node to start searching from. 138 | * @param {string} tagName the (uppercase) name of the tag you're looking for. 139 | * 140 | * Find the closest ancestor tag of a given node. 141 | * 142 | * Starts at node and goes up the DOM tree looking for a 143 | * matching nodeName, continuing until hitting document.body 144 | */ 145 | closest = function(node, tagName){ 146 | var curNode = node; 147 | 148 | while(curNode !== document.body) { // go up the dom until we find the tag we're after 149 | if(!curNode || curNode.nodeName === tagName) { return curNode; } // found 150 | curNode = curNode.parentNode; // not found, so keep going up 151 | } 152 | 153 | return null; // not found 154 | }; 155 | 156 | /** 157 | * Add all delegated event listeners 158 | * 159 | * All the events we care about bubble up to document, 160 | * so we can take advantage of event delegation. 161 | * 162 | * Note: no need to wait for DOMContentLoaded here 163 | */ 164 | document.addEventListener('touchstart', touchstart, false); 165 | document.addEventListener('touchmove', touchmove, false); 166 | document.addEventListener('touchend', touchend, false); 167 | document.addEventListener('click', click, true); // TODO: why does this use capture? 168 | 169 | })(); -------------------------------------------------------------------------------- /slate/_slate/javascripts/lib/_imagesloaded.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * imagesLoaded PACKAGED v3.1.8 3 | * JavaScript is all like "You images are done yet or what?" 4 | * MIT License 5 | */ 6 | 7 | (function(){function e(){}function t(e,t){for(var n=e.length;n--;)if(e[n].listener===t)return n;return-1}function n(e){return function(){return this[e].apply(this,arguments)}}var i=e.prototype,r=this,o=r.EventEmitter;i.getListeners=function(e){var t,n,i=this._getEvents();if("object"==typeof e){t={};for(n in i)i.hasOwnProperty(n)&&e.test(n)&&(t[n]=i[n])}else t=i[e]||(i[e]=[]);return t},i.flattenListeners=function(e){var t,n=[];for(t=0;e.length>t;t+=1)n.push(e[t].listener);return n},i.getListenersAsObject=function(e){var t,n=this.getListeners(e);return n instanceof Array&&(t={},t[e]=n),t||n},i.addListener=function(e,n){var i,r=this.getListenersAsObject(e),o="object"==typeof n;for(i in r)r.hasOwnProperty(i)&&-1===t(r[i],n)&&r[i].push(o?n:{listener:n,once:!1});return this},i.on=n("addListener"),i.addOnceListener=function(e,t){return this.addListener(e,{listener:t,once:!0})},i.once=n("addOnceListener"),i.defineEvent=function(e){return this.getListeners(e),this},i.defineEvents=function(e){for(var t=0;e.length>t;t+=1)this.defineEvent(e[t]);return this},i.removeListener=function(e,n){var i,r,o=this.getListenersAsObject(e);for(r in o)o.hasOwnProperty(r)&&(i=t(o[r],n),-1!==i&&o[r].splice(i,1));return this},i.off=n("removeListener"),i.addListeners=function(e,t){return this.manipulateListeners(!1,e,t)},i.removeListeners=function(e,t){return this.manipulateListeners(!0,e,t)},i.manipulateListeners=function(e,t,n){var i,r,o=e?this.removeListener:this.addListener,s=e?this.removeListeners:this.addListeners;if("object"!=typeof t||t instanceof RegExp)for(i=n.length;i--;)o.call(this,t,n[i]);else for(i in t)t.hasOwnProperty(i)&&(r=t[i])&&("function"==typeof r?o.call(this,i,r):s.call(this,i,r));return this},i.removeEvent=function(e){var t,n=typeof e,i=this._getEvents();if("string"===n)delete i[e];else if("object"===n)for(t in i)i.hasOwnProperty(t)&&e.test(t)&&delete i[t];else delete this._events;return this},i.removeAllListeners=n("removeEvent"),i.emitEvent=function(e,t){var n,i,r,o,s=this.getListenersAsObject(e);for(r in s)if(s.hasOwnProperty(r))for(i=s[r].length;i--;)n=s[r][i],n.once===!0&&this.removeListener(e,n.listener),o=n.listener.apply(this,t||[]),o===this._getOnceReturnValue()&&this.removeListener(e,n.listener);return this},i.trigger=n("emitEvent"),i.emit=function(e){var t=Array.prototype.slice.call(arguments,1);return this.emitEvent(e,t)},i.setOnceReturnValue=function(e){return this._onceReturnValue=e,this},i._getOnceReturnValue=function(){return this.hasOwnProperty("_onceReturnValue")?this._onceReturnValue:!0},i._getEvents=function(){return this._events||(this._events={})},e.noConflict=function(){return r.EventEmitter=o,e},"function"==typeof define&&define.amd?define("eventEmitter/EventEmitter",[],function(){return e}):"object"==typeof module&&module.exports?module.exports=e:this.EventEmitter=e}).call(this),function(e){function t(t){var n=e.event;return n.target=n.target||n.srcElement||t,n}var n=document.documentElement,i=function(){};n.addEventListener?i=function(e,t,n){e.addEventListener(t,n,!1)}:n.attachEvent&&(i=function(e,n,i){e[n+i]=i.handleEvent?function(){var n=t(e);i.handleEvent.call(i,n)}:function(){var n=t(e);i.call(e,n)},e.attachEvent("on"+n,e[n+i])});var r=function(){};n.removeEventListener?r=function(e,t,n){e.removeEventListener(t,n,!1)}:n.detachEvent&&(r=function(e,t,n){e.detachEvent("on"+t,e[t+n]);try{delete e[t+n]}catch(i){e[t+n]=void 0}});var o={bind:i,unbind:r};"function"==typeof define&&define.amd?define("eventie/eventie",o):e.eventie=o}(this),function(e,t){"function"==typeof define&&define.amd?define(["eventEmitter/EventEmitter","eventie/eventie"],function(n,i){return t(e,n,i)}):"object"==typeof exports?module.exports=t(e,require("wolfy87-eventemitter"),require("eventie")):e.imagesLoaded=t(e,e.EventEmitter,e.eventie)}(window,function(e,t,n){function i(e,t){for(var n in t)e[n]=t[n];return e}function r(e){return"[object Array]"===d.call(e)}function o(e){var t=[];if(r(e))t=e;else if("number"==typeof e.length)for(var n=0,i=e.length;i>n;n++)t.push(e[n]);else t.push(e);return t}function s(e,t,n){if(!(this instanceof s))return new s(e,t);"string"==typeof e&&(e=document.querySelectorAll(e)),this.elements=o(e),this.options=i({},this.options),"function"==typeof t?n=t:i(this.options,t),n&&this.on("always",n),this.getImages(),a&&(this.jqDeferred=new a.Deferred);var r=this;setTimeout(function(){r.check()})}function f(e){this.img=e}function c(e){this.src=e,v[e]=this}var a=e.jQuery,u=e.console,h=u!==void 0,d=Object.prototype.toString;s.prototype=new t,s.prototype.options={},s.prototype.getImages=function(){this.images=[];for(var e=0,t=this.elements.length;t>e;e++){var n=this.elements[e];"IMG"===n.nodeName&&this.addImage(n);var i=n.nodeType;if(i&&(1===i||9===i||11===i))for(var r=n.querySelectorAll("img"),o=0,s=r.length;s>o;o++){var f=r[o];this.addImage(f)}}},s.prototype.addImage=function(e){var t=new f(e);this.images.push(t)},s.prototype.check=function(){function e(e,r){return t.options.debug&&h&&u.log("confirm",e,r),t.progress(e),n++,n===i&&t.complete(),!0}var t=this,n=0,i=this.images.length;if(this.hasAnyBroken=!1,!i)return this.complete(),void 0;for(var r=0;i>r;r++){var o=this.images[r];o.on("confirm",e),o.check()}},s.prototype.progress=function(e){this.hasAnyBroken=this.hasAnyBroken||!e.isLoaded;var t=this;setTimeout(function(){t.emit("progress",t,e),t.jqDeferred&&t.jqDeferred.notify&&t.jqDeferred.notify(t,e)})},s.prototype.complete=function(){var e=this.hasAnyBroken?"fail":"done";this.isComplete=!0;var t=this;setTimeout(function(){if(t.emit(e,t),t.emit("always",t),t.jqDeferred){var n=t.hasAnyBroken?"reject":"resolve";t.jqDeferred[n](t)}})},a&&(a.fn.imagesLoaded=function(e,t){var n=new s(this,e,t);return n.jqDeferred.promise(a(this))}),f.prototype=new t,f.prototype.check=function(){var e=v[this.img.src]||new c(this.img.src);if(e.isConfirmed)return this.confirm(e.isLoaded,"cached was confirmed"),void 0;if(this.img.complete&&void 0!==this.img.naturalWidth)return this.confirm(0!==this.img.naturalWidth,"naturalWidth"),void 0;var t=this;e.on("confirm",function(e,n){return t.confirm(e.isLoaded,n),!0}),e.check()},f.prototype.confirm=function(e,t){this.isLoaded=e,this.emit("confirm",this,t)};var v={};return c.prototype=new t,c.prototype.check=function(){if(!this.isChecked){var e=new Image;n.bind(e,"load",this),n.bind(e,"error",this),e.src=this.src,this.isChecked=!0}},c.prototype.handleEvent=function(e){var t="on"+e.type;this[t]&&this[t](e)},c.prototype.onload=function(e){this.confirm(!0,"onload"),this.unbindProxyEvents(e)},c.prototype.onerror=function(e){this.confirm(!1,"onerror"),this.unbindProxyEvents(e)},c.prototype.confirm=function(e,t){this.isConfirmed=!0,this.isLoaded=e,this.emit("confirm",this,t)},c.prototype.unbindProxyEvents=function(e){n.unbind(e.target,"load",this),n.unbind(e.target,"error",this)},s}); -------------------------------------------------------------------------------- /slate/_slate/javascripts/lib/_jquery.highlight.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery Highlight plugin 3 | * 4 | * Based on highlight v3 by Johann Burkard 5 | * http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html 6 | * 7 | * Code a little bit refactored and cleaned (in my humble opinion). 8 | * Most important changes: 9 | * - has an option to highlight only entire words (wordsOnly - false by default), 10 | * - has an option to be case sensitive (caseSensitive - false by default) 11 | * - highlight element tag and class names can be specified in options 12 | * 13 | * Usage: 14 | * // wrap every occurrance of text 'lorem' in content 15 | * // with (default options) 16 | * $('#content').highlight('lorem'); 17 | * 18 | * // search for and highlight more terms at once 19 | * // so you can save some time on traversing DOM 20 | * $('#content').highlight(['lorem', 'ipsum']); 21 | * $('#content').highlight('lorem ipsum'); 22 | * 23 | * // search only for entire word 'lorem' 24 | * $('#content').highlight('lorem', { wordsOnly: true }); 25 | * 26 | * // don't ignore case during search of term 'lorem' 27 | * $('#content').highlight('lorem', { caseSensitive: true }); 28 | * 29 | * // wrap every occurrance of term 'ipsum' in content 30 | * // with 31 | * $('#content').highlight('ipsum', { element: 'em', className: 'important' }); 32 | * 33 | * // remove default highlight 34 | * $('#content').unhighlight(); 35 | * 36 | * // remove custom highlight 37 | * $('#content').unhighlight({ element: 'em', className: 'important' }); 38 | * 39 | * 40 | * Copyright (c) 2009 Bartek Szopka 41 | * 42 | * Licensed under MIT license. 43 | * 44 | */ 45 | 46 | jQuery.extend({ 47 | highlight: function (node, re, nodeName, className) { 48 | if (node.nodeType === 3) { 49 | var match = node.data.match(re); 50 | if (match) { 51 | var highlight = document.createElement(nodeName || 'span'); 52 | highlight.className = className || 'highlight'; 53 | var wordNode = node.splitText(match.index); 54 | wordNode.splitText(match[0].length); 55 | var wordClone = wordNode.cloneNode(true); 56 | highlight.appendChild(wordClone); 57 | wordNode.parentNode.replaceChild(highlight, wordNode); 58 | return 1; //skip added node in parent 59 | } 60 | } else if ((node.nodeType === 1 && node.childNodes) && // only element nodes that have children 61 | !/(script|style)/i.test(node.tagName) && // ignore script and style nodes 62 | !(node.tagName === nodeName.toUpperCase() && node.className === className)) { // skip if already highlighted 63 | for (var i = 0; i < node.childNodes.length; i++) { 64 | i += jQuery.highlight(node.childNodes[i], re, nodeName, className); 65 | } 66 | } 67 | return 0; 68 | } 69 | }); 70 | 71 | jQuery.fn.unhighlight = function (options) { 72 | var settings = { className: 'highlight', element: 'span' }; 73 | jQuery.extend(settings, options); 74 | 75 | return this.find(settings.element + "." + settings.className).each(function () { 76 | var parent = this.parentNode; 77 | parent.replaceChild(this.firstChild, this); 78 | parent.normalize(); 79 | }).end(); 80 | }; 81 | 82 | jQuery.fn.highlight = function (words, options) { 83 | var settings = { className: 'highlight', element: 'span', caseSensitive: false, wordsOnly: false }; 84 | jQuery.extend(settings, options); 85 | 86 | if (words.constructor === String) { 87 | words = [words]; 88 | } 89 | words = jQuery.grep(words, function(word, i){ 90 | return word != ''; 91 | }); 92 | words = jQuery.map(words, function(word, i) { 93 | return word.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); 94 | }); 95 | if (words.length == 0) { return this; }; 96 | 97 | var flag = settings.caseSensitive ? "" : "i"; 98 | var pattern = "(" + words.join("|") + ")"; 99 | if (settings.wordsOnly) { 100 | pattern = "\\b" + pattern + "\\b"; 101 | } 102 | var re = new RegExp(pattern, flag); 103 | 104 | return this.each(function () { 105 | jQuery.highlight(this, re, settings.element, settings.className); 106 | }); 107 | }; 108 | 109 | -------------------------------------------------------------------------------- /slate/_slate/layouts/layout.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{ .Params.Title }} 8 | {{- .Params.HTMLHead }} 9 | 10 | 11 | 13 | {{- if .Params.Search }} 14 | 15 | {{- else }} 16 | 17 | {{- end }} 18 | 19 | 20 | 21 | 22 | NAV 23 | 24 | 25 | 26 |
      27 | {{- if not .Params.Logo }} 28 | 29 | {{- else }} 30 | {{- if ne .Params.Logo "none" }} 31 | 32 | {{- end }} 33 | {{- end }} 34 | {{- if gt (len .Params.Langs) 1 }} 35 |
      36 | {{- range .Params.Langs }} 37 | {{.}} 38 | {{- end }} 39 |
      40 | {{- end }} 41 | {{- if .Params.Search }} 42 | 45 |
        46 | {{- end }} 47 |
        48 | {{- .TOC }} 49 |
        50 | 55 |
        56 |
        57 |
        58 |
        59 | {{- .Content }} 60 |
        61 | {{- if gt (len .Params.Langs) 1 }} 62 |
        63 |
        64 | {{- range .Params.Langs }} 65 | {{.}} 66 | {{- end }} 67 |
        68 |
        69 | {{- end }} 70 |
        71 | 72 | 73 | -------------------------------------------------------------------------------- /slate/_slate/stylesheets/_icon-font.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'slate'; 3 | src:font-url('slate.eot?-syv14m'); 4 | src:font-url('slate.eot?#iefix-syv14m') format('embedded-opentype'), 5 | font-url('slate.woff2?-syv14m') format('woff2'), 6 | font-url('slate.woff?-syv14m') format('woff'), 7 | font-url('slate.ttf?-syv14m') format('truetype'), 8 | font-url('slate.svg?-syv14m#slate') format('svg'); 9 | font-weight: normal; 10 | font-style: normal; 11 | } 12 | 13 | %icon { 14 | font-family: 'slate'; 15 | speak: none; 16 | font-style: normal; 17 | font-weight: normal; 18 | font-variant: normal; 19 | text-transform: none; 20 | line-height: 1; 21 | } 22 | 23 | %icon-exclamation-sign { 24 | @extend %icon; 25 | content: "\e600"; 26 | } 27 | %icon-info-sign { 28 | @extend %icon; 29 | content: "\e602"; 30 | } 31 | %icon-ok-sign { 32 | @extend %icon; 33 | content: "\e606"; 34 | } 35 | %icon-search { 36 | @extend %icon; 37 | content: "\e607"; 38 | } 39 | -------------------------------------------------------------------------------- /slate/_slate/stylesheets/_normalize.scss: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */ 2 | 3 | /** 4 | * 1. Set default font family to sans-serif. 5 | * 2. Prevent iOS text size adjust after orientation change, without disabling 6 | * user zoom. 7 | */ 8 | 9 | html { 10 | font-family: sans-serif; /* 1 */ 11 | -ms-text-size-adjust: 100%; /* 2 */ 12 | -webkit-text-size-adjust: 100%; /* 2 */ 13 | } 14 | 15 | /** 16 | * Remove default margin. 17 | */ 18 | 19 | body { 20 | margin: 0; 21 | } 22 | 23 | /* HTML5 display definitions 24 | ========================================================================== */ 25 | 26 | /** 27 | * Correct `block` display not defined for any HTML5 element in IE 8/9. 28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 29 | * and Firefox. 30 | * Correct `block` display not defined for `main` in IE 11. 31 | */ 32 | 33 | article, 34 | aside, 35 | details, 36 | figcaption, 37 | figure, 38 | footer, 39 | header, 40 | hgroup, 41 | main, 42 | menu, 43 | nav, 44 | section, 45 | summary { 46 | display: block; 47 | } 48 | 49 | /** 50 | * 1. Correct `inline-block` display not defined in IE 8/9. 51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 52 | */ 53 | 54 | audio, 55 | canvas, 56 | progress, 57 | video { 58 | display: inline-block; /* 1 */ 59 | vertical-align: baseline; /* 2 */ 60 | } 61 | 62 | /** 63 | * Prevent modern browsers from displaying `audio` without controls. 64 | * Remove excess height in iOS 5 devices. 65 | */ 66 | 67 | audio:not([controls]) { 68 | display: none; 69 | height: 0; 70 | } 71 | 72 | /** 73 | * Address `[hidden]` styling not present in IE 8/9/10. 74 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. 75 | */ 76 | 77 | [hidden], 78 | template { 79 | display: none; 80 | } 81 | 82 | /* Links 83 | ========================================================================== */ 84 | 85 | /** 86 | * Remove the gray background color from active links in IE 10. 87 | */ 88 | 89 | a { 90 | background-color: transparent; 91 | } 92 | 93 | /** 94 | * Improve readability when focused and also mouse hovered in all browsers. 95 | */ 96 | 97 | a:active, 98 | a:hover { 99 | outline: 0; 100 | } 101 | 102 | /* Text-level semantics 103 | ========================================================================== */ 104 | 105 | /** 106 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome. 107 | */ 108 | 109 | abbr[title] { 110 | border-bottom: 1px dotted; 111 | } 112 | 113 | /** 114 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. 115 | */ 116 | 117 | b, 118 | strong { 119 | font-weight: bold; 120 | } 121 | 122 | /** 123 | * Address styling not present in Safari and Chrome. 124 | */ 125 | 126 | dfn { 127 | font-style: italic; 128 | } 129 | 130 | /** 131 | * Address variable `h1` font-size and margin within `section` and `article` 132 | * contexts in Firefox 4+, Safari, and Chrome. 133 | */ 134 | 135 | h1 { 136 | font-size: 2em; 137 | margin: 0.67em 0; 138 | } 139 | 140 | /** 141 | * Address styling not present in IE 8/9. 142 | */ 143 | 144 | mark { 145 | background: #ff0; 146 | color: #000; 147 | } 148 | 149 | /** 150 | * Address inconsistent and variable font size in all browsers. 151 | */ 152 | 153 | small { 154 | font-size: 80%; 155 | } 156 | 157 | /** 158 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 159 | */ 160 | 161 | sub, 162 | sup { 163 | font-size: 75%; 164 | line-height: 0; 165 | position: relative; 166 | vertical-align: baseline; 167 | } 168 | 169 | sup { 170 | top: -0.5em; 171 | } 172 | 173 | sub { 174 | bottom: -0.25em; 175 | } 176 | 177 | /* Embedded content 178 | ========================================================================== */ 179 | 180 | /** 181 | * Remove border when inside `a` element in IE 8/9/10. 182 | */ 183 | 184 | img { 185 | border: 0; 186 | } 187 | 188 | /** 189 | * Correct overflow not hidden in IE 9/10/11. 190 | */ 191 | 192 | svg:not(:root) { 193 | overflow: hidden; 194 | } 195 | 196 | /* Grouping content 197 | ========================================================================== */ 198 | 199 | /** 200 | * Address margin not present in IE 8/9 and Safari. 201 | */ 202 | 203 | figure { 204 | margin: 1em 40px; 205 | } 206 | 207 | /** 208 | * Address differences between Firefox and other browsers. 209 | */ 210 | 211 | hr { 212 | -moz-box-sizing: content-box; 213 | box-sizing: content-box; 214 | height: 0; 215 | } 216 | 217 | /** 218 | * Contain overflow in all browsers. 219 | */ 220 | 221 | pre { 222 | overflow: auto; 223 | } 224 | 225 | /** 226 | * Address odd `em`-unit font size rendering in all browsers. 227 | */ 228 | 229 | code, 230 | kbd, 231 | pre, 232 | samp { 233 | font-family: monospace, monospace; 234 | font-size: 1em; 235 | } 236 | 237 | /* Forms 238 | ========================================================================== */ 239 | 240 | /** 241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited 242 | * styling of `select`, unless a `border` property is set. 243 | */ 244 | 245 | /** 246 | * 1. Correct color not being inherited. 247 | * Known issue: affects color of disabled elements. 248 | * 2. Correct font properties not being inherited. 249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 250 | */ 251 | 252 | button, 253 | input, 254 | optgroup, 255 | select, 256 | textarea { 257 | color: inherit; /* 1 */ 258 | font: inherit; /* 2 */ 259 | margin: 0; /* 3 */ 260 | } 261 | 262 | /** 263 | * Address `overflow` set to `hidden` in IE 8/9/10/11. 264 | */ 265 | 266 | button { 267 | overflow: visible; 268 | } 269 | 270 | /** 271 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 272 | * All other form control elements do not inherit `text-transform` values. 273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 274 | * Correct `select` style inheritance in Firefox. 275 | */ 276 | 277 | button, 278 | select { 279 | text-transform: none; 280 | } 281 | 282 | /** 283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 284 | * and `video` controls. 285 | * 2. Correct inability to style clickable `input` types in iOS. 286 | * 3. Improve usability and consistency of cursor style between image-type 287 | * `input` and others. 288 | */ 289 | 290 | button, 291 | html input[type="button"], /* 1 */ 292 | input[type="reset"], 293 | input[type="submit"] { 294 | -webkit-appearance: button; /* 2 */ 295 | cursor: pointer; /* 3 */ 296 | } 297 | 298 | /** 299 | * Re-set default cursor for disabled elements. 300 | */ 301 | 302 | button[disabled], 303 | html input[disabled] { 304 | cursor: default; 305 | } 306 | 307 | /** 308 | * Remove inner padding and border in Firefox 4+. 309 | */ 310 | 311 | button::-moz-focus-inner, 312 | input::-moz-focus-inner { 313 | border: 0; 314 | padding: 0; 315 | } 316 | 317 | /** 318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 319 | * the UA stylesheet. 320 | */ 321 | 322 | input { 323 | line-height: normal; 324 | } 325 | 326 | /** 327 | * It's recommended that you don't attempt to style these elements. 328 | * Firefox's implementation doesn't respect box-sizing, padding, or width. 329 | * 330 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 331 | * 2. Remove excess padding in IE 8/9/10. 332 | */ 333 | 334 | input[type="checkbox"], 335 | input[type="radio"] { 336 | box-sizing: border-box; /* 1 */ 337 | padding: 0; /* 2 */ 338 | } 339 | 340 | /** 341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain 342 | * `font-size` values of the `input`, it causes the cursor style of the 343 | * decrement button to change from `default` to `text`. 344 | */ 345 | 346 | input[type="number"]::-webkit-inner-spin-button, 347 | input[type="number"]::-webkit-outer-spin-button { 348 | height: auto; 349 | } 350 | 351 | /** 352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome. 353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome 354 | * (include `-moz` to future-proof). 355 | */ 356 | 357 | input[type="search"] { 358 | -webkit-appearance: textfield; /* 1 */ 359 | -moz-box-sizing: content-box; 360 | -webkit-box-sizing: content-box; /* 2 */ 361 | box-sizing: content-box; 362 | } 363 | 364 | /** 365 | * Remove inner padding and search cancel button in Safari and Chrome on OS X. 366 | * Safari (but not Chrome) clips the cancel button when the search input has 367 | * padding (and `textfield` appearance). 368 | */ 369 | 370 | input[type="search"]::-webkit-search-cancel-button, 371 | input[type="search"]::-webkit-search-decoration { 372 | -webkit-appearance: none; 373 | } 374 | 375 | /** 376 | * Define consistent border, margin, and padding. 377 | */ 378 | 379 | fieldset { 380 | border: 1px solid #c0c0c0; 381 | margin: 0 2px; 382 | padding: 0.35em 0.625em 0.75em; 383 | } 384 | 385 | /** 386 | * 1. Correct `color` not being inherited in IE 8/9/10/11. 387 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 388 | */ 389 | 390 | legend { 391 | border: 0; /* 1 */ 392 | padding: 0; /* 2 */ 393 | } 394 | 395 | /** 396 | * Remove default vertical scrollbar in IE 8/9/10/11. 397 | */ 398 | 399 | textarea { 400 | overflow: auto; 401 | } 402 | 403 | /** 404 | * Don't inherit the `font-weight` (applied by a rule above). 405 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 406 | */ 407 | 408 | optgroup { 409 | font-weight: bold; 410 | } 411 | 412 | /* Tables 413 | ========================================================================== */ 414 | 415 | /** 416 | * Remove most spacing between table cells. 417 | */ 418 | 419 | table { 420 | border-collapse: collapse; 421 | border-spacing: 0; 422 | } 423 | 424 | td, 425 | th { 426 | padding: 0; 427 | } 428 | -------------------------------------------------------------------------------- /slate/_slate/stylesheets/_rtl.scss: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // RTL Styles Variables 3 | //////////////////////////////////////////////////////////////////////////////// 4 | 5 | $default: auto; 6 | 7 | //////////////////////////////////////////////////////////////////////////////// 8 | // TABLE OF CONTENTS 9 | //////////////////////////////////////////////////////////////////////////////// 10 | 11 | #toc>ul>li>a>span { 12 | float: left; 13 | } 14 | 15 | .toc-wrapper { 16 | transition: right 0.3s ease-in-out !important; 17 | left: $default !important; 18 | #{right}: 0; 19 | } 20 | 21 | .toc-h2 { 22 | padding-#{right}: $nav-padding + $nav-indent; 23 | } 24 | 25 | #nav-button { 26 | #{right}: 0; 27 | transition: right 0.3s ease-in-out; 28 | &.open { 29 | right: $nav-width 30 | } 31 | } 32 | 33 | //////////////////////////////////////////////////////////////////////////////// 34 | // PAGE LAYOUT AND CODE SAMPLE BACKGROUND 35 | //////////////////////////////////////////////////////////////////////////////// 36 | .page-wrapper { 37 | margin-#{left}: $default !important; 38 | margin-#{right}: $nav-width; 39 | .dark-box { 40 | #{right}: $default; 41 | #{left}: 0; 42 | } 43 | } 44 | 45 | .lang-selector { 46 | width: $default !important; 47 | a { 48 | float: right; 49 | } 50 | } 51 | 52 | //////////////////////////////////////////////////////////////////////////////// 53 | // CODE SAMPLE STYLES 54 | //////////////////////////////////////////////////////////////////////////////// 55 | .content { 56 | &>h1, 57 | &>h2, 58 | &>h3, 59 | &>h4, 60 | &>h5, 61 | &>h6, 62 | &>p, 63 | &>table, 64 | &>ul, 65 | &>ol, 66 | &>aside, 67 | &>dl { 68 | margin-#{left}: $examples-width; 69 | margin-#{right}: $default !important; 70 | } 71 | &>ul, 72 | &>ol { 73 | padding-#{right}: $main-padding + 15px; 74 | } 75 | table { 76 | th, 77 | td { 78 | text-align: right; 79 | } 80 | } 81 | dd { 82 | margin-#{right}: 15px; 83 | } 84 | aside { 85 | aside:before { 86 | padding-#{left}: 0.5em; 87 | } 88 | .search-highlight { 89 | background: linear-gradient(to top right, #F7E633 0%, #F1D32F 100%); 90 | } 91 | } 92 | pre, 93 | blockquote { 94 | float: left !important; 95 | clear: left !important; 96 | } 97 | } 98 | 99 | //////////////////////////////////////////////////////////////////////////////// 100 | // TYPOGRAPHY 101 | //////////////////////////////////////////////////////////////////////////////// 102 | h1, 103 | h2, 104 | h3, 105 | h4, 106 | h5, 107 | h6, 108 | p, 109 | aside { 110 | text-align: right; 111 | direction: rtl; 112 | } 113 | 114 | .toc-wrapper { 115 | text-align: right; 116 | direction: rtl; 117 | font-weight: 100 !important; 118 | } 119 | 120 | 121 | //////////////////////////////////////////////////////////////////////////////// 122 | // RESPONSIVE DESIGN 123 | //////////////////////////////////////////////////////////////////////////////// 124 | @media (max-width: $tablet-width) { 125 | .toc-wrapper { 126 | #{right}: -$nav-width; 127 | &.open { 128 | #{right}: 0; 129 | } 130 | } 131 | .page-wrapper { 132 | margin-#{right}: 0; 133 | } 134 | } 135 | 136 | @media (max-width: $phone-width) { 137 | %left-col { 138 | margin-#{left}: 0; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /slate/_slate/stylesheets/_variables.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2008-2013 Concur Technologies, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | not use this file except in compliance with the License. You may obtain 6 | a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | License for the specific language governing permissions and limitations 14 | under the License. 15 | */ 16 | 17 | 18 | //////////////////////////////////////////////////////////////////////////////// 19 | // CUSTOMIZE SLATE 20 | //////////////////////////////////////////////////////////////////////////////// 21 | // Use these settings to help adjust the appearance of Slate 22 | 23 | 24 | // BACKGROUND COLORS 25 | //////////////////// 26 | $nav-bg: #2E3336 !default; 27 | $examples-bg: #2E3336 !default; 28 | $code-bg: #1E2224 !default; 29 | $code-annotation-bg: #191D1F !default; 30 | $nav-subitem-bg: #1E2224 !default; 31 | $nav-active-bg: #0F75D4 !default; 32 | $nav-active-parent-bg: #1E2224 !default; // parent links of the current section 33 | $lang-select-border: #000 !default; 34 | $lang-select-bg: #1E2224 !default; 35 | $lang-select-active-bg: $examples-bg !default; // feel free to change this to blue or something 36 | $lang-select-pressed-bg: #111 !default; // color of language tab bg when mouse is pressed 37 | $main-bg: #F3F7F9 !default; 38 | $aside-notice-bg: #8fbcd4 !default; 39 | $aside-warning-bg: #c97a7e !default; 40 | $aside-success-bg: #6ac174 !default; 41 | $search-notice-bg: #c97a7e !default; 42 | 43 | 44 | // TEXT COLORS 45 | //////////////////// 46 | $main-text: #333 !default; // main content text color 47 | $nav-text: #fff !default; 48 | $nav-active-text: #fff !default; 49 | $nav-active-parent-text: #fff !default; // parent links of the current section 50 | $lang-select-text: #fff !default; // color of unselected language tab text 51 | $lang-select-active-text: #fff !default; // color of selected language tab text 52 | $lang-select-pressed-text: #fff !default; // color of language tab text when mouse is pressed 53 | 54 | 55 | // SIZES 56 | //////////////////// 57 | $nav-width: 230px !default; // width of the navbar 58 | $examples-width: 50% !default; // portion of the screen taken up by code examples 59 | $logo-margin: 0px !default; // margin below logo 60 | $main-padding: 28px !default; // padding to left and right of content & examples 61 | $nav-padding: 15px !default; // padding to left and right of navbar 62 | $nav-v-padding: 10px !default; // padding used vertically around search boxes and results 63 | $nav-indent: 10px !default; // extra padding for ToC subitems 64 | $code-annotation-padding: 13px !default; // padding inside code annotations 65 | $h1-margin-bottom: 21px !default; // padding under the largest header tags 66 | $tablet-width: 930px !default; // min width before reverting to tablet size 67 | $phone-width: $tablet-width - $nav-width !default; // min width before reverting to mobile size 68 | 69 | 70 | // FONTS 71 | //////////////////// 72 | %default-font { 73 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 74 | font-size: 14px; 75 | } 76 | 77 | %header-font { 78 | @extend %default-font; 79 | font-weight: bold; 80 | } 81 | 82 | %code-font { 83 | font-family: Consolas, Menlo, Monaco, "Lucida Console", "Liberation Mono", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Courier New", monospace, serif; 84 | font-size: 12px; 85 | line-height: 1.5; 86 | } 87 | 88 | 89 | // OTHER 90 | //////////////////// 91 | $nav-footer-border-color: #666 !default; 92 | $search-box-border-color: #666 !default; 93 | 94 | 95 | //////////////////////////////////////////////////////////////////////////////// 96 | // INTERNAL 97 | //////////////////////////////////////////////////////////////////////////////// 98 | // These settings are probably best left alone. 99 | 100 | %break-words { 101 | word-break: break-all; 102 | hyphens: auto; 103 | } 104 | -------------------------------------------------------------------------------- /slate/_slate/stylesheets/print.css.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | @import 'normalize'; 3 | @import 'variables'; 4 | @import 'icon-font'; 5 | 6 | /* 7 | Copyright 2008-2013 Concur Technologies, Inc. 8 | 9 | Licensed under the Apache License, Version 2.0 (the "License"); you may 10 | not use this file except in compliance with the License. You may obtain 11 | a copy of the License at 12 | 13 | http://www.apache.org/licenses/LICENSE-2.0 14 | 15 | Unless required by applicable law or agreed to in writing, software 16 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 17 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 18 | License for the specific language governing permissions and limitations 19 | under the License. 20 | */ 21 | 22 | $print-color: #999; 23 | $print-color-light: #ccc; 24 | $print-font-size: 12px; 25 | 26 | body { 27 | @extend %default-font; 28 | } 29 | 30 | .tocify, .toc-footer, .lang-selector, .search, #nav-button { 31 | display: none; 32 | } 33 | 34 | .tocify-wrapper>img { 35 | margin: 0 auto; 36 | display: block; 37 | } 38 | 39 | .content { 40 | font-size: 12px; 41 | 42 | pre, code { 43 | @extend %code-font; 44 | @extend %break-words; 45 | border: 1px solid $print-color; 46 | border-radius: 5px; 47 | font-size: 0.8em; 48 | } 49 | 50 | pre { 51 | code { 52 | border: 0; 53 | } 54 | } 55 | 56 | pre { 57 | padding: 1.3em; 58 | } 59 | 60 | code { 61 | padding: 0.2em; 62 | } 63 | 64 | table { 65 | border: 1px solid $print-color; 66 | tr { 67 | border-bottom: 1px solid $print-color; 68 | } 69 | td,th { 70 | padding: 0.7em; 71 | } 72 | } 73 | 74 | p { 75 | line-height: 1.5; 76 | } 77 | 78 | a { 79 | text-decoration: none; 80 | color: #000; 81 | } 82 | 83 | h1 { 84 | @extend %header-font; 85 | font-size: 2.5em; 86 | padding-top: 0.5em; 87 | padding-bottom: 0.5em; 88 | margin-top: 1em; 89 | margin-bottom: $h1-margin-bottom; 90 | border: 2px solid $print-color-light; 91 | border-width: 2px 0; 92 | text-align: center; 93 | } 94 | 95 | h2 { 96 | @extend %header-font; 97 | font-size: 1.8em; 98 | margin-top: 2em; 99 | border-top: 2px solid $print-color-light; 100 | padding-top: 0.8em; 101 | } 102 | 103 | h1+h2, h1+div+h2 { 104 | border-top: none; 105 | padding-top: 0; 106 | margin-top: 0; 107 | } 108 | 109 | h3, h4 { 110 | @extend %header-font; 111 | font-size: 0.8em; 112 | margin-top: 1.5em; 113 | margin-bottom: 0.8em; 114 | text-transform: uppercase; 115 | } 116 | 117 | h5, h6 { 118 | text-transform: uppercase; 119 | } 120 | 121 | aside { 122 | padding: 1em; 123 | border: 1px solid $print-color-light; 124 | border-radius: 5px; 125 | margin-top: 1.5em; 126 | margin-bottom: 1.5em; 127 | line-height: 1.6; 128 | } 129 | 130 | aside:before { 131 | vertical-align: middle; 132 | padding-right: 0.5em; 133 | font-size: 14px; 134 | } 135 | 136 | aside.notice:before { 137 | @extend %icon-info-sign; 138 | } 139 | 140 | aside.warning:before { 141 | @extend %icon-exclamation-sign; 142 | } 143 | 144 | aside.success:before { 145 | @extend %icon-ok-sign; 146 | } 147 | } -------------------------------------------------------------------------------- /slate/_slate/stylesheets/screen.css.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | @import 'normalize'; 3 | @import 'variables'; 4 | @import 'icon-font'; 5 | // @import 'rtl'; // uncomment to switch to RTL format 6 | 7 | /* 8 | Copyright 2008-2013 Concur Technologies, Inc. 9 | 10 | Licensed under the Apache License, Version 2.0 (the "License"); you may 11 | not use this file except in compliance with the License. You may obtain 12 | a copy of the License at 13 | 14 | http://www.apache.org/licenses/LICENSE-2.0 15 | 16 | Unless required by applicable law or agreed to in writing, software 17 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 18 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 19 | License for the specific language governing permissions and limitations 20 | under the License. 21 | */ 22 | 23 | //////////////////////////////////////////////////////////////////////////////// 24 | // GENERAL STUFF 25 | //////////////////////////////////////////////////////////////////////////////// 26 | 27 | html, body { 28 | color: $main-text; 29 | padding: 0; 30 | margin: 0; 31 | -webkit-font-smoothing: antialiased; 32 | -moz-osx-font-smoothing: grayscale; 33 | @extend %default-font; 34 | background-color: $main-bg; 35 | height: 100%; 36 | -webkit-text-size-adjust: none; /* Never autoresize text */ 37 | } 38 | 39 | //////////////////////////////////////////////////////////////////////////////// 40 | // TABLE OF CONTENTS 41 | //////////////////////////////////////////////////////////////////////////////// 42 | 43 | #toc > ul > li > a > span { 44 | float: right; 45 | background-color: #2484FF; 46 | border-radius: 40px; 47 | width: 20px; 48 | } 49 | 50 | .toc-wrapper { 51 | transition: left 0.3s ease-in-out; 52 | 53 | overflow-y: auto; 54 | overflow-x: hidden; 55 | position: fixed; 56 | z-index: 30; 57 | top: 0; 58 | left: 0; 59 | bottom: 0; 60 | width: $nav-width; 61 | background-color: $nav-bg; 62 | font-size: 13px; 63 | font-weight: bold; 64 | 65 | // language selector for mobile devices 66 | .lang-selector { 67 | display: none; 68 | a { 69 | padding-top: 0.5em; 70 | padding-bottom: 0.5em; 71 | } 72 | } 73 | 74 | // This is the logo at the top of the ToC 75 | .logo { 76 | display: block; 77 | max-width: 100%; 78 | margin-bottom: $logo-margin; 79 | } 80 | 81 | &>.search { 82 | position: relative; 83 | 84 | input { 85 | background: $nav-bg; 86 | border-width: 0 0 1px 0; 87 | border-color: $search-box-border-color; 88 | padding: 6px 0 6px 20px; 89 | box-sizing: border-box; 90 | margin: $nav-v-padding $nav-padding; 91 | width: $nav-width - ($nav-padding*2); 92 | outline: none; 93 | color: $nav-text; 94 | border-radius: 0; /* ios has a default border radius */ 95 | } 96 | 97 | &:before { 98 | position: absolute; 99 | top: 17px; 100 | left: $nav-padding; 101 | color: $nav-text; 102 | @extend %icon-search; 103 | } 104 | } 105 | 106 | .search-results { 107 | margin-top: 0; 108 | box-sizing: border-box; 109 | height: 0; 110 | overflow-y: auto; 111 | overflow-x: hidden; 112 | transition-property: height, margin; 113 | transition-duration: 180ms; 114 | transition-timing-function: ease-in-out; 115 | background: $nav-subitem-bg; 116 | &.visible { 117 | height: 30%; 118 | margin-bottom: 1em; 119 | } 120 | 121 | li { 122 | margin: 1em $nav-padding; 123 | line-height: 1; 124 | } 125 | 126 | a { 127 | color: $nav-text; 128 | text-decoration: none; 129 | 130 | &:hover { 131 | text-decoration: underline; 132 | } 133 | } 134 | } 135 | 136 | 137 | // The Table of Contents is composed of multiple nested 138 | // unordered lists. These styles remove the default 139 | // styling of an unordered list because it is ugly. 140 | ul, li { 141 | list-style: none; 142 | margin: 0; 143 | padding: 0; 144 | line-height: 28px; 145 | } 146 | 147 | li { 148 | color: $nav-text; 149 | transition-property: background; 150 | transition-timing-function: linear; 151 | transition-duration: 200ms; 152 | } 153 | 154 | // This is the currently selected ToC entry 155 | .toc-link.active { 156 | background-color: $nav-active-bg; 157 | color: $nav-active-text; 158 | } 159 | 160 | // this is parent links of the currently selected ToC entry 161 | .toc-link.active-parent { 162 | background-color: $nav-active-parent-bg; 163 | color: $nav-active-parent-text; 164 | } 165 | 166 | .toc-list-h2 { 167 | display: none; 168 | background-color: $nav-subitem-bg; 169 | font-weight: 500; 170 | } 171 | 172 | .toc-h2 { 173 | padding-left: $nav-padding + $nav-indent; 174 | font-size: 12px; 175 | } 176 | 177 | .toc-footer { 178 | padding: 1em 0; 179 | margin-top: 1em; 180 | border-top: 1px dashed $nav-footer-border-color; 181 | 182 | li,a { 183 | color: $nav-text; 184 | text-decoration: none; 185 | } 186 | 187 | a:hover { 188 | text-decoration: underline; 189 | } 190 | 191 | li { 192 | font-size: 0.8em; 193 | line-height: 1.7; 194 | text-decoration: none; 195 | } 196 | } 197 | } 198 | 199 | .toc-link, .toc-footer li { 200 | padding: 0 $nav-padding 0 $nav-padding; 201 | display: block; 202 | overflow-x: hidden; 203 | white-space: nowrap; 204 | text-overflow: ellipsis; 205 | text-decoration: none; 206 | color: $nav-text; 207 | transition-property: background; 208 | transition-timing-function: linear; 209 | transition-duration: 130ms; 210 | } 211 | 212 | // button to show navigation on mobile devices 213 | #nav-button { 214 | span { 215 | display: block; 216 | $side-pad: $main-padding / 2 - 8px; 217 | padding: $side-pad $side-pad $side-pad; 218 | background-color: rgba($main-bg, 0.7); 219 | transform-origin: 0 0; 220 | transform: rotate(-90deg) translate(-100%, 0); 221 | border-radius: 0 0 0 5px; 222 | } 223 | padding: 0 1.5em 5em 0; // increase touch size area 224 | display: none; 225 | position: fixed; 226 | top: 0; 227 | left: 0; 228 | z-index: 100; 229 | color: #000; 230 | text-decoration: none; 231 | font-weight: bold; 232 | opacity: 0.7; 233 | line-height: 16px; 234 | img { 235 | height: 16px; 236 | vertical-align: bottom; 237 | } 238 | 239 | transition: left 0.3s ease-in-out; 240 | 241 | &:hover { opacity: 1; } 242 | &.open {left: $nav-width} 243 | } 244 | 245 | 246 | //////////////////////////////////////////////////////////////////////////////// 247 | // PAGE LAYOUT AND CODE SAMPLE BACKGROUND 248 | //////////////////////////////////////////////////////////////////////////////// 249 | 250 | .page-wrapper { 251 | margin-left: $nav-width; 252 | position: relative; 253 | z-index: 10; 254 | background-color: $main-bg; 255 | min-height: 100%; 256 | 257 | padding-bottom: 1px; // prevent margin overflow 258 | 259 | // The dark box is what gives the code samples their dark background. 260 | // It sits essentially under the actual content block, which has a 261 | // transparent background. 262 | // I know, it's hackish, but it's the simplist way to make the left 263 | // half of the content always this background color. 264 | .dark-box { 265 | width: $examples-width; 266 | background-color: $examples-bg; 267 | position: absolute; 268 | right: 0; 269 | top: 0; 270 | bottom: 0; 271 | } 272 | 273 | .lang-selector { 274 | position: fixed; 275 | z-index: 50; 276 | border-bottom: 5px solid $lang-select-active-bg; 277 | } 278 | } 279 | 280 | .lang-selector { 281 | background-color: $lang-select-bg; 282 | width: 100%; 283 | font-weight: bold; 284 | a { 285 | display: block; 286 | float:left; 287 | color: $lang-select-text; 288 | text-decoration: none; 289 | padding: 0 10px; 290 | line-height: 30px; 291 | outline: 0; 292 | 293 | &:active, &:focus { 294 | background-color: $lang-select-pressed-bg; 295 | color: $lang-select-pressed-text; 296 | } 297 | 298 | &.active { 299 | background-color: $lang-select-active-bg; 300 | color: $lang-select-active-text; 301 | } 302 | } 303 | 304 | &:after { 305 | content: ''; 306 | clear: both; 307 | display: block; 308 | } 309 | } 310 | 311 | //////////////////////////////////////////////////////////////////////////////// 312 | // CONTENT STYLES 313 | //////////////////////////////////////////////////////////////////////////////// 314 | // This is all the stuff with the light background in the left half of the page 315 | 316 | .content { 317 | // fixes webkit rendering bug for some: see #538 318 | -webkit-transform: translateZ(0); 319 | // to place content above the dark box 320 | position: relative; 321 | z-index: 30; 322 | 323 | &:after { 324 | content: ''; 325 | display: block; 326 | clear: both; 327 | } 328 | 329 | &>h1, &>h2, &>h3, &>h4, &>h5, &>h6, &>p, &>table, &>ul, &>ol, &>aside, &>dl { 330 | margin-right: $examples-width; 331 | padding: 0 $main-padding; 332 | box-sizing: border-box; 333 | display: block; 334 | 335 | @extend %left-col; 336 | } 337 | 338 | &>ul, &>ol { 339 | padding-left: $main-padding + 15px; 340 | } 341 | 342 | // the div is the tocify hidden div for placeholding stuff 343 | &>h1, &>h2, &>div { 344 | clear:both; 345 | } 346 | 347 | h1 { 348 | @extend %header-font; 349 | font-size: 25px; 350 | padding-top: 0.5em; 351 | padding-bottom: 0.5em; 352 | margin-bottom: $h1-margin-bottom; 353 | margin-top: 2em; 354 | border-top: 1px solid #ccc; 355 | border-bottom: 1px solid #ccc; 356 | background-color: #fdfdfd; 357 | } 358 | 359 | h1:first-child, div:first-child + h1 { 360 | border-top-width: 0; 361 | margin-top: 0; 362 | } 363 | 364 | h2 { 365 | @extend %header-font; 366 | font-size: 19px; 367 | margin-top: 4em; 368 | margin-bottom: 0; 369 | border-top: 1px solid #ccc; 370 | padding-top: 1.2em; 371 | padding-bottom: 1.2em; 372 | background-image: linear-gradient(to bottom, rgba(#fff, 0.2), rgba(#fff, 0)); 373 | } 374 | 375 | // h2s right after h1s should bump right up 376 | // against the h1s. 377 | h1 + h2, h1 + div + h2 { 378 | margin-top: $h1-margin-bottom * -1; 379 | border-top: none; 380 | } 381 | 382 | h3, h4, h5, h6 { 383 | @extend %header-font; 384 | font-size: 15px; 385 | margin-top: 2.5em; 386 | margin-bottom: 0.8em; 387 | } 388 | 389 | h4, h5, h6 { 390 | font-size: 10px; 391 | } 392 | 393 | hr { 394 | margin: 2em 0; 395 | border-top: 2px solid $examples-bg; 396 | border-bottom: 2px solid $main-bg; 397 | } 398 | 399 | table { 400 | margin-bottom: 1em; 401 | overflow: auto; 402 | th,td { 403 | text-align: left; 404 | vertical-align: top; 405 | line-height: 1.6; 406 | code { 407 | white-space: nowrap; 408 | } 409 | } 410 | 411 | th { 412 | padding: 5px 10px; 413 | border-bottom: 1px solid #ccc; 414 | vertical-align: bottom; 415 | } 416 | 417 | td { 418 | padding: 10px; 419 | } 420 | 421 | tr:last-child { 422 | border-bottom: 1px solid #ccc; 423 | } 424 | 425 | tr:nth-child(odd)>td { 426 | background-color: lighten($main-bg,4.2%); 427 | } 428 | 429 | tr:nth-child(even)>td { 430 | background-color: lighten($main-bg,2.4%); 431 | } 432 | } 433 | 434 | dt { 435 | font-weight: bold; 436 | } 437 | 438 | dd { 439 | margin-left: 15px; 440 | } 441 | 442 | p, li, dt, dd { 443 | line-height: 1.6; 444 | margin-top: 0; 445 | } 446 | 447 | img { 448 | max-width: 100%; 449 | } 450 | 451 | code { 452 | background-color: rgba(0,0,0,0.05); 453 | padding: 3px; 454 | border-radius: 3px; 455 | @extend %break-words; 456 | @extend %code-font; 457 | } 458 | 459 | pre>code { 460 | background-color: transparent; 461 | padding: 0; 462 | } 463 | 464 | aside { 465 | padding-top: 1em; 466 | padding-bottom: 1em; 467 | margin-top: 1.5em; 468 | margin-bottom: 1.5em; 469 | background: $aside-notice-bg; 470 | line-height: 1.6; 471 | 472 | &.warning { 473 | background-color: $aside-warning-bg; 474 | } 475 | 476 | &.success { 477 | background-color: $aside-success-bg; 478 | } 479 | } 480 | 481 | aside:before { 482 | vertical-align: middle; 483 | padding-right: 0.5em; 484 | font-size: 14px; 485 | } 486 | 487 | aside.notice:before { 488 | @extend %icon-info-sign; 489 | } 490 | 491 | aside.warning:before { 492 | @extend %icon-exclamation-sign; 493 | } 494 | 495 | aside.success:before { 496 | @extend %icon-ok-sign; 497 | } 498 | 499 | .search-highlight { 500 | padding: 2px; 501 | margin: -3px; 502 | border-radius: 4px; 503 | border: 1px solid #F7E633; 504 | background: linear-gradient(to top left, #F7E633 0%, #F1D32F 100%); 505 | } 506 | } 507 | 508 | //////////////////////////////////////////////////////////////////////////////// 509 | // CODE SAMPLE STYLES 510 | //////////////////////////////////////////////////////////////////////////////// 511 | // This is all the stuff that appears in the right half of the page 512 | 513 | .content { 514 | pre, blockquote { 515 | background-color: $code-bg; 516 | color: #fff; 517 | 518 | margin: 0; 519 | width: $examples-width; 520 | 521 | float:right; 522 | clear:right; 523 | 524 | box-sizing: border-box; 525 | 526 | @extend %right-col; 527 | 528 | &>p { margin: 0; } 529 | 530 | a { 531 | color: #fff; 532 | text-decoration: none; 533 | border-bottom: dashed 1px #ccc; 534 | } 535 | } 536 | 537 | pre { 538 | @extend %code-font; 539 | padding-top: 2em; 540 | padding-bottom: 2em; 541 | padding: 2em $main-padding; 542 | } 543 | 544 | blockquote { 545 | &>p { 546 | background-color: $code-annotation-bg; 547 | padding: $code-annotation-padding 2em; 548 | color: #eee; 549 | } 550 | } 551 | } 552 | 553 | //////////////////////////////////////////////////////////////////////////////// 554 | // RESPONSIVE DESIGN 555 | //////////////////////////////////////////////////////////////////////////////// 556 | // These are the styles for phones and tablets 557 | // There are also a couple styles disperesed 558 | 559 | @media (max-width: $tablet-width) { 560 | .toc-wrapper { 561 | left: -$nav-width; 562 | 563 | &.open { 564 | left: 0; 565 | } 566 | } 567 | 568 | .page-wrapper { 569 | margin-left: 0; 570 | } 571 | 572 | #nav-button { 573 | display: block; 574 | } 575 | 576 | .toc-link { 577 | padding-top: 0.3em; 578 | padding-bottom: 0.3em; 579 | } 580 | } 581 | 582 | @media (max-width: $phone-width) { 583 | .dark-box { 584 | display: none; 585 | } 586 | 587 | %left-col { 588 | margin-right: 0; 589 | } 590 | 591 | .toc-wrapper .lang-selector { 592 | display: block; 593 | } 594 | 595 | .page-wrapper .lang-selector { 596 | display: none; 597 | } 598 | 599 | %right-col { 600 | width: auto; 601 | float: none; 602 | } 603 | 604 | %right-col + %left-col { 605 | margin-top: $main-padding; 606 | } 607 | } 608 | 609 | .highlight .c, .highlight .cm, .highlight .c1, .highlight .cs { 610 | color: #909090; 611 | } 612 | 613 | .highlight, .highlight .w { 614 | background-color: $code-bg; 615 | } 616 | -------------------------------------------------------------------------------- /slate/content.go: -------------------------------------------------------------------------------- 1 | package slate 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/json" 7 | "fmt" 8 | "github.com/alecthomas/chroma" 9 | chroma_html "github.com/alecthomas/chroma/formatters/html" 10 | "github.com/alecthomas/chroma/lexers" 11 | "github.com/alecthomas/chroma/styles" 12 | "github.com/growler/go-slate/slate/internal/slate" 13 | "github.com/russross/blackfriday/v2" 14 | "github.com/spf13/afero" 15 | "github.com/tdewolff/minify" 16 | minify_html "github.com/tdewolff/minify/html" 17 | "gopkg.in/yaml.v2" 18 | "html" 19 | "io/ioutil" 20 | "path" 21 | "path/filepath" 22 | "reflect" 23 | "sort" 24 | "text/template" 25 | ) 26 | 27 | type ContentParams struct { 28 | Title string `yaml:"title,omitempty"` 29 | Search bool `yaml:"search,omitempty"` 30 | Highlight string `yaml:"highlight_style,omitempty"` 31 | Langs []string `yaml:"language_tabs,omitempty"` 32 | TocFooters []string `yaml:"toc_footers,omitempty"` 33 | Includes []string `yaml:"includes,omitempty"` 34 | Style string `yaml:"style,omitempty"` 35 | Logo string `yaml:"logo,omitempty"` 36 | RTLEnabled bool `yaml:"enable_rtl,omitempty"` 37 | HTMLHead string `yaml:"html_head,omitempty"` 38 | } 39 | 40 | type chromaTypes struct { 41 | types []chroma.TokenType 42 | names []string 43 | } 44 | 45 | func (ct *chromaTypes) Len() int { 46 | return len(ct.types) 47 | } 48 | 49 | func (ct *chromaTypes) Less(i, j int) bool { 50 | return ct.types[i] < ct.types[j] 51 | } 52 | 53 | func (ct *chromaTypes) Swap(i, j int) { 54 | t := ct.types[i] 55 | n := ct.names[i] 56 | ct.types[i] = ct.types[j] 57 | ct.names[i] = ct.names[j] 58 | ct.types[j] = t 59 | ct.names[j] = n 60 | } 61 | 62 | var ct chromaTypes 63 | 64 | func init() { 65 | ct.types = make([]chroma.TokenType, 0, len(chroma.StandardTypes)) 66 | ct.names = make([]string, 0, len(chroma.StandardTypes)) 67 | for typ, name := range chroma.StandardTypes { 68 | ct.types = append(ct.types, typ) 69 | ct.names = append(ct.names, name) 70 | } 71 | sort.Sort(&ct) 72 | } 73 | 74 | func (p *ContentParams) StyleCSS() string { 75 | var style *chroma.Style 76 | if p.Highlight == "" { 77 | style = styles.Monokai 78 | } else { 79 | style = styles.Get(p.Highlight) 80 | } 81 | var buf bytes.Buffer 82 | var bg = style.Get(chroma.Background) 83 | fmt.Fprintf(&buf, "\n.highlight pre { %s }", chroma_html.StyleEntryToCSS(bg)) 84 | fmt.Fprintf(&buf, "\n.highlight .hll { %s }", chroma_html.StyleEntryToCSS(bg)) 85 | for i, typ := range ct.types { 86 | entry := style.Get(typ) 87 | if typ != chroma.Background { 88 | entry = entry.Sub(bg) 89 | } 90 | if entry.IsZero() { 91 | continue 92 | } 93 | fmt.Fprintf(&buf, "\n.highlight .%s { %s }", ct.names[i], chroma_html.StyleEntryToCSS(entry)) 94 | } 95 | return buf.String() 96 | } 97 | 98 | type content struct { 99 | html []byte 100 | Params ContentParams 101 | } 102 | 103 | func load(fs slate.FileSystem, params Params) (*content, error) { 104 | tmplFile, err := fs.Open("layouts/layout.tmpl") 105 | if err != nil { 106 | return nil, err 107 | } 108 | tmplSrc, err := ioutil.ReadAll(tmplFile) 109 | tmplFile.Close() 110 | if err != nil { 111 | return nil, err 112 | } 113 | tmpl := template.New("layout") 114 | tmpl.Funcs(template.FuncMap{ 115 | "json": func(arg0 reflect.Value) (reflect.Value, error) { 116 | if data, err := json.Marshal(arg0.Interface()); err != nil { 117 | return reflect.Value{}, err 118 | } else { 119 | return reflect.ValueOf(string(data)), nil 120 | } 121 | }, 122 | }) 123 | if tmpl, err = tmpl.Parse(string(tmplSrc)); err != nil { 124 | return nil, err 125 | } 126 | file, err := fs.Open("index.html.md") 127 | if err != nil { 128 | return nil, err 129 | } 130 | defer file.Close() 131 | buf := bytes.Buffer{} 132 | preamble := bytes.Buffer{} 133 | lineReader := bufio.NewScanner(file) 134 | state := 0 135 | for lineReader.Scan() { 136 | line := lineReader.Text() 137 | switch state { 138 | case 0: 139 | if line == "---" { 140 | state = 1 141 | continue 142 | } else { 143 | state = 2 144 | } 145 | case 1: 146 | if line == "---" { 147 | state = 2 148 | continue 149 | } else { 150 | preamble.WriteString(line) 151 | preamble.WriteByte('\n') 152 | continue 153 | } 154 | } 155 | buf.WriteString(line) 156 | buf.WriteByte('\n') 157 | } 158 | ret := &content{} 159 | if err = yaml.Unmarshal(preamble.Bytes(), &ret.Params); err != nil { 160 | return nil, err 161 | } 162 | for _, include := range ret.Params.Includes { 163 | inc, err := fs.Open(path.Join("includes", "_"+include+".md")) 164 | if err != nil { 165 | return nil, err 166 | } 167 | data, err := ioutil.ReadAll(inc) 168 | inc.Close() 169 | if err != nil { 170 | return nil, err 171 | } 172 | buf.Write(data) 173 | buf.WriteByte('\n') 174 | } 175 | if params.LogoFile != "" { 176 | ret.Params.Logo = params.LogoFile 177 | } 178 | if params.Search != nil { 179 | ret.Params.Search = *params.Search 180 | } 181 | if params.RTL != nil { 182 | ret.Params.RTLEnabled = *params.RTL 183 | } 184 | parser := blackfriday.New(blackfriday.WithExtensions( 185 | blackfriday.CommonExtensions | blackfriday.AutoHeadingIDs, 186 | )) 187 | htmlRenderer := blackfriday.NewHTMLRenderer(blackfriday.HTMLRendererParameters{}) 188 | ast := parser.Parse(buf.Bytes()) 189 | toc := produceTOC(htmlRenderer, ast) 190 | con := produceHTML(htmlRenderer, ast) 191 | buf.Reset() 192 | err = tmpl.Execute(&buf, map[string]interface{}{ 193 | "Params": &ret.Params, 194 | "TOC": string(toc), 195 | "Content": string(con), 196 | }) 197 | ret.html = buf.Bytes() 198 | return ret, nil 199 | } 200 | 201 | func produceTOC(r blackfriday.Renderer, ast *blackfriday.Node) []byte { 202 | buf := bytes.Buffer{} 203 | 204 | headings := make([]*blackfriday.Node, 0, 8) 205 | 206 | var currentHeading *blackfriday.Node 207 | var currentText bytes.Buffer 208 | 209 | ast.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus { 210 | if node.Type == blackfriday.Heading && !node.HeadingData.IsTitleblock && node.Level < 3 { 211 | if entering { 212 | currentHeading = node 213 | headings = append(headings, node) 214 | } else { 215 | currentHeading.Title = make([]byte, currentText.Len()) 216 | copy(currentHeading.Title, currentText.Bytes()) 217 | currentText.Reset() 218 | currentHeading = nil 219 | } 220 | } else if currentHeading != nil { 221 | r.RenderNode(¤tText, node, entering) 222 | } 223 | return blackfriday.GoToNext 224 | }) 225 | currentLevel := 0 226 | for _, h := range headings { 227 | if currentLevel == h.Level { 228 | fmt.Fprintf(&buf, "\n") 229 | } else if currentLevel != 0 && currentLevel < h.Level { 230 | for i := currentLevel; i < h.Level; i++ { 231 | fmt.Fprintf(&buf, "
          \n", i+1) 232 | } 233 | } else if currentLevel > h.Level { 234 | for i := h.Level; i < currentLevel; i++ { 235 | fmt.Fprint(&buf, "\n
        \n") 236 | } 237 | } 238 | currentLevel = h.Level 239 | title := html.EscapeString(string(h.Title)) 240 | fmt.Fprintf(&buf, "
      • \n%s\n", h.HeadingID, h.Level, title, title) 241 | } 242 | for i := 1; i < currentLevel; i++ { 243 | fmt.Fprint(&buf, "
      • \n
      \n") 244 | } 245 | if currentLevel > 0 { 246 | fmt.Fprint(&buf, "\n") 247 | } 248 | return buf.Bytes() 249 | } 250 | 251 | func produceHTML(r blackfriday.Renderer, ast *blackfriday.Node) []byte { 252 | var buf bytes.Buffer 253 | ast.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus { 254 | switch node.Type { 255 | case blackfriday.CodeBlock: 256 | lang := string(node.Info) 257 | code := string(node.Literal) 258 | fmt.Fprintf(&buf, "\n
      ", lang, lang)
      259 | 			lexer := lexers.Get(lang)
      260 | 			if lexer == nil {
      261 | 				lexer = lexers.Fallback
      262 | 			}
      263 | 			tokens, err := lexer.Tokenise(nil, code)
      264 | 			if err == nil {
      265 | 				for _, tok := range tokens.Tokens() {
      266 | 					if name, ok := chroma.StandardTypes[tok.Type]; ok && name != "" {
      267 | 						fmt.Fprintf(&buf, "%s", name, html.EscapeString(tok.Value))
      268 | 					} else {
      269 | 						fmt.Fprint(&buf, html.EscapeString(tok.Value))
      270 | 					}
      271 | 				}
      272 | 			} else {
      273 | 				fmt.Fprintln(&buf, html.EscapeString(code))
      274 | 			}
      275 | 			fmt.Fprint(&buf, "
      \n") 276 | return blackfriday.GoToNext 277 | default: 278 | return r.RenderNode(&buf, node, entering) 279 | } 280 | }) 281 | return buf.Bytes() 282 | } 283 | 284 | func (c *content) produce(target *afero.Afero, minifyHTML bool) error { 285 | out, err := target.TempFile(".", ".slate") 286 | if err != nil { 287 | return err 288 | } 289 | outName := filepath.Base(out.Name()) 290 | defer func() { 291 | out.Close() 292 | target.Remove(outName) 293 | }() 294 | var result []byte 295 | if minifyHTML { 296 | m := minify.New() 297 | if minifyHTML { 298 | m.AddFunc("text/html", minify_html.Minify) 299 | } 300 | result, err = m.Bytes("text/html", c.html) 301 | if err != nil { 302 | return err 303 | } 304 | } else { 305 | result = c.html 306 | } 307 | if _, err = out.Write(result); err != nil { 308 | return err 309 | } 310 | if err = out.Close(); err != nil { 311 | return err 312 | } 313 | if err = target.Rename(outName, "index.html"); err != nil { 314 | return err 315 | } 316 | return nil 317 | } 318 | -------------------------------------------------------------------------------- /slate/internal/slate/index.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | // Package slate holds binary resources embedded into Go executable 4 | package slate 5 | 6 | import ( 7 | "os" 8 | "io" 9 | "bytes" 10 | "path/filepath" 11 | "sort" 12 | "path" 13 | "compress/gzip" 14 | "io/ioutil" 15 | "time" 16 | ) 17 | 18 | func blob_bytes(uint32) []byte 19 | func blob_string(uint32) string 20 | 21 | // Asset represents binary resource stored within Go executable. Asset implements 22 | // fmt.Stringer and io.WriterTo interfaces, decompressing binary data if necessary. 23 | type Asset struct { 24 | name string // File name 25 | size int32 // File size (uncompressed) 26 | blob []byte // Resource blob []byte 27 | str_blob string // Resource blob as string 28 | isCompressed bool // true if resources was compressed with gzip 29 | mime string // MIME Type 30 | tag string // Tag is essentially a Tag of resource content and can be used as a value for "Etag" HTTP header 31 | } 32 | 33 | // Name returns the base name of the asset 34 | func (a *Asset) Name() string { return a.name } 35 | // MimeType returns MIME Type of the asset 36 | func (a *Asset) MimeType() string { return a.mime } 37 | // IsCompressed returns true of asset has been compressed 38 | func (a *Asset) IsCompressed() bool { return a.isCompressed } 39 | // String returns (uncompressed, if necessary) content of asset as a string 40 | func (a *Asset) String() string { 41 | if a.isCompressed { 42 | ungzip, _ := gzip.NewReader(bytes.NewReader(a.blob)) 43 | ret, _ := ioutil.ReadAll(ungzip) 44 | ungzip.Close() 45 | return string(ret) 46 | } 47 | return a.str_blob 48 | } 49 | 50 | // Bytes returns (uncompressed) content of asset as a []byte 51 | func (a *Asset) Bytes() []byte { 52 | if a.isCompressed { 53 | ungzip, _ := gzip.NewReader(bytes.NewReader(a.blob)) 54 | ret, _ := ioutil.ReadAll(ungzip) 55 | ungzip.Close() 56 | return ret 57 | } 58 | ret := make([]byte, len(a.blob)) 59 | copy(ret, a.blob) 60 | return ret 61 | } 62 | 63 | // Size implements os.FileInfo and returns the size of the asset (uncompressed, if asset has been compressed) 64 | func (a *Asset) Size() int64 { return int64(a.size) } 65 | // Mode implements os.FileInfo and always returns 0444 66 | func (a *Asset) Mode() os.FileMode { return 0444 } 67 | // ModTime implements os.FileInfo and returns the time stamp when this package has been produced (the same value for all the assets) 68 | func (a *Asset) ModTime() time.Time { return stamp } 69 | // IsDir implements os.FileInfo and returns false 70 | func (a *Asset) IsDir() bool { return false } 71 | // Sys implements os.FileInfo and returns nil 72 | func (a *Asset) Sys() interface{} { return a } 73 | 74 | // WriteTo implements io.WriterTo interface and writes content of the asset to w 75 | func (a *Asset) WriteTo(w io.Writer) (int64, error) { 76 | if a.isCompressed { 77 | ungzip, _ := gzip.NewReader(bytes.NewReader(a.blob)) 78 | n, err := io.Copy(w, ungzip) 79 | ungzip.Close() 80 | return n, err 81 | } 82 | n, err := w.Write(a.blob) 83 | return int64(n), err 84 | } 85 | 86 | type assetReader struct { 87 | bytes.Reader 88 | } 89 | 90 | func (r *assetReader) Close() error { 91 | r.Reset(nil) 92 | return nil 93 | } 94 | 95 | // Returns content of the asset as io.ReaderCloser. 96 | func (a *Asset) Reader() io.ReadCloser { 97 | if a.isCompressed { 98 | ungzip, _ := gzip.NewReader(bytes.NewReader(a.blob)) 99 | return ungzip 100 | } else { 101 | ret := &assetReader{} 102 | ret.Reset(a.blob) 103 | return ret 104 | } 105 | } 106 | 107 | func cleanPath(path string) string { 108 | path = filepath.Clean(path) 109 | if filepath.IsAbs(path) { 110 | path = path[len(filepath.VolumeName(path)):] 111 | if len(path) > 0 || os.IsPathSeparator(path[0]) { 112 | path = path[1:] 113 | } 114 | } else if path == "." { 115 | return "" 116 | } 117 | return filepath.ToSlash(path) 118 | } 119 | 120 | // Opens asset as an io.ReadCloser. Returns os.ErrNotExist if no asset is found. 121 | func Open(name string) (File, error) { 122 | return FS().Open(name) 123 | } 124 | 125 | // Gets asset by name. Returns nil if no asset found. 126 | func Get(name string) *Asset { 127 | if entry, ok := fidx[name]; ok { 128 | return entry 129 | } else { 130 | return nil 131 | } 132 | } 133 | 134 | // Get asset by name. Panics if no asset found. 135 | func Must(name string) *Asset { 136 | if entry, ok := fidx[name]; ok { 137 | return entry 138 | } else { 139 | panic("asset " + name + " not found") 140 | } 141 | } 142 | 143 | type directoryAsset struct { 144 | name string 145 | dirs []directoryAsset 146 | files []Asset 147 | } 148 | 149 | var root *directoryAsset 150 | 151 | // A simple FileSystem abstraction 152 | type FileSystem interface { 153 | Open(name string) (File, error) 154 | Stat(name string) (os.FileInfo, error) 155 | // As in filepath.Walk 156 | Walk(root string, walkFunc filepath.WalkFunc) error 157 | } 158 | 159 | // The CopyTo method extracts all mentioned files 160 | // to a specified location, keeping directory structure. 161 | // If supplied file is a directory, than it will be extracted 162 | // recursively. CopyTo with no file mentioned will extract 163 | // the whole content of the embedded filesystem. 164 | // CopyTo returns error if there is a file with the same name 165 | // at the target location, unless overwrite is set to true, or 166 | // file has the same size and modification file as the extracted 167 | // file. 168 | // slate.CopyTo(".", mode, false) will effectively 169 | // extract content of the filesystem to the current directory (which 170 | // makes it the most space-wise inefficient self-extracting archive 171 | // ever). 172 | func CopyTo(target string, mode os.FileMode, overwrite bool, files ...string) error { 173 | mode = mode&0777 174 | dirmode := os.ModeDir|((mode&0444)>>2)|mode 175 | if len(files) == 0 { 176 | files = []string{""} 177 | } 178 | for _, file := range files { 179 | file = cleanPath(file) 180 | err := FS().Walk(file, func(path string, info os.FileInfo, err error) error { 181 | if err != nil { 182 | return err 183 | } 184 | targetPath := filepath.Join(target, path) 185 | fi, err := os.Stat(targetPath) 186 | if err == nil { 187 | if info.IsDir() && fi.IsDir() { 188 | return nil 189 | } else if info.IsDir() != fi.IsDir() { 190 | return os.ErrExist 191 | } else if !overwrite { 192 | if info.Size() == fi.Size() && info.ModTime().Equal(fi.ModTime()) { 193 | return nil 194 | } else { 195 | return os.ErrExist 196 | } 197 | } 198 | } 199 | if info.IsDir() { 200 | return os.MkdirAll(targetPath, dirmode) 201 | } 202 | asset := Get(path) 203 | if asset == nil { 204 | return os.ErrNotExist 205 | } 206 | targetPathDir := filepath.Dir(targetPath) 207 | if err = os.MkdirAll(targetPathDir, dirmode); err != nil { 208 | return err 209 | } 210 | dst, err := ioutil.TempFile(targetPathDir, ".imbed") 211 | if err != nil { 212 | return err 213 | } 214 | defer func() { 215 | dst.Close() 216 | os.Remove(dst.Name()) 217 | }() 218 | _, err = asset.WriteTo(dst) 219 | if err != nil { 220 | return err 221 | } 222 | dst.Close() 223 | os.Chtimes(dst.Name(), info.ModTime(), info.ModTime()) 224 | os.Chmod(dst.Name(), mode) 225 | return os.Rename(dst.Name(), targetPath) 226 | }) 227 | if err != nil { 228 | return err 229 | } 230 | } 231 | return nil 232 | } 233 | 234 | type fileInfoSlice []os.FileInfo 235 | func (fis *fileInfoSlice) Len() int { return len(*fis) } 236 | func (fis *fileInfoSlice) Less(i, j int) bool { return (*fis)[i].Name() < (*fis)[j].Name() } 237 | func (fis *fileInfoSlice) Swap(i, j int) { 238 | s := (*fis)[i] 239 | (*fis)[i] = (*fis)[j] 240 | (*fis)[j] = s 241 | } 242 | 243 | func walkRec(fs FileSystem, info os.FileInfo, p string, walkFn filepath.WalkFunc) error { 244 | var ( 245 | dir File 246 | fis fileInfoSlice 247 | err error 248 | ) 249 | err = walkFn(p, info, nil) 250 | if err != nil { 251 | if info.IsDir() && err == filepath.SkipDir { 252 | return nil 253 | } 254 | return err 255 | } 256 | if !info.IsDir() { 257 | return nil 258 | } 259 | dir, err = fs.Open(p) 260 | if err != nil { 261 | return walkFn(p, info, err) 262 | } 263 | fis, err = dir.Readdir(-1) 264 | if err != nil { 265 | return walkFn(p, info, err) 266 | } 267 | sort.Sort(&fis) 268 | for i := range fis { 269 | fn := path.Join(p, fis[i].Name()) 270 | err = walkRec(fs, fis[i], fn, walkFn) 271 | if err != nil { 272 | if !fis[i].IsDir() || err != filepath.SkipDir { 273 | return err 274 | } 275 | } 276 | } 277 | return nil 278 | } 279 | 280 | func walk(fs FileSystem, name string, walkFunc filepath.WalkFunc) error { 281 | var r os.FileInfo 282 | var err error 283 | name = cleanPath(name) 284 | r, err = fs.Stat(name) 285 | if err != nil { 286 | return err 287 | } 288 | return walkRec(fs, r, name, walkFunc) 289 | } 290 | 291 | type assetFs struct{} 292 | 293 | // Returns embedded FileSystem 294 | func FS() FileSystem { 295 | return &assetFs{} 296 | } 297 | 298 | func (fs *assetFs) Walk(root string, walkFunc filepath.WalkFunc) error { 299 | return walk(fs, root, walkFunc) 300 | } 301 | 302 | func (fs *assetFs) Stat(name string) (os.FileInfo, error) { 303 | name = cleanPath(name) 304 | if name == "" { 305 | return root, nil 306 | } 307 | if dir, ok := didx[name]; ok { 308 | return dir, nil 309 | } 310 | if asset, ok := fidx[name]; ok { 311 | return asset, nil 312 | } 313 | return nil, os.ErrNotExist 314 | } 315 | 316 | func (fs *assetFs) Open(name string) (File, error) { 317 | name = cleanPath(name) 318 | if name == "" { 319 | return root.open(""), nil 320 | } 321 | if dir, ok := didx[name]; ok { 322 | return dir.open(name), nil 323 | } 324 | if asset, ok := fidx[name]; ok { 325 | return asset.open(name), nil 326 | } 327 | return nil, os.ErrNotExist 328 | } 329 | 330 | 331 | // A File is returned by virtual FileSystem's Open method. 332 | // The methods should behave the same as those on an *os.File. 333 | type File interface { 334 | io.Closer 335 | io.Reader 336 | io.Seeker 337 | Readdir(count int) ([]os.FileInfo, error) 338 | Stat() (os.FileInfo, error) 339 | } 340 | 341 | func (a *Asset) open(name string) File { 342 | if a.isCompressed { 343 | ret := &assetCompressedFile{ 344 | asset: a, 345 | name: name, 346 | } 347 | ret.Reset(bytes.NewReader(a.blob)) 348 | return ret 349 | } else { 350 | ret := &assetFile{ 351 | asset: a, 352 | name: name, 353 | } 354 | ret.Reset(a.blob) 355 | return ret 356 | } 357 | } 358 | 359 | func (d *directoryAsset) open(name string) File { 360 | return &directoryAssetFile{ 361 | dir: d, 362 | name: name, 363 | pos: 0, 364 | } 365 | } 366 | 367 | type directoryAssetFile struct { 368 | dir *directoryAsset 369 | name string 370 | pos int 371 | } 372 | 373 | func (d *directoryAssetFile) Name() string { 374 | return d.name 375 | } 376 | 377 | func (d *directoryAssetFile) checkClosed() error { 378 | if d.pos < 0 { 379 | return os.ErrClosed 380 | } 381 | return nil 382 | } 383 | 384 | func (d *directoryAssetFile) Close() error { 385 | if err := d.checkClosed(); err != nil { 386 | return err 387 | } 388 | d.pos = -1 389 | return nil 390 | } 391 | 392 | func (d *directoryAssetFile) Read([]byte) (int, error) { 393 | if err := d.checkClosed(); err != nil { 394 | return 0, err 395 | } 396 | return 0, io.EOF 397 | } 398 | 399 | func (d *directoryAssetFile) Stat() (os.FileInfo, error) { 400 | if err := d.checkClosed(); err != nil { 401 | return nil, err 402 | } 403 | return d.dir, nil 404 | } 405 | 406 | func (d *directoryAssetFile) Seek(pos int64, whence int) (int64, error) { 407 | if err := d.checkClosed(); err != nil { 408 | return 0, err 409 | } 410 | return 0, os.ErrInvalid 411 | } 412 | 413 | func (d *directoryAssetFile) Readdir(count int) ([]os.FileInfo, error) { 414 | if err := d.checkClosed(); err != nil { 415 | return nil, err 416 | } 417 | var ( 418 | last int 419 | total = len(d.dir.dirs) + len(d.dir.files) 420 | ) 421 | if d.pos > total { 422 | if count > 0 { 423 | return nil, io.EOF 424 | } else { 425 | return nil, nil 426 | } 427 | } 428 | if count <= 0 || (d.pos + count) <= total { 429 | last = total 430 | } else { 431 | last = d.pos + count 432 | } 433 | ret := make([]os.FileInfo, 0, last - d.pos) 434 | if d.pos < len(d.dir.dirs) { 435 | var stop int 436 | if last > len(d.dir.dirs) { 437 | stop = len(d.dir.dirs) 438 | } else { 439 | stop = last 440 | } 441 | for i := d.pos; i < stop; i++ { 442 | ret = append(ret, &d.dir.dirs[i]) 443 | } 444 | d.pos = stop 445 | } 446 | var start, stop int 447 | start = d.pos - len(d.dir.dirs) 448 | stop = last - len(d.dir.dirs) 449 | for i := start; i < stop; i++ { 450 | ret = append(ret, &d.dir.files[i]) 451 | } 452 | d.pos = last 453 | return ret, nil 454 | } 455 | 456 | func (d *directoryAsset) Name() string { return d.name } 457 | func (d *directoryAsset) Size() int64 { return 0 } 458 | func (d *directoryAsset) Mode() os.FileMode { return os.ModeDir | 0555 } 459 | func (d *directoryAsset) ModTime() time.Time { return stamp } 460 | func (d *directoryAsset) IsDir() bool { return true } 461 | func (d *directoryAsset) Sys() interface{} { return d } 462 | 463 | type assetFile struct { 464 | assetReader 465 | name string 466 | asset *Asset 467 | } 468 | 469 | func (a *assetFile) Name() string { 470 | return a.name 471 | } 472 | 473 | func (a *assetFile) Stat() (os.FileInfo, error) { 474 | return a.asset, nil 475 | } 476 | 477 | func (a *assetFile) Readdir(int) ([]os.FileInfo, error) { 478 | return nil, os.ErrInvalid 479 | } 480 | type assetCompressedFile struct { 481 | gzip.Reader 482 | name string 483 | asset *Asset 484 | } 485 | 486 | func (a *assetCompressedFile) Name() string { 487 | return a.name 488 | } 489 | 490 | func (a *assetCompressedFile) Stat() (os.FileInfo, error) { 491 | return a.asset, nil 492 | } 493 | 494 | func (a *assetCompressedFile) Seek(int64, int) (int64, error) { 495 | return 0, os.ErrInvalid 496 | } 497 | 498 | func (a *assetCompressedFile) Readdir(count int) ([]os.FileInfo, error) { 499 | return nil, os.ErrInvalid 500 | } 501 | 502 | type unionFs struct { 503 | root string 504 | } 505 | 506 | func NewUnionFS(src string) (FileSystem, error) { 507 | abs, err := filepath.Abs(src) 508 | if err != nil { 509 | return nil, err 510 | } 511 | return &unionFs{ 512 | root: abs, 513 | }, nil 514 | } 515 | 516 | func (fs *unionFs) Stat(name string) (os.FileInfo, error) { 517 | name = cleanPath(name) 518 | fname := filepath.Join(fs.root, filepath.FromSlash(name)) 519 | fi, err := os.Stat(fname) 520 | if err == nil { 521 | return fi, nil 522 | } 523 | return FS().Stat(name) 524 | } 525 | 526 | func (fs *unionFs) Open(name string) (File, error) { 527 | name = cleanPath(name) 528 | fname := filepath.Join(fs.root, filepath.FromSlash(name)) 529 | fi, err := os.Stat(fname) 530 | if err == nil { 531 | file, err := os.OpenFile(fname, os.O_RDONLY, 0) 532 | if err == nil { 533 | if !fi.IsDir() { 534 | return &unionFsFile{ 535 | name: name, 536 | file: file, 537 | }, nil 538 | } else { 539 | dir, _ := didx[name] 540 | return &unionFsDirectoryFile{ 541 | name: name, 542 | dir: dir, 543 | fsDir: file, 544 | pos: 0, 545 | }, nil 546 | } 547 | } 548 | } 549 | return FS().Open(name) 550 | } 551 | 552 | func (fs *unionFs) Walk(root string, walkFunc filepath.WalkFunc) error { 553 | return walk(fs, root, walkFunc) 554 | } 555 | 556 | type unionFsFile struct { 557 | name string 558 | file *os.File 559 | } 560 | 561 | func (f *unionFsFile) Name() string { return f.name } 562 | func (f *unionFsFile) Close() error { return f.file.Close() } 563 | func (f *unionFsFile) Read(d []byte) (int, error) { return f.file.Read(d) } 564 | func (f *unionFsFile) Stat() (os.FileInfo, error) { return f.file.Stat() } 565 | func (f *unionFsFile) Seek(pos int64, whence int) (int64, error) { return f.file.Seek(pos, whence) } 566 | func (f *unionFsFile) Readdir(count int) ([]os.FileInfo, error) { return f.file.Readdir(count) } 567 | 568 | type unionFsDirectoryFile struct { 569 | name string 570 | dir *directoryAsset 571 | fsDir *os.File 572 | pos int 573 | } 574 | 575 | func (d *unionFsDirectoryFile) Name() string { return d.name } 576 | func (d *unionFsDirectoryFile) Close() error { 577 | if d.fsDir == nil { 578 | return os.ErrClosed 579 | } 580 | err := d.fsDir.Close() 581 | d.fsDir = nil 582 | return err 583 | } 584 | 585 | func (d *unionFsDirectoryFile) Read([]byte) (int, error) { 586 | if d.fsDir == nil { 587 | return 0, os.ErrClosed 588 | } 589 | return 0, io.EOF 590 | } 591 | 592 | func (d *unionFsDirectoryFile) Stat() (os.FileInfo, error) { 593 | if d.fsDir == nil { 594 | return nil, os.ErrClosed 595 | } 596 | return d.fsDir.Stat() 597 | } 598 | 599 | func (d *unionFsDirectoryFile) Seek(pos int64, whence int) (int64, error) { 600 | if d.fsDir == nil { 601 | return 0, os.ErrClosed 602 | } 603 | return 0, os.ErrInvalid 604 | } 605 | func (d *unionFsDirectoryFile) Readdir(count int) ([]os.FileInfo, error) { 606 | if d.fsDir == nil { 607 | return nil, os.ErrClosed 608 | } 609 | if d.pos < 0 { 610 | if count > 0 { 611 | return nil, io.EOF 612 | } else { 613 | return nil, nil 614 | } 615 | } 616 | if d.dir == nil { 617 | return d.fsDir.Readdir(count) 618 | } 619 | ret, err := d.fsDir.Readdir(count) 620 | if count > 0 && err == nil { 621 | return ret, err 622 | } 623 | embedded := make([]os.FileInfo, 0, len(d.dir.dirs) + len(d.dir.files)) 624 | for i := range d.dir.dirs { 625 | embedded = append(embedded, &d.dir.dirs[i]) 626 | } 627 | for i := range d.dir.files { 628 | embedded = append(embedded, &d.dir.files[i]) 629 | } 630 | for _, fi := range embedded[d.pos:] { 631 | if count > 0 && len(ret) >= count { 632 | return ret, nil 633 | } 634 | d.pos++ 635 | if _, err := os.Stat(filepath.Join(d.fsDir.Name(), fi.Name())); err == nil { 636 | continue 637 | } 638 | ret = append(ret, fi) 639 | } 640 | d.pos = -1 641 | return ret, nil 642 | } 643 | 644 | var fidx = make(map[string]*Asset) 645 | var didx = make(map[string]*directoryAsset) 646 | var stamp time.Time 647 | 648 | func init() { 649 | stamp = time.Unix(1557821140, 738068000) 650 | bb := blob_bytes(183384) 651 | bs := blob_string(183384) 652 | root = &directoryAsset{ 653 | dirs: []directoryAsset{ 654 | { 655 | name: "fonts", 656 | files: []Asset{ 657 | { 658 | name: "slate.eot", 659 | blob: bb[13360:15236], 660 | str_blob: bs[13360:15236], 661 | mime: "application/vnd.ms-fontobject", 662 | tag: "2r7uuaqqmoiv4", 663 | size: 1876, 664 | isCompressed: false, 665 | }, 666 | { 667 | name: "slate.svg", 668 | blob: bb[15240:16169], 669 | str_blob: bs[15240:16169], 670 | mime: "image/svg+xml", 671 | tag: "gplxd3enqktrg", 672 | size: 2936, 673 | isCompressed: true, 674 | }, 675 | { 676 | name: "slate.ttf", 677 | blob: bb[16176:17896], 678 | str_blob: bs[16176:17896], 679 | mime: "font/ttf", 680 | tag: "cjghtr7siys7o", 681 | size: 1720, 682 | isCompressed: false, 683 | }, 684 | { 685 | name: "slate.woff", 686 | blob: bb[17896:19692], 687 | str_blob: bs[17896:19692], 688 | mime: "font/woff", 689 | tag: "li2hxyu6dv65w", 690 | size: 1796, 691 | isCompressed: false, 692 | }, 693 | { 694 | name: "slate.woff2", 695 | blob: bb[19696:20492], 696 | str_blob: bs[19696:20492], 697 | mime: "font/woff2", 698 | tag: "ucope254cfna4", 699 | size: 796, 700 | isCompressed: false, 701 | }, 702 | }, 703 | }, 704 | { 705 | name: "images", 706 | files: []Asset{ 707 | { 708 | name: "logo.png", 709 | blob: bb[20496:42813], 710 | str_blob: bs[20496:42813], 711 | mime: "image/png", 712 | tag: "yai3uqds5i2y6", 713 | size: 22317, 714 | isCompressed: false, 715 | }, 716 | { 717 | name: "navbar.png", 718 | blob: bb[42816:42912], 719 | str_blob: bs[42816:42912], 720 | mime: "image/png", 721 | tag: "hrzkfootpekwg", 722 | size: 96, 723 | isCompressed: false, 724 | }, 725 | }, 726 | }, 727 | { 728 | name: "includes", 729 | files: []Asset{ 730 | { 731 | name: "_errors.md", 732 | blob: bb[42912:44075], 733 | str_blob: bs[42912:44075], 734 | mime: "application/binary", 735 | tag: "o6y3nrj6ft6ci", 736 | size: 1163, 737 | isCompressed: false, 738 | }, 739 | }, 740 | }, 741 | { 742 | name: "javascripts", 743 | dirs: []directoryAsset{ 744 | { 745 | name: "app", 746 | files: []Asset{ 747 | { 748 | name: "_lang.js", 749 | blob: bb[49032:50877], 750 | str_blob: bs[49032:50877], 751 | mime: "application/javascript", 752 | tag: "wfrqvnel6mft6", 753 | size: 4897, 754 | isCompressed: true, 755 | }, 756 | { 757 | name: "_search.js", 758 | blob: bb[50880:51803], 759 | str_blob: bs[50880:51803], 760 | mime: "application/javascript", 761 | tag: "tvsgfshjey3aq", 762 | size: 2396, 763 | isCompressed: true, 764 | }, 765 | { 766 | name: "_toc.js", 767 | blob: bb[51808:53102], 768 | str_blob: bs[51808:53102], 769 | mime: "application/javascript", 770 | tag: "qq77nggsovz2m", 771 | size: 3802, 772 | isCompressed: true, 773 | }, 774 | }, 775 | }, 776 | { 777 | name: "lib", 778 | files: []Asset{ 779 | { 780 | name: "_energize.js", 781 | blob: bb[53104:55249], 782 | str_blob: bs[53104:55249], 783 | mime: "application/javascript", 784 | tag: "4dgb3gjo2q5qo", 785 | size: 5455, 786 | isCompressed: true, 787 | }, 788 | { 789 | name: "_imagesloaded.min.js", 790 | blob: bb[55256:57594], 791 | str_blob: bs[55256:57594], 792 | mime: "application/javascript", 793 | tag: "bfj46g3p5l44g", 794 | size: 6949, 795 | isCompressed: true, 796 | }, 797 | { 798 | name: "_jquery.highlight.js", 799 | blob: bb[57600:59043], 800 | str_blob: bs[57600:59043], 801 | mime: "application/javascript", 802 | tag: "mhe3grcpsfjco", 803 | size: 3983, 804 | isCompressed: true, 805 | }, 806 | { 807 | name: "_jquery.js", 808 | blob: bb[59048:138538], 809 | str_blob: bs[59048:138538], 810 | mime: "application/javascript", 811 | tag: "emqozcy4gd5va", 812 | size: 268039, 813 | isCompressed: true, 814 | }, 815 | { 816 | name: "_lunr.js", 817 | blob: bb[138544:150758], 818 | str_blob: bs[138544:150758], 819 | mime: "application/javascript", 820 | tag: "4nzt4yhfywnru", 821 | size: 52362, 822 | isCompressed: true, 823 | }, 824 | }, 825 | }, 826 | }, 827 | files: []Asset{ 828 | { 829 | name: "all.js", 830 | blob: bb[48712:48771], 831 | str_blob: bs[48712:48771], 832 | mime: "application/javascript", 833 | tag: "dp6vkvg62mdf4", 834 | size: 53, 835 | isCompressed: true, 836 | }, 837 | { 838 | name: "all_nosearch.js", 839 | blob: bb[48776:49032], 840 | str_blob: bs[48776:49032], 841 | mime: "application/javascript", 842 | tag: "yscp6kzazppu4", 843 | size: 387, 844 | isCompressed: true, 845 | }, 846 | }, 847 | }, 848 | { 849 | name: "layouts", 850 | files: []Asset{ 851 | { 852 | name: "layout.tmpl", 853 | blob: bb[150760:152878], 854 | str_blob: bs[150760:152878], 855 | mime: "application/binary", 856 | tag: "gqa2hcaksx6vq", 857 | size: 2118, 858 | isCompressed: false, 859 | }, 860 | }, 861 | }, 862 | { 863 | name: "stylesheets", 864 | files: []Asset{ 865 | { 866 | name: "_icon-font.scss", 867 | blob: bb[152880:153677], 868 | str_blob: bs[152880:153677], 869 | mime: "application/binary", 870 | tag: "flig32x2gxww6", 871 | size: 797, 872 | isCompressed: false, 873 | }, 874 | { 875 | name: "_normalize.scss", 876 | blob: bb[153680:161606], 877 | str_blob: bs[153680:161606], 878 | mime: "application/binary", 879 | tag: "7w7nsc2eik5dy", 880 | size: 7926, 881 | isCompressed: false, 882 | }, 883 | { 884 | name: "_rtl.scss", 885 | blob: bb[161608:164536], 886 | str_blob: bs[161608:164536], 887 | mime: "application/binary", 888 | tag: "7ruwsdsbygfls", 889 | size: 2928, 890 | isCompressed: false, 891 | }, 892 | { 893 | name: "_variables.scss", 894 | blob: bb[164536:168329], 895 | str_blob: bs[164536:168329], 896 | mime: "application/binary", 897 | tag: "wv2nchckgitqs", 898 | size: 3793, 899 | isCompressed: false, 900 | }, 901 | { 902 | name: "print.css.scss", 903 | blob: bb[168336:170915], 904 | str_blob: bs[168336:170915], 905 | mime: "application/binary", 906 | tag: "6cazyz5hdscfm", 907 | size: 2579, 908 | isCompressed: false, 909 | }, 910 | { 911 | name: "screen.css.scss", 912 | blob: bb[170920:183381], 913 | str_blob: bs[170920:183381], 914 | mime: "application/binary", 915 | tag: "4bp2yjjknyhau", 916 | size: 12461, 917 | isCompressed: false, 918 | }, 919 | }, 920 | }, 921 | }, 922 | files: []Asset{ 923 | { 924 | name: "CHANGELOG.md", 925 | blob: bb[0:5868], 926 | str_blob: bs[0:5868], 927 | mime: "application/binary", 928 | tag: "tinn5zimkgd4y", 929 | size: 5868, 930 | isCompressed: false, 931 | }, 932 | { 933 | name: "LICENSE", 934 | blob: bb[5872:6441], 935 | str_blob: bs[5872:6441], 936 | mime: "application/binary", 937 | tag: "nknfjbqbkn6ay", 938 | size: 569, 939 | isCompressed: false, 940 | }, 941 | { 942 | name: "README.md", 943 | blob: bb[6448:13353], 944 | str_blob: bs[6448:13353], 945 | mime: "application/binary", 946 | tag: "a7d2dj6jtspf2", 947 | size: 6905, 948 | isCompressed: false, 949 | }, 950 | { 951 | name: "index.html.md", 952 | blob: bb[44080:48705], 953 | str_blob: bs[44080:48705], 954 | mime: "application/binary", 955 | tag: "dylorlkgytwes", 956 | size: 4625, 957 | isCompressed: false, 958 | }, 959 | }, 960 | } 961 | didx[""] = root 962 | didx["fonts"] = &root.dirs[0] 963 | fidx["fonts/slate.eot"] = &root.dirs[0].files[0] 964 | fidx["fonts/slate.svg"] = &root.dirs[0].files[1] 965 | fidx["fonts/slate.ttf"] = &root.dirs[0].files[2] 966 | fidx["fonts/slate.woff"] = &root.dirs[0].files[3] 967 | fidx["fonts/slate.woff2"] = &root.dirs[0].files[4] 968 | didx["images"] = &root.dirs[1] 969 | fidx["images/logo.png"] = &root.dirs[1].files[0] 970 | fidx["images/navbar.png"] = &root.dirs[1].files[1] 971 | didx["includes"] = &root.dirs[2] 972 | fidx["includes/_errors.md"] = &root.dirs[2].files[0] 973 | didx["javascripts"] = &root.dirs[3] 974 | didx["javascripts/app"] = &root.dirs[3].dirs[0] 975 | fidx["javascripts/app/_lang.js"] = &root.dirs[3].dirs[0].files[0] 976 | fidx["javascripts/app/_search.js"] = &root.dirs[3].dirs[0].files[1] 977 | fidx["javascripts/app/_toc.js"] = &root.dirs[3].dirs[0].files[2] 978 | didx["javascripts/lib"] = &root.dirs[3].dirs[1] 979 | fidx["javascripts/lib/_energize.js"] = &root.dirs[3].dirs[1].files[0] 980 | fidx["javascripts/lib/_imagesloaded.min.js"] = &root.dirs[3].dirs[1].files[1] 981 | fidx["javascripts/lib/_jquery.highlight.js"] = &root.dirs[3].dirs[1].files[2] 982 | fidx["javascripts/lib/_jquery.js"] = &root.dirs[3].dirs[1].files[3] 983 | fidx["javascripts/lib/_lunr.js"] = &root.dirs[3].dirs[1].files[4] 984 | fidx["javascripts/all.js"] = &root.dirs[3].files[0] 985 | fidx["javascripts/all_nosearch.js"] = &root.dirs[3].files[1] 986 | didx["layouts"] = &root.dirs[4] 987 | fidx["layouts/layout.tmpl"] = &root.dirs[4].files[0] 988 | didx["stylesheets"] = &root.dirs[5] 989 | fidx["stylesheets/_icon-font.scss"] = &root.dirs[5].files[0] 990 | fidx["stylesheets/_normalize.scss"] = &root.dirs[5].files[1] 991 | fidx["stylesheets/_rtl.scss"] = &root.dirs[5].files[2] 992 | fidx["stylesheets/_variables.scss"] = &root.dirs[5].files[3] 993 | fidx["stylesheets/print.css.scss"] = &root.dirs[5].files[4] 994 | fidx["stylesheets/screen.css.scss"] = &root.dirs[5].files[5] 995 | fidx["CHANGELOG.md"] = &root.files[0] 996 | fidx["LICENSE"] = &root.files[1] 997 | fidx["README.md"] = &root.files[2] 998 | fidx["index.html.md"] = &root.files[3] 999 | } 1000 | -------------------------------------------------------------------------------- /slate/internal/slate/index_386.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | #include "textflag.h" 4 | 5 | TEXT ·blob_bytes(SB),NOSPLIT,$0-4 6 | LEAL ·d(SB), AX 7 | MOVL AX, ret+4(FP) 8 | MOVL len+0(FP), AX 9 | MOVL AX, ret+8(FP) 10 | MOVL AX, ret+12(FP) 11 | RET 12 | 13 | TEXT ·blob_string(SB),NOSPLIT,$0-4 14 | LEAL ·d(SB), AX 15 | MOVL AX, ret+4(FP) 16 | MOVL len+0(FP), AX 17 | MOVL AX, ret+8(FP) 18 | RET 19 | -------------------------------------------------------------------------------- /slate/internal/slate/index_amd64.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | #include "textflag.h" 4 | 5 | TEXT ·blob_bytes(SB),NOSPLIT,$0-4 6 | LEAQ ·d(SB), AX 7 | MOVQ AX, ret+8(FP) 8 | MOVL len+0(FP), AX 9 | MOVLQSX AX, AX 10 | MOVQ AX, ret+16(FP) 11 | MOVQ AX, ret+24(FP) 12 | RET 13 | 14 | TEXT ·blob_string(SB),NOSPLIT,$0-4 15 | LEAQ ·d(SB), AX 16 | MOVQ AX, ret+8(FP) 17 | MOVL len+0(FP), AX 18 | MOVLQSX AX, AX 19 | MOVQ AX, ret+16(FP) 20 | RET 21 | -------------------------------------------------------------------------------- /slate/internal/slate/index_arm.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | #include "textflag.h" 4 | 5 | TEXT ·blob_bytes(SB),NOSPLIT,$0-4 6 | MOVW $·d(SB), R0 7 | MOVW R0, ret+4(FP) 8 | MOVW len+0(FP), R0 9 | MOVW R0, ret+8(FP) 10 | MOVW R0, ret+12(FP) 11 | RET 12 | 13 | TEXT ·blob_string(SB),NOSPLIT,$0-4 14 | MOVW $·d(SB), R0 15 | MOVW R0, ret+4(FP) 16 | MOVW len+0(FP), R0 17 | MOVW R0, ret+8(FP) 18 | RET 19 | -------------------------------------------------------------------------------- /slate/internal/slate/index_arm64.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | #include "textflag.h" 4 | 5 | TEXT ·blob_bytes(SB),NOSPLIT,$0-8 6 | MOVD $·d(SB), R0 7 | MOVD R0, ret+8(FP) 8 | MOVW len+0(FP), R0 9 | MOVD R0, ret+16(FP) 10 | MOVD R0, ret+24(FP) 11 | RET 12 | 13 | TEXT ·blob_string(SB),NOSPLIT,$0-8 14 | MOVD $·d(SB), R0 15 | MOVD R0, ret+8(FP) 16 | MOVD len+0(FP), R0 17 | MOVD R0, ret+16(FP) 18 | RET 19 | -------------------------------------------------------------------------------- /slate/internal/slate/index_mips64x.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | // +build mips64 mips64le 4 | 5 | #include "textflag.h" 6 | 7 | TEXT ·blob_bytes(SB),NOSPLIT,$0-8 8 | MOVV $·d(SB), R1 9 | MOVV R1, ret+8(FP) 10 | MOVV len+0(FP), R1 11 | MOVV R1, ret+16(FP) 12 | MOVV R1, ret+24(FP) 13 | JMP (R31) 14 | 15 | TEXT ·blob_string(SB),NOSPLIT,$0-8 16 | MOVV $·d(SB), R1 17 | MOVV R1, ret+8(FP) 18 | MOVV len+0(FP), R1 19 | MOVV R1, ret+16(FP) 20 | JMP (R31) 21 | -------------------------------------------------------------------------------- /slate/internal/slate/index_mipsx.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | // +build mips mipsle 4 | 5 | #include "textflag.h" 6 | 7 | TEXT ·blob_bytes(SB),NOSPLIT,$0-4 8 | MOVW $·d(SB), R1 9 | MOVW R1, ret+4(FP) 10 | MOVW len+0(FP), R1 11 | MOVW R1, ret+8(FP) 12 | MOVW R1, ret+12(FP) 13 | JMP (R31) 14 | 15 | TEXT ·blob_string(SB),NOSPLIT,$0-4 16 | MOVW $·d(SB), R1 17 | MOVW R1, ret+4(FP) 18 | MOVW len+0(FP), R1 19 | MOVW R1, ret+8(FP) 20 | JMP (R31) 21 | -------------------------------------------------------------------------------- /slate/internal/slate/index_ppc64x.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | // +build ppc64 ppc64le 4 | 5 | #include "textflag.h" 6 | 7 | TEXT ·blob_bytes(SB),NOSPLIT,$0-8 8 | MOVD $·d(SB), R3 9 | MOVD R3, ret+8(FP) 10 | MOVD len+0(FP), R3 11 | MOVD R3, ret+16(FP) 12 | MOVD R3, ret+24(FP) 13 | RET 14 | 15 | TEXT ·blob_string(SB),NOSPLIT,$0-8 16 | MOVD $·d(SB), R3 17 | MOVD R3, ret+8(FP) 18 | MOVD len+0(FP), R3 19 | MOVD R3, ret+16(FP) 20 | RET 21 | -------------------------------------------------------------------------------- /slate/internal/slate/index_s390x.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | #include "textflag.h" 4 | 5 | TEXT ·blob_bytes(SB),NOSPLIT|NOFRAME,$0-8 6 | MOVD $·d(SB), R0 7 | MOVW len+0(FP), R1 8 | MOVD R1, R2 9 | STMG R0, R2, ret+8(FP) 10 | JMP R14 11 | 12 | TEXT ·blob_string(SB),NOSPLIT|NOFRAME,$0-8 13 | MOVD $·d(SB), R0 14 | MOVW len+0(FP), R1 15 | STMG R0, R1, ret+8(FP) 16 | JMP R14 17 | -------------------------------------------------------------------------------- /slate/internal/slate/index_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | package slate 4 | 5 | import ( 6 | "encoding/base32" 7 | "encoding/binary" 8 | "hash/crc64" 9 | "testing" 10 | "math/rand" 11 | "os" 12 | "io/ioutil" 13 | "path/filepath" 14 | "bytes" 15 | "fmt" 16 | "errors" 17 | ) 18 | 19 | var randomName = func() string { 20 | var buf [16]byte 21 | binary.LittleEndian.PutUint64(buf[:8], rand.Uint64()) 22 | binary.LittleEndian.PutUint64(buf[8:], rand.Uint64()) 23 | return b32Enc.EncodeToString(buf[:]) 24 | }() 25 | 26 | var b32Enc = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567").WithPadding(base32.NoPadding) 27 | 28 | func getTag(data []byte) string { 29 | var crcBuf [8]byte 30 | binary.LittleEndian.PutUint64(crcBuf[:], crc64.Checksum(data, crc64.MakeTable(crc64.ECMA))) 31 | return b32Enc.EncodeToString(crcBuf[:]) 32 | } 33 | 34 | func TestBytes(t *testing.T) { 35 | for n, a := range fidx { 36 | if getTag(a.Bytes()) != a.tag { 37 | t.Fatalf("checksum for asset %s doesn't match recorded", n) 38 | } 39 | } 40 | } 41 | 42 | func TestString(t *testing.T) { 43 | for n, a := range fidx { 44 | if getTag([]byte(a.String())) != a.tag { 45 | t.Fatalf("checksum for asset %s doesn't match recorded", n) 46 | } 47 | } 48 | } 49 | func TestWalkOpen(t *testing.T) { 50 | FS().Walk("", func(path string, info os.FileInfo, err error) error { 51 | if err != nil || info.IsDir() { 52 | return nil 53 | } 54 | asset := Get(path) 55 | if asset == nil { 56 | return fmt.Errorf("asset %s nout found", path) 57 | } 58 | if getTag(asset.Bytes()) != asset.tag { 59 | return fmt.Errorf("checksum for asset %s doesn't match recorded", path) 60 | } 61 | if getTag([]byte(asset.String())) != asset.tag { 62 | return fmt.Errorf("checksum for asset %s doesn't match recorded", path) 63 | } 64 | rdr := asset.Reader() 65 | data, err := ioutil.ReadAll(rdr) 66 | rdr.Close() 67 | if err != nil { 68 | return err 69 | } 70 | rdr, err = FS().Open(path) 71 | if err != nil { 72 | return err 73 | } 74 | data, err = ioutil.ReadAll(rdr) 75 | rdr.Close() 76 | if err != nil { 77 | return err 78 | } 79 | if getTag(data) != asset.tag { 80 | return fmt.Errorf("checksum for asset %s doesn't match recorded", path) 81 | } 82 | rdr.Close() 83 | return nil 84 | }) 85 | } 86 | 87 | func rmtree(name string) { 88 | var files []string 89 | var dirs []string 90 | filepath.Walk(name, func(path string, info os.FileInfo, err error) error { 91 | if err != nil { 92 | return nil 93 | } 94 | if info.IsDir() { 95 | dirs = append(dirs, path) 96 | } else { 97 | files = append(files, path) 98 | } 99 | return nil 100 | }) 101 | for j := len(files) - 1; j >= 0; j-- { 102 | os.Remove(files[j]) 103 | } 104 | for j := len(dirs) - 1; j >= 0; j-- { 105 | os.Remove(dirs[j]) 106 | } 107 | } 108 | 109 | func TestCopyTo(t *testing.T) { 110 | tmp, err := ioutil.TempDir(os.TempDir(), ".test-test") 111 | if err != nil { 112 | t.Fatal(err) 113 | } 114 | defer rmtree(tmp) 115 | // a whole tree 116 | if err = CopyTo(tmp, 0640, false); err != nil { 117 | t.Fatal(err) 118 | } 119 | var testDir string 120 | var testFile string 121 | var testData []byte 122 | err = FS().Walk("", func(path string, info os.FileInfo, err error) error { 123 | if err != nil { 124 | return err 125 | } 126 | if info.IsDir() { 127 | if testDir == "" { 128 | testDir = path 129 | } 130 | return nil 131 | } 132 | f, err := os.OpenFile(filepath.Join(tmp, filepath.FromSlash(path)), os.O_RDONLY, 0) 133 | if err != nil { 134 | return err 135 | } 136 | data, err := ioutil.ReadAll(f) 137 | f.Close() 138 | if err != nil { 139 | return err 140 | } 141 | if testFile == "" { 142 | testFile = path 143 | testData = Must(path).Bytes() 144 | } 145 | if bytes.Compare(data, Must(path).Bytes()) != 0 { 146 | return fmt.Errorf("data differs for %s", path) 147 | } 148 | return nil 149 | }) 150 | if err != nil { 151 | t.Fatal(err) 152 | } 153 | if testFile == "" { 154 | // an empty archive? 155 | return 156 | } 157 | // single file 158 | targetTestFile := filepath.Join(tmp, testFile) 159 | os.Remove(targetTestFile) 160 | if err = CopyTo(tmp, 0644, false, testFile); err != nil { 161 | t.Fatal(err) 162 | } 163 | f, err := os.OpenFile(targetTestFile, os.O_RDONLY, 0) 164 | if err != nil { 165 | t.Fatal(err) 166 | } 167 | data, err := ioutil.ReadAll(f) 168 | f.Close() 169 | if err != nil { 170 | t.Fatal(err) 171 | } 172 | if bytes.Compare(testData, data) != 0 { 173 | t.Fatalf("data differs for single file extract") 174 | } 175 | if err = CopyTo(tmp, 0644, false, testFile); err != nil { 176 | t.Fatalf("expected no error, got %v", err) 177 | } 178 | f, err = os.OpenFile(targetTestFile, os.O_WRONLY | os.O_APPEND, 0600) 179 | if err != nil { 180 | t.Fatal(err) 181 | } 182 | _, err = f.WriteString(randomName) 183 | f.Close() 184 | if err != nil { 185 | t.Fatal(err) 186 | } 187 | if err = CopyTo(tmp, 0644, false, testFile); err != os.ErrExist { 188 | t.Fatalf("expected os.ErrExist, got %v", err) 189 | } 190 | if err = CopyTo(tmp, 0644, true, testFile); err != nil { 191 | t.Fatal(err) 192 | } 193 | f, err = os.OpenFile(targetTestFile, os.O_RDONLY, 0) 194 | if err != nil { 195 | t.Fatal(err) 196 | } 197 | data, err = ioutil.ReadAll(f) 198 | f.Close() 199 | if err != nil { 200 | t.Fatal(err) 201 | } 202 | if bytes.Compare(testData, data) != 0 { 203 | t.Fatalf("data differs for single file extract") 204 | } 205 | os.Remove(targetTestFile) 206 | if testDir == "" { 207 | // no directories in archive 208 | return 209 | } 210 | targetTestFile = filepath.Join(tmp, randomName) 211 | if err = CopyTo(targetTestFile, 0644, false, testDir); err != nil { 212 | t.Fatal(err) 213 | } 214 | err = FS().Walk(testDir, func(path string, info os.FileInfo, err error) error { 215 | if err != nil || info.IsDir() { 216 | return err 217 | } 218 | testData := Must(path).Bytes() 219 | f, err := os.OpenFile(filepath.Join(targetTestFile, filepath.FromSlash(path)), os.O_RDONLY, 0) 220 | if err != nil { 221 | return err 222 | } 223 | data, err := ioutil.ReadAll(f) 224 | f.Close() 225 | if err != nil { 226 | return err 227 | } 228 | if bytes.Compare(data, testData) != 0 { 229 | return fmt.Errorf("trees differ after partial extract") 230 | } 231 | return nil 232 | }) 233 | if err != nil { 234 | t.Fatal(err) 235 | } 236 | } 237 | 238 | var contentDiffers = errors.New("asset content differs") 239 | 240 | func checkFileContent(fs FileSystem, name string, content []byte) error { 241 | file, err := fs.Open(name) 242 | if err != nil { 243 | return err 244 | } 245 | newContent, err := ioutil.ReadAll(file) 246 | file.Close() 247 | if err != nil { 248 | return err 249 | } 250 | if bytes.Compare(content, newContent) != 0 { 251 | return contentDiffers 252 | } 253 | return nil 254 | } 255 | 256 | func TestUnionFs(t *testing.T) { 257 | tmp, err := ioutil.TempDir(os.TempDir(), ".slate-test") 258 | if err != nil { 259 | t.Fatal(err) 260 | } 261 | defer rmtree(tmp) 262 | fs, err := NewUnionFS(tmp) 263 | if err != nil { 264 | t.Fatal(err) 265 | } 266 | var testFile string 267 | var testFileData []byte 268 | cnt := 0 269 | err = FS().Walk("", func(path string, info os.FileInfo, err error) error { 270 | if err != nil { 271 | return err 272 | } 273 | cnt++ 274 | if testFile == "" && !info.IsDir() { 275 | testFile = path 276 | testFileData = Must(path).Bytes() 277 | } 278 | return nil 279 | }) 280 | if err != nil { 281 | t.Fatal(err) 282 | } 283 | if err = CopyTo(tmp, 0640, false); err != nil { 284 | t.Fatal(err) 285 | } 286 | if err = checkFileContent(fs, testFile, testFileData); err != nil { 287 | t.Fatal(err) 288 | } 289 | testTarget := filepath.Join(tmp, filepath.FromSlash(testFile)) 290 | file, err := os.OpenFile(testTarget, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) 291 | if err != nil { 292 | t.Fatal(err) 293 | } 294 | _, err = file.WriteString(randomName) 295 | file.Close() 296 | if err != nil { 297 | t.Fatal(err) 298 | } 299 | if err = checkFileContent(fs, testFile, testFileData); err == nil { 300 | t.Fatalf("content is not changed") 301 | } else if err != contentDiffers { 302 | t.Fatal(err) 303 | } 304 | if err = os.Remove(testTarget); err != nil { 305 | t.Fatal(err) 306 | } 307 | if err = checkFileContent(fs, testFile, testFileData); err != nil { 308 | t.Fatal(err) 309 | } 310 | testTarget = filepath.Join(tmp, randomName) 311 | file, err = os.OpenFile(testTarget, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) 312 | if err != nil { 313 | t.Fatal(err) 314 | } 315 | _, err = file.Write(testFileData) 316 | file.Close() 317 | if err != nil { 318 | t.Fatal(err) 319 | } 320 | if err = checkFileContent(fs, randomName, testFileData); err != nil { 321 | t.Fatal(err) 322 | } 323 | if err = os.Remove(testTarget); err != nil { 324 | t.Fatal(err) 325 | } 326 | if _, err = fs.Stat(randomName); err != os.ErrNotExist { 327 | t.Fatalf("temp file is not removed") 328 | } 329 | rcnt := 0 330 | err = fs.Walk("", func(path string, info os.FileInfo, err error) error { 331 | if err != nil { 332 | return err 333 | } 334 | rcnt++ 335 | if !info.IsDir() { 336 | if err = checkFileContent(fs, path, Must(path).Bytes()); err != nil { 337 | return err 338 | } 339 | } 340 | return nil 341 | }) 342 | if err != nil { 343 | t.Fatal(err) 344 | } 345 | if cnt != rcnt { 346 | t.Fatalf("number of walked items differ (%d != %d)", cnt, rcnt) 347 | } 348 | } -------------------------------------------------------------------------------- /slate/slate.go: -------------------------------------------------------------------------------- 1 | package slate 2 | 3 | //go:generate go-imbed --no-http-handler --fs --union-fs _slate internal/slate 4 | 5 | import ( 6 | "bufio" 7 | "bytes" 8 | "regexp" 9 | 10 | "github.com/growler/go-slate/slate/internal/slate" 11 | "github.com/tdewolff/minify" 12 | "github.com/wellington/go-libsass" 13 | "fmt" 14 | "strings" 15 | "path/filepath" 16 | "errors" 17 | "io" 18 | "io/ioutil" 19 | "context" 20 | "github.com/spf13/afero" 21 | "github.com/tdewolff/minify/js" 22 | "net/url" 23 | "path" 24 | ) 25 | 26 | const GoSlateVersion = "v1.0.1" 27 | const SlateVersion = "v2.3.1" 28 | 29 | func copyJavaScripts(fs slate.FileSystem, target *afero.Afero, search bool, minifyJs bool) error { 30 | var files []string 31 | jsDir, err := fs.Open("javascripts") 32 | if err != nil { 33 | return err 34 | } 35 | defer jsDir.Close() 36 | jsFiles, err := jsDir.Readdir(-1) 37 | if err != nil { 38 | return err 39 | } 40 | for i := range jsFiles { 41 | if !jsFiles[i].IsDir() && strings.HasSuffix(jsFiles[i].Name(), ".js") { 42 | files = append(files, jsFiles[i].Name()) 43 | } 44 | } 45 | for _, jsFile := range files { 46 | if jsFile == "all.js" && !search { 47 | continue 48 | } else if jsFile == "all_nosearch.js" && search { 49 | continue 50 | } 51 | if err := produceJavaScript(fs, jsFile, target, minifyJs); err != nil { 52 | return err 53 | } 54 | } 55 | return nil 56 | } 57 | 58 | var jsReqRE = regexp.MustCompile(`^//= require (.+)$`) 59 | 60 | func produceJavaScript(fs slate.FileSystem, src string, target *afero.Afero, minifyJs bool) error { 61 | var buf bytes.Buffer 62 | var loaded []string 63 | var err error 64 | if err := loadJS(fs, src, &buf, &loaded); err != nil { 65 | return err 66 | } 67 | var result []byte 68 | if minifyJs { 69 | m := minify.New() 70 | m.AddFunc("text/javascript", js.Minify) 71 | result, err = m.Bytes("text/javascript", buf.Bytes()) 72 | if err != nil { 73 | return err 74 | } 75 | } else { 76 | result = buf.Bytes() 77 | } 78 | file, err := target.TempFile("javascripts", ".slate") 79 | fileName := filepath.Join("javascripts", filepath.Base(file.Name())) 80 | if err != nil { 81 | return err 82 | } 83 | defer func() { 84 | file.Close() 85 | target.Remove(fileName) 86 | }() 87 | if _, err = file.Write(result); err != nil { 88 | return err 89 | } 90 | if err = file.Close(); err != nil { 91 | return err 92 | } else { 93 | return target.Rename(fileName, filepath.Join("javascripts", src)) 94 | } 95 | } 96 | 97 | func loadJS(fs slate.FileSystem, source string, buffer *bytes.Buffer, loaded *[]string) error { 98 | for _, s := range *loaded { 99 | if s == source { 100 | return nil 101 | } 102 | } 103 | *loaded = append(*loaded, source) 104 | inp, err := fs.Open(path.Join("javascripts", source)) 105 | if err != nil { 106 | return err 107 | } 108 | defer inp.Close() 109 | dir := path.Dir(source) 110 | rdr := bufio.NewScanner(inp) 111 | flg := true 112 | for rdr.Scan() { 113 | line := rdr.Text() 114 | if flg { 115 | m := jsReqRE.FindAllStringSubmatch(rdr.Text(), -1) 116 | if len(m) > 0 { 117 | required := filepath.Clean(filepath.Join(dir, m[0][1]) + ".js") 118 | if err != nil { 119 | return err 120 | } 121 | err = loadJS(fs, required, buffer, loaded) 122 | if err != nil { 123 | return err 124 | } 125 | continue 126 | } else { 127 | flg = false 128 | } 129 | } 130 | buffer.WriteString(line) 131 | buffer.WriteByte('\n') 132 | } 133 | return err 134 | } 135 | 136 | func updateTarget(name string, fs slate.FileSystem, target *afero.Afero) error { 137 | fi, err := fs.Stat(name) 138 | if err != nil { 139 | return err 140 | } 141 | dfi, err := target.Stat(name) 142 | if err == nil { 143 | if fi.Size() == dfi.Size() && fi.ModTime() == dfi.ModTime() { 144 | return nil 145 | } 146 | } 147 | dst, err := target.TempFile(path.Dir(name), ".image") 148 | dstName := filepath.Join(path.Dir(name), filepath.Base(dst.Name())) 149 | if err != nil { 150 | return err 151 | } 152 | defer func() { 153 | dst.Close() 154 | target.Remove(dstName) 155 | }() 156 | src, err := fs.Open(name) 157 | if err != nil { 158 | return err 159 | } 160 | defer src.Close() 161 | _, err = io.Copy(dst, src) 162 | if err != nil { 163 | return err 164 | } 165 | return target.Rename(dstName, name) 166 | } 167 | 168 | func copyImages(fs slate.FileSystem, target *afero.Afero, logo string) error { 169 | imagesDir, err := fs.Open("images") 170 | if err != nil { 171 | return fmt.Errorf("error copying images: %s", err) 172 | } 173 | imagesFiles, err := imagesDir.Readdir(-1) 174 | if err != nil { 175 | return fmt.Errorf("error copying images: %s", err) 176 | } 177 | for _, fi := range imagesFiles { 178 | if fi.IsDir() || (fi.Name() == "logo.png" && logo != "" && logo != fi.Name()) { 179 | continue 180 | } 181 | err := updateTarget(path.Join("images", fi.Name()), fs, target) 182 | if err != nil { 183 | return fmt.Errorf("error copying image file %s: %s", fi.Name(), err) 184 | } 185 | } 186 | return nil 187 | } 188 | 189 | func copyStylesheetsAndFonts(fs slate.FileSystem, target *afero.Afero, style []string, rtlEnabled bool, minifyCss bool) error { 190 | var fonts = make(map[string]bool) 191 | for _, s := range style { 192 | libsass.RegisterHeader(s) 193 | } 194 | libsass.RegisterSassFunc("font-url($url)", func(ctx context.Context, in libsass.SassValue) (*libsass.SassValue, error) { 195 | var args []string 196 | err := libsass.Unmarshal(in, &args) 197 | if err != nil { 198 | return nil, err 199 | } 200 | if len(args) != 1 { 201 | return nil, errors.New("invalid font-url call") 202 | } 203 | url, err := url.Parse(args[0]) 204 | if err != nil { 205 | return nil, fmt.Errorf("illegal font url font-url(%s)", args[0]) 206 | } 207 | fonts[url.Path] = true 208 | ret, err := libsass.Marshal(fmt.Sprintf("url(../fonts/%s)", args[0])) 209 | if err != nil { 210 | return nil, err 211 | } 212 | return &ret, nil 213 | }) 214 | var targets []string 215 | imports := libsass.NewImports() 216 | imports.Init() 217 | styleDir, err := fs.Open("stylesheets") 218 | if err != nil { 219 | return fmt.Errorf("can't open Slate stylesheets directory: %s", err) 220 | } 221 | styleFiles, err := styleDir.Readdir(-1) 222 | if err != nil { 223 | return fmt.Errorf("can't list Slate stylesheets directory: %s", err) 224 | } 225 | if !rtlEnabled { 226 | imports.Add("", "rtl", []byte{}) 227 | } 228 | for _, fi := range styleFiles { 229 | s := fi.Name() 230 | if fi.IsDir() { 231 | continue 232 | } 233 | if strings.HasPrefix(s, "_") && strings.HasSuffix(s, ".scss") { 234 | if !rtlEnabled && s == "_rtl.scss" { 235 | continue 236 | } 237 | file, err := fs.Open(path.Join("stylesheets", s)) 238 | if err != nil { 239 | return fmt.Errorf("can't open Slate stylesheets file %s %s", s, err) 240 | } 241 | data, err := ioutil.ReadAll(file) 242 | file.Close() 243 | if err != nil { 244 | return err 245 | } 246 | imports.Add("", s[1:len(s)-5], data) 247 | } else if strings.HasSuffix(s, ".css.scss") { 248 | targets = append(targets, s[:len(s)-5]) 249 | } 250 | } 251 | for _, s := range targets { 252 | err := func () error { 253 | src, err := fs.Open(path.Join("stylesheets", s+".scss")) 254 | if err != nil { 255 | return fmt.Errorf("can't open Slate stylesheet source file %s: %s", s+".scss", err) 256 | } 257 | defer src.Close() 258 | dst, err := target.TempFile("stylesheets", ".scss") 259 | dstName := filepath.Join("stylesheets", filepath.Base(dst.Name())) 260 | if err != nil { 261 | return err 262 | } 263 | defer func() { 264 | dst.Close() 265 | target.Remove(dstName) 266 | }() 267 | var style int 268 | if minifyCss { 269 | style = libsass.COMPRESSED_STYLE 270 | } else { 271 | style = libsass.EXPANDED_STYLE 272 | } 273 | compiler, err := libsass.New(dst, src, 274 | libsass.ImportsOption(imports), 275 | libsass.FontDir("fonts"), 276 | libsass.ImgDir("images"), 277 | libsass.OutputStyle(style), 278 | ) 279 | if err != nil { 280 | return err 281 | } 282 | if err = compiler.Run(); err != nil { 283 | return err 284 | } 285 | src.Close() 286 | dst.Close() 287 | return target.Rename(dstName, filepath.Join("stylesheets", s)) 288 | }() 289 | if err != nil { 290 | return err 291 | } 292 | } 293 | for k := range fonts { 294 | err := updateTarget(path.Join("fonts", k), fs, target) 295 | if err != nil { 296 | return fmt.Errorf("error copying font %s: %s", k, err) 297 | } 298 | } 299 | return nil 300 | } 301 | -------------------------------------------------------------------------------- /slate/slateficate.go: -------------------------------------------------------------------------------- 1 | package slate 2 | 3 | import ( 4 | "os" 5 | "io" 6 | "github.com/growler/go-slate/slate/internal/slate" 7 | "sort" 8 | "strings" 9 | "fmt" 10 | "github.com/spf13/afero" 11 | "io/ioutil" 12 | ) 13 | 14 | func makeTargetDirs(fs *afero.Afero, dirs ...string) error { 15 | for _, d := range dirs { 16 | if err := fs.MkdirAll(d, 0755); err != nil { 17 | return err 18 | } 19 | } 20 | return nil 21 | } 22 | 23 | // Configuration 24 | type Params struct { 25 | MinifyHTML bool // produce compact HTML 26 | MinifyJS bool // minify Javascript 27 | MinifyCSS bool // produce compact CSS 28 | StyleFile string // load SCSS overrides 29 | LogoFile string // use this logo (which should be located in images/ directory) 30 | Search *bool // if nil, use the default from index.html.md preamble 31 | RTL *bool // Right-to-Left CSS, if nil, use the default from index.html.md preamble 32 | } 33 | 34 | // Go Slate! 35 | func Slateficate(src string, target *afero.Afero, params Params) error { 36 | var err error 37 | fs, err := slate.NewUnionFS(src) 38 | if err != nil { 39 | return err 40 | } 41 | err = makeTargetDirs(target, "javascripts", "stylesheets", "fonts", "images") 42 | if err != nil { 43 | return err 44 | } 45 | input, err := load(fs, params) 46 | if err != nil { 47 | return err 48 | } 49 | if err = input.produce(target, params.MinifyHTML); err != nil { 50 | return err 51 | } 52 | if err = copyJavaScripts(fs, target, input.Params.Search, params.MinifyJS); err != nil { 53 | return err 54 | } 55 | var styles []string 56 | if input.Params.Style != "" { 57 | styles = append(styles, input.Params.Style) 58 | } 59 | if params.StyleFile != "" { 60 | file, err := os.OpenFile(params.StyleFile, os.O_RDONLY, 0) 61 | if err != nil { 62 | return err 63 | } 64 | data, err := ioutil.ReadAll(file) 65 | file.Close() 66 | if err != nil { 67 | return err 68 | } 69 | styles = append(styles, string(data)) 70 | } 71 | if err = copyStylesheetsAndFonts(fs, target, styles, input.Params.RTLEnabled, params.MinifyCSS); err != nil { 72 | return err 73 | } 74 | if err = copyImages(fs, target, input.Params.Logo); err != nil { 75 | return err 76 | } 77 | return nil 78 | } 79 | 80 | // Extract embedded slate components to target 81 | func Extract(target string, overwrite bool, components ...string) error { 82 | var srcFiles []string 83 | var dstFiles = make(map[string]bool) 84 | var dst []string 85 | slate.FS().Walk("", func(path string, info os.FileInfo, err error) error { 86 | if !info.IsDir() { 87 | srcFiles = append(srcFiles, path) 88 | } 89 | return nil 90 | }) 91 | sort.Strings(srcFiles) 92 | for _, c := range components { 93 | switch c { 94 | case "all": 95 | dst = srcFiles 96 | goto Copy 97 | case "contents": 98 | dstFiles["index.html.md"] = true 99 | dstFiles["includes/_errors.md"] = true 100 | case "fonts", "images", "layouts", "javascripts", "stylesheets": 101 | for _, s := range srcFiles { 102 | if strings.HasPrefix(s, c + "/") { 103 | dstFiles[s] = true 104 | } 105 | } 106 | default: 107 | if n := sort.SearchStrings(srcFiles, c); n < len(srcFiles) && srcFiles[n] == c { 108 | dstFiles[c] = true 109 | } else { 110 | return fmt.Errorf("unknown slate component or file %s", c) 111 | } 112 | } 113 | } 114 | for f := range dstFiles { 115 | dst = append(dst, f) 116 | } 117 | Copy: 118 | if err := slate.CopyTo(target, 0640, overwrite, dst...); err != nil { 119 | return err 120 | } 121 | return nil 122 | } 123 | 124 | func ListBundled(w io.Writer) { 125 | var srcFiles []string 126 | slate.FS().Walk("", func(path string, info os.FileInfo, err error) error { 127 | if !info.IsDir() { 128 | srcFiles = append(srcFiles, path) 129 | } 130 | return nil 131 | }) 132 | sort.Strings(srcFiles) 133 | for _, s := range srcFiles { 134 | w.Write([]byte(s)) 135 | w.Write([]byte{'\n'}) 136 | } 137 | } --------------------------------------------------------------------------------