diff --git a/.gitignore b/.gitignore index 2c9063eed..665360d49 100644 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,2 @@ -hugo -docs/public* /.idea -hugo.exe -*.test -*.prof -nohup.out -cover.out -*.swp -*.swo -.DS_Store -*~ -vendor/*/ -*.bench -coverage*.out - -GoBuilds -dist +/public diff --git a/.goxc.json b/.goxc.json deleted file mode 100644 index 10f19d226..000000000 --- a/.goxc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "ArtifactsDest": "GoBuilds/", - "OutPath": "{{.Dest}}{{.PS}}{{.AppName}}{{.PS}}{{.Version}}{{.PS}}{{.AppName}}_{{.Version}}_{{.Os}}_{{.Arch}}{{.Ext}}", - "BuildConstraints": "linux windows darwin freebsd netbsd openbsd dragonfly", - "ConfigVersion": "0.9" -} diff --git a/.mailmap b/.mailmap deleted file mode 100644 index e93adabc1..000000000 --- a/.mailmap +++ /dev/null @@ -1,3 +0,0 @@ -spf13 Steve Francia -bep Bjørn Erik Pedersen - diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 514ab1ddf..000000000 --- a/.travis.yml +++ /dev/null @@ -1,23 +0,0 @@ -language: go -sudo: required -go: - - 1.7.6 - - 1.8.3 - - tip -os: - - linux - - osx -matrix: - allow_failures: - - go: tip - fast_finish: true -install: - - make vendor -script: - - make hugo-race check - - ./hugo -s docs/ - - ./hugo --renderToMemory -s docs/ -before_install: - # gem install must be run with sudo on OSX - - sudo gem install asciidoctor | gem install asciidoctor - - sudo pip install docutils diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 2414a651c..000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,165 +0,0 @@ -# Contributing to Hugo - -We welcome contributions to Hugo of any kind including documentation, themes, -organization, tutorials, blog posts, bug reports, issues, feature requests, -feature implementations, pull requests, answering questions on the forum, -helping to manage issues, etc. - -The Hugo community and maintainers are [very active](https://github.com/gohugoio/hugo/pulse/monthly) and helpful, and the project benefits greatly from this activity. We created a [step by step guide](https://gohugo.io/tutorials/how-to-contribute-to-hugo/) if you're unfamiliar with GitHub or contributing to open source projects in general. - -*Note that this repository only contains the actual source code of Hugo. For **only** documentation-related pull requests / issues please refer to the [hugoDocs](https://github.com/gohugoio/hugoDocs) repository.* - -*Pull requests that contain changes on the code base **and** related documentation, e.g. for a new feature, shall remain a single, atomic one.* - -## Table of Contents - -* [Asking Support Questions](#asking-support-questions) -* [Reporting Issues](#reporting-issues) -* [Submitting Patches](#submitting-patches) - * [Code Contribution Guidelines](#code-contribution-guidelines) - * [Git Commit Message Guidelines](#git-commit-message-guidelines) - * [Vendored Dependencies](#vendored-dependencies) - * [Fetching the Sources From GitHub](#fetching-the-sources-from-github) - * [Using Git Remotes](#using-git-remotes) - * [Build Hugo with Your Changes](#build-hugo-with-your-changes) - * [Updating the Hugo Sources](#updating-the-hugo-sources) - -## Asking Support Questions - -We have an active [discussion forum](https://discourse.gohugo.io) where users and developers can ask questions. -Please don't use the GitHub issue tracker to ask questions. - -## Reporting Issues - -If you believe you have found a defect in Hugo or its documentation, use -the GitHub [issue tracker](https://github.com/gohugoio/hugo/issues) to report the problem to the Hugo maintainers. -If you're not sure if it's a bug or not, start by asking in the [discussion forum](https://discourse.gohugo.io). -When reporting the issue, please provide the version of Hugo in use (`hugo version`) and your operating system. - -## Submitting Patches - -The Hugo project welcomes all contributors and contributions regardless of skill or experience level. -If you are interested in helping with the project, we will help you with your contribution. -Hugo is a very active project with many contributions happening daily. -Because we want to create the best possible product for our users and the best contribution experience for our developers, -we have a set of guidelines which ensure that all contributions are acceptable. -The guidelines are not intended as a filter or barrier to participation. -If you are unfamiliar with the contribution process, the Hugo team will help you and teach you how to bring your contribution in accordance with the guidelines. - -### Code Contribution Guidelines - -To make the contribution process as seamless as possible, we ask for the following: - -* Go ahead and fork the project and make your changes. We encourage pull requests to allow for review and discussion of code changes. -* When you’re ready to create a pull request, be sure to: - * Sign the [CLA](https://cla-assistant.io/gohugoio/hugo). - * Have test cases for the new code. If you have questions about how to do this, please ask in your pull request. - * Run `go fmt`. - * Add documentation if you are adding new features or changing functionality. The docs site lives in `/docs`. - * Squash your commits into a single commit. `git rebase -i`. It’s okay to force update your pull request with `git push -f`. - * Ensure that `make check` succeeds. [Travis CI](https://travis-ci.org/gohugoio/hugo) (Linux and macOS) and [AppVeyor](https://ci.appveyor.com/project/gohugoio/hugo/branch/master) (Windows) will fail the build if `make check` fails. - * Follow the **Git Commit Message Guidelines** below. - -### Git Commit Message Guidelines - -This [blog article](http://chris.beams.io/posts/git-commit/) is a good resource for learning how to write good commit messages, -the most important part being that each commit message should have a title/subject in imperative mood starting with a capital letter and no trailing period: -*"Return error on wrong use of the Paginator"*, **NOT** *"returning some error."* - -Also, if your commit references one or more GitHub issues, always end your commit message body with *See #1234* or *Fixes #1234*. -Replace *1234* with the GitHub issue ID. The last example will close the issue when the commit is merged into *master*. - -Sometimes it makes sense to prefix the commit message with the packagename (or docs folder) all lowercased ending with a colon. -That is fine, but the rest of the rules above apply. -So it is "tpl: Add emojify template func", not "tpl: add emojify template func.", and "docs: Document emoji", not "doc: document emoji." - -Please consider to use a short and descriptive branch name, e.g. **NOT** "patch-1". It's very common but creates a naming conflict each time when a submission is pulled for a review. - -An example: - -```text -tpl: Add custom index function - -Add a custom index template function that deviates from the stdlib simply by not -returning an "index out of range" error if an array, slice or string index is -out of range. Instead, we just return nil values. This should help make the -new default function more useful for Hugo users. - -Fixes #1949 -``` - -### Vendored Dependencies - -Hugo uses [govendor](https://github.com/kardianos/govendor) to vendor dependencies, but we don't commit the vendored packages themselves to the Hugo git repository. -Therefore, a simple `go get` is not supported since `go get` is not vendor-aware. -You **must use govendor** to fetch and manage Hugo's dependencies. - -### Fetch the Sources From GitHub - -``` -go get -u github.com/kardianos/govendor -govendor get github.com/gohugoio/hugo -``` - -### Using Git Remotes - -Due to the way Go handles package imports, the best approach for working on a -Hugo fork is to use Git Remotes. Here's a simple walk-through for getting -started: - -1. Fetch the Hugo sources as described above. - -1. Change to the Hugo source directory: - - ``` - cd $HOME/go/src/github.com/gohugoio/hugo - ``` - -1. Create a new branch for your changes (the branch name is arbitrary): - - ``` - git checkout -b iss1234 - ``` - -1. After making your changes, commit them to your new branch: - - ``` - git commit -a -v - ``` - -1. Fork Hugo in GitHub. - -1. Add your fork as a new remote (the remote name, "fork" in this example, is arbitrary): - - ``` - git remote add fork git://github.com/USERNAME/hugo.git - ``` - -1. Push the changes to your new remote: - - ``` - git push --set-upstream fork iss1234 - ``` - -1. You're now ready to submit a PR based upon the new branch in your forked repository. - -### Build Hugo with Your Changes - -```bash -cd $HOME/go/src/github.com/gohugoio/hugo -make hugo -# or to install in $HOME/go/bin: -make install -``` - -### Updating the Hugo Sources - -If you want to stay in sync with the Hugo repository, you can easily pull down -the source changes, but you'll need to keep the vendored packages up-to-date as -well. - -``` -git pull -make vendor -``` - diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index f8ec73a86..000000000 --- a/Dockerfile +++ /dev/null @@ -1,34 +0,0 @@ -FROM alpine:3.6 - -ENV GOPATH /go -ENV PATH $GOPATH/bin:$PATH - -RUN \ - adduser -h /site -s /sbin/nologin -u 1000 -D hugo && \ - apk add --no-cache \ - dumb-init && \ - apk add --no-cache --virtual .build-deps \ - gcc \ - musl-dev \ - go \ - git && \ - mkdir -p \ - ${GOPATH}/bin \ - ${GOPATH}/pkg \ - ${GOPATH}/src && \ - go get github.com/kardianos/govendor && \ - govendor get github.com/gohugoio/hugo && \ - cd $GOPATH/src/github.com/gohugoio/hugo && \ - rm -f $GOPATH/bin/hugo && \ - go install -ldflags '-s -w' && \ - cd $GOPATH && \ - rm -rf pkg src .cache bin/govendor && \ - apk del .build-deps - -USER hugo -WORKDIR /site -VOLUME /site -EXPOSE 1313 - -ENTRYPOINT ["/usr/bin/dumb-init", "--", "hugo"] -CMD [ "--help" ] diff --git a/Makefile b/Makefile deleted file mode 100644 index 0518967ce..000000000 --- a/Makefile +++ /dev/null @@ -1,86 +0,0 @@ -# A Self-Documenting Makefile: http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html - -PACKAGE = github.com/gohugoio/hugo -COMMIT_HASH = `git rev-parse --short HEAD 2>/dev/null` -BUILD_DATE = `date +%FT%T%z` -LDFLAGS = -ldflags "-X ${PACKAGE}/hugolib.CommitHash=${COMMIT_HASH} -X ${PACKAGE}/hugolib.BuildDate=${BUILD_DATE}" -NOGI_LDFLAGS = -ldflags "-X ${PACKAGE}/hugolib.BuildDate=${BUILD_DATE}" - -# allow user to override go executable by running as GOEXE=xxx make ... on unix-like systems -GOEXE ?= go - -.PHONY: vendor docker check fmt lint test test-race vet test-cover-html help -.DEFAULT_GOAL := help - -vendor: ## Install govendor and sync Hugo's vendored dependencies - ${GOEXE} get github.com/kardianos/govendor - govendor sync ${PACKAGE} - -hugo: vendor ## Build hugo binary - ${GOEXE} build ${LDFLAGS} ${PACKAGE} - -hugo-race: vendor ## Build hugo binary with race detector enabled - ${GOEXE} build -race ${LDFLAGS} ${PACKAGE} - -install: vendor ## Install hugo binary - ${GOEXE} install ${LDFLAGS} ${PACKAGE} - -hugo-no-gitinfo: LDFLAGS = ${NOGI_LDFLAGS} -hugo-no-gitinfo: vendor hugo ## Build hugo without git info - -docker: ## Build hugo Docker container - docker build -t hugo . - docker rm -f hugo-build || true - docker run --name hugo-build hugo ls /go/bin - docker cp hugo-build:/go/bin/hugo . - docker rm hugo-build - -govendor: vendor # Deprecated: use "vendor" target -get: vendor # Deprecated: use "vendor" -gitinfo: hugo # Deprecated: use "hugo" target -install-gitinfo: install # Deprecated: use "install" target -no-git-info: hugo-no-gitinfo # Deprecated: use "hugo-no-gitinfo" target - -check: test-race test386 fmt vet ## Run tests and linters - -test386: ## Run tests in 32-bit mode - GOARCH=386 govendor test +local - -test: ## Run tests - govendor test +local - -test-race: ## Run tests with race detector - govendor test -race +local - -fmt: ## Run gofmt linter - @for d in `govendor list -no-status +local | sed 's/github.com.gohugoio.hugo/./'` ; do \ - if [ "`gofmt -l $$d/*.go | tee /dev/stderr`" ]; then \ - echo "^ improperly formatted go files" && echo && exit 1; \ - fi \ - done - -lint: ## Run golint linter - @for d in `govendor list -no-status +local | sed 's/github.com.gohugoio.hugo/./'` ; do \ - if [ "`golint $$d | tee /dev/stderr`" ]; then \ - echo "^ golint errors!" && echo && exit 1; \ - fi \ - done - -vet: ## Run go vet linter - @if [ "`govendor vet +local | tee /dev/stderr`" ]; then \ - echo "^ go vet errors!" && echo && exit 1; \ - fi - -test-cover-html: PACKAGES = $(shell govendor list -no-status +local | sed 's/github.com.gohugoio.hugo/./') -test-cover-html: ## Generate test coverage report - echo "mode: count" > coverage-all.out - $(foreach pkg,$(PACKAGES),\ - govendor test -coverprofile=coverage.out -covermode=count $(pkg);\ - tail -n +2 coverage.out >> coverage-all.out;) - ${GOEXE} tool cover -html=coverage-all.out - -check-vendor: ## Verify that vendored packages match git HEAD - @git diff-index --quiet HEAD vendor/ || (echo "check-vendor target failed: vendored packages out of sync" && echo && git diff vendor/ && exit 1) - -help: - @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' diff --git a/README.md b/README.md index 30bfa8b10..3fbfcf1ca 100644 --- a/README.md +++ b/README.md @@ -1,120 +1,35 @@ -![Hugo](https://raw.githubusercontent.com/gohugoio/hugoDocs/master/static/img/hugo-logo.png) +# Hugo Docs -A Fast and Flexible Static Site Generator built with love by [spf13](http://spf13.com/) and [friends](https://github.com/gohugoio/hugo/graphs/contributors) in [Go][]. +Documentation site for [Hugo](https://github.com/gohugoio/hugo), the very fast and flexible static site generator built with love in GoLang. -[Website](https://gohugo.io) | -[Forum](https://discourse.gohugo.io) | -[Developer Chat (no support)](https://gitter.im/gohugoio/hugo) | -[Documentation](https://gohugo.io/overview/introduction/) | -[Installation Guide](https://gohugo.io/overview/installing/) | -[Contribution Guide](CONTRIBUTING.md) | -[Twitter](http://twitter.com/gohugoio) +## Contributing -[![GoDoc](https://godoc.org/github.com/gohugoio/hugo?status.svg)](https://godoc.org/github.com/gohugoio/hugo) -[![Linux and macOS Build Status](https://api.travis-ci.org/gohugoio/hugo.svg?branch=master&label=Linux+and+macOS+build "Linux and macOS Build Status")](https://travis-ci.org/gohugoio/hugo) -[![Windows Build Status](https://ci.appveyor.com/api/projects/status/a5mr220vsd091kua?svg=true&label=Windows+build "Windows Build Status")](https://ci.appveyor.com/project/bep/hugo/branch/master) -[![Dev chat at https://gitter.im/gohugoio/hugo](https://img.shields.io/badge/gitter-developer_chat-46bc99.svg)](https://gitter.im/spf13/hugo?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![Go Report Card](https://goreportcard.com/badge/github.com/gohugoio/hugo)](https://goreportcard.com/report/github.com/gohugoio/hugo) +We welcome contributions to Hugo of any kind including documentation, suggestions, bug reports, pull requests etc. Also check out our [contribution guide](https://gohugo.io/contribute/documentation/). We would love to hear from you. -## Overview +Note that this repository contains solely the documentation for Hugo. For contributions that aren't documentation-related please refer to the [hugo](https://github.com/gohugoio/hugo) repository. -Hugo is a static HTML and CSS website generator written in [Go][]. -It is optimized for speed, easy use and configurability. -Hugo takes a directory with content and templates and renders them into a full HTML website. +*Pull requests shall **only** contain changes to the actual documentation. However, changes on the code base of Hugo **and** the documentation shall be a single, atomic pull request in the [hugo](https://github.com/gohugoio/hugo) repository.* -Hugo relies on Markdown files with front matter for meta data. -And you can run Hugo from any directory. -This works well for shared hosts and other systems where you don’t have a privileged account. -Hugo renders a typical website of moderate size in a fraction of a second. -A good rule of thumb is that each piece of content renders in around 1 millisecond. +## Build -Hugo is designed to work well for any kind of website including blogs, tumbles and docs. - -#### Supported Architectures - -Currently, we provide pre-built Hugo binaries for Windows, Linux, FreeBSD, NetBSD and macOS (Darwin) and [Android](https://gist.github.com/bep/a0d8a26cf6b4f8bc992729b8e50b480b) for x64, i386 and ARM architectures. - -Hugo may also be compiled from source wherever the Go compiler tool chain can run, e.g. for other operating systems including DragonFly BSD, OpenBSD, Plan 9 and Solaris. - -**Complete documentation is available at [Hugo Documentation][].** - -## Choose How to Install - -If you want to use Hugo as your site generator, simply install the Hugo binaries. -The Hugo binaries have no external dependencies. - -To contribute to the Hugo source code or documentation, you should [fork the Hugo GitHub project](https://github.com/gohugoio/hugo#fork-destination-box) and clone it to your local machine. - -Finally, you can install the Hugo source code with `go`, build the binaries yourself, and run Hugo that way. -Building the binaries is an easy task for an experienced `go` getter. - -### Install Hugo as Your Site Generator (Binary Install) - -Use the [installation instructions in the Hugo documentation](https://gohugo.io/overview/installing/). - -### Build and Install the Binaries from Source (Advanced Install) - -Add Hugo and its package dependencies to your go `src` directory. - - go get -v github.com/gohugoio/hugo - -Once the `get` completes, you should find your new `hugo` (or `hugo.exe`) executable sitting inside `$GOPATH/bin/`. - -To update Hugo’s dependencies, use `go get` with the `-u` option. - - go get -u -v github.com/gohugoio/hugo - -## The Hugo Documentation - -The Hugo documentation now lives in its own repository, see https://github.com/gohugoio/hugoDocs. But we do keep a version of that documentation as a `git subtree` in this repository. To build the sub folder `/docs` as a Hugo site, you need to clone this repo with submodules: +To view the documentation site locally, you need to clone this repository: ```bash -git clone --recursive git@github.com:gohugoio/hugo.git +git clone https://github.com/gohugoio/hugoDocs.git ``` -Or after you have cloned it you can do: +Also note that the documentation version for a given version of Hugo can also be found in the `/docs` sub-folder of the [Hugo source repository](https://github.com/gohugoio/hugo). + +Then to view the docs in your browser, run Hugo and open up the link: ```bash -git submodule update --init +▶ hugo server + +Started building sites ... +. +. +Serving pages from memory +Web Server is available at http://localhost:1313/ (bind address 127.0.0.1) +Press Ctrl+C to stop ``` - -## Contributing to Hugo - -For a complete guide to contributing to Hugo, see the [Contribution Guide](CONTRIBUTING.md). - -We welcome contributions to Hugo of any kind including documentation, themes, -organization, tutorials, blog posts, bug reports, issues, feature requests, -feature implementations, pull requests, answering questions on the forum, -helping to manage issues, etc. - -The Hugo community and maintainers are [very active](https://github.com/gohugoio/hugo/pulse/monthly) and helpful, and the project benefits greatly from this activity. - -### Asking Support Questions - -We have an active [discussion forum](https://discourse.gohugo.io) where users and developers can ask questions. -Please don't use the GitHub issue tracker to ask questions. - -### Reporting Issues - -If you believe you have found a defect in Hugo or its documentation, use -the GitHub issue tracker to report the problem to the Hugo maintainers. -If you're not sure if it's a bug or not, start by asking in the [discussion forum](https://discourse.gohugo.io). -When reporting the issue, please provide the version of Hugo in use (`hugo version`). - -### Submitting Patches - -The Hugo project welcomes all contributors and contributions regardless of skill or experience level. -If you are interested in helping with the project, we will help you with your contribution. -Hugo is a very active project with many contributions happening daily. -Because we want to create the best possible product for our users and the best contribution experience for our developers, -we have a set of guidelines which ensure that all contributions are acceptable. -The guidelines are not intended as a filter or barrier to participation. -If you are unfamiliar with the contribution process, the Hugo team will help you and teach you how to bring your contribution in accordance with the guidelines. - -For a complete guide to contributing code to Hugo, see the [Contribution Guide](CONTRIBUTING.md). - -[![Analytics](https://ga-beacon.appspot.com/UA-7131036-6/hugo/readme)](https://github.com/igrigorik/ga-beacon) - -[Go]: https://golang.org/ -[Hugo Documentation]: https://gohugo.io/overview/introduction/ diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 0b05a1ab0..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,18 +0,0 @@ -init: - - copy c:\MinGW\bin\mingw32-make.exe c:\MinGW\bin\make.exe - - set PATH=%PATH%;C:\MinGW\bin;%GOPATH%\bin - - go version - - go env - -# clones and cd's to path -clone_folder: C:\GOPATH\src\github.com\gohugoio\hugo - -install: - - git submodule update --init --recursive - - gem install asciidoctor - - pip install docutils - -build_script: - - make hugo-race check - - hugo -s docs/ - - hugo --renderToMemory -s docs/ diff --git a/archetypes/default.md b/archetypes/default.md new file mode 100644 index 000000000..0b7f8fdbf --- /dev/null +++ b/archetypes/default.md @@ -0,0 +1,11 @@ +--- +title: "{{ replace .TranslationBaseName "-" " " | title }}" +date: {{ .Date }} +description: "" +categories: [] +#tags: [] +slug: "" +aliases: [] +toc: false +draft: true +--- diff --git a/bench.sh b/bench.sh deleted file mode 100755 index c6a20a7e3..000000000 --- a/bench.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env bash - -# allow user to override go executable by running as GOEXE=xxx make ... -GOEXE="${GOEXE-go}" - -# Convenience script to -# - For a given branch -# - Run benchmark tests for a given package -# - Do the same for master -# - then compare the two runs with benchcmp - -benchFilter=".*" - -if (( $# < 2 )); - then - echo "USAGE: ./bench.sh (and (regexp, optional))" - exit 1 -fi - - - -if [ $# -eq 3 ]; then - benchFilter=$3 -fi - - -BRANCH=$1 -PACKAGE=$2 - -git checkout $BRANCH -"${GOEXE}" test -test.run=NONE -bench="$benchFilter" -test.benchmem=true ./$PACKAGE > /tmp/bench-$PACKAGE-$BRANCH.txt - -git checkout master -"${GOEXE}" test -test.run=NONE -bench="$benchFilter" -test.benchmem=true ./$PACKAGE > /tmp/bench-$PACKAGE-master.txt - - -benchcmp /tmp/bench-$PACKAGE-master.txt /tmp/bench-$PACKAGE-$BRANCH.txt diff --git a/benchSite.sh b/benchSite.sh deleted file mode 100755 index fd088a5ec..000000000 --- a/benchSite.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -# allow user to override go executable by running as GOEXE=xxx make ... -GOEXE="${GOEXE-go}" - -# Send in a regexp mathing the benchmarks you want to run, i.e. './benchSite.sh "YAML"'. -# Note the quotes, which will be needed for more complex expressions. -# The above will run all variations, but only for front matter YAML. - -echo "Running with BenchmarkSiteBuilding/${1}" - -"${GOEXE}" test -run="NONE" -bench="BenchmarkSiteBuilding/${1}$" -test.benchmem=true ./hugolib -memprofile mem.prof -cpuprofile cpu.prof diff --git a/bufferpool/bufpool.go b/bufferpool/bufpool.go deleted file mode 100644 index c1e4105d0..000000000 --- a/bufferpool/bufpool.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package bufferpool provides a pool of bytes buffers. -package bufferpool - -import ( - "bytes" - "sync" -) - -var bufferPool = &sync.Pool{ - New: func() interface{} { - return &bytes.Buffer{} - }, -} - -// GetBuffer returns a buffer from the pool. -func GetBuffer() (buf *bytes.Buffer) { - return bufferPool.Get().(*bytes.Buffer) -} - -// PutBuffer returns a buffer to the pool. -// The buffer is reset before it is put back into circulation. -func PutBuffer(buf *bytes.Buffer) { - buf.Reset() - bufferPool.Put(buf) -} diff --git a/bufferpool/bufpool_test.go b/bufferpool/bufpool_test.go deleted file mode 100644 index cfa247f62..000000000 --- a/bufferpool/bufpool_test.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2016-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package bufferpool - -import ( - "github.com/stretchr/testify/assert" - "testing" -) - -func TestBufferPool(t *testing.T) { - buff := GetBuffer() - buff.WriteString("do be do be do") - assert.Equal(t, "do be do be do", buff.String()) - PutBuffer(buff) - assert.Equal(t, 0, buff.Len()) -} diff --git a/cache/partitioned_lazy_cache.go b/cache/partitioned_lazy_cache.go deleted file mode 100644 index 9baf0377d..000000000 --- a/cache/partitioned_lazy_cache.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2017-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cache - -import ( - "sync" -) - -// Partition represents a cache partition where Load is the callback -// for when the partition is needed. -type Partition struct { - Key string - Load func() (map[string]interface{}, error) -} - -type lazyPartition struct { - initSync sync.Once - cache map[string]interface{} - load func() (map[string]interface{}, error) -} - -func (l *lazyPartition) init() error { - var err error - l.initSync.Do(func() { - var c map[string]interface{} - c, err = l.load() - l.cache = c - }) - - return err -} - -// PartitionedLazyCache is a lazily loaded cache paritioned by a supplied string key. -type PartitionedLazyCache struct { - partitions map[string]*lazyPartition -} - -// NewPartitionedLazyCache creates a new NewPartitionedLazyCache with the supplied -// partitions. -func NewPartitionedLazyCache(partitions ...Partition) *PartitionedLazyCache { - lazyPartitions := make(map[string]*lazyPartition, len(partitions)) - for _, partition := range partitions { - lazyPartitions[partition.Key] = &lazyPartition{load: partition.Load} - } - cache := &PartitionedLazyCache{partitions: lazyPartitions} - - return cache -} - -// Get initializes the partition if not already done so, then looks up the given -// key in the given partition, returns nil if no value found. -func (c *PartitionedLazyCache) Get(partition, key string) (interface{}, error) { - p, found := c.partitions[partition] - - if !found { - return nil, nil - } - - if err := p.init(); err != nil { - return nil, err - } - - if v, found := p.cache[key]; found { - return v, nil - } - - return nil, nil - -} diff --git a/cache/partitioned_lazy_cache_test.go b/cache/partitioned_lazy_cache_test.go deleted file mode 100644 index ba8b6a454..000000000 --- a/cache/partitioned_lazy_cache_test.go +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright 2017-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cache - -import ( - "errors" - "sync" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestNewPartitionedLazyCache(t *testing.T) { - t.Parallel() - - assert := require.New(t) - - p1 := Partition{ - Key: "p1", - Load: func() (map[string]interface{}, error) { - return map[string]interface{}{ - "p1_1": "p1v1", - "p1_2": "p1v2", - "p1_nil": nil, - }, nil - }, - } - - p2 := Partition{ - Key: "p2", - Load: func() (map[string]interface{}, error) { - return map[string]interface{}{ - "p2_1": "p2v1", - "p2_2": "p2v2", - "p2_3": "p2v3", - }, nil - }, - } - - cache := NewPartitionedLazyCache(p1, p2) - - v, err := cache.Get("p1", "p1_1") - assert.NoError(err) - assert.Equal("p1v1", v) - - v, err = cache.Get("p1", "p2_1") - assert.NoError(err) - assert.Nil(v) - - v, err = cache.Get("p1", "p1_nil") - assert.NoError(err) - assert.Nil(v) - - v, err = cache.Get("p2", "p2_3") - assert.NoError(err) - assert.Equal("p2v3", v) - - v, err = cache.Get("doesnotexist", "p1_1") - assert.NoError(err) - assert.Nil(v) - - v, err = cache.Get("p1", "doesnotexist") - assert.NoError(err) - assert.Nil(v) - - errorP := Partition{ - Key: "p3", - Load: func() (map[string]interface{}, error) { - return nil, errors.New("Failed") - }, - } - - cache = NewPartitionedLazyCache(errorP) - - v, err = cache.Get("p1", "doesnotexist") - assert.NoError(err) - assert.Nil(v) - - _, err = cache.Get("p3", "doesnotexist") - assert.Error(err) - -} - -func TestConcurrentPartitionedLazyCache(t *testing.T) { - t.Parallel() - - assert := require.New(t) - - var wg sync.WaitGroup - - p1 := Partition{ - Key: "p1", - Load: func() (map[string]interface{}, error) { - return map[string]interface{}{ - "p1_1": "p1v1", - "p1_2": "p1v2", - "p1_nil": nil, - }, nil - }, - } - - p2 := Partition{ - Key: "p2", - Load: func() (map[string]interface{}, error) { - return map[string]interface{}{ - "p2_1": "p2v1", - "p2_2": "p2v2", - "p2_3": "p2v3", - }, nil - }, - } - - cache := NewPartitionedLazyCache(p1, p2) - - for i := 0; i < 100; i++ { - wg.Add(1) - go func() { - defer wg.Done() - for j := 0; j < 10; j++ { - v, err := cache.Get("p1", "p1_1") - assert.NoError(err) - assert.Equal("p1v1", v) - } - }() - } - wg.Wait() -} diff --git a/commands/benchmark.go b/commands/benchmark.go deleted file mode 100644 index 51f2be876..000000000 --- a/commands/benchmark.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package commands - -import ( - "os" - "runtime" - "runtime/pprof" - "time" - - "github.com/spf13/cobra" - jww "github.com/spf13/jwalterweatherman" -) - -var ( - benchmarkTimes int - cpuProfileFile string - memProfileFile string -) - -var benchmarkCmd = &cobra.Command{ - Use: "benchmark", - Short: "Benchmark Hugo by building a site a number of times.", - Long: `Hugo can build a site many times over and analyze the running process -creating a benchmark.`, -} - -func init() { - initHugoBuilderFlags(benchmarkCmd) - initBenchmarkBuildingFlags(benchmarkCmd) - - benchmarkCmd.Flags().StringVar(&cpuProfileFile, "cpuprofile", "", "path/filename for the CPU profile file") - benchmarkCmd.Flags().StringVar(&memProfileFile, "memprofile", "", "path/filename for the memory profile file") - benchmarkCmd.Flags().IntVarP(&benchmarkTimes, "count", "n", 13, "number of times to build the site") - - benchmarkCmd.RunE = benchmark -} - -func benchmark(cmd *cobra.Command, args []string) error { - cfg, err := InitializeConfig(benchmarkCmd) - if err != nil { - return err - } - - c, err := newCommandeer(cfg) - if err != nil { - return err - } - - var memProf *os.File - if memProfileFile != "" { - memProf, err = os.Create(memProfileFile) - if err != nil { - return err - } - } - - var cpuProf *os.File - if cpuProfileFile != "" { - cpuProf, err = os.Create(cpuProfileFile) - if err != nil { - return err - } - } - - var memStats runtime.MemStats - runtime.ReadMemStats(&memStats) - memAllocated := memStats.TotalAlloc - mallocs := memStats.Mallocs - if cpuProf != nil { - pprof.StartCPUProfile(cpuProf) - } - - t := time.Now() - for i := 0; i < benchmarkTimes; i++ { - if err = c.resetAndBuildSites(false); err != nil { - return err - } - } - totalTime := time.Since(t) - - if memProf != nil { - pprof.WriteHeapProfile(memProf) - memProf.Close() - } - if cpuProf != nil { - pprof.StopCPUProfile() - cpuProf.Close() - } - - runtime.ReadMemStats(&memStats) - totalMemAllocated := memStats.TotalAlloc - memAllocated - totalMallocs := memStats.Mallocs - mallocs - - jww.FEEDBACK.Println() - jww.FEEDBACK.Printf("Average time per operation: %vms\n", int(1000*totalTime.Seconds()/float64(benchmarkTimes))) - jww.FEEDBACK.Printf("Average memory allocated per operation: %vkB\n", totalMemAllocated/uint64(benchmarkTimes)/1024) - jww.FEEDBACK.Printf("Average allocations per operation: %v\n", totalMallocs/uint64(benchmarkTimes)) - - return nil -} diff --git a/commands/check.go b/commands/check.go deleted file mode 100644 index e5dbc1ffa..000000000 --- a/commands/check.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package commands - -import ( - "github.com/spf13/cobra" -) - -var checkCmd = &cobra.Command{ - Use: "check", - Short: "Contains some verification checks", -} diff --git a/commands/commandeer.go b/commands/commandeer.go deleted file mode 100644 index 7de185d2f..000000000 --- a/commands/commandeer.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2017 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package commands - -import ( - "github.com/gohugoio/hugo/deps" - "github.com/gohugoio/hugo/helpers" - "github.com/gohugoio/hugo/hugofs" -) - -type commandeer struct { - *deps.DepsCfg - pathSpec *helpers.PathSpec - configured bool -} - -func (c *commandeer) Set(key string, value interface{}) { - if c.configured { - panic("commandeer cannot be changed") - } - c.Cfg.Set(key, value) -} - -// PathSpec lazily creates a new PathSpec, as all the paths must -// be configured before it is created. -func (c *commandeer) PathSpec() *helpers.PathSpec { - c.configured = true - return c.pathSpec -} - -func (c *commandeer) initFs(fs *hugofs.Fs) error { - c.DepsCfg.Fs = fs - ps, err := helpers.NewPathSpec(fs, c.Cfg) - if err != nil { - return err - } - c.pathSpec = ps - return nil -} - -func newCommandeer(cfg *deps.DepsCfg) (*commandeer, error) { - l := cfg.Language - if l == nil { - l = helpers.NewDefaultLanguage(cfg.Cfg) - } - ps, err := helpers.NewPathSpec(cfg.Fs, l) - if err != nil { - return nil, err - } - return &commandeer{DepsCfg: cfg, pathSpec: ps}, nil -} diff --git a/commands/convert.go b/commands/convert.go deleted file mode 100644 index 298ff6019..000000000 --- a/commands/convert.go +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package commands - -import ( - "errors" - "fmt" - "path/filepath" - "time" - - "github.com/gohugoio/hugo/hugolib" - "github.com/gohugoio/hugo/parser" - "github.com/spf13/cast" - "github.com/spf13/cobra" -) - -var outputDir string -var unsafe bool - -var convertCmd = &cobra.Command{ - Use: "convert", - Short: "Convert your content to different formats", - Long: `Convert your content (e.g. front matter) to different formats. - -See convert's subcommands toJSON, toTOML and toYAML for more information.`, - RunE: nil, -} - -var toJSONCmd = &cobra.Command{ - Use: "toJSON", - Short: "Convert front matter to JSON", - Long: `toJSON converts all front matter in the content directory -to use JSON for the front matter.`, - RunE: func(cmd *cobra.Command, args []string) error { - return convertContents(rune([]byte(parser.JSONLead)[0])) - }, -} - -var toTOMLCmd = &cobra.Command{ - Use: "toTOML", - Short: "Convert front matter to TOML", - Long: `toTOML converts all front matter in the content directory -to use TOML for the front matter.`, - RunE: func(cmd *cobra.Command, args []string) error { - return convertContents(rune([]byte(parser.TOMLLead)[0])) - }, -} - -var toYAMLCmd = &cobra.Command{ - Use: "toYAML", - Short: "Convert front matter to YAML", - Long: `toYAML converts all front matter in the content directory -to use YAML for the front matter.`, - RunE: func(cmd *cobra.Command, args []string) error { - return convertContents(rune([]byte(parser.YAMLLead)[0])) - }, -} - -func init() { - convertCmd.AddCommand(toJSONCmd) - convertCmd.AddCommand(toTOMLCmd) - convertCmd.AddCommand(toYAMLCmd) - convertCmd.PersistentFlags().StringVarP(&outputDir, "output", "o", "", "filesystem path to write files to") - convertCmd.PersistentFlags().StringVarP(&source, "source", "s", "", "filesystem path to read files relative from") - convertCmd.PersistentFlags().BoolVar(&unsafe, "unsafe", false, "enable less safe operations, please backup first") - convertCmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{}) -} - -func convertContents(mark rune) error { - cfg, err := InitializeConfig() - if err != nil { - return err - } - - h, err := hugolib.NewHugoSites(*cfg) - if err != nil { - return err - } - - site := h.Sites[0] - - if err = site.Initialise(); err != nil { - return err - } - - if site.Source == nil { - panic("site.Source not set") - } - if len(site.Source.Files()) < 1 { - return errors.New("No source files found") - } - - contentDir := site.PathSpec.AbsPathify(site.Cfg.GetString("contentDir")) - site.Log.FEEDBACK.Println("processing", len(site.Source.Files()), "content files") - for _, file := range site.Source.Files() { - site.Log.INFO.Println("Attempting to convert", file.LogicalName()) - page, err := site.NewPage(file.LogicalName()) - if err != nil { - return err - } - - psr, err := parser.ReadFrom(file.Contents) - if err != nil { - site.Log.ERROR.Println("Error processing file:", file.Path()) - return err - } - metadata, err := psr.Metadata() - if err != nil { - site.Log.ERROR.Println("Error processing file:", file.Path()) - return err - } - - // better handling of dates in formats that don't have support for them - if mark == parser.FormatToLeadRune("json") || mark == parser.FormatToLeadRune("yaml") || mark == parser.FormatToLeadRune("toml") { - newMetadata := cast.ToStringMap(metadata) - for k, v := range newMetadata { - switch vv := v.(type) { - case time.Time: - newMetadata[k] = vv.Format(time.RFC3339) - } - } - metadata = newMetadata - } - - page.SetDir(filepath.Join(contentDir, file.Dir())) - page.SetSourceContent(psr.Content()) - if err = page.SetSourceMetaData(metadata, mark); err != nil { - site.Log.ERROR.Printf("Failed to set source metadata for file %q: %s. For more info see For more info see https://github.com/gohugoio/hugo/issues/2458", page.FullFilePath(), err) - continue - } - - if outputDir != "" { - if err = page.SaveSourceAs(filepath.Join(outputDir, page.FullFilePath())); err != nil { - return fmt.Errorf("Failed to save file %q: %s", page.FullFilePath(), err) - } - } else { - if unsafe { - if err = page.SaveSource(); err != nil { - return fmt.Errorf("Failed to save file %q: %s", page.FullFilePath(), err) - } - } else { - site.Log.FEEDBACK.Println("Unsafe operation not allowed, use --unsafe or set a different output path") - } - } - } - return nil -} diff --git a/commands/env.go b/commands/env.go deleted file mode 100644 index 54c98d527..000000000 --- a/commands/env.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2016 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package commands - -import ( - "runtime" - - "github.com/spf13/cobra" - jww "github.com/spf13/jwalterweatherman" -) - -var envCmd = &cobra.Command{ - Use: "env", - Short: "Print Hugo version and environment info", - Long: `Print Hugo version and environment info. This is useful in Hugo bug reports.`, - RunE: func(cmd *cobra.Command, args []string) error { - printHugoVersion() - jww.FEEDBACK.Printf("GOOS=%q\n", runtime.GOOS) - jww.FEEDBACK.Printf("GOARCH=%q\n", runtime.GOARCH) - jww.FEEDBACK.Printf("GOVERSION=%q\n", runtime.Version()) - - return nil - }, -} diff --git a/commands/gen.go b/commands/gen.go deleted file mode 100644 index 62a84b0d0..000000000 --- a/commands/gen.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package commands - -import ( - "github.com/spf13/cobra" -) - -var genCmd = &cobra.Command{ - Use: "gen", - Short: "A collection of several useful generators.", -} diff --git a/commands/genautocomplete.go b/commands/genautocomplete.go deleted file mode 100644 index c2004ab22..000000000 --- a/commands/genautocomplete.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package commands - -import ( - "github.com/spf13/cobra" - jww "github.com/spf13/jwalterweatherman" -) - -var autocompleteTarget string - -// bash for now (zsh and others will come) -var autocompleteType string - -var genautocompleteCmd = &cobra.Command{ - Use: "autocomplete", - Short: "Generate shell autocompletion script for Hugo", - Long: `Generates a shell autocompletion script for Hugo. - -NOTE: The current version supports Bash only. - This should work for *nix systems with Bash installed. - -By default, the file is written directly to /etc/bash_completion.d -for convenience, and the command may need superuser rights, e.g.: - - $ sudo hugo gen autocomplete - -Add ` + "`--completionfile=/path/to/file`" + ` flag to set alternative -file-path and name. - -Logout and in again to reload the completion scripts, -or just source them in directly: - - $ . /etc/bash_completion`, - - RunE: func(cmd *cobra.Command, args []string) error { - if autocompleteType != "bash" { - return newUserError("Only Bash is supported for now") - } - - err := cmd.Root().GenBashCompletionFile(autocompleteTarget) - - if err != nil { - return err - } - - jww.FEEDBACK.Println("Bash completion file for Hugo saved to", autocompleteTarget) - - return nil - }, -} - -func init() { - genautocompleteCmd.PersistentFlags().StringVarP(&autocompleteTarget, "completionfile", "", "/etc/bash_completion.d/hugo.sh", "autocompletion file") - genautocompleteCmd.PersistentFlags().StringVarP(&autocompleteType, "type", "", "bash", "autocompletion type (currently only bash supported)") - - // For bash-completion - genautocompleteCmd.PersistentFlags().SetAnnotation("completionfile", cobra.BashCompFilenameExt, []string{}) -} diff --git a/commands/gendoc.go b/commands/gendoc.go deleted file mode 100644 index c4840050b..000000000 --- a/commands/gendoc.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2016 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package commands - -import ( - "fmt" - "path" - "path/filepath" - "strings" - "time" - - "github.com/gohugoio/hugo/helpers" - "github.com/gohugoio/hugo/hugofs" - "github.com/spf13/cobra" - "github.com/spf13/cobra/doc" - jww "github.com/spf13/jwalterweatherman" -) - -const gendocFrontmatterTemplate = `--- -date: %s -title: "%s" -slug: %s -url: %s ---- -` - -var gendocdir string -var gendocCmd = &cobra.Command{ - Use: "doc", - Short: "Generate Markdown documentation for the Hugo CLI.", - Long: `Generate Markdown documentation for the Hugo CLI. - -This command is, mostly, used to create up-to-date documentation -of Hugo's command-line interface for http://gohugo.io/. - -It creates one Markdown file per command with front matter suitable -for rendering in Hugo.`, - - RunE: func(cmd *cobra.Command, args []string) error { - if !strings.HasSuffix(gendocdir, helpers.FilePathSeparator) { - gendocdir += helpers.FilePathSeparator - } - if found, _ := helpers.Exists(gendocdir, hugofs.Os); !found { - jww.FEEDBACK.Println("Directory", gendocdir, "does not exist, creating...") - if err := hugofs.Os.MkdirAll(gendocdir, 0777); err != nil { - return err - } - } - now := time.Now().Format(time.RFC3339) - prepender := func(filename string) string { - name := filepath.Base(filename) - base := strings.TrimSuffix(name, path.Ext(name)) - url := "/commands/" + strings.ToLower(base) + "/" - return fmt.Sprintf(gendocFrontmatterTemplate, now, strings.Replace(base, "_", " ", -1), base, url) - } - - linkHandler := func(name string) string { - base := strings.TrimSuffix(name, path.Ext(name)) - return "/commands/" + strings.ToLower(base) + "/" - } - - jww.FEEDBACK.Println("Generating Hugo command-line documentation in", gendocdir, "...") - doc.GenMarkdownTreeCustom(cmd.Root(), gendocdir, prepender, linkHandler) - jww.FEEDBACK.Println("Done.") - - return nil - }, -} - -func init() { - gendocCmd.PersistentFlags().StringVar(&gendocdir, "dir", "/tmp/hugodoc/", "the directory to write the doc.") - - // For bash-completion - gendocCmd.PersistentFlags().SetAnnotation("dir", cobra.BashCompSubdirsInDir, []string{}) -} diff --git a/commands/gendocshelper.go b/commands/gendocshelper.go deleted file mode 100644 index ca781242e..000000000 --- a/commands/gendocshelper.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2017-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package commands - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - - "github.com/gohugoio/hugo/docshelper" - "github.com/spf13/cobra" -) - -type genDocsHelper struct { - target string - cmd *cobra.Command -} - -func createGenDocsHelper() *genDocsHelper { - g := &genDocsHelper{ - cmd: &cobra.Command{ - Use: "docshelper", - Short: "Generate some data files for the Hugo docs.", - Hidden: true, - }, - } - - g.cmd.RunE = func(cmd *cobra.Command, args []string) error { - return g.generate() - } - - g.cmd.PersistentFlags().StringVarP(&g.target, "dir", "", "docs/data", "data dir") - - return g -} - -func (g *genDocsHelper) generate() error { - fmt.Println("Generate docs data to", g.target) - - targetFile := filepath.Join(g.target, "docs.json") - - f, err := os.Create(targetFile) - if err != nil { - return err - } - defer f.Close() - - enc := json.NewEncoder(f) - enc.SetIndent("", " ") - - if err := enc.Encode(docshelper.DocProviders); err != nil { - return err - } - - fmt.Println("Done!") - return nil - -} diff --git a/commands/genman.go b/commands/genman.go deleted file mode 100644 index 004e669e7..000000000 --- a/commands/genman.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2016 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package commands - -import ( - "fmt" - "strings" - - "github.com/gohugoio/hugo/helpers" - "github.com/gohugoio/hugo/hugofs" - "github.com/spf13/cobra" - "github.com/spf13/cobra/doc" - jww "github.com/spf13/jwalterweatherman" -) - -var genmandir string -var genmanCmd = &cobra.Command{ - Use: "man", - Short: "Generate man pages for the Hugo CLI", - Long: `This command automatically generates up-to-date man pages of Hugo's -command-line interface. By default, it creates the man page files -in the "man" directory under the current directory.`, - - RunE: func(cmd *cobra.Command, args []string) error { - header := &doc.GenManHeader{ - Section: "1", - Manual: "Hugo Manual", - Source: fmt.Sprintf("Hugo %s", helpers.CurrentHugoVersion), - } - if !strings.HasSuffix(genmandir, helpers.FilePathSeparator) { - genmandir += helpers.FilePathSeparator - } - if found, _ := helpers.Exists(genmandir, hugofs.Os); !found { - jww.FEEDBACK.Println("Directory", genmandir, "does not exist, creating...") - if err := hugofs.Os.MkdirAll(genmandir, 0777); err != nil { - return err - } - } - cmd.Root().DisableAutoGenTag = true - - jww.FEEDBACK.Println("Generating Hugo man pages in", genmandir, "...") - doc.GenManTree(cmd.Root(), header, genmandir) - - jww.FEEDBACK.Println("Done.") - - return nil - }, -} - -func init() { - genmanCmd.PersistentFlags().StringVar(&genmandir, "dir", "man/", "the directory to write the man pages.") - - // For bash-completion - genmanCmd.PersistentFlags().SetAnnotation("dir", cobra.BashCompSubdirsInDir, []string{}) -} diff --git a/commands/hugo.go b/commands/hugo.go deleted file mode 100644 index d8527a3aa..000000000 --- a/commands/hugo.go +++ /dev/null @@ -1,1087 +0,0 @@ -// Copyright 2016 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package commands defines and implements command-line commands and flags -// used by Hugo. Commands and flags are implemented using Cobra. -package commands - -import ( - "fmt" - "io/ioutil" - - "github.com/gohugoio/hugo/hugofs" - - "log" - "net/http" - "os" - "path/filepath" - "runtime" - "strings" - "sync" - "time" - - "github.com/gohugoio/hugo/config" - - "github.com/gohugoio/hugo/parser" - flag "github.com/spf13/pflag" - - "regexp" - - "github.com/fsnotify/fsnotify" - "github.com/gohugoio/hugo/deps" - "github.com/gohugoio/hugo/helpers" - "github.com/gohugoio/hugo/hugolib" - "github.com/gohugoio/hugo/livereload" - "github.com/gohugoio/hugo/utils" - "github.com/gohugoio/hugo/watcher" - "github.com/spf13/afero" - "github.com/spf13/cobra" - "github.com/spf13/fsync" - jww "github.com/spf13/jwalterweatherman" - "github.com/spf13/nitro" - "github.com/spf13/viper" -) - -// Hugo represents the Hugo sites to build. This variable is exported as it -// is used by at least one external library (the Hugo caddy plugin). We should -// provide a cleaner external API, but until then, this is it. -var Hugo *hugolib.HugoSites - -// Reset resets Hugo ready for a new full build. This is mainly only useful -// for benchmark testing etc. via the CLI commands. -func Reset() error { - Hugo = nil - return nil -} - -// commandError is an error used to signal different error situations in command handling. -type commandError struct { - s string - userError bool -} - -func (c commandError) Error() string { - return c.s -} - -func (c commandError) isUserError() bool { - return c.userError -} - -func newUserError(a ...interface{}) commandError { - return commandError{s: fmt.Sprintln(a...), userError: true} -} - -func newSystemError(a ...interface{}) commandError { - return commandError{s: fmt.Sprintln(a...), userError: false} -} - -func newSystemErrorF(format string, a ...interface{}) commandError { - return commandError{s: fmt.Sprintf(format, a...), userError: false} -} - -// Catch some of the obvious user errors from Cobra. -// We don't want to show the usage message for every error. -// The below may be to generic. Time will show. -var userErrorRegexp = regexp.MustCompile("argument|flag|shorthand") - -func isUserError(err error) bool { - if cErr, ok := err.(commandError); ok && cErr.isUserError() { - return true - } - - return userErrorRegexp.MatchString(err.Error()) -} - -// HugoCmd is Hugo's root command. -// Every other command attached to HugoCmd is a child command to it. -var HugoCmd = &cobra.Command{ - Use: "hugo", - Short: "hugo builds your site", - Long: `hugo is the main command, used to build your Hugo site. - -Hugo is a Fast and Flexible Static Site Generator -built with love by spf13 and friends in Go. - -Complete documentation is available at http://gohugo.io/.`, - RunE: func(cmd *cobra.Command, args []string) error { - cfg, err := InitializeConfig() - if err != nil { - return err - } - - c, err := newCommandeer(cfg) - if err != nil { - return err - } - - if buildWatch { - cfg.Cfg.Set("disableLiveReload", true) - c.watchConfig() - } - - return c.build() - }, -} - -var hugoCmdV *cobra.Command - -// Flags that are to be added to commands. -var ( - buildWatch bool - logging bool - renderToMemory bool // for benchmark testing - verbose bool - verboseLog bool - debug bool - quiet bool -) - -var ( - baseURL string - cacheDir string - contentDir string - layoutDir string - cfgFile string - destination string - logFile string - theme string - themesDir string - source string - logI18nWarnings bool - disableKinds []string -) - -// Execute adds all child commands to the root command HugoCmd and sets flags appropriately. -func Execute() { - HugoCmd.SetGlobalNormalizationFunc(helpers.NormalizeHugoFlags) - - HugoCmd.SilenceUsage = true - - AddCommands() - - if c, err := HugoCmd.ExecuteC(); err != nil { - if isUserError(err) { - c.Println("") - c.Println(c.UsageString()) - } - - os.Exit(-1) - } -} - -// AddCommands adds child commands to the root command HugoCmd. -func AddCommands() { - HugoCmd.AddCommand(serverCmd) - HugoCmd.AddCommand(versionCmd) - HugoCmd.AddCommand(envCmd) - HugoCmd.AddCommand(configCmd) - HugoCmd.AddCommand(checkCmd) - HugoCmd.AddCommand(benchmarkCmd) - HugoCmd.AddCommand(convertCmd) - HugoCmd.AddCommand(newCmd) - HugoCmd.AddCommand(listCmd) - HugoCmd.AddCommand(undraftCmd) - HugoCmd.AddCommand(importCmd) - - HugoCmd.AddCommand(genCmd) - genCmd.AddCommand(genautocompleteCmd) - genCmd.AddCommand(gendocCmd) - genCmd.AddCommand(genmanCmd) - genCmd.AddCommand(createGenDocsHelper().cmd) -} - -// initHugoBuilderFlags initializes all common flags, typically used by the -// core build commands, namely hugo itself, server, check and benchmark. -func initHugoBuilderFlags(cmd *cobra.Command) { - initHugoBuildCommonFlags(cmd) -} - -func initRootPersistentFlags() { - HugoCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is path/config.yaml|json|toml)") - HugoCmd.PersistentFlags().BoolVar(&quiet, "quiet", false, "build in quiet mode") - - // Set bash-completion - validConfigFilenames := []string{"json", "js", "yaml", "yml", "toml", "tml"} - _ = HugoCmd.PersistentFlags().SetAnnotation("config", cobra.BashCompFilenameExt, validConfigFilenames) -} - -// initHugoBuildCommonFlags initialize common flags related to the Hugo build. -// Called by initHugoBuilderFlags. -func initHugoBuildCommonFlags(cmd *cobra.Command) { - cmd.Flags().Bool("cleanDestinationDir", false, "remove files from destination not found in static directories") - cmd.Flags().BoolP("buildDrafts", "D", false, "include content marked as draft") - cmd.Flags().BoolP("buildFuture", "F", false, "include content with publishdate in the future") - cmd.Flags().BoolP("buildExpired", "E", false, "include expired content") - cmd.Flags().Bool("disable404", false, "do not render 404 page") - cmd.Flags().Bool("disableRSS", false, "do not build RSS files") - cmd.Flags().Bool("disableSitemap", false, "do not build Sitemap file") - cmd.Flags().StringVarP(&source, "source", "s", "", "filesystem path to read files relative from") - cmd.Flags().StringVarP(&contentDir, "contentDir", "c", "", "filesystem path to content directory") - cmd.Flags().StringVarP(&layoutDir, "layoutDir", "l", "", "filesystem path to layout directory") - cmd.Flags().StringVarP(&cacheDir, "cacheDir", "", "", "filesystem path to cache directory. Defaults: $TMPDIR/hugo_cache/") - cmd.Flags().BoolP("ignoreCache", "", false, "ignores the cache directory") - cmd.Flags().StringVarP(&destination, "destination", "d", "", "filesystem path to write files to") - cmd.Flags().StringVarP(&theme, "theme", "t", "", "theme to use (located in /themes/THEMENAME/)") - cmd.Flags().StringVarP(&themesDir, "themesDir", "", "", "filesystem path to themes directory") - cmd.Flags().Bool("uglyURLs", false, "if true, use /filename.html instead of /filename/") - cmd.Flags().Bool("canonifyURLs", false, "if true, all relative URLs will be canonicalized using baseURL") - cmd.Flags().StringVarP(&baseURL, "baseURL", "b", "", "hostname (and path) to the root, e.g. http://spf13.com/") - cmd.Flags().Bool("enableGitInfo", false, "add Git revision, date and author info to the pages") - - cmd.Flags().BoolVar(&nitro.AnalysisOn, "stepAnalysis", false, "display memory and timing of different steps of the program") - cmd.Flags().Bool("pluralizeListTitles", true, "pluralize titles in lists using inflect") - cmd.Flags().Bool("preserveTaxonomyNames", false, `preserve taxonomy names as written ("Gérard Depardieu" vs "gerard-depardieu")`) - cmd.Flags().BoolP("forceSyncStatic", "", false, "copy all files when static is changed.") - cmd.Flags().BoolP("noTimes", "", false, "don't sync modification time of files") - cmd.Flags().BoolP("noChmod", "", false, "don't sync permission mode of files") - cmd.Flags().BoolVarP(&logI18nWarnings, "i18n-warnings", "", false, "print missing translations") - - cmd.Flags().StringSliceVar(&disableKinds, "disableKinds", []string{}, "disable different kind of pages (home, RSS etc.)") - - // Set bash-completion. - // Each flag must first be defined before using the SetAnnotation() call. - _ = cmd.Flags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{}) - _ = cmd.Flags().SetAnnotation("cacheDir", cobra.BashCompSubdirsInDir, []string{}) - _ = cmd.Flags().SetAnnotation("destination", cobra.BashCompSubdirsInDir, []string{}) - _ = cmd.Flags().SetAnnotation("theme", cobra.BashCompSubdirsInDir, []string{"themes"}) -} - -func initBenchmarkBuildingFlags(cmd *cobra.Command) { - cmd.Flags().BoolVar(&renderToMemory, "renderToMemory", false, "render to memory (only useful for benchmark testing)") -} - -// init initializes flags. -func init() { - HugoCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output") - HugoCmd.PersistentFlags().BoolVarP(&debug, "debug", "", false, "debug output") - HugoCmd.PersistentFlags().BoolVar(&logging, "log", false, "enable Logging") - HugoCmd.PersistentFlags().StringVar(&logFile, "logFile", "", "log File path (if set, logging enabled automatically)") - HugoCmd.PersistentFlags().BoolVar(&verboseLog, "verboseLog", false, "verbose logging") - - initRootPersistentFlags() - initHugoBuilderFlags(HugoCmd) - initBenchmarkBuildingFlags(HugoCmd) - - HugoCmd.Flags().BoolVarP(&buildWatch, "watch", "w", false, "watch filesystem for changes and recreate as needed") - hugoCmdV = HugoCmd - - // Set bash-completion - _ = HugoCmd.PersistentFlags().SetAnnotation("logFile", cobra.BashCompFilenameExt, []string{}) -} - -// InitializeConfig initializes a config file with sensible default configuration flags. -func InitializeConfig(subCmdVs ...*cobra.Command) (*deps.DepsCfg, error) { - - var cfg *deps.DepsCfg = &deps.DepsCfg{} - - // Init file systems. This may be changed at a later point. - osFs := hugofs.Os - - config, err := hugolib.LoadConfig(osFs, source, cfgFile) - if err != nil { - return cfg, err - } - - // Init file systems. This may be changed at a later point. - cfg.Cfg = config - - c, err := newCommandeer(cfg) - if err != nil { - return nil, err - } - - for _, cmdV := range append([]*cobra.Command{hugoCmdV}, subCmdVs...) { - c.initializeFlags(cmdV) - } - - if len(disableKinds) > 0 { - c.Set("disableKinds", disableKinds) - } - - logger, err := createLogger(cfg.Cfg) - if err != nil { - return cfg, err - } - - cfg.Logger = logger - - config.Set("logI18nWarnings", logI18nWarnings) - - if baseURL != "" { - config.Set("baseURL", baseURL) - } - - if !config.GetBool("relativeURLs") && config.GetString("baseURL") == "" { - cfg.Logger.ERROR.Println("No 'baseURL' set in configuration or as a flag. Features like page menus will not work without one.") - } - - if theme != "" { - config.Set("theme", theme) - } - - if themesDir != "" { - config.Set("themesDir", themesDir) - } - - if destination != "" { - config.Set("publishDir", destination) - } - - var dir string - if source != "" { - dir, _ = filepath.Abs(source) - } else { - dir, _ = os.Getwd() - } - config.Set("workingDir", dir) - - fs := hugofs.NewFrom(osFs, config) - - // Hugo writes the output to memory instead of the disk. - // This is only used for benchmark testing. Cause the content is only visible - // in memory. - if renderToMemory { - fs.Destination = new(afero.MemMapFs) - // Rendering to memoryFS, publish to Root regardless of publishDir. - c.Set("publishDir", "/") - } - - if contentDir != "" { - config.Set("contentDir", contentDir) - } - - if layoutDir != "" { - config.Set("layoutDir", layoutDir) - } - - if cacheDir != "" { - config.Set("cacheDir", cacheDir) - } - - cacheDir = config.GetString("cacheDir") - if cacheDir != "" { - if helpers.FilePathSeparator != cacheDir[len(cacheDir)-1:] { - cacheDir = cacheDir + helpers.FilePathSeparator - } - isDir, err := helpers.DirExists(cacheDir, fs.Source) - utils.CheckErr(cfg.Logger, err) - if !isDir { - mkdir(cacheDir) - } - config.Set("cacheDir", cacheDir) - } else { - config.Set("cacheDir", helpers.GetTempDir("hugo_cache", fs.Source)) - } - - if err := c.initFs(fs); err != nil { - return nil, err - } - - cfg.Logger.INFO.Println("Using config file:", viper.ConfigFileUsed()) - - themeDir := c.PathSpec().GetThemeDir() - if themeDir != "" { - if _, err := cfg.Fs.Source.Stat(themeDir); os.IsNotExist(err) { - return cfg, newSystemError("Unable to find theme Directory:", themeDir) - } - } - - themeVersionMismatch, minVersion := c.isThemeVsHugoVersionMismatch() - - if themeVersionMismatch { - cfg.Logger.ERROR.Printf("Current theme does not support Hugo version %s. Minimum version required is %s\n", - helpers.CurrentHugoVersion.ReleaseVersion(), minVersion) - } - - return cfg, nil - -} - -func createLogger(cfg config.Provider) (*jww.Notepad, error) { - var ( - logHandle = ioutil.Discard - logThreshold = jww.LevelWarn - logFile = cfg.GetString("logFile") - outHandle = os.Stdout - stdoutThreshold = jww.LevelError - ) - - if verboseLog || logging || (logFile != "") { - var err error - if logFile != "" { - logHandle, err = os.OpenFile(logFile, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666) - if err != nil { - return nil, newSystemError("Failed to open log file:", logFile, err) - } - } else { - logHandle, err = ioutil.TempFile("", "hugo") - if err != nil { - return nil, newSystemError(err) - } - } - } else if !quiet && cfg.GetBool("verbose") { - stdoutThreshold = jww.LevelInfo - } - - if cfg.GetBool("debug") { - stdoutThreshold = jww.LevelDebug - } - - if verboseLog { - logThreshold = jww.LevelInfo - if cfg.GetBool("debug") { - logThreshold = jww.LevelDebug - } - } - - // The global logger is used in some few cases. - jww.SetLogOutput(logHandle) - jww.SetLogThreshold(logThreshold) - jww.SetStdoutThreshold(stdoutThreshold) - helpers.InitLoggers() - - return jww.NewNotepad(stdoutThreshold, logThreshold, outHandle, logHandle, "", log.Ldate|log.Ltime), nil -} - -func (c *commandeer) initializeFlags(cmd *cobra.Command) { - persFlagKeys := []string{"debug", "verbose", "logFile"} - flagKeys := []string{ - "cleanDestinationDir", - "buildDrafts", - "buildFuture", - "buildExpired", - "uglyURLs", - "canonifyURLs", - "disable404", - "disableRSS", - "disableSitemap", - "enableRobotsTXT", - "enableGitInfo", - "pluralizeListTitles", - "preserveTaxonomyNames", - "ignoreCache", - "forceSyncStatic", - "noTimes", - "noChmod", - } - - // Remove these in Hugo 0.23. - if cmd.Flags().Changed("disable404") { - helpers.Deprecated("command line", "--disable404", "Use --disableKinds=404", false) - } - - if cmd.Flags().Changed("disableRSS") { - helpers.Deprecated("command line", "--disableRSS", "Use --disableKinds=RSS", false) - } - - if cmd.Flags().Changed("disableSitemap") { - helpers.Deprecated("command line", "--disableSitemap", "Use --disableKinds=sitemap", false) - } - - for _, key := range persFlagKeys { - c.setValueFromFlag(cmd.PersistentFlags(), key) - } - for _, key := range flagKeys { - c.setValueFromFlag(cmd.Flags(), key) - } - -} - -func (c *commandeer) setValueFromFlag(flags *flag.FlagSet, key string) { - if flags.Changed(key) { - f := flags.Lookup(key) - c.Set(key, f.Value.String()) - } -} - -func (c *commandeer) watchConfig() { - v := c.Cfg.(*viper.Viper) - v.WatchConfig() - v.OnConfigChange(func(e fsnotify.Event) { - c.Logger.FEEDBACK.Println("Config file changed:", e.Name) - // Force a full rebuild - utils.CheckErr(c.Logger, c.recreateAndBuildSites(true)) - if !c.Cfg.GetBool("disableLiveReload") { - // Will block forever trying to write to a channel that nobody is reading if livereload isn't initialized - livereload.ForceRefresh() - } - }) -} - -func (c *commandeer) build(watches ...bool) error { - if err := c.copyStatic(); err != nil { - return fmt.Errorf("Error copying static files to %s: %s", c.PathSpec().AbsPathify(c.Cfg.GetString("publishDir")), err) - } - watch := false - if len(watches) > 0 && watches[0] { - watch = true - } - if err := c.buildSites(buildWatch || watch); err != nil { - return fmt.Errorf("Error building site: %s", err) - } - - if buildWatch { - c.Logger.FEEDBACK.Println("Watching for changes in", c.PathSpec().AbsPathify(c.Cfg.GetString("contentDir"))) - c.Logger.FEEDBACK.Println("Press Ctrl+C to stop") - utils.CheckErr(c.Logger, c.newWatcher(0)) - } - - return nil -} - -func (c *commandeer) getStaticSourceFs() afero.Fs { - source := c.Fs.Source - themeDir, err := c.PathSpec().GetThemeStaticDirPath() - staticDir := c.PathSpec().GetStaticDirPath() + helpers.FilePathSeparator - useTheme := true - useStatic := true - - if err != nil { - if err != helpers.ErrThemeUndefined { - c.Logger.WARN.Println(err) - } - useTheme = false - } else { - if _, err := source.Stat(themeDir); os.IsNotExist(err) { - c.Logger.WARN.Println("Unable to find Theme Static Directory:", themeDir) - useTheme = false - } - } - - if _, err := source.Stat(staticDir); os.IsNotExist(err) { - c.Logger.WARN.Println("Unable to find Static Directory:", staticDir) - useStatic = false - } - - if !useStatic && !useTheme { - return nil - } - - if !useStatic { - c.Logger.INFO.Println(themeDir, "is the only static directory available to sync from") - return afero.NewReadOnlyFs(afero.NewBasePathFs(source, themeDir)) - } - - if !useTheme { - c.Logger.INFO.Println(staticDir, "is the only static directory available to sync from") - return afero.NewReadOnlyFs(afero.NewBasePathFs(source, staticDir)) - } - - c.Logger.INFO.Println("using a UnionFS for static directory comprised of:") - c.Logger.INFO.Println("Base:", themeDir) - c.Logger.INFO.Println("Overlay:", staticDir) - base := afero.NewReadOnlyFs(afero.NewBasePathFs(source, themeDir)) - overlay := afero.NewReadOnlyFs(afero.NewBasePathFs(source, staticDir)) - return afero.NewCopyOnWriteFs(base, overlay) -} - -func (c *commandeer) copyStatic() error { - publishDir := c.PathSpec().AbsPathify(c.Cfg.GetString("publishDir")) + helpers.FilePathSeparator - - // If root, remove the second '/' - if publishDir == "//" { - publishDir = helpers.FilePathSeparator - } - - // Includes both theme/static & /static - staticSourceFs := c.getStaticSourceFs() - - if staticSourceFs == nil { - c.Logger.WARN.Println("No static directories found to sync") - return nil - } - - syncer := fsync.NewSyncer() - syncer.NoTimes = c.Cfg.GetBool("noTimes") - syncer.NoChmod = c.Cfg.GetBool("noChmod") - syncer.SrcFs = staticSourceFs - syncer.DestFs = c.Fs.Destination - // Now that we are using a unionFs for the static directories - // We can effectively clean the publishDir on initial sync - syncer.Delete = c.Cfg.GetBool("cleanDestinationDir") - - if syncer.Delete { - c.Logger.INFO.Println("removing all files from destination that don't exist in static dirs") - - syncer.DeleteFilter = func(f os.FileInfo) bool { - return f.IsDir() && strings.HasPrefix(f.Name(), ".") - } - } - c.Logger.INFO.Println("syncing static files to", publishDir) - - // because we are using a baseFs (to get the union right). - // set sync src to root - return syncer.Sync(publishDir, helpers.FilePathSeparator) -} - -// getDirList provides NewWatcher() with a list of directories to watch for changes. -func (c *commandeer) getDirList() []string { - var a []string - dataDir := c.PathSpec().AbsPathify(c.Cfg.GetString("dataDir")) - i18nDir := c.PathSpec().AbsPathify(c.Cfg.GetString("i18nDir")) - layoutDir := c.PathSpec().GetLayoutDirPath() - staticDir := c.PathSpec().GetStaticDirPath() - - walker := func(path string, fi os.FileInfo, err error) error { - if err != nil { - if path == dataDir && os.IsNotExist(err) { - c.Logger.WARN.Println("Skip dataDir:", err) - return nil - } - - if path == i18nDir && os.IsNotExist(err) { - c.Logger.WARN.Println("Skip i18nDir:", err) - return nil - } - - if path == layoutDir && os.IsNotExist(err) { - c.Logger.WARN.Println("Skip layoutDir:", err) - return nil - } - - if path == staticDir && os.IsNotExist(err) { - c.Logger.WARN.Println("Skip staticDir:", err) - return nil - } - - if os.IsNotExist(err) { - // Ignore. - return nil - } - - c.Logger.ERROR.Println("Walker: ", err) - return nil - } - - // Skip .git directories. - // Related to https://github.com/gohugoio/hugo/issues/3468. - if fi.Name() == ".git" { - return nil - } - - if fi.Mode()&os.ModeSymlink == os.ModeSymlink { - link, err := filepath.EvalSymlinks(path) - if err != nil { - c.Logger.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", path, err) - return nil - } - linkfi, err := c.Fs.Source.Stat(link) - if err != nil { - c.Logger.ERROR.Printf("Cannot stat '%s', error was: %s", link, err) - return nil - } - if !linkfi.Mode().IsRegular() { - c.Logger.ERROR.Printf("Symbolic links for directories not supported, skipping '%s'", path) - } - return nil - } - - if fi.IsDir() { - if fi.Name() == ".git" || - fi.Name() == "node_modules" || fi.Name() == "bower_components" { - return filepath.SkipDir - } - a = append(a, path) - } - return nil - } - - // SymbolicWalk will log anny ERRORs - _ = helpers.SymbolicWalk(c.Fs.Source, dataDir, walker) - _ = helpers.SymbolicWalk(c.Fs.Source, c.PathSpec().AbsPathify(c.Cfg.GetString("contentDir")), walker) - _ = helpers.SymbolicWalk(c.Fs.Source, i18nDir, walker) - _ = helpers.SymbolicWalk(c.Fs.Source, layoutDir, walker) - _ = helpers.SymbolicWalk(c.Fs.Source, staticDir, walker) - - if c.PathSpec().ThemeSet() { - themesDir := c.PathSpec().GetThemeDir() - _ = helpers.SymbolicWalk(c.Fs.Source, filepath.Join(themesDir, "layouts"), walker) - _ = helpers.SymbolicWalk(c.Fs.Source, filepath.Join(themesDir, "static"), walker) - _ = helpers.SymbolicWalk(c.Fs.Source, filepath.Join(themesDir, "i18n"), walker) - _ = helpers.SymbolicWalk(c.Fs.Source, filepath.Join(themesDir, "data"), walker) - } - - return a -} - -func (c *commandeer) recreateAndBuildSites(watching bool) (err error) { - if err := c.initSites(); err != nil { - return err - } - if !quiet { - c.Logger.FEEDBACK.Println("Started building sites ...") - } - return Hugo.Build(hugolib.BuildCfg{CreateSitesFromConfig: true, Watching: watching, PrintStats: !quiet}) -} - -func (c *commandeer) resetAndBuildSites(watching bool) (err error) { - if err = c.initSites(); err != nil { - return - } - if !quiet { - c.Logger.FEEDBACK.Println("Started building sites ...") - } - return Hugo.Build(hugolib.BuildCfg{ResetState: true, Watching: watching, PrintStats: !quiet}) -} - -func (c *commandeer) initSites() error { - if Hugo != nil { - return nil - } - h, err := hugolib.NewHugoSites(*c.DepsCfg) - - if err != nil { - return err - } - Hugo = h - - return nil -} - -func (c *commandeer) buildSites(watching bool) (err error) { - if err := c.initSites(); err != nil { - return err - } - if !quiet { - c.Logger.FEEDBACK.Println("Started building sites ...") - } - return Hugo.Build(hugolib.BuildCfg{Watching: watching, PrintStats: !quiet}) -} - -func (c *commandeer) rebuildSites(events []fsnotify.Event) error { - if err := c.initSites(); err != nil { - return err - } - return Hugo.Build(hugolib.BuildCfg{PrintStats: !quiet, Watching: true}, events...) -} - -// newWatcher creates a new watcher to watch filesystem events. -func (c *commandeer) newWatcher(port int) error { - if runtime.GOOS == "darwin" { - tweakLimit() - } - - watcher, err := watcher.New(1 * time.Second) - var wg sync.WaitGroup - - if err != nil { - return err - } - - defer watcher.Close() - - wg.Add(1) - - for _, d := range c.getDirList() { - if d != "" { - _ = watcher.Add(d) - } - } - - go func() { - for { - select { - case evs := <-watcher.Events: - c.Logger.INFO.Println("Received System Events:", evs) - - staticEvents := []fsnotify.Event{} - dynamicEvents := []fsnotify.Event{} - - for _, ev := range evs { - ext := filepath.Ext(ev.Name) - baseName := filepath.Base(ev.Name) - istemp := strings.HasSuffix(ext, "~") || - (ext == ".swp") || // vim - (ext == ".swx") || // vim - (ext == ".tmp") || // generic temp file - (ext == ".DS_Store") || // OSX Thumbnail - baseName == "4913" || // vim - strings.HasPrefix(ext, ".goutputstream") || // gnome - strings.HasSuffix(ext, "jb_old___") || // intelliJ - strings.HasSuffix(ext, "jb_tmp___") || // intelliJ - strings.HasSuffix(ext, "jb_bak___") || // intelliJ - strings.HasPrefix(ext, ".sb-") || // byword - strings.HasPrefix(baseName, ".#") || // emacs - strings.HasPrefix(baseName, "#") // emacs - if istemp { - continue - } - // Sometimes during rm -rf operations a '"": REMOVE' is triggered. Just ignore these - if ev.Name == "" { - continue - } - - // Write and rename operations are often followed by CHMOD. - // There may be valid use cases for rebuilding the site on CHMOD, - // but that will require more complex logic than this simple conditional. - // On OS X this seems to be related to Spotlight, see: - // https://github.com/go-fsnotify/fsnotify/issues/15 - // A workaround is to put your site(s) on the Spotlight exception list, - // but that may be a little mysterious for most end users. - // So, for now, we skip reload on CHMOD. - // We do have to check for WRITE though. On slower laptops a Chmod - // could be aggregated with other important events, and we still want - // to rebuild on those - if ev.Op&(fsnotify.Chmod|fsnotify.Write|fsnotify.Create) == fsnotify.Chmod { - continue - } - - walkAdder := func(path string, f os.FileInfo, err error) error { - if f.IsDir() { - c.Logger.FEEDBACK.Println("adding created directory to watchlist", path) - if err := watcher.Add(path); err != nil { - return err - } - } else if !c.isStatic(path) { - // Hugo's rebuilding logic is entirely file based. When you drop a new folder into - // /content on OSX, the above logic will handle future watching of those files, - // but the initial CREATE is lost. - dynamicEvents = append(dynamicEvents, fsnotify.Event{Name: path, Op: fsnotify.Create}) - } - return nil - } - - // recursively add new directories to watch list - // When mkdir -p is used, only the top directory triggers an event (at least on OSX) - if ev.Op&fsnotify.Create == fsnotify.Create { - if s, err := c.Fs.Source.Stat(ev.Name); err == nil && s.Mode().IsDir() { - _ = helpers.SymbolicWalk(c.Fs.Source, ev.Name, walkAdder) - } - } - - if c.isStatic(ev.Name) { - staticEvents = append(staticEvents, ev) - } else { - dynamicEvents = append(dynamicEvents, ev) - } - } - - if len(staticEvents) > 0 { - publishDir := c.PathSpec().AbsPathify(c.Cfg.GetString("publishDir")) + helpers.FilePathSeparator - - // If root, remove the second '/' - if publishDir == "//" { - publishDir = helpers.FilePathSeparator - } - - c.Logger.FEEDBACK.Println("\nStatic file changes detected") - const layout = "2006-01-02 15:04 -0700" - c.Logger.FEEDBACK.Println(time.Now().Format(layout)) - - if c.Cfg.GetBool("forceSyncStatic") { - c.Logger.FEEDBACK.Printf("Syncing all static files\n") - err := c.copyStatic() - if err != nil { - utils.StopOnErr(c.Logger, err, fmt.Sprintf("Error copying static files to %s", publishDir)) - } - } else { - staticSourceFs := c.getStaticSourceFs() - - if staticSourceFs == nil { - c.Logger.WARN.Println("No static directories found to sync") - return - } - - syncer := fsync.NewSyncer() - syncer.NoTimes = c.Cfg.GetBool("noTimes") - syncer.NoChmod = c.Cfg.GetBool("noChmod") - syncer.SrcFs = staticSourceFs - syncer.DestFs = c.Fs.Destination - - // prevent spamming the log on changes - logger := helpers.NewDistinctFeedbackLogger() - - for _, ev := range staticEvents { - // Due to our approach of layering both directories and the content's rendered output - // into one we can't accurately remove a file not in one of the source directories. - // If a file is in the local static dir and also in the theme static dir and we remove - // it from one of those locations we expect it to still exist in the destination - // - // If Hugo generates a file (from the content dir) over a static file - // the content generated file should take precedence. - // - // Because we are now watching and handling individual events it is possible that a static - // event that occupies the same path as a content generated file will take precedence - // until a regeneration of the content takes places. - // - // Hugo assumes that these cases are very rare and will permit this bad behavior - // The alternative is to track every single file and which pipeline rendered it - // and then to handle conflict resolution on every event. - - fromPath := ev.Name - - // If we are here we already know the event took place in a static dir - relPath, err := c.PathSpec().MakeStaticPathRelative(fromPath) - if err != nil { - c.Logger.ERROR.Println(err) - continue - } - - // Remove || rename is harder and will require an assumption. - // Hugo takes the following approach: - // If the static file exists in any of the static source directories after this event - // Hugo will re-sync it. - // If it does not exist in all of the static directories Hugo will remove it. - // - // This assumes that Hugo has not generated content on top of a static file and then removed - // the source of that static file. In this case Hugo will incorrectly remove that file - // from the published directory. - if ev.Op&fsnotify.Rename == fsnotify.Rename || ev.Op&fsnotify.Remove == fsnotify.Remove { - if _, err := staticSourceFs.Stat(relPath); os.IsNotExist(err) { - // If file doesn't exist in any static dir, remove it - toRemove := filepath.Join(publishDir, relPath) - logger.Println("File no longer exists in static dir, removing", toRemove) - _ = c.Fs.Destination.RemoveAll(toRemove) - } else if err == nil { - // If file still exists, sync it - logger.Println("Syncing", relPath, "to", publishDir) - if err := syncer.Sync(filepath.Join(publishDir, relPath), relPath); err != nil { - c.Logger.ERROR.Println(err) - } - } else { - c.Logger.ERROR.Println(err) - } - - continue - } - - // For all other event operations Hugo will sync static. - logger.Println("Syncing", relPath, "to", publishDir) - if err := syncer.Sync(filepath.Join(publishDir, relPath), relPath); err != nil { - c.Logger.ERROR.Println(err) - } - } - } - - if !buildWatch && !c.Cfg.GetBool("disableLiveReload") { - // Will block forever trying to write to a channel that nobody is reading if livereload isn't initialized - - // force refresh when more than one file - if len(staticEvents) > 0 { - for _, ev := range staticEvents { - path, _ := c.PathSpec().MakeStaticPathRelative(ev.Name) - livereload.RefreshPath(path) - } - - } else { - livereload.ForceRefresh() - } - } - } - - if len(dynamicEvents) > 0 { - c.Logger.FEEDBACK.Println("\nChange detected, rebuilding site") - const layout = "2006-01-02 15:04 -0700" - c.Logger.FEEDBACK.Println(time.Now().Format(layout)) - - if err := c.rebuildSites(dynamicEvents); err != nil { - c.Logger.ERROR.Println("Failed to rebuild site:", err) - } - - if !buildWatch && !c.Cfg.GetBool("disableLiveReload") { - - navigate := c.Cfg.GetBool("navigateToChanged") - - var p *hugolib.Page - - if navigate { - - // It is probably more confusing than useful - // to navigate to a new URL on RENAME etc. - // so for now we use the WRITE and CREATE events only. - name := pickOneWriteOrCreatePath(dynamicEvents) - - if name != "" { - p = Hugo.GetContentPage(name) - } - } - - if p != nil { - livereload.NavigateToPath(p.RelPermalink()) - } else { - livereload.ForceRefresh() - } - } - } - case err := <-watcher.Errors: - if err != nil { - c.Logger.ERROR.Println(err) - } - } - } - }() - - if port > 0 { - if !c.Cfg.GetBool("disableLiveReload") { - livereload.Initialize() - http.HandleFunc("/livereload.js", livereload.ServeJS) - http.HandleFunc("/livereload", livereload.Handler) - } - - go c.serve(port) - } - - wg.Wait() - return nil -} - -func pickOneWriteOrCreatePath(events []fsnotify.Event) string { - name := "" - - for _, ev := range events { - if (ev.Op&fsnotify.Write == fsnotify.Write || ev.Op&fsnotify.Create == fsnotify.Create) && len(ev.Name) > len(name) { - name = ev.Name - } - } - - return name -} - -func (c *commandeer) isStatic(path string) bool { - return strings.HasPrefix(path, c.PathSpec().GetStaticDirPath()) || (len(c.PathSpec().GetThemesDirPath()) > 0 && strings.HasPrefix(path, c.PathSpec().GetThemesDirPath())) -} - -// isThemeVsHugoVersionMismatch returns whether the current Hugo version is -// less than the theme's min_version. -func (c *commandeer) isThemeVsHugoVersionMismatch() (mismatch bool, requiredMinVersion string) { - if !c.PathSpec().ThemeSet() { - return - } - - themeDir := c.PathSpec().GetThemeDir() - - path := filepath.Join(themeDir, "theme.toml") - - exists, err := helpers.Exists(path, c.Fs.Source) - - if err != nil || !exists { - return - } - - b, err := afero.ReadFile(c.Fs.Source, path) - - tomlMeta, err := parser.HandleTOMLMetaData(b) - - if err != nil { - return - } - - config := tomlMeta.(map[string]interface{}) - - if minVersion, ok := config["min_version"]; ok { - return helpers.CompareVersion(minVersion) > 0, fmt.Sprint(minVersion) - } - - return -} diff --git a/commands/hugo_windows.go b/commands/hugo_windows.go deleted file mode 100644 index 7342f21a0..000000000 --- a/commands/hugo_windows.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package commands - -import "github.com/spf13/cobra" - -func init() { - // This message to show to Windows users if Hugo is opened from explorer.exe - cobra.MousetrapHelpText = ` - - Hugo is a command-line tool for generating static website. - - You need to open cmd.exe and run Hugo from there. - - Visit http://gohugo.io/ for more information.` -} diff --git a/commands/import_jekyll.go b/commands/import_jekyll.go deleted file mode 100644 index 3d89fee0d..000000000 --- a/commands/import_jekyll.go +++ /dev/null @@ -1,614 +0,0 @@ -// Copyright 2016 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package commands - -import ( - "bytes" - "errors" - "io" - "io/ioutil" - "os" - "path/filepath" - "regexp" - "strconv" - "strings" - "time" - - "github.com/gohugoio/hugo/helpers" - "github.com/gohugoio/hugo/hugofs" - "github.com/gohugoio/hugo/hugolib" - "github.com/gohugoio/hugo/parser" - "github.com/spf13/afero" - "github.com/spf13/cast" - "github.com/spf13/cobra" - jww "github.com/spf13/jwalterweatherman" -) - -func init() { - importCmd.AddCommand(importJekyllCmd) -} - -var importCmd = &cobra.Command{ - Use: "import", - Short: "Import your site from others.", - Long: `Import your site from other web site generators like Jekyll. - -Import requires a subcommand, e.g. ` + "`hugo import jekyll jekyll_root_path target_path`.", - RunE: nil, -} - -var importJekyllCmd = &cobra.Command{ - Use: "jekyll", - Short: "hugo import from Jekyll", - Long: `hugo import from Jekyll. - -Import from Jekyll requires two paths, e.g. ` + "`hugo import jekyll jekyll_root_path target_path`.", - RunE: importFromJekyll, -} - -func init() { - importJekyllCmd.Flags().Bool("force", false, "allow import into non-empty target directory") -} - -func importFromJekyll(cmd *cobra.Command, args []string) error { - - if len(args) < 2 { - return newUserError(`Import from Jekyll requires two paths, e.g. ` + "`hugo import jekyll jekyll_root_path target_path`.") - } - - jekyllRoot, err := filepath.Abs(filepath.Clean(args[0])) - if err != nil { - return newUserError("Path error:", args[0]) - } - - targetDir, err := filepath.Abs(filepath.Clean(args[1])) - if err != nil { - return newUserError("Path error:", args[1]) - } - - jww.INFO.Println("Import Jekyll from:", jekyllRoot, "to:", targetDir) - - if strings.HasPrefix(filepath.Dir(targetDir), jekyllRoot) { - return newUserError("Target path should not be inside the Jekyll root, aborting.") - } - - forceImport, _ := cmd.Flags().GetBool("force") - - fs := afero.NewOsFs() - jekyllPostDirs, hasAnyPost := getJekyllDirInfo(fs, jekyllRoot) - if !hasAnyPost { - return errors.New("Your Jekyll root contains neither posts nor drafts, aborting.") - } - - site, err := createSiteFromJekyll(jekyllRoot, targetDir, jekyllPostDirs, forceImport) - - if err != nil { - return newUserError(err) - } - - jww.FEEDBACK.Println("Importing...") - - fileCount := 0 - callback := func(path string, fi os.FileInfo, err error) error { - if err != nil { - return err - } - - if fi.IsDir() { - return nil - } - - relPath, err := filepath.Rel(jekyllRoot, path) - if err != nil { - return newUserError("Get rel path error:", path) - } - - relPath = filepath.ToSlash(relPath) - draft := false - - switch { - case strings.Contains(relPath, "_posts/"): - relPath = filepath.Join("content/post", strings.Replace(relPath, "_posts/", "", -1)) - case strings.Contains(relPath, "_drafts/"): - relPath = filepath.Join("content/draft", strings.Replace(relPath, "_drafts/", "", -1)) - draft = true - default: - return nil - } - - fileCount++ - return convertJekyllPost(site, path, relPath, targetDir, draft) - } - - for jekyllPostDir, hasAnyPostInDir := range jekyllPostDirs { - if hasAnyPostInDir { - if err = helpers.SymbolicWalk(hugofs.Os, filepath.Join(jekyllRoot, jekyllPostDir), callback); err != nil { - return err - } - } - } - - jww.FEEDBACK.Println("Congratulations!", fileCount, "post(s) imported!") - jww.FEEDBACK.Println("Now, start Hugo by yourself:\n" + - "$ git clone https://github.com/spf13/herring-cove.git " + args[1] + "/themes/herring-cove") - jww.FEEDBACK.Println("$ cd " + args[1] + "\n$ hugo server --theme=herring-cove") - - jww.FEEDBACK.Println("Congratulations!", fileCount, "post(s) imported!") - jww.FEEDBACK.Println("Now, start Hugo by yourself:\n" + - "$ git clone https://github.com/spf13/herring-cove.git " + args[1] + "/themes/herring-cove") - jww.FEEDBACK.Println("$ cd " + args[1] + "\n$ hugo server --theme=herring-cove") - - return nil -} - -func getJekyllDirInfo(fs afero.Fs, jekyllRoot string) (map[string]bool, bool) { - postDirs := make(map[string]bool) - hasAnyPost := false - if entries, err := ioutil.ReadDir(jekyllRoot); err == nil { - for _, entry := range entries { - if entry.IsDir() { - subDir := filepath.Join(jekyllRoot, entry.Name()) - if isPostDir, hasAnyPostInDir := retrieveJekyllPostDir(fs, subDir); isPostDir { - postDirs[entry.Name()] = hasAnyPostInDir - if hasAnyPostInDir { - hasAnyPost = true - } - } - } - } - } - return postDirs, hasAnyPost -} - -func retrieveJekyllPostDir(fs afero.Fs, dir string) (bool, bool) { - if strings.HasSuffix(dir, "_posts") || strings.HasSuffix(dir, "_drafts") { - isEmpty, _ := helpers.IsEmpty(dir, fs) - return true, !isEmpty - } - - if entries, err := ioutil.ReadDir(dir); err == nil { - for _, entry := range entries { - if entry.IsDir() { - subDir := filepath.Join(dir, entry.Name()) - if isPostDir, hasAnyPost := retrieveJekyllPostDir(fs, subDir); isPostDir { - return isPostDir, hasAnyPost - } - } - } - } - - return false, true -} - -func createSiteFromJekyll(jekyllRoot, targetDir string, jekyllPostDirs map[string]bool, force bool) (*hugolib.Site, error) { - s, err := hugolib.NewSiteDefaultLang() - if err != nil { - return nil, err - } - - fs := s.Fs.Source - if exists, _ := helpers.Exists(targetDir, fs); exists { - if isDir, _ := helpers.IsDir(targetDir, fs); !isDir { - return nil, errors.New("Target path \"" + targetDir + "\" already exists but not a directory") - } - - isEmpty, _ := helpers.IsEmpty(targetDir, fs) - - if !isEmpty && !force { - return nil, errors.New("Target path \"" + targetDir + "\" already exists and is not empty") - } - } - - jekyllConfig := loadJekyllConfig(fs, jekyllRoot) - - mkdir(targetDir, "layouts") - mkdir(targetDir, "content") - mkdir(targetDir, "archetypes") - mkdir(targetDir, "static") - mkdir(targetDir, "data") - mkdir(targetDir, "themes") - - createConfigFromJekyll(fs, targetDir, "yaml", jekyllConfig) - - copyJekyllFilesAndFolders(jekyllRoot, filepath.Join(targetDir, "static"), jekyllPostDirs) - - return s, nil -} - -func loadJekyllConfig(fs afero.Fs, jekyllRoot string) map[string]interface{} { - path := filepath.Join(jekyllRoot, "_config.yml") - - exists, err := helpers.Exists(path, fs) - - if err != nil || !exists { - jww.WARN.Println("_config.yaml not found: Is the specified Jekyll root correct?") - return nil - } - - f, err := fs.Open(path) - if err != nil { - return nil - } - - defer f.Close() - - b, err := ioutil.ReadAll(f) - - if err != nil { - return nil - } - - c, err := parser.HandleYAMLMetaData(b) - - if err != nil { - return nil - } - - return c.(map[string]interface{}) -} - -func createConfigFromJekyll(fs afero.Fs, inpath string, kind string, jekyllConfig map[string]interface{}) (err error) { - title := "My New Hugo Site" - baseURL := "http://example.org/" - - for key, value := range jekyllConfig { - lowerKey := strings.ToLower(key) - - switch lowerKey { - case "title": - if str, ok := value.(string); ok { - title = str - } - - case "url": - if str, ok := value.(string); ok { - baseURL = str - } - } - } - - in := map[string]interface{}{ - "baseURL": baseURL, - "title": title, - "languageCode": "en-us", - "disablePathToLower": true, - } - kind = parser.FormatSanitize(kind) - - var buf bytes.Buffer - err = parser.InterfaceToConfig(in, parser.FormatToLeadRune(kind), &buf) - if err != nil { - return err - } - - return helpers.WriteToDisk(filepath.Join(inpath, "config."+kind), &buf, fs) -} - -func copyFile(source string, dest string) error { - sf, err := os.Open(source) - if err != nil { - return err - } - defer sf.Close() - df, err := os.Create(dest) - if err != nil { - return err - } - defer df.Close() - _, err = io.Copy(df, sf) - if err == nil { - si, err := os.Stat(source) - if err != nil { - err = os.Chmod(dest, si.Mode()) - - if err != nil { - return err - } - } - - } - return nil -} - -func copyDir(source string, dest string) error { - fi, err := os.Stat(source) - if err != nil { - return err - } - if !fi.IsDir() { - return errors.New(source + " is not a directory") - } - err = os.MkdirAll(dest, fi.Mode()) - if err != nil { - return err - } - entries, err := ioutil.ReadDir(source) - for _, entry := range entries { - sfp := filepath.Join(source, entry.Name()) - dfp := filepath.Join(dest, entry.Name()) - if entry.IsDir() { - err = copyDir(sfp, dfp) - if err != nil { - jww.ERROR.Println(err) - } - } else { - err = copyFile(sfp, dfp) - if err != nil { - jww.ERROR.Println(err) - } - } - - } - return nil -} - -func copyJekyllFilesAndFolders(jekyllRoot, dest string, jekyllPostDirs map[string]bool) (err error) { - fi, err := os.Stat(jekyllRoot) - if err != nil { - return err - } - if !fi.IsDir() { - return errors.New(jekyllRoot + " is not a directory") - } - err = os.MkdirAll(dest, fi.Mode()) - if err != nil { - return err - } - entries, err := ioutil.ReadDir(jekyllRoot) - for _, entry := range entries { - sfp := filepath.Join(jekyllRoot, entry.Name()) - dfp := filepath.Join(dest, entry.Name()) - if entry.IsDir() { - if entry.Name()[0] != '_' && entry.Name()[0] != '.' { - if _, ok := jekyllPostDirs[entry.Name()]; !ok { - err = copyDir(sfp, dfp) - if err != nil { - jww.ERROR.Println(err) - } - } - } - } else { - lowerEntryName := strings.ToLower(entry.Name()) - exceptSuffix := []string{".md", ".markdown", ".html", ".htm", - ".xml", ".textile", "rakefile", "gemfile", ".lock"} - isExcept := false - for _, suffix := range exceptSuffix { - if strings.HasSuffix(lowerEntryName, suffix) { - isExcept = true - break - } - } - - if !isExcept && entry.Name()[0] != '.' && entry.Name()[0] != '_' { - err = copyFile(sfp, dfp) - if err != nil { - jww.ERROR.Println(err) - } - } - } - - } - return nil -} - -func parseJekyllFilename(filename string) (time.Time, string, error) { - re := regexp.MustCompile(`(\d+-\d+-\d+)-(.+)\..*`) - r := re.FindAllStringSubmatch(filename, -1) - if len(r) == 0 { - return time.Now(), "", errors.New("filename not match") - } - - postDate, err := time.Parse("2006-1-2", r[0][1]) - if err != nil { - return time.Now(), "", err - } - - postName := r[0][2] - - return postDate, postName, nil -} - -func convertJekyllPost(s *hugolib.Site, path, relPath, targetDir string, draft bool) error { - jww.TRACE.Println("Converting", path) - - filename := filepath.Base(path) - postDate, postName, err := parseJekyllFilename(filename) - if err != nil { - jww.WARN.Printf("Failed to parse filename '%s': %s. Skipping.", filename, err) - return nil - } - - jww.TRACE.Println(filename, postDate, postName) - - targetFile := filepath.Join(targetDir, relPath) - targetParentDir := filepath.Dir(targetFile) - os.MkdirAll(targetParentDir, 0777) - - contentBytes, err := ioutil.ReadFile(path) - if err != nil { - jww.ERROR.Println("Read file error:", path) - return err - } - - psr, err := parser.ReadFrom(bytes.NewReader(contentBytes)) - if err != nil { - jww.ERROR.Println("Parse file error:", path) - return err - } - - metadata, err := psr.Metadata() - if err != nil { - jww.ERROR.Println("Processing file error:", path) - return err - } - - newmetadata, err := convertJekyllMetaData(metadata, postName, postDate, draft) - if err != nil { - jww.ERROR.Println("Convert metadata error:", path) - return err - } - - jww.TRACE.Println(newmetadata) - content := convertJekyllContent(newmetadata, string(psr.Content())) - - page, err := s.NewPage(filename) - if err != nil { - jww.ERROR.Println("New page error", filename) - return err - } - - page.SetDir(targetParentDir) - page.SetSourceContent([]byte(content)) - page.SetSourceMetaData(newmetadata, parser.FormatToLeadRune("yaml")) - page.SaveSourceAs(targetFile) - - jww.TRACE.Println("Target file:", targetFile) - - return nil -} - -func convertJekyllMetaData(m interface{}, postName string, postDate time.Time, draft bool) (interface{}, error) { - url := postDate.Format("/2006/01/02/") + postName + "/" - - metadata, err := cast.ToStringMapE(m) - if err != nil { - return nil, err - } - - if draft { - metadata["draft"] = true - } - - for key, value := range metadata { - lowerKey := strings.ToLower(key) - - switch lowerKey { - case "layout": - delete(metadata, key) - case "permalink": - if str, ok := value.(string); ok { - url = str - } - delete(metadata, key) - case "category": - if str, ok := value.(string); ok { - metadata["categories"] = []string{str} - } - delete(metadata, key) - case "excerpt_separator": - if key != lowerKey { - delete(metadata, key) - metadata[lowerKey] = value - } - case "date": - if str, ok := value.(string); ok { - re := regexp.MustCompile(`(\d+):(\d+):(\d+)`) - r := re.FindAllStringSubmatch(str, -1) - if len(r) > 0 { - hour, _ := strconv.Atoi(r[0][1]) - minute, _ := strconv.Atoi(r[0][2]) - second, _ := strconv.Atoi(r[0][3]) - postDate = time.Date(postDate.Year(), postDate.Month(), postDate.Day(), hour, minute, second, 0, time.UTC) - } - } - delete(metadata, key) - } - - } - - metadata["url"] = url - metadata["date"] = postDate.Format(time.RFC3339) - - return metadata, nil -} - -func convertJekyllContent(m interface{}, content string) string { - metadata, _ := cast.ToStringMapE(m) - - lines := strings.Split(content, "\n") - var resultLines []string - for _, line := range lines { - resultLines = append(resultLines, strings.Trim(line, "\r\n")) - } - - content = strings.Join(resultLines, "\n") - - excerptSep := "" - if value, ok := metadata["excerpt_separator"]; ok { - if str, strOk := value.(string); strOk { - content = strings.Replace(content, strings.TrimSpace(str), excerptSep, -1) - } - } - - replaceList := []struct { - re *regexp.Regexp - replace string - }{ - {regexp.MustCompile("(?i)"), ""}, - {regexp.MustCompile(`\{%\s*raw\s*%\}\s*(.*?)\s*\{%\s*endraw\s*%\}`), "$1"}, - {regexp.MustCompile(`{%\s*highlight\s*(.*?)\s*%}`), "{{< highlight $1 >}}"}, - {regexp.MustCompile(`{%\s*endhighlight\s*%}`), "{{< / highlight >}}"}, - } - - for _, replace := range replaceList { - content = replace.re.ReplaceAllString(content, replace.replace) - } - - replaceListFunc := []struct { - re *regexp.Regexp - replace func(string) string - }{ - // Octopress image tag: http://octopress.org/docs/plugins/image-tag/ - {regexp.MustCompile(`{%\s+img\s*(.*?)\s*%}`), replaceImageTag}, - } - - for _, replace := range replaceListFunc { - content = replace.re.ReplaceAllStringFunc(content, replace.replace) - } - - return content -} - -func replaceImageTag(match string) string { - r := regexp.MustCompile(`{%\s+img\s*(\p{L}*)\s+([\S]*/[\S]+)\s+(\d*)\s*(\d*)\s*(.*?)\s*%}`) - result := bytes.NewBufferString("{{< figure ") - parts := r.FindStringSubmatch(match) - // Index 0 is the entire string, ignore - replaceOptionalPart(result, "class", parts[1]) - replaceOptionalPart(result, "src", parts[2]) - replaceOptionalPart(result, "width", parts[3]) - replaceOptionalPart(result, "height", parts[4]) - // title + alt - part := parts[5] - if len(part) > 0 { - splits := strings.Split(part, "'") - lenSplits := len(splits) - if lenSplits == 1 { - replaceOptionalPart(result, "title", splits[0]) - } else if lenSplits == 3 { - replaceOptionalPart(result, "title", splits[1]) - } else if lenSplits == 5 { - replaceOptionalPart(result, "title", splits[1]) - replaceOptionalPart(result, "alt", splits[3]) - } - } - result.WriteString(">}}") - return result.String() - -} -func replaceOptionalPart(buffer *bytes.Buffer, partName string, part string) { - if len(part) > 0 { - buffer.WriteString(partName + "=\"" + part + "\" ") - } -} diff --git a/commands/import_jekyll_test.go b/commands/import_jekyll_test.go deleted file mode 100644 index 90a05c01c..000000000 --- a/commands/import_jekyll_test.go +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package commands - -import ( - "encoding/json" - "github.com/stretchr/testify/assert" - "testing" - "time" -) - -func TestParseJekyllFilename(t *testing.T) { - filenameArray := []string{ - "2015-01-02-test.md", - "2012-03-15-中文.markup", - } - - expectResult := []struct { - postDate time.Time - postName string - }{ - {time.Date(2015, time.January, 2, 0, 0, 0, 0, time.UTC), "test"}, - {time.Date(2012, time.March, 15, 0, 0, 0, 0, time.UTC), "中文"}, - } - - for i, filename := range filenameArray { - postDate, postName, err := parseJekyllFilename(filename) - assert.Equal(t, err, nil) - assert.Equal(t, expectResult[i].postDate.Format("2006-01-02"), postDate.Format("2006-01-02")) - assert.Equal(t, expectResult[i].postName, postName) - } -} - -func TestConvertJekyllMetadata(t *testing.T) { - testDataList := []struct { - metadata interface{} - postName string - postDate time.Time - draft bool - expect string - }{ - {map[interface{}]interface{}{}, "testPost", time.Date(2015, 10, 1, 0, 0, 0, 0, time.UTC), false, - `{"date":"2015-10-01T00:00:00Z","url":"/2015/10/01/testPost/"}`}, - {map[interface{}]interface{}{}, "testPost", time.Date(2015, 10, 1, 0, 0, 0, 0, time.UTC), true, - `{"date":"2015-10-01T00:00:00Z","draft":true,"url":"/2015/10/01/testPost/"}`}, - {map[interface{}]interface{}{"Permalink": "/permalink.html", "layout": "post"}, - "testPost", time.Date(2015, 10, 1, 0, 0, 0, 0, time.UTC), false, - `{"date":"2015-10-01T00:00:00Z","url":"/permalink.html"}`}, - {map[interface{}]interface{}{"permalink": "/permalink.html"}, - "testPost", time.Date(2015, 10, 1, 0, 0, 0, 0, time.UTC), false, - `{"date":"2015-10-01T00:00:00Z","url":"/permalink.html"}`}, - {map[interface{}]interface{}{"category": nil, "permalink": 123}, - "testPost", time.Date(2015, 10, 1, 0, 0, 0, 0, time.UTC), false, - `{"date":"2015-10-01T00:00:00Z","url":"/2015/10/01/testPost/"}`}, - {map[interface{}]interface{}{"Excerpt_Separator": "sep"}, - "testPost", time.Date(2015, 10, 1, 0, 0, 0, 0, time.UTC), false, - `{"date":"2015-10-01T00:00:00Z","excerpt_separator":"sep","url":"/2015/10/01/testPost/"}`}, - {map[interface{}]interface{}{"category": "book", "layout": "post", "Others": "Goods", "Date": "2015-10-01 12:13:11"}, - "testPost", time.Date(2015, 10, 1, 0, 0, 0, 0, time.UTC), false, - `{"Others":"Goods","categories":["book"],"date":"2015-10-01T12:13:11Z","url":"/2015/10/01/testPost/"}`}, - } - - for _, data := range testDataList { - result, err := convertJekyllMetaData(data.metadata, data.postName, data.postDate, data.draft) - assert.Equal(t, nil, err) - jsonResult, err := json.Marshal(result) - assert.Equal(t, nil, err) - assert.Equal(t, data.expect, string(jsonResult)) - } -} - -func TestConvertJekyllContent(t *testing.T) { - testDataList := []struct { - metadata interface{} - content string - expect string - }{ - {map[interface{}]interface{}{}, - `Test content\n\npart2 content`, `Test content\n\npart2 content`}, - {map[interface{}]interface{}{}, - `Test content\n\npart2 content`, `Test content\n\npart2 content`}, - {map[interface{}]interface{}{"excerpt_separator": ""}, - `Test content\n\npart2 content`, `Test content\n\npart2 content`}, - {map[interface{}]interface{}{}, "{% raw %}text{% endraw %}", "text"}, - {map[interface{}]interface{}{}, "{%raw%} text2 {%endraw %}", "text2"}, - {map[interface{}]interface{}{}, - "{% highlight go %}\nvar s int\n{% endhighlight %}", - "{{< highlight go >}}\nvar s int\n{{< / highlight >}}"}, - - // Octopress image tag - {map[interface{}]interface{}{}, - "{% img http://placekitten.com/890/280 %}", - "{{< figure src=\"http://placekitten.com/890/280\" >}}"}, - {map[interface{}]interface{}{}, - "{% img left http://placekitten.com/320/250 Place Kitten #2 %}", - "{{< figure class=\"left\" src=\"http://placekitten.com/320/250\" title=\"Place Kitten #2\" >}}"}, - {map[interface{}]interface{}{}, - "{% img right http://placekitten.com/300/500 150 250 'Place Kitten #3' %}", - "{{< figure class=\"right\" src=\"http://placekitten.com/300/500\" width=\"150\" height=\"250\" title=\"Place Kitten #3\" >}}"}, - {map[interface{}]interface{}{}, - "{% img right http://placekitten.com/300/500 150 250 'Place Kitten #4' 'An image of a very cute kitten' %}", - "{{< figure class=\"right\" src=\"http://placekitten.com/300/500\" width=\"150\" height=\"250\" title=\"Place Kitten #4\" alt=\"An image of a very cute kitten\" >}}"}, - {map[interface{}]interface{}{}, - "{% img http://placekitten.com/300/500 150 250 'Place Kitten #4' 'An image of a very cute kitten' %}", - "{{< figure src=\"http://placekitten.com/300/500\" width=\"150\" height=\"250\" title=\"Place Kitten #4\" alt=\"An image of a very cute kitten\" >}}"}, - {map[interface{}]interface{}{}, - "{% img right /placekitten/300/500 'Place Kitten #4' 'An image of a very cute kitten' %}", - "{{< figure class=\"right\" src=\"/placekitten/300/500\" title=\"Place Kitten #4\" alt=\"An image of a very cute kitten\" >}}"}, - } - - for _, data := range testDataList { - result := convertJekyllContent(data.metadata, data.content) - assert.Equal(t, data.expect, result) - } -} diff --git a/commands/limit_darwin.go b/commands/limit_darwin.go deleted file mode 100644 index 9246f4497..000000000 --- a/commands/limit_darwin.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package commands - -import ( - "syscall" - - "github.com/spf13/cobra" - jww "github.com/spf13/jwalterweatherman" -) - -func init() { - checkCmd.AddCommand(limit) -} - -var limit = &cobra.Command{ - Use: "ulimit", - Short: "Check system ulimit settings", - Long: `Hugo will inspect the current ulimit settings on the system. -This is primarily to ensure that Hugo can watch enough files on some OSs`, - RunE: func(cmd *cobra.Command, args []string) error { - var rLimit syscall.Rlimit - err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit) - if err != nil { - return newSystemError("Error Getting Rlimit ", err) - } - - jww.FEEDBACK.Println("Current rLimit:", rLimit) - - jww.FEEDBACK.Println("Attempting to increase limit") - rLimit.Max = 999999 - rLimit.Cur = 999999 - err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit) - if err != nil { - return newSystemError("Error Setting rLimit ", err) - } - err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit) - if err != nil { - return newSystemError("Error Getting rLimit ", err) - } - jww.FEEDBACK.Println("rLimit after change:", rLimit) - - return nil - }, -} - -func tweakLimit() { - var rLimit syscall.Rlimit - err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit) - if err != nil { - jww.ERROR.Println("Unable to obtain rLimit", err) - } - if rLimit.Cur < rLimit.Max { - rLimit.Max = 64000 - rLimit.Cur = 64000 - err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit) - if err != nil { - jww.WARN.Println("Unable to increase number of open files limit", err) - } - } -} diff --git a/commands/limit_others.go b/commands/limit_others.go deleted file mode 100644 index c757f174e..000000000 --- a/commands/limit_others.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// +build !darwin -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package commands - -func tweakLimit() { - // nothing to do -} diff --git a/commands/list.go b/commands/list.go deleted file mode 100644 index b2a6b5395..000000000 --- a/commands/list.go +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package commands - -import ( - "path/filepath" - - "github.com/gohugoio/hugo/hugolib" - "github.com/spf13/cobra" - jww "github.com/spf13/jwalterweatherman" -) - -func init() { - listCmd.AddCommand(listDraftsCmd) - listCmd.AddCommand(listFutureCmd) - listCmd.AddCommand(listExpiredCmd) - listCmd.PersistentFlags().StringVarP(&source, "source", "s", "", "filesystem path to read files relative from") - listCmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{}) -} - -var listCmd = &cobra.Command{ - Use: "list", - Short: "Listing out various types of content", - Long: `Listing out various types of content. - -List requires a subcommand, e.g. ` + "`hugo list drafts`.", - RunE: nil, -} - -var listDraftsCmd = &cobra.Command{ - Use: "drafts", - Short: "List all drafts", - Long: `List all of the drafts in your content directory.`, - RunE: func(cmd *cobra.Command, args []string) error { - - cfg, err := InitializeConfig() - if err != nil { - return err - } - - c, err := newCommandeer(cfg) - if err != nil { - return err - } - - c.Set("buildDrafts", true) - - sites, err := hugolib.NewHugoSites(*cfg) - - if err != nil { - return newSystemError("Error creating sites", err) - } - - if err := sites.Build(hugolib.BuildCfg{SkipRender: true}); err != nil { - return newSystemError("Error Processing Source Content", err) - } - - for _, p := range sites.Pages() { - if p.IsDraft() { - jww.FEEDBACK.Println(filepath.Join(p.File.Dir(), p.File.LogicalName())) - } - - } - - return nil - - }, -} - -var listFutureCmd = &cobra.Command{ - Use: "future", - Short: "List all posts dated in the future", - Long: `List all of the posts in your content directory which will be -posted in the future.`, - RunE: func(cmd *cobra.Command, args []string) error { - - cfg, err := InitializeConfig() - if err != nil { - return err - } - - c, err := newCommandeer(cfg) - if err != nil { - return err - } - - c.Set("buildFuture", true) - - sites, err := hugolib.NewHugoSites(*cfg) - - if err != nil { - return newSystemError("Error creating sites", err) - } - - if err := sites.Build(hugolib.BuildCfg{SkipRender: true}); err != nil { - return newSystemError("Error Processing Source Content", err) - } - - for _, p := range sites.Pages() { - if p.IsFuture() { - jww.FEEDBACK.Println(filepath.Join(p.File.Dir(), p.File.LogicalName())) - } - - } - - return nil - - }, -} - -var listExpiredCmd = &cobra.Command{ - Use: "expired", - Short: "List all posts already expired", - Long: `List all of the posts in your content directory which has already -expired.`, - RunE: func(cmd *cobra.Command, args []string) error { - - cfg, err := InitializeConfig() - if err != nil { - return err - } - - c, err := newCommandeer(cfg) - if err != nil { - return err - } - - c.Set("buildExpired", true) - - sites, err := hugolib.NewHugoSites(*cfg) - - if err != nil { - return newSystemError("Error creating sites", err) - } - - if err := sites.Build(hugolib.BuildCfg{SkipRender: true}); err != nil { - return newSystemError("Error Processing Source Content", err) - } - - for _, p := range sites.Pages() { - if p.IsExpired() { - jww.FEEDBACK.Println(filepath.Join(p.File.Dir(), p.File.LogicalName())) - } - - } - - return nil - - }, -} diff --git a/commands/list_config.go b/commands/list_config.go deleted file mode 100644 index f47f6e144..000000000 --- a/commands/list_config.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License.Print the version number of Hug - -package commands - -import ( - "reflect" - "sort" - - "github.com/spf13/cobra" - jww "github.com/spf13/jwalterweatherman" - "github.com/spf13/viper" -) - -var configCmd = &cobra.Command{ - Use: "config", - Short: "Print the site configuration", - Long: `Print the site configuration, both default and custom settings.`, -} - -func init() { - configCmd.RunE = printConfig -} - -func printConfig(cmd *cobra.Command, args []string) error { - cfg, err := InitializeConfig(configCmd) - - if err != nil { - return err - } - - allSettings := cfg.Cfg.(*viper.Viper).AllSettings() - - var separator string - if allSettings["metadataformat"] == "toml" { - separator = " = " - } else { - separator = ": " - } - - var keys []string - for k := range allSettings { - keys = append(keys, k) - } - sort.Strings(keys) - for _, k := range keys { - kv := reflect.ValueOf(allSettings[k]) - if kv.Kind() == reflect.String { - jww.FEEDBACK.Printf("%s%s\"%+v\"\n", k, separator, allSettings[k]) - } else { - jww.FEEDBACK.Printf("%s%s%+v\n", k, separator, allSettings[k]) - } - } - - return nil -} diff --git a/commands/new.go b/commands/new.go deleted file mode 100644 index 03d15ee9e..000000000 --- a/commands/new.go +++ /dev/null @@ -1,399 +0,0 @@ -// Copyright 2016 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package commands - -import ( - "bytes" - "errors" - "fmt" - "os" - "path/filepath" - "strings" - "time" - - "github.com/gohugoio/hugo/create" - "github.com/gohugoio/hugo/helpers" - "github.com/gohugoio/hugo/hugofs" - "github.com/gohugoio/hugo/hugolib" - "github.com/gohugoio/hugo/parser" - "github.com/spf13/afero" - "github.com/spf13/cobra" - jww "github.com/spf13/jwalterweatherman" - "github.com/spf13/viper" -) - -var ( - configFormat string - contentEditor string - contentType string -) - -func init() { - newSiteCmd.Flags().StringVarP(&configFormat, "format", "f", "toml", "config & frontmatter format") - newSiteCmd.Flags().Bool("force", false, "init inside non-empty directory") - newCmd.Flags().StringVarP(&contentType, "kind", "k", "", "content type to create") - newCmd.PersistentFlags().StringVarP(&source, "source", "s", "", "filesystem path to read files relative from") - newCmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{}) - newCmd.Flags().StringVar(&contentEditor, "editor", "", "edit new content with this editor, if provided") - - newCmd.AddCommand(newSiteCmd) - newCmd.AddCommand(newThemeCmd) - -} - -var newCmd = &cobra.Command{ - Use: "new [path]", - Short: "Create new content for your site", - Long: `Create a new content file and automatically set the date and title. -It will guess which kind of file to create based on the path provided. - -You can also specify the kind with ` + "`-k KIND`" + `. - -If archetypes are provided in your theme or site, they will be used.`, - - RunE: NewContent, -} - -var newSiteCmd = &cobra.Command{ - Use: "site [path]", - Short: "Create a new site (skeleton)", - Long: `Create a new site in the provided directory. -The new site will have the correct structure, but no content or theme yet. -Use ` + "`hugo new [contentPath]`" + ` to create new content.`, - RunE: NewSite, -} - -var newThemeCmd = &cobra.Command{ - Use: "theme [name]", - Short: "Create a new theme", - Long: `Create a new theme (skeleton) called [name] in the current directory. -New theme is a skeleton. Please add content to the touched files. Add your -name to the copyright line in the license and adjust the theme.toml file -as you see fit.`, - RunE: NewTheme, -} - -// NewContent adds new content to a Hugo site. -func NewContent(cmd *cobra.Command, args []string) error { - cfg, err := InitializeConfig() - - if err != nil { - return err - } - - c, err := newCommandeer(cfg) - if err != nil { - return err - } - - if cmd.Flags().Changed("editor") { - c.Set("newContentEditor", contentEditor) - } - - if len(args) < 1 { - return newUserError("path needs to be provided") - } - - createPath := args[0] - - var kind string - - createPath, kind = newContentPathSection(createPath) - - if contentType != "" { - kind = contentType - } - - ps, err := helpers.NewPathSpec(cfg.Fs, cfg.Cfg) - if err != nil { - return err - } - - // If a site isn't in use in the archetype template, we can skip the build. - siteFactory := func(filename string, siteUsed bool) (*hugolib.Site, error) { - if !siteUsed { - return hugolib.NewSite(*cfg) - } - var s *hugolib.Site - if err := c.initSites(); err != nil { - return nil, err - } - - if err := Hugo.Build(hugolib.BuildCfg{SkipRender: true, PrintStats: false}); err != nil { - return nil, err - } - - s = Hugo.Sites[0] - - if len(Hugo.Sites) > 1 { - // Find the best match. - for _, ss := range Hugo.Sites { - if strings.Contains(createPath, "."+ss.Language.Lang) { - s = ss - break - } - } - } - return s, nil - } - - return create.NewContent(ps, siteFactory, kind, createPath) -} - -func doNewSite(fs *hugofs.Fs, basepath string, force bool) error { - archeTypePath := filepath.Join(basepath, "archetypes") - dirs := []string{ - filepath.Join(basepath, "layouts"), - filepath.Join(basepath, "content"), - archeTypePath, - filepath.Join(basepath, "static"), - filepath.Join(basepath, "data"), - filepath.Join(basepath, "themes"), - } - - if exists, _ := helpers.Exists(basepath, fs.Source); exists { - if isDir, _ := helpers.IsDir(basepath, fs.Source); !isDir { - return errors.New(basepath + " already exists but not a directory") - } - - isEmpty, _ := helpers.IsEmpty(basepath, fs.Source) - - switch { - case !isEmpty && !force: - return errors.New(basepath + " already exists and is not empty") - - case !isEmpty && force: - all := append(dirs, filepath.Join(basepath, "config."+configFormat)) - for _, path := range all { - if exists, _ := helpers.Exists(path, fs.Source); exists { - return errors.New(path + " already exists") - } - } - } - } - - for _, dir := range dirs { - if err := fs.Source.MkdirAll(dir, 0777); err != nil { - return fmt.Errorf("Failed to create dir: %s", err) - } - } - - createConfig(fs, basepath, configFormat) - - // Create a defaul archetype file. - helpers.SafeWriteToDisk(filepath.Join(archeTypePath, "default.md"), - strings.NewReader(create.ArchetypeTemplateTemplate), fs.Source) - - jww.FEEDBACK.Printf("Congratulations! Your new Hugo site is created in %s.\n\n", basepath) - jww.FEEDBACK.Println(nextStepsText()) - - return nil -} - -func nextStepsText() string { - var nextStepsText bytes.Buffer - - nextStepsText.WriteString(`Just a few more steps and you're ready to go: - -1. Download a theme into the same-named folder. - Choose a theme from https://themes.gohugo.io/, or - create your own with the "hugo new theme " command. -2. Perhaps you want to add some content. You can add single files - with "hugo new `) - - nextStepsText.WriteString(filepath.Join("", ".")) - - nextStepsText.WriteString(`". -3. Start the built-in live server via "hugo server". - -Visit https://gohugo.io/ for quickstart guide and full documentation.`) - - return nextStepsText.String() -} - -// NewSite creates a new Hugo site and initializes a structured Hugo directory. -func NewSite(cmd *cobra.Command, args []string) error { - if len(args) < 1 { - return newUserError("path needs to be provided") - } - - createpath, err := filepath.Abs(filepath.Clean(args[0])) - if err != nil { - return newUserError(err) - } - - forceNew, _ := cmd.Flags().GetBool("force") - - return doNewSite(hugofs.NewDefault(viper.New()), createpath, forceNew) -} - -// NewTheme creates a new Hugo theme. -func NewTheme(cmd *cobra.Command, args []string) error { - cfg, err := InitializeConfig() - - if err != nil { - return err - } - - if len(args) < 1 { - return newUserError("theme name needs to be provided") - } - - c, err := newCommandeer(cfg) - if err != nil { - return err - } - - createpath := c.PathSpec().AbsPathify(filepath.Join(c.Cfg.GetString("themesDir"), args[0])) - jww.INFO.Println("creating theme at", createpath) - - if x, _ := helpers.Exists(createpath, cfg.Fs.Source); x { - return newUserError(createpath, "already exists") - } - - mkdir(createpath, "layouts", "_default") - mkdir(createpath, "layouts", "partials") - - touchFile(cfg.Fs.Source, createpath, "layouts", "index.html") - touchFile(cfg.Fs.Source, createpath, "layouts", "404.html") - touchFile(cfg.Fs.Source, createpath, "layouts", "_default", "list.html") - touchFile(cfg.Fs.Source, createpath, "layouts", "_default", "single.html") - - touchFile(cfg.Fs.Source, createpath, "layouts", "partials", "header.html") - touchFile(cfg.Fs.Source, createpath, "layouts", "partials", "footer.html") - - mkdir(createpath, "archetypes") - - archDefault := []byte("+++\n+++\n") - - err = helpers.WriteToDisk(filepath.Join(createpath, "archetypes", "default.md"), bytes.NewReader(archDefault), cfg.Fs.Source) - if err != nil { - return err - } - - mkdir(createpath, "static", "js") - mkdir(createpath, "static", "css") - - by := []byte(`The MIT License (MIT) - -Copyright (c) ` + time.Now().Format("2006") + ` YOUR_NAME_HERE - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -`) - - err = helpers.WriteToDisk(filepath.Join(createpath, "LICENSE.md"), bytes.NewReader(by), cfg.Fs.Source) - if err != nil { - return err - } - - createThemeMD(cfg.Fs, createpath) - - return nil -} - -func mkdir(x ...string) { - p := filepath.Join(x...) - - err := os.MkdirAll(p, 0777) // before umask - if err != nil { - jww.FATAL.Fatalln(err) - } -} - -func touchFile(fs afero.Fs, x ...string) { - inpath := filepath.Join(x...) - mkdir(filepath.Dir(inpath)) - err := helpers.WriteToDisk(inpath, bytes.NewReader([]byte{}), fs) - if err != nil { - jww.FATAL.Fatalln(err) - } -} - -func createThemeMD(fs *hugofs.Fs, inpath string) (err error) { - - by := []byte(`# theme.toml template for a Hugo theme -# See https://github.com/gohugoio/hugoThemes#themetoml for an example - -name = "` + strings.Title(helpers.MakeTitle(filepath.Base(inpath))) + `" -license = "MIT" -licenselink = "https://github.com/yourname/yourtheme/blob/master/LICENSE.md" -description = "" -homepage = "http://example.com/" -tags = [] -features = [] -min_version = "0.26" - -[author] - name = "" - homepage = "" - -# If porting an existing theme -[original] - name = "" - homepage = "" - repo = "" -`) - - err = helpers.WriteToDisk(filepath.Join(inpath, "theme.toml"), bytes.NewReader(by), fs.Source) - if err != nil { - return - } - - return nil -} - -func newContentPathSection(path string) (string, string) { - // Forward slashes is used in all examples. Convert if needed. - // Issue #1133 - createpath := filepath.FromSlash(path) - var section string - // assume the first directory is the section (kind) - if strings.Contains(createpath[1:], helpers.FilePathSeparator) { - section = helpers.GuessSection(createpath) - } - - return createpath, section -} - -func createConfig(fs *hugofs.Fs, inpath string, kind string) (err error) { - in := map[string]string{ - "baseURL": "http://example.org/", - "title": "My New Hugo Site", - "languageCode": "en-us", - } - kind = parser.FormatSanitize(kind) - - var buf bytes.Buffer - err = parser.InterfaceToConfig(in, parser.FormatToLeadRune(kind), &buf) - if err != nil { - return err - } - - return helpers.WriteToDisk(filepath.Join(inpath, "config."+kind), &buf, fs.Source) -} diff --git a/commands/new_test.go b/commands/new_test.go deleted file mode 100644 index c9adb83d4..000000000 --- a/commands/new_test.go +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright 2016 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package commands - -import ( - "path/filepath" - "testing" - - "github.com/gohugoio/hugo/hugofs" - "github.com/spf13/viper" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// Issue #1133 -func TestNewContentPathSectionWithForwardSlashes(t *testing.T) { - p, s := newContentPathSection("/post/new.md") - assert.Equal(t, filepath.FromSlash("/post/new.md"), p) - assert.Equal(t, "post", s) -} - -func checkNewSiteInited(fs *hugofs.Fs, basepath string, t *testing.T) { - - paths := []string{ - filepath.Join(basepath, "layouts"), - filepath.Join(basepath, "content"), - filepath.Join(basepath, "archetypes"), - filepath.Join(basepath, "static"), - filepath.Join(basepath, "data"), - filepath.Join(basepath, "config.toml"), - } - - for _, path := range paths { - _, err := fs.Source.Stat(path) - require.NoError(t, err) - } -} - -func TestDoNewSite(t *testing.T) { - basepath := filepath.Join("base", "blog") - _, fs := newTestCfg() - - require.NoError(t, doNewSite(fs, basepath, false)) - - checkNewSiteInited(fs, basepath, t) -} - -func TestDoNewSite_noerror_base_exists_but_empty(t *testing.T) { - basepath := filepath.Join("base", "blog") - _, fs := newTestCfg() - - require.NoError(t, fs.Source.MkdirAll(basepath, 777)) - - require.NoError(t, doNewSite(fs, basepath, false)) -} - -func TestDoNewSite_error_base_exists(t *testing.T) { - basepath := filepath.Join("base", "blog") - _, fs := newTestCfg() - - require.NoError(t, fs.Source.MkdirAll(basepath, 777)) - _, err := fs.Source.Create(filepath.Join(basepath, "foo")) - require.NoError(t, err) - // Since the directory already exists and isn't empty, expect an error - require.Error(t, doNewSite(fs, basepath, false)) - -} - -func TestDoNewSite_force_empty_dir(t *testing.T) { - basepath := filepath.Join("base", "blog") - _, fs := newTestCfg() - - require.NoError(t, fs.Source.MkdirAll(basepath, 777)) - - require.NoError(t, doNewSite(fs, basepath, true)) - - checkNewSiteInited(fs, basepath, t) -} - -func TestDoNewSite_error_force_dir_inside_exists(t *testing.T) { - basepath := filepath.Join("base", "blog") - _, fs := newTestCfg() - - contentPath := filepath.Join(basepath, "content") - - require.NoError(t, fs.Source.MkdirAll(contentPath, 777)) - require.Error(t, doNewSite(fs, basepath, true)) -} - -func TestDoNewSite_error_force_config_inside_exists(t *testing.T) { - basepath := filepath.Join("base", "blog") - _, fs := newTestCfg() - - configPath := filepath.Join(basepath, "config.toml") - require.NoError(t, fs.Source.MkdirAll(basepath, 777)) - _, err := fs.Source.Create(configPath) - require.NoError(t, err) - - require.Error(t, doNewSite(fs, basepath, true)) -} - -func newTestCfg() (*viper.Viper, *hugofs.Fs) { - - v := viper.New() - fs := hugofs.NewMem(v) - - v.SetFs(fs.Source) - - return v, fs - -} diff --git a/commands/release.go b/commands/release.go deleted file mode 100644 index 0764685f0..000000000 --- a/commands/release.go +++ /dev/null @@ -1,68 +0,0 @@ -// +build release - -// Copyright 2017-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package commands - -import ( - "errors" - - "github.com/gohugoio/hugo/releaser" - "github.com/spf13/cobra" -) - -func init() { - HugoCmd.AddCommand(createReleaser().cmd) -} - -type releaseCommandeer struct { - cmd *cobra.Command - - version string - - skipPublish bool - try bool - - step int -} - -func createReleaser() *releaseCommandeer { - // Note: This is a command only meant for internal use and must be run - // via "go run -tags release main.go release" on the actual code base that is in the release. - r := &releaseCommandeer{ - cmd: &cobra.Command{ - Use: "release", - Short: "Release a new version of Hugo.", - Hidden: true, - }, - } - - r.cmd.RunE = func(cmd *cobra.Command, args []string) error { - return r.release() - } - - r.cmd.PersistentFlags().StringVarP(&r.version, "rel", "r", "", "new release version, i.e. 0.25.1") - r.cmd.PersistentFlags().IntVarP(&r.step, "step", "s", -1, "release step, defaults to -1 for all steps.") - r.cmd.PersistentFlags().BoolVarP(&r.skipPublish, "skip-publish", "", false, "skip all publishing pipes of the release") - r.cmd.PersistentFlags().BoolVarP(&r.try, "try", "", false, "simulate a release, i.e. no changes") - - return r -} - -func (r *releaseCommandeer) release() error { - if r.version == "" { - return errors.New("must set the --rel flag to the relevant version number") - } - return releaser.New(r.version, r.step, r.skipPublish, r.try).Run() -} diff --git a/commands/server.go b/commands/server.go deleted file mode 100644 index 89d5c239e..000000000 --- a/commands/server.go +++ /dev/null @@ -1,315 +0,0 @@ -// Copyright 2016 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package commands - -import ( - "fmt" - "net" - "net/http" - "net/url" - "os" - "runtime" - "strconv" - "strings" - "time" - - "github.com/gohugoio/hugo/config" - "github.com/gohugoio/hugo/helpers" - "github.com/spf13/afero" - "github.com/spf13/cobra" - jww "github.com/spf13/jwalterweatherman" -) - -var ( - disableLiveReload bool - navigateToChanged bool - renderToDisk bool - serverAppend bool - serverInterface string - serverPort int - serverWatch bool -) - -var serverCmd = &cobra.Command{ - Use: "server", - Aliases: []string{"serve"}, - Short: "A high performance webserver", - Long: `Hugo provides its own webserver which builds and serves the site. -While hugo server is high performance, it is a webserver with limited options. -Many run it in production, but the standard behavior is for people to use it -in development and use a more full featured server such as Nginx or Caddy. - -'hugo server' will avoid writing the rendered and served content to disk, -preferring to store it in memory. - -By default hugo will also watch your files for any changes you make and -automatically rebuild the site. It will then live reload any open browser pages -and push the latest content to them. As most Hugo sites are built in a fraction -of a second, you will be able to save and see your changes nearly instantly.`, - //RunE: server, -} - -type filesOnlyFs struct { - fs http.FileSystem -} - -type noDirFile struct { - http.File -} - -func (fs filesOnlyFs) Open(name string) (http.File, error) { - f, err := fs.fs.Open(name) - if err != nil { - return nil, err - } - return noDirFile{f}, nil -} - -func (f noDirFile) Readdir(count int) ([]os.FileInfo, error) { - return nil, nil -} - -func init() { - initHugoBuilderFlags(serverCmd) - - serverCmd.Flags().IntVarP(&serverPort, "port", "p", 1313, "port on which the server will listen") - serverCmd.Flags().StringVarP(&serverInterface, "bind", "", "127.0.0.1", "interface to which the server will bind") - serverCmd.Flags().BoolVarP(&serverWatch, "watch", "w", true, "watch filesystem for changes and recreate as needed") - serverCmd.Flags().BoolVarP(&serverAppend, "appendPort", "", true, "append port to baseURL") - serverCmd.Flags().BoolVar(&disableLiveReload, "disableLiveReload", false, "watch without enabling live browser reload on rebuild") - serverCmd.Flags().BoolVar(&navigateToChanged, "navigateToChanged", false, "navigate to changed content file on live browser reload") - serverCmd.Flags().BoolVar(&renderToDisk, "renderToDisk", false, "render to Destination path (default is render to memory & serve from there)") - serverCmd.Flags().String("memstats", "", "log memory usage to this file") - serverCmd.Flags().String("meminterval", "100ms", "interval to poll memory usage (requires --memstats), valid time units are \"ns\", \"us\" (or \"µs\"), \"ms\", \"s\", \"m\", \"h\".") - - serverCmd.RunE = server - -} - -func server(cmd *cobra.Command, args []string) error { - cfg, err := InitializeConfig(serverCmd) - if err != nil { - return err - } - - c, err := newCommandeer(cfg) - if err != nil { - return err - } - - if cmd.Flags().Changed("disableLiveReload") { - c.Set("disableLiveReload", disableLiveReload) - } - - if cmd.Flags().Changed("navigateToChanged") { - c.Set("navigateToChanged", navigateToChanged) - } - - if serverWatch { - c.Set("watch", true) - } - - if c.Cfg.GetBool("watch") { - serverWatch = true - c.watchConfig() - } - - l, err := net.Listen("tcp", net.JoinHostPort(serverInterface, strconv.Itoa(serverPort))) - if err == nil { - l.Close() - } else { - if serverCmd.Flags().Changed("port") { - // port set explicitly by user -- he/she probably meant it! - return newSystemErrorF("Server startup failed: %s", err) - } - jww.ERROR.Println("port", serverPort, "already in use, attempting to use an available port") - sp, err := helpers.FindAvailablePort() - if err != nil { - return newSystemError("Unable to find alternative port to use:", err) - } - serverPort = sp.Port - } - - c.Set("port", serverPort) - - baseURL, err = fixURL(c.Cfg, baseURL) - if err != nil { - return err - } - c.Set("baseURL", baseURL) - - if err := memStats(); err != nil { - jww.ERROR.Println("memstats error:", err) - } - - // If a Destination is provided via flag write to disk - if destination != "" { - renderToDisk = true - } - - // Hugo writes the output to memory instead of the disk - if !renderToDisk { - cfg.Fs.Destination = new(afero.MemMapFs) - // Rendering to memoryFS, publish to Root regardless of publishDir. - c.Set("publishDir", "/") - } - - if err := c.build(serverWatch); err != nil { - return err - } - - for _, s := range Hugo.Sites { - s.RegisterMediaTypes() - } - - // Watch runs its own server as part of the routine - if serverWatch { - watchDirs := c.getDirList() - baseWatchDir := c.Cfg.GetString("workingDir") - for i, dir := range watchDirs { - watchDirs[i], _ = helpers.GetRelativePath(dir, baseWatchDir) - } - - rootWatchDirs := strings.Join(helpers.UniqueStrings(helpers.ExtractRootPaths(watchDirs)), ",") - - jww.FEEDBACK.Printf("Watching for changes in %s%s{%s}\n", baseWatchDir, helpers.FilePathSeparator, rootWatchDirs) - err := c.newWatcher(serverPort) - - if err != nil { - return err - } - } - - c.serve(serverPort) - - return nil -} - -func (c *commandeer) serve(port int) { - if renderToDisk { - jww.FEEDBACK.Println("Serving pages from " + c.PathSpec().AbsPathify(c.Cfg.GetString("publishDir"))) - } else { - jww.FEEDBACK.Println("Serving pages from memory") - } - - httpFs := afero.NewHttpFs(c.Fs.Destination) - fs := filesOnlyFs{httpFs.Dir(c.PathSpec().AbsPathify(c.Cfg.GetString("publishDir")))} - fileserver := http.FileServer(fs) - - // We're only interested in the path - u, err := url.Parse(c.Cfg.GetString("baseURL")) - if err != nil { - jww.ERROR.Fatalf("Invalid baseURL: %s", err) - } - if u.Path == "" || u.Path == "/" { - http.Handle("/", fileserver) - } else { - http.Handle(u.Path, http.StripPrefix(u.Path, fileserver)) - } - - jww.FEEDBACK.Printf("Web Server is available at %s (bind address %s)\n", u.String(), serverInterface) - jww.FEEDBACK.Println("Press Ctrl+C to stop") - - endpoint := net.JoinHostPort(serverInterface, strconv.Itoa(port)) - err = http.ListenAndServe(endpoint, nil) - if err != nil { - jww.ERROR.Printf("Error: %s\n", err.Error()) - os.Exit(1) - } -} - -// fixURL massages the baseURL into a form needed for serving -// all pages correctly. -func fixURL(cfg config.Provider, s string) (string, error) { - useLocalhost := false - if s == "" { - s = cfg.GetString("baseURL") - useLocalhost = true - } - - if !strings.HasSuffix(s, "/") { - s = s + "/" - } - - // do an initial parse of the input string - u, err := url.Parse(s) - if err != nil { - return "", err - } - - // if no Host is defined, then assume that no schema or double-slash were - // present in the url. Add a double-slash and make a best effort attempt. - if u.Host == "" && s != "/" { - s = "//" + s - - u, err = url.Parse(s) - if err != nil { - return "", err - } - } - - if useLocalhost { - if u.Scheme == "https" { - u.Scheme = "http" - } - u.Host = "localhost" - } - - if serverAppend { - if strings.Contains(u.Host, ":") { - u.Host, _, err = net.SplitHostPort(u.Host) - if err != nil { - return "", fmt.Errorf("Failed to split baseURL hostpost: %s", err) - } - } - u.Host += fmt.Sprintf(":%d", serverPort) - } - - return u.String(), nil -} - -func memStats() error { - memstats := serverCmd.Flags().Lookup("memstats").Value.String() - if memstats != "" { - interval, err := time.ParseDuration(serverCmd.Flags().Lookup("meminterval").Value.String()) - if err != nil { - interval, _ = time.ParseDuration("100ms") - } - - fileMemStats, err := os.Create(memstats) - if err != nil { - return err - } - - fileMemStats.WriteString("# Time\tHeapSys\tHeapAlloc\tHeapIdle\tHeapReleased\n") - - go func() { - var stats runtime.MemStats - - start := time.Now().UnixNano() - - for { - runtime.ReadMemStats(&stats) - if fileMemStats != nil { - fileMemStats.WriteString(fmt.Sprintf("%d\t%d\t%d\t%d\t%d\n", - (time.Now().UnixNano()-start)/1000000, stats.HeapSys, stats.HeapAlloc, stats.HeapIdle, stats.HeapReleased)) - time.Sleep(interval) - } else { - break - } - } - }() - } - return nil -} diff --git a/commands/server_test.go b/commands/server_test.go deleted file mode 100644 index 3f1518aaa..000000000 --- a/commands/server_test.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package commands - -import ( - "testing" - - "github.com/spf13/viper" -) - -func TestFixURL(t *testing.T) { - type data struct { - TestName string - CLIBaseURL string - CfgBaseURL string - AppendPort bool - Port int - Result string - } - tests := []data{ - {"Basic http localhost", "", "http://foo.com", true, 1313, "http://localhost:1313/"}, - {"Basic https production, http localhost", "", "https://foo.com", true, 1313, "http://localhost:1313/"}, - {"Basic subdir", "", "http://foo.com/bar", true, 1313, "http://localhost:1313/bar/"}, - {"Basic production", "http://foo.com", "http://foo.com", false, 80, "http://foo.com/"}, - {"Production subdir", "http://foo.com/bar", "http://foo.com/bar", false, 80, "http://foo.com/bar/"}, - {"No http", "", "foo.com", true, 1313, "//localhost:1313/"}, - {"Override configured port", "", "foo.com:2020", true, 1313, "//localhost:1313/"}, - {"No http production", "foo.com", "foo.com", false, 80, "//foo.com/"}, - {"No http production with port", "foo.com", "foo.com", true, 2020, "//foo.com:2020/"}, - {"No config", "", "", true, 1313, "//localhost:1313/"}, - } - - for i, test := range tests { - v := viper.New() - baseURL = test.CLIBaseURL - v.Set("baseURL", test.CfgBaseURL) - serverAppend = test.AppendPort - serverPort = test.Port - result, err := fixURL(v, baseURL) - if err != nil { - t.Errorf("Test #%d %s: unexpected error %s", i, test.TestName, err) - } - if result != test.Result { - t.Errorf("Test #%d %s: expected %q, got %q", i, test.TestName, test.Result, result) - } - } -} diff --git a/commands/undraft.go b/commands/undraft.go deleted file mode 100644 index 8d4bffb93..000000000 --- a/commands/undraft.go +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package commands - -import ( - "bytes" - "errors" - "os" - "time" - - "github.com/gohugoio/hugo/parser" - "github.com/spf13/cobra" -) - -var undraftCmd = &cobra.Command{ - Use: "undraft path/to/content", - Short: "Undraft resets the content's draft status", - Long: `Undraft resets the content's draft status -and updates the date to the current date and time. -If the content's draft status is 'False', nothing is done.`, - RunE: Undraft, -} - -// Undraft publishes the specified content by setting its draft status -// to false and setting its publish date to now. If the specified content is -// not a draft, it will log an error. -func Undraft(cmd *cobra.Command, args []string) error { - cfg, err := InitializeConfig() - - if err != nil { - return err - } - - if len(args) < 1 { - return newUserError("a piece of content needs to be specified") - } - - location := args[0] - // open the file - f, err := cfg.Fs.Source.Open(location) - if err != nil { - return err - } - - // get the page from file - p, err := parser.ReadFrom(f) - f.Close() - if err != nil { - return err - } - - w, err := undraftContent(p) - if err != nil { - return newSystemErrorF("an error occurred while undrafting %q: %s", location, err) - } - - f, err = cfg.Fs.Source.OpenFile(location, os.O_WRONLY|os.O_TRUNC, 0644) - if err != nil { - return newSystemErrorF("%q not be undrafted due to error opening file to save changes: %q\n", location, err) - } - defer f.Close() - _, err = w.WriteTo(f) - if err != nil { - return newSystemErrorF("%q not be undrafted due to save error: %q\n", location, err) - } - return nil -} - -// undraftContent: if the content is a draft, change its draft status to -// 'false' and set the date to time.Now(). If the draft status is already -// 'false', don't do anything. -func undraftContent(p parser.Page) (bytes.Buffer, error) { - var buff bytes.Buffer - // get the metadata; easiest way to see if it's a draft - meta, err := p.Metadata() - if err != nil { - return buff, err - } - // since the metadata was obtainable, we can also get the key/value separator for - // Front Matter - fm := p.FrontMatter() - if fm == nil { - return buff, errors.New("Front Matter was found, nothing was finalized") - } - - var isDraft, gotDate bool - var date string -L: - for k, v := range meta.(map[string]interface{}) { - switch k { - case "draft": - if !v.(bool) { - return buff, errors.New("not a Draft: nothing was done") - } - isDraft = true - if gotDate { - break L - } - case "date": - date = v.(string) // capture the value to make replacement easier - gotDate = true - if isDraft { - break L - } - } - } - - // if draft wasn't found in FrontMatter, it isn't a draft. - if !isDraft { - return buff, errors.New("not a Draft: nothing was done") - } - - // get the front matter as bytes and split it into lines - var lineEnding []byte - fmLines := bytes.Split(fm, []byte("\n")) - if len(fmLines) == 1 { // if the result is only 1 element, try to split on dos line endings - fmLines = bytes.Split(fm, []byte("\r\n")) - if len(fmLines) == 1 { - return buff, errors.New("unable to split FrontMatter into lines") - } - lineEnding = append(lineEnding, []byte("\r\n")...) - } else { - lineEnding = append(lineEnding, []byte("\n")...) - } - - // Write the front matter lines to the buffer, replacing as necessary - for _, v := range fmLines { - pos := bytes.Index(v, []byte("draft")) - if pos != -1 { - continue - } - pos = bytes.Index(v, []byte("date")) - if pos != -1 { // if date field wasn't found, add it - v = bytes.Replace(v, []byte(date), []byte(time.Now().Format(time.RFC3339)), 1) - } - buff.Write(v) - buff.Write(lineEnding) - } - - // append the actual content - buff.Write(p.Content()) - - return buff, nil -} diff --git a/commands/undraft_test.go b/commands/undraft_test.go deleted file mode 100644 index 259e3479b..000000000 --- a/commands/undraft_test.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package commands - -// TODO Support Mac Encoding (\r) - -import ( - "bytes" - "strings" - "testing" - "time" - - "github.com/gohugoio/hugo/parser" -) - -var ( - jsonFM = "{\n \"date\": \"12-04-06\",\n \"title\": \"test json\"\n}" - jsonDraftFM = "{\n \"draft\": true,\n \"date\": \"12-04-06\",\n \"title\":\"test json\"\n}" - tomlFM = "+++\n date= \"12-04-06\"\n title= \"test toml\"\n+++" - tomlDraftFM = "+++\n draft= true\n date= \"12-04-06\"\n title=\"test toml\"\n+++" - yamlFM = "---\n date: \"12-04-06\"\n title: \"test yaml\"\n---" - yamlDraftFM = "---\n draft: true\n date: \"12-04-06\"\n title: \"test yaml\"\n---" - yamlYesDraftFM = "---\n draft: yes\n date: \"12-04-06\"\n title: \"test yaml\"\n---" -) - -func TestUndraftContent(t *testing.T) { - tests := []struct { - fm string - expectedErr string - }{ - {jsonFM, "not a Draft: nothing was done"}, - {jsonDraftFM, ""}, - {tomlFM, "not a Draft: nothing was done"}, - {tomlDraftFM, ""}, - {yamlFM, "not a Draft: nothing was done"}, - {yamlDraftFM, ""}, - {yamlYesDraftFM, ""}, - } - - for i, test := range tests { - r := bytes.NewReader([]byte(test.fm)) - p, _ := parser.ReadFrom(r) - res, err := undraftContent(p) - if test.expectedErr != "" { - if err == nil { - t.Errorf("[%d] Expected error, got none", i) - continue - } - if err.Error() != test.expectedErr { - t.Errorf("[%d] Expected %q, got %q", i, test.expectedErr, err) - continue - } - } else { - r = bytes.NewReader(res.Bytes()) - p, _ = parser.ReadFrom(r) - meta, err := p.Metadata() - if err != nil { - t.Errorf("[%d] unexpected error %q", i, err) - continue - } - for k, v := range meta.(map[string]interface{}) { - if k == "draft" { - if v.(bool) { - t.Errorf("[%d] Expected %q to be \"false\", got \"true\"", i, k) - continue - } - } - if k == "date" { - if !strings.HasPrefix(v.(string), time.Now().Format("2006-01-02")) { - t.Errorf("[%d] Expected %v to start with %v", i, v.(string), time.Now().Format("2006-01-02")) - } - } - } - } - } -} diff --git a/commands/version.go b/commands/version.go deleted file mode 100644 index 5cd398b2b..000000000 --- a/commands/version.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package commands - -import ( - "os" - "path/filepath" - "runtime" - "strings" - "time" - - "github.com/gohugoio/hugo/helpers" - "github.com/gohugoio/hugo/hugolib" - "github.com/kardianos/osext" - "github.com/spf13/cobra" - jww "github.com/spf13/jwalterweatherman" -) - -var versionCmd = &cobra.Command{ - Use: "version", - Short: "Print the version number of Hugo", - Long: `All software has versions. This is Hugo's.`, - RunE: func(cmd *cobra.Command, args []string) error { - printHugoVersion() - return nil - }, -} - -func printHugoVersion() { - if hugolib.BuildDate == "" { - setBuildDate() // set the build date from executable's mdate - } else { - formatBuildDate() // format the compile time - } - if hugolib.CommitHash == "" { - jww.FEEDBACK.Printf("Hugo Static Site Generator v%s %s/%s BuildDate: %s\n", helpers.CurrentHugoVersion, runtime.GOOS, runtime.GOARCH, hugolib.BuildDate) - } else { - jww.FEEDBACK.Printf("Hugo Static Site Generator v%s-%s %s/%s BuildDate: %s\n", helpers.CurrentHugoVersion, strings.ToUpper(hugolib.CommitHash), runtime.GOOS, runtime.GOARCH, hugolib.BuildDate) - } -} - -// setBuildDate checks the ModTime of the Hugo executable and returns it as a -// formatted string. This assumes that the executable name is Hugo, if it does -// not exist, an empty string will be returned. This is only called if the -// hugolib.BuildDate wasn't set during compile time. -// -// osext is used for cross-platform. -func setBuildDate() { - fname, _ := osext.Executable() - dir, err := filepath.Abs(filepath.Dir(fname)) - if err != nil { - jww.ERROR.Println(err) - return - } - fi, err := os.Lstat(filepath.Join(dir, filepath.Base(fname))) - if err != nil { - jww.ERROR.Println(err) - return - } - t := fi.ModTime() - hugolib.BuildDate = t.Format(time.RFC3339) -} - -// formatBuildDate formats the hugolib.BuildDate according to the value in -// .Params.DateFormat, if it's set. -func formatBuildDate() { - t, _ := time.Parse("2006-01-02T15:04:05-0700", hugolib.BuildDate) - hugolib.BuildDate = t.Format(time.RFC3339) -} diff --git a/config.toml b/config.toml new file mode 100644 index 000000000..4ed86586a --- /dev/null +++ b/config.toml @@ -0,0 +1,236 @@ +baseURL = "https://gohugo.io/" +paginate = 100 +defaultContentLanguage = "en" +enableEmoji = true +# Set the unicode character used for the "return" link in page footnotes. +footnotereturnlinkcontents = "↩" +languageCode = "en-us" +metaDataFormat = "yaml" +title = "Hugo" +theme = "gohugoioTheme" + +googleAnalytics = "UA-7131036-4" + +pluralizeListTitles = false + +# We do redirects via Netlify's _redirects file, generated by Hugo (see "outputs" below). +disableAliases = true + + +# Highlighting config (Pygments) +# It is (currently) not in use, but you can do ```go in a content file if you want to. +pygmentsCodeFences = true + +# See https://help.farbox.com/pygments.html +pygmentsStyle = "friendly" + +[outputs] +home = [ "HTML", "RSS", "REDIR", "HEADERS" ] +section = [ "HTML", "RSS"] + +[mediaTypes] +[mediaTypes."text/netlify"] +suffix = "" +delimiter = "" + +[outputFormats] +[outputFormats.REDIR] +mediatype = "text/netlify" +baseName = "_redirects" +isPlainText = true +notAlternative = true +[outputFormats.HEADERS] +mediatype = "text/netlify" +baseName = "_headers" +isPlainText = true +notAlternative = true + +[social] +twitter = "GoHugoIO" + +#CUSTOM PARAMS +[params] + description = "The world’s fastest framework for building websites" + ## Used for views in rendered HTML (i.e., rather than using the .Hugo variable) + release = "0.26" + ## Setting this to true will add a "noindex" to *EVERY* page on the site + removefromexternalsearch = false + ## Gh repo for site footer (include trailing slash) + ghrepo = "https://github.com/gohugoio/hugoDocs/" + ## GH Repo for filing a new issue + github_repo = "https://github.com/gohugoio/hugo/issues/new" + ### Edit content repo (set to automatically enter "edit" mode; this is good for "improve this page" links) + ghdocsrepo = "https://github.com/gohugoio/hugoDocs/tree/master/docs" + ## Gitter URL + gitter = "https://gitter.im/spf13/hugo" + ## Discuss Forum URL + forum = "https://discourse.gohugo.io/" + ## Google Tag Manager + gtmid = "" + + # First one is picked as the Twitter card image if not set on page. + images = ["images/gohugoio-card.png"] + + flex_box_interior_classes = "flex-auto w-100 w-40-l mr3 mb3 bg-white ba b--moon-gray nested-copy-line-height" + + #sidebar_direction = "sidebar_left" + +# MARKDOWN +## Configuration for BlackFriday markdown parser: https://github.com/russross/blackfriday +[blackfriday] + plainIDAnchors = true + hrefTargetBlank = true + angledQuotes = false + latexDashes = true + +## As of v0.20, all content files include a default "categories" value that's the same as the section. This was a cheap future-proofing method and should/could be changed accordingly. +[taxonomies] + category = "categories" + +# High level items + +[[menu.docs]] + name = "About Hugo" + weight = 1 + identifier = "about" + url = "/about/" + +[[menu.docs]] + name = "Getting Started" + weight = 5 + identifier = "getting-started" + url = "/getting-started/" + + +[[menu.docs]] + name = "Themes" + weight = 15 + identifier = "themes" + post = "break" + url = "/themes/" + +# Core Menus + +[[menu.docs]] + name = "Content Management" + weight = 20 + identifier = "content-management" + post = "expanded" + url = "/content-management/" + +[[menu.docs]] + name = "Templates" + weight = 25 + identifier = "templates" + + url = "/templates/" + +[[menu.docs]] + name = "Functions" + weight = 30 + identifier = "functions" + url = "/functions/" + +[[menu.docs]] + name = "Variables" + weight = 35 + identifier = "variables" + url = "/variables/" + +[[menu.docs]] + name = "CLI" + weight = 40 + post = "break" + identifier = "commands" + url = "/commands/" + + + +# LOW LEVEL ITEMS + + +[[menu.docs]] + name = "Troubleshooting" + weight = 60 + identifier = "troubleshooting" + url = "/troubleshooting/" + +[[menu.docs]] + name = "Tools" + weight = 70 + identifier = "tools" + url = "/tools/" + +[[menu.docs]] + name = "Hosting & Deployment" + weight = 80 + identifier = "hosting-and-deployment" + url = "/hosting-and-deployment/" + +[[menu.docs]] + name = "Contribute" + weight = 100 + post = "break" + identifier = "contribute" + url = "/contribute/" + +#[[menu.docs]] +# name = "Tags" +# weight = 120 +# identifier = "tags" +# url = "/tags/" + + +# [[menu.docs]] +# name = "Categories" +# weight = 140 +# identifier = "categories" +# url = "/categories/" + +######## QUICKLINKS + + [[menu.quicklinks]] + name = "Fundamentals" + weight = 1 + identifier = "fundamentals" + url = "/tags/fundamentals/" + + + + +######## GLOBAL ITEMS TO BE SHARED WITH THE HUGO SITES + +[[menu.global]] + name = "News" + weight = 1 + identifier = "news" + url = "/news/" + + [[menu.global]] + name = "Docs" + weight = 5 + identifier = "docs" + url = "/documentation/" + + [[menu.global]] + name = "Themes" + weight = 10 + identifier = "themes" + url = "https://themes.gohugo.io/" + + # Anything with a weight > 100 gets an external icon + [[menu.global]] + name = "Community" + weight = 150 + icon = true + identifier = "community" + post = "external" + url = "https://discourse.gohugo.io/" + + + [[menu.global]] + name = "GitHub" + weight = 200 + identifier = "github" + post = "external" + url = "https://github.com/gohugoio/hugo" diff --git a/config/configProvider.go b/config/configProvider.go deleted file mode 100644 index 870341f7f..000000000 --- a/config/configProvider.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2017-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package config - -// Provider provides the configuration settings for Hugo. -type Provider interface { - GetString(key string) string - GetInt(key string) int - GetBool(key string) bool - GetStringMap(key string) map[string]interface{} - GetStringMapString(key string) map[string]string - Get(key string) interface{} - Set(key string, value interface{}) - IsSet(key string) bool -} diff --git a/content/_index.md b/content/_index.md new file mode 100644 index 000000000..ec8883c44 --- /dev/null +++ b/content/_index.md @@ -0,0 +1,49 @@ +--- +title: "A Fast and Flexible Website Generator" +date: 2017-03-02T12:00:00-05:00 +features: + - heading: Blistering Speed + image_path: /images/icon-fast.svg + tagline: What's modern about waiting for your site to build? + copy: Hugo is the fastest tool of its kind. At <1 ms per page, the average site builds in less than a second. + + - heading: Robust Content Management + image_path: /images/icon-content-management.svg + tagline: Flexibility rules. Hugo is a content strategist's dream. + copy: Hugo supports unlimited content types, taxonomies, menus, dynamic API-driven content, and more, all without plugins. + + - heading: Shortcodes + image_path: /images/icon-shortcodes.svg + tagline: Hugo's shortcodes are Markdown's hidden superpower. + copy: We love the beautiful simplicity of markdown’s syntax, but there are times when we want more flexibility. Hugo shortcodes allow for both beauty and flexibility. + + - heading: Built-in Templates + image_path: /images/icon-built-in-templates.svg + tagline: Hugo has common patterns to get your work done quickly. + copy: Hugo ships with pre-made templates to make quick work of SEO, commenting, analytics and other functions. One line of code, and you're done. + + - heading: Multilingual and i18n + image_path: /images/icon-multilingual2.svg + tagline: Polyglot baked in. + copy: Hugo provides full i18n support for multi-language sites with the same straightforward development experience Hugo users love in single-language sites. + + - heading: Custom Outputs + image_path: /images/icon-custom-outputs.svg + tagline: HTML not enough? + copy: Hugo allows you to output your content in multiple formats, including JSON or AMP, and makes it easy to create your own. +sections: + - heading: "100s of Themes" + cta: Check out the Hugo's themes. + link: http://themes.gohugo.io/ + color_classes: bg-accent-color white + image: /images/homepage-screenshot-hugo-themes.jpg + copy: "Hugo provides a robust theming system that is easy to implement but capable of producing even the most complicated websites." + - heading: "Capable Templating" + cta: Get Started. + link: templates/ + color_classes: bg-primary-color-light black + image: /images/home-page-templating-example.png + copy: "Hugo's Go-based templating provides just the right amount of logic to build anything from the simple to complex. If you prefer Jade/Pug-like syntax, you can also use Amber, Ace, or any combination of the three." +--- + +Hugo is one of the most popular open-source static site generators. With its amazing speed and flexibility, Hugo makes building websites fun again. diff --git a/content/about/_index.md b/content/about/_index.md new file mode 100644 index 000000000..422eb1d05 --- /dev/null +++ b/content/about/_index.md @@ -0,0 +1,20 @@ +--- +title: About Hugo +linktitle: Overview +description: Hugo's features, roadmap, license, and motivation. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +categories: [] +#tags: [] +menu: + docs: + parent: "about" + weight: 1 +weight: 1 +draft: false +aliases: [/about-hugo/,/docs/] +toc: false +--- + +Hugo is not your average static site generator. diff --git a/content/about/benefits.md b/content/about/benefits.md new file mode 100644 index 000000000..87d2f23b5 --- /dev/null +++ b/content/about/benefits.md @@ -0,0 +1,43 @@ +--- +title: The Benefits of Static Site Generators +linktitle: The Benefits of Static +description: Improved performance, security and ease of use are just a few of the reasons static site generators are so appealing. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +#tags: [ssg,static,performance,security] +menu: + docs: + parent: "about" + weight: 30 +weight: 30 +sections_weight: 30 +draft: false +aliases: [] +toc: false +--- + +The purpose of website generators is to render content into HTML files. Most are "dynamic site generators." That means the HTTP server---i.e., the program that sends files to the browser to be viewed---runs the generator to create a new HTML file every time an end user requests a page. + +Over time, dynamic site generators were programmed to cache their HTML files to prevent unnecessary delays in delivering pages to end users. A cached page is a static version of a web page. + +Hugo takes caching a step further and all HTML files are rendered on your computer. You can review the files locally before copying them to the computer hosting the HTTP server. Since the HTML files aren't generated dynamically, we say that Hugo is a *static site generator*. + +This has many benefits. The most noticeable is performance. HTTP servers are *very* good at sending files---so good, in fact, that you can effectively serve the same number of pages with a fraction of the memory and CPU needed for a dynamic site. + +## More on Static Site Generators + +* ["An Introduction to Static Site Generators", David Walsh][] +* ["Hugo vs. Wordpress page load speed comparison: Hugo leaves WordPress in its dust", GettingThingsTech][hugovwordpress] +* ["Static Site Generators", O-Reilly][] +* [StaticGen: Top Open-Source Static Site Generators (GitHub Stars)][] +* ["Top 10 Static Website Generators", Netlify blog][] +* ["The Resurgence of Static", dotCMS][dotcms] + + +["An Introduction to Static Site Generators", David Walsh]: https://davidwalsh.name/introduction-static-site-generators +["Static Site Generators", O-Reilly]: /documents/oreilly-static-site-generators.pdf +["Top 10 Static Website Generators", Netlify blog]: https://www.netlify.com/blog/2016/05/02/top-ten-static-website-generators/ +[hugovwordpress]: https://gettingthingstech.com/hugo-vs.-wordpress-page-load-speed-comparison-hugo-leaves-wordpress-in-its-dust/ +[StaticGen: Top Open-Source Static Site Generators (GitHub Stars)]: https://www.staticgen.com/ +[dotcms]: https://dotcms.com/blog/post/the-resurgence-of-static diff --git a/content/about/features.md b/content/about/features.md new file mode 100644 index 000000000..f3f490cba --- /dev/null +++ b/content/about/features.md @@ -0,0 +1,90 @@ +--- +title: Hugo Features +linktitle: Hugo Features +description: Hugo boasts blistering speed, robust content management, and a powerful templating language making it a great fit for all kinds of static websites. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +menu: + docs: + parent: "about" + weight: 20 +weight: 20 +sections_weight: 20 +draft: false +aliases: [/about/features] +toc: true +--- + +## General + +* [Extremely fast][] build times (< 1 ms per page) +* Completely cross platform, with [easy installation][install] on macOS, Linux, Windows, and more +* Renders changes on the fly with [LiveReload][] as you develop +* [Powerful theming][] +* [Host your site anywhere][hostanywhere] + +## Organization + +* Straightforward [organization for your projects][], including website sections +* Customizable [URLs][] +* Support for configurable [taxonomies][], including categories and tags +* [Sort content][] as you desire through powerful template [functions][] +* Automatic [table of contents][] generation +* [Dynamic menu][] creation +* [Pretty URLs][] support +* [Permalink][] pattern support +* Redirects via [aliases][] + +## Content + +* Native Markdown and Emacs Org-Mode support, as well as other languages via *external helpers* (see [supported formats][]) +* TOML, YAML, and JSON metadata support in [front matter][] +* Customizable [homepage][] +* Multiple [content types][] +* Automatic and user defined [content summaries][] +* [Shortcodes][] to enable rich content inside of Markdown +* ["Minutes to Read"][pagevars] functionality +* ["Wordcount"][pagevars] functionality + +## Additional Features + +* Integrated [Disqus][] comment support +* Integrated [Google Analytics][] support +* Automatic [RSS][] creation +* Support for [Go][], [Amber], and [Ace][] HTML templates +* [Syntax highlighting][] powered by [Pygments][] + +See what's coming next in the [Hugo roadmap][]. + +[Ace]: /templates/alternatives/ +[aliases]: /content-management/urls/#aliases +[Amber]: https://github.com/eknkc/amber +[content summaries]: /content-management/summaries/ +[content types]: /content-management/types/ +[Disqus]: https://disqus.com/ +[Dynamic menu]: /templates/menus/ +[Extremely fast]: https://github.com/bep/hugo-benchmark +[front matter]: /content-management/front-matter/ +[functions]: /functions/ +[Go]: http://golang.org/pkg/html/template/ +[Google Analytics]: https://google-analytics.com/ +[homepage]: /templates/homepage/ +[hostanywhere]: /hosting-and-deployment/ +[Hugo roadmap]: /about/roadmap +[install]: /getting-started/installing/ +[LiveReload]: /getting-started/usage/ +[organization for your projects]: /getting-started/directory-structure/ +[pagevars]: /variables/page/ +[Permalink]: /content-management/urls/#permalinks +[Powerful theming]: /themes/ +[Pretty URLs]: /content-management/urls/ +[Pygments]: http://pygments.org/ +[RSS]: /templates/rss/ +[Shortcodes]: /content-management/shortcodes/ +[sort content]: /templates/ +[supported formats]: /content-management/formats/ +[Syntax highlighting]: /tools/syntax-highlighting/ +[table of contents]: /content-management/toc/ +[taxonomies]: /content-management/taxonomies/ +[URLs]: /content-management/urls/ diff --git a/content/about/license.md b/content/about/license.md new file mode 100644 index 000000000..7575b79c8 --- /dev/null +++ b/content/about/license.md @@ -0,0 +1,165 @@ +--- +title: Apache License +linktitle: License +description: Hugo v0.15 and later are released under the Apache 2.0 license. +date: 2016-02-01 +publishdate: 2016-02-01 +lastmod: 2016-03-02 +categories: ["about hugo"] +#tags: ["License","apache"] +menu: + docs: + parent: "about" + weight: 60 +weight: 60 +sections_weight: 60 +aliases: [/meta/license] +toc: true +--- + +{{% note %}} +Hugo v0.15 and later are released under the Apache 2.0 license. +Earlier versions of Hugo were released under the [Simple Public License](https://opensource.org/licenses/Simple-2.0). +{{% /note %}} + +_Version 2.0, January 2004_
+ + +*Terms and Conditions for use, reproduction, and distribution* + +## 1. Definitions + +“License” shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +“Licensor” shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +“Legal Entity” shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, “control” means **(i)** the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the +outstanding shares, or **(iii)** beneficial ownership of such entity. + +“You” (or “Your”) shall mean an individual or Legal Entity exercising +permissions granted by this License. + +“Source” form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +“Object” form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +“Work” shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +“Derivative Works” shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +“Contribution” shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +“submitted” means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as “Not a Contribution.” + +“Contributor” shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +## 2. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +## 3. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +## 4. Redistribution + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* **(a)** You must give any other recipients of the Work or Derivative Works a copy of +this License; and +* **(b)** You must cause any modified files to carry prominent notices stating that You +changed the files; and +* **\(c)** You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + +You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +## 5. Submission of Contributions + +Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +## 6. Trademarks + +This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +## 7. Disclaimer of Warranty + +Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +## 8. Limitation of Liability + +In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +## 9. Accepting Warranty or Additional Liability + +While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +_END OF TERMS AND CONDITIONS_ + +## APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets `[]` replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same “printed page” as the copyright notice for easier identification within third-party archives. + +{{< code file="apache-notice.txt" download="apache-notice.txt" >}} +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +{{< /code >}} diff --git a/content/about/roadmap.md b/content/about/roadmap.md new file mode 100644 index 000000000..b69126a89 --- /dev/null +++ b/content/about/roadmap.md @@ -0,0 +1,51 @@ +--- +title: Roadmap +linktitle: Roadmap +description: Take a look at what's in the pipeline for future versions of the Hugo project. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +categories: [about hugo] +#tags: [about,contribute,roadmap] +menu: + docs: + parent: "about" + weight: 50 +weight: 50 +sections_weight: 50 +draft: false +aliases: [/meta/roadmap] +toc: false +--- + +To track Hugo's progress, see our [GitHub Milestones][milestones]. + +In no particular order, here are some other features currently being worked on: + +* Even easier deployment to S3, SSH, GitHub, rsync. Give the [hosting and deployment][] section a shot. +* Import from other website systems. There are already [existing migration tools][migrate], but they don’t cover all major platforms. +* An interactive web-based editor (See the [related forum thread][]) +* Additional [themes][], which are always ongoing and [contributions are welcome][themescontrib]! +* Dynamic image resizing via shortcodes ({{< gh 1014 >}}) +* Native support for additional content formats (AsciiDoc {{< gh 1435>}}, reST {{< gh 1436 >}}) +* And, last but not least, [***your*** best ideas!][] + +## Contributions Welcome + +Feel free to [contribute to Hugo's development][devcontribute], [improve Hugo's documentation][doccontribute], or [open a new issue][newissue] if you have an idea for a new feature. + +[#98]: https://github.com/gohugoio/hugo/issues/98 +[#1014]: https://github.com/gohugoio/hugo/issues/1014 +[#1435]: https://github.com/gohugoio/hugo/issues/1435 +[#1436]: https://github.com/gohugoio/hugo/issues/1436 +[devcontribute]: /contribute/development/ +[doccontribute]: /contribute/documentation/ +[hosting and deployment]: /hosting-and-deployment/ +[migrate]: /tools/migrations/ +[milestones]: https://github.com/gohugoio/hugo/milestone/14 +[newissue]: https://github.com/gohugoio/hugo/issues/ +[related forum thread]: https://discourse.gohugo.io/t/web-based-editor/155 +[themes]: /themes/ +[themescontrib]: /contribute/themes/ +[tutorials]: /tutorials +[***your*** best ideas!]: /contribute/ diff --git a/content/about/what-is-hugo.md b/content/about/what-is-hugo.md new file mode 100644 index 000000000..c61fa2d40 --- /dev/null +++ b/content/about/what-is-hugo.md @@ -0,0 +1,69 @@ +--- +title: What is Hugo +linktitle: What is Hugo +description: Hugo is a fast and modern static site generator written in Go, and designed to make website creation fun again. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +layout: single +menu: + main: + parent: "about" + weight: 10 +weight: 10 +sections_weight: 10 +draft: false +aliases: [/overview/introduction/,/about/why-i-built-hugo/] +toc: true +--- + +Hugo is a general-purpose website framework. Technically speaking, Hugo is a [static site generator][]. Unlike systems that dynamically build a page with each visitor request, Hugo builds pages when you create or update your content. Since websites are viewed far more often than they are edited, Hugo is designed to provide an optimal viewing experience for your website's end users and an ideal writing experience for website authors. + +Websites built with Hugo are extremely fast and secure. Hugo sites can be hosted anywhere, including [Netlify][], [Heroku][], [GoDaddy][], [DreamHost][], [GitHub Pages][], [Surge][], [Aerobatic][], [Firebase][], [Google Cloud Storage][], [Amazon S3][], [Rackspace][], [Azure][], and [CloudFront][] and work well with CDNs. Hugo sites run without the need for a database or dependencies on expensive runtimes like Ruby, Python, or PHP. + +We think of Hugo as the ideal website creation tool with nearly instant build times, able to rebuild whenever a change is made. + +## How Fast is Hugo? + +{{< youtube "CdiDYZ51a2o" >}} + +## What Does Hugo Do? + +In technical terms, Hugo takes a source directory of files and templates and uses these as input to create a complete website. + +## Who Should Use Hugo? + +Hugo is for people that prefer writing in a text editor over a browser. + +Hugo is for people who want to hand code their own website without worrying about setting up complicated runtimes, dependencies and databases. + +Hugo is for people building a blog, a company site, a portfolio site, documentation, a single landing page, or a website with thousands of pages. + + + +[@spf13]: https://twitter.com/@spf13 +[Aerobatic]: https://www.aerobatic.com/ +[Amazon S3]: http://aws.amazon.com/s3/ +[Azure]: https://blogs.msdn.microsoft.com/acoat/2016/01/28/publish-a-static-web-site-using-azure-web-apps/ +[CloudFront]: http://aws.amazon.com/cloudfront/ "Amazon CloudFront" +[contributing to it]: https://github.com/gohugoio/hugo +[DreamHost]: http://www.dreamhost.com/ +[Firebase]: https://firebase.google.com/docs/hosting/ "Firebase static hosting" +[GitHub Pages]: https://pages.github.com/ +[GitLab]: https://about.gitlab.com +[Go language]: https://golang.org/ +[GoDaddy]: https://www.godaddy.com/ "Godaddy.com Hosting" +[Google Cloud Storage]: http://cloud.google.com/storage/ +[Heroku]: https://www.heroku.com/ +[Jekyll]: http://jekyllrb.com/ +[Jekyll]: https://jekyllrb.com/ +[Middleman]: https://middlemanapp.com/ +[Middleman]: https://middlemanapp.com/ +[Nanoc]: http://nanoc.ws/ +[Nanoc]: https://nanoc.ws/ +[Netlify]: https://netlify.com +[rackspace]: https://www.rackspace.com/cloud/files +[static site generator]: /about/benefits/ +[Rackspace]: https://www.rackspace.com/cloud/files +[static site generator]: /about/benefits/ +[Surge]: https://surge.sh diff --git a/content/commands/_index.md b/content/commands/_index.md new file mode 100644 index 000000000..a4ddd54f3 --- /dev/null +++ b/content/commands/_index.md @@ -0,0 +1,22 @@ +--- +title: Command Line Reference +linktitle: CLI Overview +description: Comprehensive list of Hugo templating functions, including basic and advanced usage examples. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +categories: [commands] +#tags: [cli,command line] +menu: + docs: + parent: "commands" + weight: 1 +weight: 01 #rem +draft: false +aliases: [/cli/] +--- + +The following list contains auto-generated and up-to-date (thanks to [Cobra][]) documentation for all the CLI commands in Hugo. + + +[Cobra]: https://github.com/spf13/cobra diff --git a/content/commands/hugo.md b/content/commands/hugo.md new file mode 100644 index 000000000..1246f78ca --- /dev/null +++ b/content/commands/hugo.md @@ -0,0 +1,80 @@ +--- +date: 2017-07-16T23:23:14+02:00 +title: "hugo" +slug: hugo +url: /commands/hugo/ +--- +## hugo + +hugo builds your site + +### Synopsis + + +hugo is the main command, used to build your Hugo site. + +Hugo is a Fast and Flexible Static Site Generator +built with love by spf13 and friends in Go. + +Complete documentation is available at http://gohugo.io/. + +``` +hugo [flags] +``` + +### Options + +``` + -b, --baseURL string hostname (and path) to the root, e.g. http://spf13.com/ + -D, --buildDrafts include content marked as draft + -E, --buildExpired include expired content + -F, --buildFuture include content with publishdate in the future + --cacheDir string filesystem path to cache directory. Defaults: $TMPDIR/hugo_cache/ + --canonifyURLs if true, all relative URLs will be canonicalized using baseURL + --cleanDestinationDir remove files from destination not found in static directories + --config string config file (default is path/config.yaml|json|toml) + -c, --contentDir string filesystem path to content directory + -d, --destination string filesystem path to write files to + --disable404 do not render 404 page + --disableKinds stringSlice disable different kind of pages (home, RSS etc.) + --disableRSS do not build RSS files + --disableSitemap do not build Sitemap file + --enableGitInfo add Git revision, date and author info to the pages + --forceSyncStatic copy all files when static is changed. + -h, --help help for hugo + --i18n-warnings print missing translations + --ignoreCache ignores the cache directory + -l, --layoutDir string filesystem path to layout directory + --log enable Logging + --logFile string log File path (if set, logging enabled automatically) + --noChmod don't sync permission mode of files + --noTimes don't sync modification time of files + --pluralizeListTitles pluralize titles in lists using inflect (default true) + --preserveTaxonomyNames preserve taxonomy names as written ("Gérard Depardieu" vs "gerard-depardieu") + --quiet build in quiet mode + --renderToMemory render to memory (only useful for benchmark testing) + -s, --source string filesystem path to read files relative from + --stepAnalysis display memory and timing of different steps of the program + -t, --theme string theme to use (located in /themes/THEMENAME/) + --themesDir string filesystem path to themes directory + --uglyURLs if true, use /filename.html instead of /filename/ + -v, --verbose verbose output + --verboseLog verbose logging + -w, --watch watch filesystem for changes and recreate as needed +``` + +### SEE ALSO +* [hugo benchmark](/commands/hugo_benchmark/) - Benchmark Hugo by building a site a number of times. +* [hugo check](/commands/hugo_check/) - Contains some verification checks +* [hugo config](/commands/hugo_config/) - Print the site configuration +* [hugo convert](/commands/hugo_convert/) - Convert your content to different formats +* [hugo env](/commands/hugo_env/) - Print Hugo version and environment info +* [hugo gen](/commands/hugo_gen/) - A collection of several useful generators. +* [hugo import](/commands/hugo_import/) - Import your site from others. +* [hugo list](/commands/hugo_list/) - Listing out various types of content +* [hugo new](/commands/hugo_new/) - Create new content for your site +* [hugo server](/commands/hugo_server/) - A high performance webserver +* [hugo undraft](/commands/hugo_undraft/) - Undraft resets the content's draft status +* [hugo version](/commands/hugo_version/) - Print the version number of Hugo + +###### Auto generated by spf13/cobra on 16-Jul-2017 diff --git a/content/commands/hugo_benchmark.md b/content/commands/hugo_benchmark.md new file mode 100644 index 000000000..2a7f9f8f4 --- /dev/null +++ b/content/commands/hugo_benchmark.md @@ -0,0 +1,72 @@ +--- +date: 2017-07-16T23:23:14+02:00 +title: "hugo benchmark" +slug: hugo_benchmark +url: /commands/hugo_benchmark/ +--- +## hugo benchmark + +Benchmark Hugo by building a site a number of times. + +### Synopsis + + +Hugo can build a site many times over and analyze the running process +creating a benchmark. + +``` +hugo benchmark [flags] +``` + +### Options + +``` + -b, --baseURL string hostname (and path) to the root, e.g. http://spf13.com/ + -D, --buildDrafts include content marked as draft + -E, --buildExpired include expired content + -F, --buildFuture include content with publishdate in the future + --cacheDir string filesystem path to cache directory. Defaults: $TMPDIR/hugo_cache/ + --canonifyURLs if true, all relative URLs will be canonicalized using baseURL + --cleanDestinationDir remove files from destination not found in static directories + -c, --contentDir string filesystem path to content directory + -n, --count int number of times to build the site (default 13) + --cpuprofile string path/filename for the CPU profile file + -d, --destination string filesystem path to write files to + --disable404 do not render 404 page + --disableKinds stringSlice disable different kind of pages (home, RSS etc.) + --disableRSS do not build RSS files + --disableSitemap do not build Sitemap file + --enableGitInfo add Git revision, date and author info to the pages + --forceSyncStatic copy all files when static is changed. + -h, --help help for benchmark + --i18n-warnings print missing translations + --ignoreCache ignores the cache directory + -l, --layoutDir string filesystem path to layout directory + --memprofile string path/filename for the memory profile file + --noChmod don't sync permission mode of files + --noTimes don't sync modification time of files + --pluralizeListTitles pluralize titles in lists using inflect (default true) + --preserveTaxonomyNames preserve taxonomy names as written ("Gérard Depardieu" vs "gerard-depardieu") + --renderToMemory render to memory (only useful for benchmark testing) + -s, --source string filesystem path to read files relative from + --stepAnalysis display memory and timing of different steps of the program + -t, --theme string theme to use (located in /themes/THEMENAME/) + --themesDir string filesystem path to themes directory + --uglyURLs if true, use /filename.html instead of /filename/ +``` + +### Options inherited from parent commands + +``` + --config string config file (default is path/config.yaml|json|toml) + --log enable Logging + --logFile string log File path (if set, logging enabled automatically) + --quiet build in quiet mode + -v, --verbose verbose output + --verboseLog verbose logging +``` + +### SEE ALSO +* [hugo](/commands/hugo/) - hugo builds your site + +###### Auto generated by spf13/cobra on 16-Jul-2017 diff --git a/content/commands/hugo_check.md b/content/commands/hugo_check.md new file mode 100644 index 000000000..b7fb2c843 --- /dev/null +++ b/content/commands/hugo_check.md @@ -0,0 +1,37 @@ +--- +date: 2017-07-16T23:23:14+02:00 +title: "hugo check" +slug: hugo_check +url: /commands/hugo_check/ +--- +## hugo check + +Contains some verification checks + +### Synopsis + + +Contains some verification checks + +### Options + +``` + -h, --help help for check +``` + +### Options inherited from parent commands + +``` + --config string config file (default is path/config.yaml|json|toml) + --log enable Logging + --logFile string log File path (if set, logging enabled automatically) + --quiet build in quiet mode + -v, --verbose verbose output + --verboseLog verbose logging +``` + +### SEE ALSO +* [hugo](/commands/hugo/) - hugo builds your site +* [hugo check ulimit](/commands/hugo_check_ulimit/) - Check system ulimit settings + +###### Auto generated by spf13/cobra on 16-Jul-2017 diff --git a/content/commands/hugo_check_ulimit.md b/content/commands/hugo_check_ulimit.md new file mode 100644 index 000000000..c98c85111 --- /dev/null +++ b/content/commands/hugo_check_ulimit.md @@ -0,0 +1,41 @@ +--- +date: 2017-07-16T23:23:14+02:00 +title: "hugo check ulimit" +slug: hugo_check_ulimit +url: /commands/hugo_check_ulimit/ +--- +## hugo check ulimit + +Check system ulimit settings + +### Synopsis + + +Hugo will inspect the current ulimit settings on the system. +This is primarily to ensure that Hugo can watch enough files on some OSs + +``` +hugo check ulimit [flags] +``` + +### Options + +``` + -h, --help help for ulimit +``` + +### Options inherited from parent commands + +``` + --config string config file (default is path/config.yaml|json|toml) + --log enable Logging + --logFile string log File path (if set, logging enabled automatically) + --quiet build in quiet mode + -v, --verbose verbose output + --verboseLog verbose logging +``` + +### SEE ALSO +* [hugo check](/commands/hugo_check/) - Contains some verification checks + +###### Auto generated by spf13/cobra on 16-Jul-2017 diff --git a/content/commands/hugo_config.md b/content/commands/hugo_config.md new file mode 100644 index 000000000..0dd2052f5 --- /dev/null +++ b/content/commands/hugo_config.md @@ -0,0 +1,40 @@ +--- +date: 2017-07-16T23:23:14+02:00 +title: "hugo config" +slug: hugo_config +url: /commands/hugo_config/ +--- +## hugo config + +Print the site configuration + +### Synopsis + + +Print the site configuration, both default and custom settings. + +``` +hugo config [flags] +``` + +### Options + +``` + -h, --help help for config +``` + +### Options inherited from parent commands + +``` + --config string config file (default is path/config.yaml|json|toml) + --log enable Logging + --logFile string log File path (if set, logging enabled automatically) + --quiet build in quiet mode + -v, --verbose verbose output + --verboseLog verbose logging +``` + +### SEE ALSO +* [hugo](/commands/hugo/) - hugo builds your site + +###### Auto generated by spf13/cobra on 16-Jul-2017 diff --git a/content/commands/hugo_convert.md b/content/commands/hugo_convert.md new file mode 100644 index 000000000..4202534ce --- /dev/null +++ b/content/commands/hugo_convert.md @@ -0,0 +1,44 @@ +--- +date: 2017-07-16T23:23:14+02:00 +title: "hugo convert" +slug: hugo_convert +url: /commands/hugo_convert/ +--- +## hugo convert + +Convert your content to different formats + +### Synopsis + + +Convert your content (e.g. front matter) to different formats. + +See convert's subcommands toJSON, toTOML and toYAML for more information. + +### Options + +``` + -h, --help help for convert + -o, --output string filesystem path to write files to + -s, --source string filesystem path to read files relative from + --unsafe enable less safe operations, please backup first +``` + +### Options inherited from parent commands + +``` + --config string config file (default is path/config.yaml|json|toml) + --log enable Logging + --logFile string log File path (if set, logging enabled automatically) + --quiet build in quiet mode + -v, --verbose verbose output + --verboseLog verbose logging +``` + +### SEE ALSO +* [hugo](/commands/hugo/) - hugo builds your site +* [hugo convert toJSON](/commands/hugo_convert_tojson/) - Convert front matter to JSON +* [hugo convert toTOML](/commands/hugo_convert_totoml/) - Convert front matter to TOML +* [hugo convert toYAML](/commands/hugo_convert_toyaml/) - Convert front matter to YAML + +###### Auto generated by spf13/cobra on 16-Jul-2017 diff --git a/content/commands/hugo_convert_toJSON.md b/content/commands/hugo_convert_toJSON.md new file mode 100644 index 000000000..36b1ffe2e --- /dev/null +++ b/content/commands/hugo_convert_toJSON.md @@ -0,0 +1,44 @@ +--- +date: 2017-07-16T23:23:14+02:00 +title: "hugo convert toJSON" +slug: hugo_convert_toJSON +url: /commands/hugo_convert_tojson/ +--- +## hugo convert toJSON + +Convert front matter to JSON + +### Synopsis + + +toJSON converts all front matter in the content directory +to use JSON for the front matter. + +``` +hugo convert toJSON [flags] +``` + +### Options + +``` + -h, --help help for toJSON +``` + +### Options inherited from parent commands + +``` + --config string config file (default is path/config.yaml|json|toml) + --log enable Logging + --logFile string log File path (if set, logging enabled automatically) + -o, --output string filesystem path to write files to + --quiet build in quiet mode + -s, --source string filesystem path to read files relative from + --unsafe enable less safe operations, please backup first + -v, --verbose verbose output + --verboseLog verbose logging +``` + +### SEE ALSO +* [hugo convert](/commands/hugo_convert/) - Convert your content to different formats + +###### Auto generated by spf13/cobra on 16-Jul-2017 diff --git a/content/commands/hugo_convert_toTOML.md b/content/commands/hugo_convert_toTOML.md new file mode 100644 index 000000000..29c4f045e --- /dev/null +++ b/content/commands/hugo_convert_toTOML.md @@ -0,0 +1,44 @@ +--- +date: 2017-07-16T23:23:14+02:00 +title: "hugo convert toTOML" +slug: hugo_convert_toTOML +url: /commands/hugo_convert_totoml/ +--- +## hugo convert toTOML + +Convert front matter to TOML + +### Synopsis + + +toTOML converts all front matter in the content directory +to use TOML for the front matter. + +``` +hugo convert toTOML [flags] +``` + +### Options + +``` + -h, --help help for toTOML +``` + +### Options inherited from parent commands + +``` + --config string config file (default is path/config.yaml|json|toml) + --log enable Logging + --logFile string log File path (if set, logging enabled automatically) + -o, --output string filesystem path to write files to + --quiet build in quiet mode + -s, --source string filesystem path to read files relative from + --unsafe enable less safe operations, please backup first + -v, --verbose verbose output + --verboseLog verbose logging +``` + +### SEE ALSO +* [hugo convert](/commands/hugo_convert/) - Convert your content to different formats + +###### Auto generated by spf13/cobra on 16-Jul-2017 diff --git a/content/commands/hugo_convert_toYAML.md b/content/commands/hugo_convert_toYAML.md new file mode 100644 index 000000000..37d305d32 --- /dev/null +++ b/content/commands/hugo_convert_toYAML.md @@ -0,0 +1,44 @@ +--- +date: 2017-07-16T23:23:14+02:00 +title: "hugo convert toYAML" +slug: hugo_convert_toYAML +url: /commands/hugo_convert_toyaml/ +--- +## hugo convert toYAML + +Convert front matter to YAML + +### Synopsis + + +toYAML converts all front matter in the content directory +to use YAML for the front matter. + +``` +hugo convert toYAML [flags] +``` + +### Options + +``` + -h, --help help for toYAML +``` + +### Options inherited from parent commands + +``` + --config string config file (default is path/config.yaml|json|toml) + --log enable Logging + --logFile string log File path (if set, logging enabled automatically) + -o, --output string filesystem path to write files to + --quiet build in quiet mode + -s, --source string filesystem path to read files relative from + --unsafe enable less safe operations, please backup first + -v, --verbose verbose output + --verboseLog verbose logging +``` + +### SEE ALSO +* [hugo convert](/commands/hugo_convert/) - Convert your content to different formats + +###### Auto generated by spf13/cobra on 16-Jul-2017 diff --git a/content/commands/hugo_env.md b/content/commands/hugo_env.md new file mode 100644 index 000000000..1d3b45127 --- /dev/null +++ b/content/commands/hugo_env.md @@ -0,0 +1,40 @@ +--- +date: 2017-07-16T23:23:14+02:00 +title: "hugo env" +slug: hugo_env +url: /commands/hugo_env/ +--- +## hugo env + +Print Hugo version and environment info + +### Synopsis + + +Print Hugo version and environment info. This is useful in Hugo bug reports. + +``` +hugo env [flags] +``` + +### Options + +``` + -h, --help help for env +``` + +### Options inherited from parent commands + +``` + --config string config file (default is path/config.yaml|json|toml) + --log enable Logging + --logFile string log File path (if set, logging enabled automatically) + --quiet build in quiet mode + -v, --verbose verbose output + --verboseLog verbose logging +``` + +### SEE ALSO +* [hugo](/commands/hugo/) - hugo builds your site + +###### Auto generated by spf13/cobra on 16-Jul-2017 diff --git a/content/commands/hugo_gen.md b/content/commands/hugo_gen.md new file mode 100644 index 000000000..2aa9e228a --- /dev/null +++ b/content/commands/hugo_gen.md @@ -0,0 +1,39 @@ +--- +date: 2017-07-16T23:23:14+02:00 +title: "hugo gen" +slug: hugo_gen +url: /commands/hugo_gen/ +--- +## hugo gen + +A collection of several useful generators. + +### Synopsis + + +A collection of several useful generators. + +### Options + +``` + -h, --help help for gen +``` + +### Options inherited from parent commands + +``` + --config string config file (default is path/config.yaml|json|toml) + --log enable Logging + --logFile string log File path (if set, logging enabled automatically) + --quiet build in quiet mode + -v, --verbose verbose output + --verboseLog verbose logging +``` + +### SEE ALSO +* [hugo](/commands/hugo/) - hugo builds your site +* [hugo gen autocomplete](/commands/hugo_gen_autocomplete/) - Generate shell autocompletion script for Hugo +* [hugo gen doc](/commands/hugo_gen_doc/) - Generate Markdown documentation for the Hugo CLI. +* [hugo gen man](/commands/hugo_gen_man/) - Generate man pages for the Hugo CLI + +###### Auto generated by spf13/cobra on 16-Jul-2017 diff --git a/content/commands/hugo_gen_autocomplete.md b/content/commands/hugo_gen_autocomplete.md new file mode 100644 index 000000000..95002dae8 --- /dev/null +++ b/content/commands/hugo_gen_autocomplete.md @@ -0,0 +1,58 @@ +--- +date: 2017-07-16T23:23:14+02:00 +title: "hugo gen autocomplete" +slug: hugo_gen_autocomplete +url: /commands/hugo_gen_autocomplete/ +--- +## hugo gen autocomplete + +Generate shell autocompletion script for Hugo + +### Synopsis + + +Generates a shell autocompletion script for Hugo. + +NOTE: The current version supports Bash only. + This should work for *nix systems with Bash installed. + +By default, the file is written directly to /etc/bash_completion.d +for convenience, and the command may need superuser rights, e.g.: + + $ sudo hugo gen autocomplete + +Add `--completionfile=/path/to/file` flag to set alternative +file-path and name. + +Logout and in again to reload the completion scripts, +or just source them in directly: + + $ . /etc/bash_completion + +``` +hugo gen autocomplete [flags] +``` + +### Options + +``` + --completionfile string autocompletion file (default "/etc/bash_completion.d/hugo.sh") + -h, --help help for autocomplete + --type string autocompletion type (currently only bash supported) (default "bash") +``` + +### Options inherited from parent commands + +``` + --config string config file (default is path/config.yaml|json|toml) + --log enable Logging + --logFile string log File path (if set, logging enabled automatically) + --quiet build in quiet mode + -v, --verbose verbose output + --verboseLog verbose logging +``` + +### SEE ALSO +* [hugo gen](/commands/hugo_gen/) - A collection of several useful generators. + +###### Auto generated by spf13/cobra on 16-Jul-2017 diff --git a/content/commands/hugo_gen_doc.md b/content/commands/hugo_gen_doc.md new file mode 100644 index 000000000..e7dbd7ba3 --- /dev/null +++ b/content/commands/hugo_gen_doc.md @@ -0,0 +1,47 @@ +--- +date: 2017-07-16T23:23:14+02:00 +title: "hugo gen doc" +slug: hugo_gen_doc +url: /commands/hugo_gen_doc/ +--- +## hugo gen doc + +Generate Markdown documentation for the Hugo CLI. + +### Synopsis + + +Generate Markdown documentation for the Hugo CLI. + +This command is, mostly, used to create up-to-date documentation +of Hugo's command-line interface for http://gohugo.io/. + +It creates one Markdown file per command with front matter suitable +for rendering in Hugo. + +``` +hugo gen doc [flags] +``` + +### Options + +``` + --dir string the directory to write the doc. (default "/tmp/hugodoc/") + -h, --help help for doc +``` + +### Options inherited from parent commands + +``` + --config string config file (default is path/config.yaml|json|toml) + --log enable Logging + --logFile string log File path (if set, logging enabled automatically) + --quiet build in quiet mode + -v, --verbose verbose output + --verboseLog verbose logging +``` + +### SEE ALSO +* [hugo gen](/commands/hugo_gen/) - A collection of several useful generators. + +###### Auto generated by spf13/cobra on 16-Jul-2017 diff --git a/content/commands/hugo_gen_man.md b/content/commands/hugo_gen_man.md new file mode 100644 index 000000000..2e03d3714 --- /dev/null +++ b/content/commands/hugo_gen_man.md @@ -0,0 +1,43 @@ +--- +date: 2017-07-16T23:23:14+02:00 +title: "hugo gen man" +slug: hugo_gen_man +url: /commands/hugo_gen_man/ +--- +## hugo gen man + +Generate man pages for the Hugo CLI + +### Synopsis + + +This command automatically generates up-to-date man pages of Hugo's +command-line interface. By default, it creates the man page files +in the "man" directory under the current directory. + +``` +hugo gen man [flags] +``` + +### Options + +``` + --dir string the directory to write the man pages. (default "man/") + -h, --help help for man +``` + +### Options inherited from parent commands + +``` + --config string config file (default is path/config.yaml|json|toml) + --log enable Logging + --logFile string log File path (if set, logging enabled automatically) + --quiet build in quiet mode + -v, --verbose verbose output + --verboseLog verbose logging +``` + +### SEE ALSO +* [hugo gen](/commands/hugo_gen/) - A collection of several useful generators. + +###### Auto generated by spf13/cobra on 16-Jul-2017 diff --git a/content/commands/hugo_import.md b/content/commands/hugo_import.md new file mode 100644 index 000000000..c46b1d0b9 --- /dev/null +++ b/content/commands/hugo_import.md @@ -0,0 +1,39 @@ +--- +date: 2017-07-16T23:23:14+02:00 +title: "hugo import" +slug: hugo_import +url: /commands/hugo_import/ +--- +## hugo import + +Import your site from others. + +### Synopsis + + +Import your site from other web site generators like Jekyll. + +Import requires a subcommand, e.g. `hugo import jekyll jekyll_root_path target_path`. + +### Options + +``` + -h, --help help for import +``` + +### Options inherited from parent commands + +``` + --config string config file (default is path/config.yaml|json|toml) + --log enable Logging + --logFile string log File path (if set, logging enabled automatically) + --quiet build in quiet mode + -v, --verbose verbose output + --verboseLog verbose logging +``` + +### SEE ALSO +* [hugo](/commands/hugo/) - hugo builds your site +* [hugo import jekyll](/commands/hugo_import_jekyll/) - hugo import from Jekyll + +###### Auto generated by spf13/cobra on 16-Jul-2017 diff --git a/content/commands/hugo_import_jekyll.md b/content/commands/hugo_import_jekyll.md new file mode 100644 index 000000000..5edf76e29 --- /dev/null +++ b/content/commands/hugo_import_jekyll.md @@ -0,0 +1,43 @@ +--- +date: 2017-07-16T23:23:14+02:00 +title: "hugo import jekyll" +slug: hugo_import_jekyll +url: /commands/hugo_import_jekyll/ +--- +## hugo import jekyll + +hugo import from Jekyll + +### Synopsis + + +hugo import from Jekyll. + +Import from Jekyll requires two paths, e.g. `hugo import jekyll jekyll_root_path target_path`. + +``` +hugo import jekyll [flags] +``` + +### Options + +``` + --force allow import into non-empty target directory + -h, --help help for jekyll +``` + +### Options inherited from parent commands + +``` + --config string config file (default is path/config.yaml|json|toml) + --log enable Logging + --logFile string log File path (if set, logging enabled automatically) + --quiet build in quiet mode + -v, --verbose verbose output + --verboseLog verbose logging +``` + +### SEE ALSO +* [hugo import](/commands/hugo_import/) - Import your site from others. + +###### Auto generated by spf13/cobra on 16-Jul-2017 diff --git a/content/commands/hugo_list.md b/content/commands/hugo_list.md new file mode 100644 index 000000000..87d81bd15 --- /dev/null +++ b/content/commands/hugo_list.md @@ -0,0 +1,42 @@ +--- +date: 2017-07-16T23:23:14+02:00 +title: "hugo list" +slug: hugo_list +url: /commands/hugo_list/ +--- +## hugo list + +Listing out various types of content + +### Synopsis + + +Listing out various types of content. + +List requires a subcommand, e.g. `hugo list drafts`. + +### Options + +``` + -h, --help help for list + -s, --source string filesystem path to read files relative from +``` + +### Options inherited from parent commands + +``` + --config string config file (default is path/config.yaml|json|toml) + --log enable Logging + --logFile string log File path (if set, logging enabled automatically) + --quiet build in quiet mode + -v, --verbose verbose output + --verboseLog verbose logging +``` + +### SEE ALSO +* [hugo](/commands/hugo/) - hugo builds your site +* [hugo list drafts](/commands/hugo_list_drafts/) - List all drafts +* [hugo list expired](/commands/hugo_list_expired/) - List all posts already expired +* [hugo list future](/commands/hugo_list_future/) - List all posts dated in the future + +###### Auto generated by spf13/cobra on 16-Jul-2017 diff --git a/content/commands/hugo_list_drafts.md b/content/commands/hugo_list_drafts.md new file mode 100644 index 000000000..92d9fbc0d --- /dev/null +++ b/content/commands/hugo_list_drafts.md @@ -0,0 +1,41 @@ +--- +date: 2017-07-16T23:23:14+02:00 +title: "hugo list drafts" +slug: hugo_list_drafts +url: /commands/hugo_list_drafts/ +--- +## hugo list drafts + +List all drafts + +### Synopsis + + +List all of the drafts in your content directory. + +``` +hugo list drafts [flags] +``` + +### Options + +``` + -h, --help help for drafts +``` + +### Options inherited from parent commands + +``` + --config string config file (default is path/config.yaml|json|toml) + --log enable Logging + --logFile string log File path (if set, logging enabled automatically) + --quiet build in quiet mode + -s, --source string filesystem path to read files relative from + -v, --verbose verbose output + --verboseLog verbose logging +``` + +### SEE ALSO +* [hugo list](/commands/hugo_list/) - Listing out various types of content + +###### Auto generated by spf13/cobra on 16-Jul-2017 diff --git a/content/commands/hugo_list_expired.md b/content/commands/hugo_list_expired.md new file mode 100644 index 000000000..697ffe83d --- /dev/null +++ b/content/commands/hugo_list_expired.md @@ -0,0 +1,42 @@ +--- +date: 2017-07-16T23:23:14+02:00 +title: "hugo list expired" +slug: hugo_list_expired +url: /commands/hugo_list_expired/ +--- +## hugo list expired + +List all posts already expired + +### Synopsis + + +List all of the posts in your content directory which has already +expired. + +``` +hugo list expired [flags] +``` + +### Options + +``` + -h, --help help for expired +``` + +### Options inherited from parent commands + +``` + --config string config file (default is path/config.yaml|json|toml) + --log enable Logging + --logFile string log File path (if set, logging enabled automatically) + --quiet build in quiet mode + -s, --source string filesystem path to read files relative from + -v, --verbose verbose output + --verboseLog verbose logging +``` + +### SEE ALSO +* [hugo list](/commands/hugo_list/) - Listing out various types of content + +###### Auto generated by spf13/cobra on 16-Jul-2017 diff --git a/content/commands/hugo_list_future.md b/content/commands/hugo_list_future.md new file mode 100644 index 000000000..51b6089d1 --- /dev/null +++ b/content/commands/hugo_list_future.md @@ -0,0 +1,42 @@ +--- +date: 2017-07-16T23:23:14+02:00 +title: "hugo list future" +slug: hugo_list_future +url: /commands/hugo_list_future/ +--- +## hugo list future + +List all posts dated in the future + +### Synopsis + + +List all of the posts in your content directory which will be +posted in the future. + +``` +hugo list future [flags] +``` + +### Options + +``` + -h, --help help for future +``` + +### Options inherited from parent commands + +``` + --config string config file (default is path/config.yaml|json|toml) + --log enable Logging + --logFile string log File path (if set, logging enabled automatically) + --quiet build in quiet mode + -s, --source string filesystem path to read files relative from + -v, --verbose verbose output + --verboseLog verbose logging +``` + +### SEE ALSO +* [hugo list](/commands/hugo_list/) - Listing out various types of content + +###### Auto generated by spf13/cobra on 16-Jul-2017 diff --git a/content/commands/hugo_new.md b/content/commands/hugo_new.md new file mode 100644 index 000000000..fa4eab688 --- /dev/null +++ b/content/commands/hugo_new.md @@ -0,0 +1,50 @@ +--- +date: 2017-07-16T23:23:14+02:00 +title: "hugo new" +slug: hugo_new +url: /commands/hugo_new/ +--- +## hugo new + +Create new content for your site + +### Synopsis + + +Create a new content file and automatically set the date and title. +It will guess which kind of file to create based on the path provided. + +You can also specify the kind with `-k KIND`. + +If archetypes are provided in your theme or site, they will be used. + +``` +hugo new [path] [flags] +``` + +### Options + +``` + --editor string edit new content with this editor, if provided + -h, --help help for new + -k, --kind string content type to create + -s, --source string filesystem path to read files relative from +``` + +### Options inherited from parent commands + +``` + --config string config file (default is path/config.yaml|json|toml) + --log enable Logging + --logFile string log File path (if set, logging enabled automatically) + --quiet build in quiet mode + -v, --verbose verbose output + --verboseLog verbose logging +``` + +### SEE ALSO +* [hugo](/commands/hugo/) - hugo builds your site +* [hugo new site](/commands/hugo_new_site/) - Create a new site (skeleton) +* [hugo new theme](/commands/hugo_new_theme/) - Create a new theme + +###### Auto generated by spf13/cobra on 16-Jul-2017 diff --git a/content/commands/hugo_new_site.md b/content/commands/hugo_new_site.md new file mode 100644 index 000000000..3accfbbea --- /dev/null +++ b/content/commands/hugo_new_site.md @@ -0,0 +1,45 @@ +--- +date: 2017-07-16T23:23:14+02:00 +title: "hugo new site" +slug: hugo_new_site +url: /commands/hugo_new_site/ +--- +## hugo new site + +Create a new site (skeleton) + +### Synopsis + + +Create a new site in the provided directory. +The new site will have the correct structure, but no content or theme yet. +Use `hugo new [contentPath]` to create new content. + +``` +hugo new site [path] [flags] +``` + +### Options + +``` + --force init inside non-empty directory + -f, --format string config & frontmatter format (default "toml") + -h, --help help for site +``` + +### Options inherited from parent commands + +``` + --config string config file (default is path/config.yaml|json|toml) + --log enable Logging + --logFile string log File path (if set, logging enabled automatically) + --quiet build in quiet mode + -s, --source string filesystem path to read files relative from + -v, --verbose verbose output + --verboseLog verbose logging +``` + +### SEE ALSO +* [hugo new](/commands/hugo_new/) - Create new content for your site + +###### Auto generated by spf13/cobra on 16-Jul-2017 diff --git a/content/commands/hugo_new_theme.md b/content/commands/hugo_new_theme.md new file mode 100644 index 000000000..928d11b56 --- /dev/null +++ b/content/commands/hugo_new_theme.md @@ -0,0 +1,44 @@ +--- +date: 2017-07-16T23:23:14+02:00 +title: "hugo new theme" +slug: hugo_new_theme +url: /commands/hugo_new_theme/ +--- +## hugo new theme + +Create a new theme + +### Synopsis + + +Create a new theme (skeleton) called [name] in the current directory. +New theme is a skeleton. Please add content to the touched files. Add your +name to the copyright line in the license and adjust the theme.toml file +as you see fit. + +``` +hugo new theme [name] [flags] +``` + +### Options + +``` + -h, --help help for theme +``` + +### Options inherited from parent commands + +``` + --config string config file (default is path/config.yaml|json|toml) + --log enable Logging + --logFile string log File path (if set, logging enabled automatically) + --quiet build in quiet mode + -s, --source string filesystem path to read files relative from + -v, --verbose verbose output + --verboseLog verbose logging +``` + +### SEE ALSO +* [hugo new](/commands/hugo_new/) - Create new content for your site + +###### Auto generated by spf13/cobra on 16-Jul-2017 diff --git a/content/commands/hugo_server.md b/content/commands/hugo_server.md new file mode 100644 index 000000000..5b58a52f0 --- /dev/null +++ b/content/commands/hugo_server.md @@ -0,0 +1,87 @@ +--- +date: 2017-07-16T23:23:14+02:00 +title: "hugo server" +slug: hugo_server +url: /commands/hugo_server/ +--- +## hugo server + +A high performance webserver + +### Synopsis + + +Hugo provides its own webserver which builds and serves the site. +While hugo server is high performance, it is a webserver with limited options. +Many run it in production, but the standard behavior is for people to use it +in development and use a more full featured server such as Nginx or Caddy. + +'hugo server' will avoid writing the rendered and served content to disk, +preferring to store it in memory. + +By default hugo will also watch your files for any changes you make and +automatically rebuild the site. It will then live reload any open browser pages +and push the latest content to them. As most Hugo sites are built in a fraction +of a second, you will be able to save and see your changes nearly instantly. + +``` +hugo server [flags] +``` + +### Options + +``` + --appendPort append port to baseURL (default true) + -b, --baseURL string hostname (and path) to the root, e.g. http://spf13.com/ + --bind string interface to which the server will bind (default "127.0.0.1") + -D, --buildDrafts include content marked as draft + -E, --buildExpired include expired content + -F, --buildFuture include content with publishdate in the future + --cacheDir string filesystem path to cache directory. Defaults: $TMPDIR/hugo_cache/ + --canonifyURLs if true, all relative URLs will be canonicalized using baseURL + --cleanDestinationDir remove files from destination not found in static directories + -c, --contentDir string filesystem path to content directory + -d, --destination string filesystem path to write files to + --disable404 do not render 404 page + --disableKinds stringSlice disable different kind of pages (home, RSS etc.) + --disableLiveReload watch without enabling live browser reload on rebuild + --disableRSS do not build RSS files + --disableSitemap do not build Sitemap file + --enableGitInfo add Git revision, date and author info to the pages + --forceSyncStatic copy all files when static is changed. + -h, --help help for server + --i18n-warnings print missing translations + --ignoreCache ignores the cache directory + -l, --layoutDir string filesystem path to layout directory + --meminterval string interval to poll memory usage (requires --memstats), valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". (default "100ms") + --memstats string log memory usage to this file + --navigateToChanged navigate to changed content file on live browser reload + --noChmod don't sync permission mode of files + --noTimes don't sync modification time of files + --pluralizeListTitles pluralize titles in lists using inflect (default true) + -p, --port int port on which the server will listen (default 1313) + --preserveTaxonomyNames preserve taxonomy names as written ("Gérard Depardieu" vs "gerard-depardieu") + --renderToDisk render to Destination path (default is render to memory & serve from there) + -s, --source string filesystem path to read files relative from + --stepAnalysis display memory and timing of different steps of the program + -t, --theme string theme to use (located in /themes/THEMENAME/) + --themesDir string filesystem path to themes directory + --uglyURLs if true, use /filename.html instead of /filename/ + -w, --watch watch filesystem for changes and recreate as needed (default true) +``` + +### Options inherited from parent commands + +``` + --config string config file (default is path/config.yaml|json|toml) + --log enable Logging + --logFile string log File path (if set, logging enabled automatically) + --quiet build in quiet mode + -v, --verbose verbose output + --verboseLog verbose logging +``` + +### SEE ALSO +* [hugo](/commands/hugo/) - hugo builds your site + +###### Auto generated by spf13/cobra on 16-Jul-2017 diff --git a/content/commands/hugo_undraft.md b/content/commands/hugo_undraft.md new file mode 100644 index 000000000..bb408b80b --- /dev/null +++ b/content/commands/hugo_undraft.md @@ -0,0 +1,42 @@ +--- +date: 2017-07-16T23:23:14+02:00 +title: "hugo undraft" +slug: hugo_undraft +url: /commands/hugo_undraft/ +--- +## hugo undraft + +Undraft resets the content's draft status + +### Synopsis + + +Undraft resets the content's draft status +and updates the date to the current date and time. +If the content's draft status is 'False', nothing is done. + +``` +hugo undraft path/to/content [flags] +``` + +### Options + +``` + -h, --help help for undraft +``` + +### Options inherited from parent commands + +``` + --config string config file (default is path/config.yaml|json|toml) + --log enable Logging + --logFile string log File path (if set, logging enabled automatically) + --quiet build in quiet mode + -v, --verbose verbose output + --verboseLog verbose logging +``` + +### SEE ALSO +* [hugo](/commands/hugo/) - hugo builds your site + +###### Auto generated by spf13/cobra on 16-Jul-2017 diff --git a/content/commands/hugo_version.md b/content/commands/hugo_version.md new file mode 100644 index 000000000..522ff1008 --- /dev/null +++ b/content/commands/hugo_version.md @@ -0,0 +1,40 @@ +--- +date: 2017-07-16T23:23:14+02:00 +title: "hugo version" +slug: hugo_version +url: /commands/hugo_version/ +--- +## hugo version + +Print the version number of Hugo + +### Synopsis + + +All software has versions. This is Hugo's. + +``` +hugo version [flags] +``` + +### Options + +``` + -h, --help help for version +``` + +### Options inherited from parent commands + +``` + --config string config file (default is path/config.yaml|json|toml) + --log enable Logging + --logFile string log File path (if set, logging enabled automatically) + --quiet build in quiet mode + -v, --verbose verbose output + --verboseLog verbose logging +``` + +### SEE ALSO +* [hugo](/commands/hugo/) - hugo builds your site + +###### Auto generated by spf13/cobra on 16-Jul-2017 diff --git a/content/content-management/_index.md b/content/content-management/_index.md new file mode 100644 index 000000000..96ff44d2f --- /dev/null +++ b/content/content-management/_index.md @@ -0,0 +1,20 @@ +--- +title: Content Management +linktitle: Content Management Overview +description: Hugo makes managing large static sites easy with support for archetypes, content types, menus, cross references, summaries, and more. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +menu: + docs: + parent: "content-management" + weight: 1 +#tags: [source, organization] +categories: [content management] +weight: 01 #rem +draft: false +aliases: [/content/,/content/organization] +toc: false +--- + +A static site generator needs to extend beyond front matter and a couple templates to be both scalable and *manageable*. Hugo was designed with not only developers in mind, but also content managers and authors. diff --git a/content/content-management/archetypes.md b/content/content-management/archetypes.md new file mode 100644 index 000000000..235de4ff2 --- /dev/null +++ b/content/content-management/archetypes.md @@ -0,0 +1,192 @@ +--- +title: Archetypes +linktitle: Archetypes +description: Archetypes allow you to create new instances of content types and set default parameters from the command line. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +#tags: [archetypes,generators,metadata,front matter] +categories: ["content management"] +menu: + docs: + parent: "content-management" + weight: 70 + quicklinks: +weight: 70 #rem +draft: false +aliases: [/content/archetypes/] +toc: true +--- + +{{% note %}} +This section is outdated, see https://github.com/gohugoio/hugoDocs/issues/11 +{{% /note %}} +{{% todo %}} +See above +{{% /todo %}} + +## What are Archetypes? + +**Archetypes** are content files in the [archetypes directory][] of your project that contain preconfigured [front matter][] for your website's [content types][]. Archetypes facilitate consistent metadata across your website content and allow content authors to quickly generate instances of a content type via the `hugo new` command. + +{{< youtube S3Tj3UcTFz8 >}} + +The `hugo new` generator for archetypes assumes your working directory is the content folder at the root of your project. Hugo is able to infer the appropriate archetype by assuming the content type from the content section passed to the CLI command: + +``` +hugo new / +``` + +We can use this pattern to create a new `.md` file in the `posts` section: + +{{< code file="archetype-example.sh" >}} +hugo new posts/my-first-post.md +{{< /code >}} + +{{% note "Override Content Type in a New File" %}} +To override the content type Hugo infers from `[content-section]`, add the `--kind` flag to the end of the `hugo new` command. +{{% /note %}} + +Running this command in a new site that does not have default or custom archetypes will create the following file: + +{{< output file="content/posts/my-first-post.md" >}} ++++ +date = "2017-02-01T19:20:04-07:00" +title = "my first post" +draft = true ++++ +{{< /output >}} + +{{% note %}} +In this example, if you do not already have a `content/posts` directory, Hugo will create both `content/posts/` and `content/posts/my-first-post.md` for you. +{{% /note %}} + +The auto-populated fields are worth examining: + +* `title` is generated from the new content's filename (i.e. in this case, `my-first-post` becomes `"my first post"`) +* `date` and `title` are the variables that ship with Hugo and are therefore included in *all* content files created with the Hugo CLI. `date` is generated in [RFC 3339 format][] by way of Go's [`now()`][] function, which returns the current time. +* The third variable, `draft = true`, is *not* inherited by your default or custom archetypes but is included in Hugo's automatically scaffolded `default.md` archetype for convenience. + +Three variables per content file are often not enough for effective content management of larger websites. Luckily, Hugo provides a simple mechanism for extending the number of variables through custom archetypes, as well as default archetypes to keep content creation DRY. + +## Lookup Order for Archetypes + +Similar to the [lookup order for templates][lookup] in your `layouts` directory, Hugo looks for a section- or type-specific archetype, then a default archetype, and finally an internal archetype that ships with Hugo. For example, Hugo will look for an archetype for `content/posts/my-first-post.md` in the following order: + +1. `archetypes/posts.md` +2. `archetypes/default.md` +3. `themes//archetypes/posts.md` +4. `themes//archetypes/default.md` (Auto-generated with `hugo new site`) + +{{% note "Using a Theme Archetype" %}} +If you wish to use archetypes that ship with a theme, the `theme` field must be specified in your [configuration file](/getting-started/configuration/). +{{% /note %}} + +## Choose Your Archetype's Front Matter Format + +By default, `hugo new` content files include front matter in the TOML format regardless of the format used in `archetypes/*.md`. + +You can specify a different default format in your site [configuration file][] file using the `metaDataFormat` directive. Possible values are `toml`, `yaml`, and `json`. + +## Default Archetypes + +Default archetypes are convenient if your content's front matter stays consistent across multiple [content sections][sections]. + +### Create the Default Archetype + +When you create a new Hugo project using `hugo new site`, you'll notice that Hugo has already scaffolded a file at `archetypes/default.md`. + +The following examples are from a site that's using `tags` and `categories` as [taxonomies][]. If we assume that all content files will require these two key-values, we can create a `default.md` archetype that *extends* Hugo's base archetype. In this example, we are including "golang" and "hugo" as tags and "web development" as a category. + +{{< code file="archetypes/default.md" >}} ++++ +tags = ["golang", "hugo"] +categories = ["web development"] ++++ +{{< /code >}} + +{{% warning "EOL Characters in Text Editors"%}} +If you get an `EOF error` when using `hugo new`, add a carriage return after the closing `+++` or `---` for your TOML or YAML front matter, respectively. (See the [troubleshooting article on EOF errors](/troubleshooting/eof-error/) for more information.) +{{% /warning %}} + +### Use the Default Archetype + +With an `archetypes/default.md` in place, we can use the CLI to create a new post in the `posts` content section: + +{{< code file="new-post-from-default.sh" >}} +$ hugo new posts/my-new-post.md +{{< /code >}} + +Hugo then creates a new markdown file with the following front matter: + +{{< output file="content/posts/my-new-post.md" >}} ++++ +categories = ["web development"] +date = "2017-02-01T19:20:04-07:00" +tags = ["golang", "hugo"] +title = "my new post" ++++ +{{< /output >}} + +We see that the `title` and `date` key-values have been added in addition to the `tags` and `categories` key-values from `archetypes/default.md`. + +{{% note "Ordering of Front Matter" %}} +You may notice that content files created with `hugo new` do not respect the order of the key-values specified in your archetype files. This is a [known issue](https://github.com/gohugoio/hugo/issues/452). +{{% /note %}} + +## Custom Archetypes + +Suppose your site's `posts` section requires more sophisticated front matter than what has been specified in `archetypes/default.md`. You can create a custom archetype for your posts at `archetypes/posts.md` that includes the full set of front matter to be added to the two default archetypes fields. + +### Create a Custom Archetype + +{{< code file="archetypes/posts.md">}} ++++ +description = "" +tags = "" +categories = "" ++++ +{{< /code >}} + +### Use a Custom Archetype + +With an `archetypes/posts.md` in place, you can use the Hugo CLI to create a new post with your preconfigured front matter in the `posts` content section: + +{{< code file="new-post-from-custom.sh" >}} +$ hugo new posts/post-from-custom.md +{{< /code >}} + +This time, Hugo recognizes our custom `archetypes/posts.md` archetype and uses it instead of `archetypes/default.md`. The generated file will now include the full list of front matter parameters, as well as the base archetype's `title` and `date`: + +{{< output file="content/posts/post-from-custom-archetype.md" >}} ++++ +categories = "" +date = 2017-02-13T17:24:43-08:00 +description = "" +tags = "" +title = "post from custom archetype" ++++ +{{< /output >}} + +### Hugo Docs Custom Archetype + +As an example of archetypes in practice, the following is the `functions` archetype from the Hugo docs: + +{{< code file="archetypes/functions.md" >}} +{{< readfile file="/themes/gohugoioTheme/archetypes/functions.md" >}} +{{< /code >}} + +{{% note %}} +The preceding archetype is kept up to date with every Hugo build by using Hugo's [`readFile` function](/functions/readfile/). For similar examples, see [Local File Templates](/templates/files/). +{{% /note %}} + +[archetypes directory]: /getting-started/directory-structure/ +[`now()`]: http://golang.org/pkg/time/#Now +[configuration file]: /getting-started/configuration/ +[sections]: /content-management/sections/ +[content types]: /content-management/types/ +[front matter]: /content-management/front-matter/ +[RFC 3339 format]: https://www.ietf.org/rfc/rfc3339.txt +[taxonomies]: /content-management/taxonomies/ +[lookup]: /templates/lookup/ +[templates]: /templates/ diff --git a/content/content-management/authors.md b/content/content-management/authors.md new file mode 100644 index 000000000..0a0d1799d --- /dev/null +++ b/content/content-management/authors.md @@ -0,0 +1,185 @@ +--- +title: Authors +linktitle: Authors +description: +date: 2016-08-22 +publishdate: 2017-03-12 +lastmod: 2017-03-12 +#tags: [authors] +categories: ["content management"] +menu: + docs: + parent: "content-management" + weight: 55 +weight: 55 #rem +draft: true +aliases: [/content/archetypes/] +toc: true +comments: Before this page is published, need to also update both site- and page-level variables documentation. +--- + + + +Larger sites often have multiple content authors. Hugo provides standardized author profiles to organize relationships between content and content creators for sites operating under a distributed authorship model. + +## Author Profiles + +You can create a profile containing metadata for each author on your website. These profiles have to be saved under `data/_authors/`. The filename of the profile will later be used as an identifier. This way Hugo can associate content with one or multiple authors. An author's profile can be defined in the JSON, YAML, or TOML format. + +### Example: Author Profile + +Let's suppose Alice Allison is a blogger. A simple unique identifier would be `alice`. Now, we have to create a file called `alice.toml` in the `data/_authors/` directory. The following example is the standardized template written in TOML: + +{{< code file="data/_authors/alice.toml" >}} +givenName = "Alice" # or firstName as alias +familyName = "Allison" # or lastName as alias +displayName = "Alice Allison" +thumbnail = "static/authors/alice-thumb.jpg" +image = "static/authors/alice-full.jpg" +shortBio = "My name is Alice and I'm a blogger." +bio = "My name is Alice and I'm a blogger... some other stuff" +email = "alice.allison@email.com" +weight = 10 + +[social] + facebook = "alice.allison" + twitter = "alice" + googleplus = "aliceallison1" + website = "www.example.com" + +[params] + random = "whatever you want" +{{< /code >}} + +All variables are optional but it's advised to fill all important ones (e.g. names and biography) because themes can vary in their usage. + +You can store files for the `thumbnail` and `image` attributes in the `static` folder. Then add the path to the photos relative to `static`; e.g., `/static/path/to/thumbnail.jpg`. + +`weight` allows you to define the order of an author in an `.Authors` list and can be accessed on list or via the `.Site.Authors` variable. + +The `social` section contains all the links to the social network accounts of an author. Hugo is able to generate the account links for the most popular social networks automatically. This way, you only have to enter your username. You can find a list of all supported social networks [here](#linking-social-network-accounts-automatically). All other variables, like `website` in the example above remain untouched. + +The `params` section can contain arbitrary data much like the same-named section in the config file. What it contains is up to you. + +## Associate Content Through Identifiers + +Earlier it was mentioned that content can be associated with an author through their corresponding identifier. In our case, blogger Alice has the identifier `alice`. In the front matter of a content file, you can create a list of identifiers and assign it to the `authors` variable. Here are examples for `alice` using YAML and TOML, respectively. + +``` +--- +title: Why Hugo is so Awesome +date: 2016-08-22T14:27:502:00 +authors: ["alice"] +--- + +Nothing to read here. Move along... +``` + +``` ++++ +title = Why Hugo is so Awesome +date = "2016-08-22T14:27:502:00" +authors: ["alice"] ++++ + +Nothing to read here. Move along... +``` + +Future authors who might work on this blog post can append their identifiers to the `authors` array in the front matter as well. + +## Work with Templates + +After a successful setup it's time to give some credit to the authors by showing them on the website. Within the templates Hugo provides a list of the author's profiles if they are listed in the `authors` variable within the front matter. + +The list is accessible via the `.Authors` template variable. Printing all authors of a the blog post is straight forward: + +``` +{{ range .Authors }} + {{ .DisplayName }} +{{ end }} +=> Alice Allison +``` + +Even if there are co-authors you may only want to show the main author. For this case you can use the `.Author` template variable **(note the singular form)**. The template variable contains the profile of the author that is first listed with his identifier in the front matter. + +{{% note %}} +You can find a list of all template variables to access the profile information in [Author Variables](/variables/authors/). +{{% /note %}} + +### Link Social Network Accounts + +As aforementioned, Hugo is able to generate links to profiles of the most popular social networks. The following social networks with their corrersponding identifiers are supported: `github`, `facebook`, `twitter`, `googleplus`, `pinterest`, `instagram`, `youtube` and `linkedin`. + +This is can be done with the `.Social.URL` function. Its only parameter is the name of the social network as they are defined in the profile (e.g. `facebook`, `googleplus`). Custom variables like `website` remain as they are. + +Most articles feature a small section with information about the author at the end. Let's create one containing the author's name, a thumbnail, a (summarized) biography and links to all social networks: + +{{< code file="layouts/partials/author-info.html" download="author-info.html" >}} +{{ with .Author }} +

{{ .DisplayName }}

+ {{ .DisplayName }} +

{{ .ShortBio }}

+
    + {{ range $network, $username := .Social }} +
  • {{ $network }}
  • + {{ end }} +
+{{ end }} +{{< /code >}} + +## Who Published What? + +That question can be answered with a list of all authors and another list containing all articles that they each have written. Now we have to translate this idea into templates. The [taxonomy][] feature allows us to logically group content based on information that they have in common; e.g. a tag or a category. Well, many articles share the same author, so this should sound familiar, right? + +In order to let Hugo know that we want to group content based on their author, we have to create a new taxonomy called `author` (the name corresponds to the variable in the front matter). Here is the snippet in a `config.yaml` and `config.toml`, respectively: + +``` +taxonomies: + author: authors +``` + +``` +[taxonomies] + author = "authors" +``` + + +### List All Authors + +In the next step we can create a template to list all authors of your website. Later, the list can be accessed at `www.example.com/authors/`. Create a new template in the `layouts/taxonomy/` directory called `authors.term.html`. This template will be exclusively used for this taxonomy. + +{{< code file="layouts/taxonomy/author.term.html" download="author.term.html" >}} + +{{< /code >}} + +`.Data.Terms` contains the identifiers of all authors and we can range over it to create a list with all author names. The `$profile` variable gives us access to the profile of the current author. This allows you to generate a nice info box with a thumbnail, a biography and social media links, like at the [end of a blog post](#linking-social-network-accounts-automatically). + +### List Each Author's Publications + +Last but not least, we have to create the second list that contains all publications of an author. Each list will be shown in its own page and can be accessed at `www.example.com/authors/`. Replace `` with a valid author identifier like `alice`. + +The layout for this page can be defined in the template `layouts/taxonomy/author.html`. + +{{< code file="layouts/taxonomy/author.html" download="author.html" >}} +{{ range .Data.Pages }} +

{{ .Title }}

+ written by {{ .Author.DisplayName }} + {{ .Summary }} +{{ end }} +{{< /code >}} + +The example above generates a simple list of all posts written by a single author. Inside the loop you've access to the complete set of [page variables][pagevars]. Therefore, you can add additional information about the current posts like the publishing date or the tags. + +With a lot of content this list can quickly become very long. Consider to use the [pagination][] feature. It splits the list into smaller chunks and spreads them over multiple pages. + +[pagevars]: /variables/page/ +[pagination]: /templates/pagination/ diff --git a/content/content-management/comments.md b/content/content-management/comments.md new file mode 100644 index 000000000..2db449738 --- /dev/null +++ b/content/content-management/comments.md @@ -0,0 +1,84 @@ +--- +title: Comments +linktitle: Comments +description: Hugo ships with an internal Disqus template, but this isn't the only commenting system that will work with your new Hugo website. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-03-09 +#tags: [sections,content,organization] +categories: [project organization, fundamentals] +menu: + docs: + parent: "content-management" + weight: 140 +weight: 140 #rem +draft: false +aliases: [/extras/comments/] +toc: true +--- + +Hugo ships with support for [Disqus](https://disqus.com/), a third-party service that provides comment and community capabilities to websites via JavaScript. + +Your theme may already support Disqus, but if not, it is easy to add to your templates via [Hugo's built-in Disqus partial][disquspartial]. + +## Add Disqus + +Hugo comes with all the code you need to load Disqus into your templates. Before adding Disqus to your site, you'll need to [set up an account][disqussetup]. + +### Configure Disqus + +Disqus comments require you set a single value in your [site's configuration file][configuration]. The following show the configuration variable in a `config.toml` and `config.yml`, respectively: + +``` +disqusShortname = "yourdiscussshortname" +``` + +``` +disqusShortname: "yourdiscussshortname" +``` + +For many websites, this is enough configuration. However, you also have the option to set the following in the [front matter][] of a single content file: + +* `disqus_identifier` +* `disqus_title` +* `disqus_url` + +### Render Hugo's Built-in Disqus Partial Template + +See [Partial Templates][partials] to learn how to add the Disqus partial to your Hugo website's templates. + +## Comments Alternatives + +There are a few alternatives to commenting on static sites for those who do not want to use Disqus: + +* [Static Man](https://staticman.net/) +* [txtpen](https://txtpen.com) +* [IntenseDebate](http://intensedebate.com/) +* [Graph Comment][] +* [Muut](http://muut.com/) +* [isso](http://posativ.org/isso/) (Self-hosted, Python) + * [Tutorial on Implementing Isso with Hugo][issotutorial] + + + + + + + +[configuration]: /getting-started/configuration/ +[disquspartial]: /templates/partials/#disqus +[disqussetup]: https://disqus.com/profile/signup/ +[forum]: https://discourse.gohugo.io +[front matter]: /content-management/front-matter/ +[Graph Comment]: https://graphcomment.com/ +[kaijuissue]: https://github.com/spf13/kaiju/issues/new +[issotutorial]: https://stiobhart.net/2017-02-24-isso-comments/ +[partials]: /templates/partials/ +[MongoDB]: https://www.mongodb.com/ +[tweet]: https://twitter.com/spf13 diff --git a/content/content-management/cross-references.md b/content/content-management/cross-references.md new file mode 100644 index 000000000..358152672 --- /dev/null +++ b/content/content-management/cross-references.md @@ -0,0 +1,123 @@ +--- +title: Cross References +description: Hugo makes it easy to link documents together. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-03-31 +categories: [content management] +#tags: ["cross references","references", "anchors", "urls"] +menu: + docs: + parent: "content-management" + weight: 100 +weight: 100 #rem +aliases: [/extras/crossreferences/] +toc: true +--- + + + The `ref` and `relref` shortcodes link documents together, both of which are [built-in Hugo shortcodes][]. These shortcodes are also used to provide links to headings inside of your content, whether across documents or within a document. The only difference between `ref` and `relref` is whether the resulting URL is absolute (`http://1.com/about/`) or relative (`/about/`), respectively. + +## Use `ref` and `relref` + +``` +{{}} +{{}} +{{}} +{{}} +{{}} +{{}} +``` + +The single parameter to `ref` is a string with a content `documentname` (e.g., `about.md`) with or without an appended in-document `anchor` (`#who`) without spaces. + +### Document Names + +The `documentname` is the name of a document, including the format extension; this may be just the filename, or the relative path from the `content/` directory. With a document `content/blog/post.md`, either format will produce the same result: + +``` +{{}} => `/blog/post/` +{{}} => `/blog/post/` +``` + +If you have the same filename used across multiple sections, you should only use the relative path format; otherwise, the behavior will be `undefined`. This is best illustrated with an example `content` directory: + +``` +. +└── content + ├── events + │   └── my-birthday.md + ├── galleries + │   └── my-birthday.md + ├── meta + │   └── my-article.md + └── posts + └── my-birthday.md +``` + +To be sure to get the correct reference in this case, use the full path: + +{{< code file="content/meta/my-article.md" copy="false" >}} +{{}} => /events/my-birthday/ +{{< /code >}} + +{{< todo >}}Remove this warning when https://github.com/gohugoio/hugo/issues/3703 is released.{{< /todo >}} + +A relative document name must *not* begin with a slash (`/`). +``` +{{}} => "" +``` + +### With Multiple Output Formats + +If the page exists in multiple [output formats][], `ref` or `relref` can be used with a output format name: + +``` + [Neat]({{}}) +``` + +### Anchors + +When an `anchor` is provided by itself, the current page’s unique identifier will be appended; when an `anchor` is provided appended to `documentname`, the found page's unique identifier will be appended: + +``` +{{}} => #anchors:9decaf7 +{{}} => /blog/post/#who:badcafe +``` + +The above examples render as follows for this very page as well as a reference to the "Content" heading in the Hugo docs features pageyoursite + +``` +{{}} => #who:9decaf7 +{{}} => /blog/post/#who:badcafe +``` + +More information about document unique identifiers and headings can be found [below]({{< ref "#hugo-heading-anchors" >}}). + +### Examples + +* `{{}}` => `https://example.com/blog/post/` +* `{{}}` => `https://example.com/blog/post/#tldr:caffebad` +* `{{}}` => `/blog/post/` +* `{{}}` => `/blog/post/#tldr:caffebad` +* `{{}}` => `#tldr:badcaffe` +* `{{}}` => `#tldr:badcaffe` + +## Hugo Heading Anchors + +When using Markdown document types, Hugo generates heading anchors automatically. The generated anchor for this section is `hugo-heading-anchors`. Because the heading anchors are generated automatically, Hugo takes some effort to ensure that heading anchors are unique both inside a document and across the entire site. + +Ensuring heading uniqueness across the site is accomplished with a unique identifier for each document based on its path. Unless a document is renamed or moved between sections *in the filesystem*, the unique identifier for the document will not change: `blog/post.md` will always have a unique identifier of `81df004c333b392d34a49fd3a91ba720`. + +`ref` and `relref` were added so you can make these reference links without having to know the document’s unique identifier. (The links in document tables of contents are automatically up-to-date with this value.) + +``` +{{}} +/content-management/cross-references/#hugo-heading-anchors:77cd9ea530577debf4ce0f28c8dca242 +``` + + +[built-in Hugo shortcodes]: /content-management/shortcodes/#using-the-built-in-shortcodes +[lists]: /templates/lists/ +[output formats]: /templates/output-formats/ +[shortcode]: /content-management/shortcodes/ \ No newline at end of file diff --git a/content/content-management/formats.md b/content/content-management/formats.md new file mode 100644 index 000000000..c2837ffc4 --- /dev/null +++ b/content/content-management/formats.md @@ -0,0 +1,241 @@ +--- +title: Supported Content Formats +linktitle: Supported Content Formats +description: Markdown and Emacs Org-Mode have native support, and additional formats (e.g. Asciidoc) come via external helpers. +date: 2017-01-10 +publishdate: 2017-01-10 +lastmod: 2017-04-06 +categories: [content management] +#tags: [markdown,asciidoc,mmark,content format] +menu: + docs: + parent: "content-management" + weight: 20 +weight: 20 #rem +draft: false +aliases: [/content/markdown-extras/,/content/supported-formats/,/doc/supported-formats/,/tutorials/mathjax/] +toc: true +--- + +**Markdown is the main content format** and comes in two flavours: The excellent [Blackfriday project][blackfriday] (name your files `*.md` or set `markup = "markdown"` in front matter) or its fork [Mmark][mmark] (name your files `*.mmark` or set `markup = "mmark"` in front matter), both very fast markdown engines written in Go. + +For Emacs users, [goorgeous](https://github.com/chaseadamsio/goorgeous) provides built-in native support for Org-mode (name your files `*.org` or set `markup = "org"` in front matter) + +{{% note "Deeply Nested Lists" %}} +Before you begin writing your content in markdown, Blackfriday has a known issue [(#329)](https://github.com/russross/blackfriday/issues/329) with handling deeply nested lists. Luckily, there is an easy workaround. Use 4-spaces (i.e., tab) rather than 2-space indentations. +{{% /note %}} + +## Configure BlackFriday Markdown Rendering + +You can configure multiple aspects of Blackfriday as show in the following list. See the docs on [Configuration][config] for the full list of explicit directions you can give to Hugo when rendering your site. + +{{< readfile file="/content/readfiles/bfconfig.md" markdown="true" >}} + +## Extend Markdown + +Hugo provides some convenient methods for extending markdown. + +### Task Lists + +Hugo supports [GitHub-styled task lists (i.e., TODO lists)][gfmtasks] for the Blackfriday markdown renderer. If you do not want to use this feature, you can disable it in your configuration. + +#### Example Task List Input + +{{< code file="content/my-to-do-list.md" >}} +- [ ] a task list item +- [ ] list syntax required +- [ ] incomplete +- [x] completed +{{< /code >}} + +#### Example Task List Output + +The preceding markdown produces the following HTML in your rendered website: + +``` +
    +
  • a task list item
  • +
  • list syntax required
  • +
  • incomplete
  • +
  • completed
  • +
+``` + +#### Example Task List Display + +The following shows how the example task list will look to the end users of your website. Note that visual styling of lists is up to you. This list has been styled according to [the Hugo Docs stylesheet][hugocss]. + +- [ ] a task list item +- [ ] list syntax required +- [ ] incomplete +- [x] completed + +### Emojis + +To add emojis directly to content, set `enableEmoji` to `true` in your [site configuration][config]. To use emojis in templates or shortcodes, see [`emojify` function][]. + +For a full list of emojis, see the [Emoji cheat sheet][emojis]. + +### Shortcodes + +If you write in Markdown and find yourself frequently embedding your content with raw HTML, Hugo provides built-in shortcodes functionality. This is one of the most powerful features in Hugo and allows you to create your own Markdown extensions very quickly. + +See [Shortcodes][sc] for usage, particularly for the built-in shortcodes that ship with Hugo, and [Shortcode Templating][sct] to learn how to build your own. + +### Code Blocks + +Hugo supports GitHub-flavored markdown's use of triple back ticks, as well as provides a special [`highlight` nested shortcode][hlsc] to render syntax highlighting via [Pygments][]. For usage examples and a complete explanation, see the [syntax highlighting documentation][hl] in [developer tools][]. + +## Mmark + +Mmark is a [fork of BlackFriday][mmark] and markdown superset that is well suited for writing [IETF documentation][ietf]. You can see examples of the syntax in the [Mmark GitHub repository][mmarkgh] or the full syntax on [Miek Gieben's website][]. + +### Use Mmark + +As Hugo ships with Mmark, using the syntax is as easy as changing the extension of your content files from `.md` to `.mmark`. + +In the event that you want to only use Mmark in specific files, you can also define the Mmark syntax in your content's front matter: + +``` +--- +title: My Post +date: 2017-04-01 +markup: mmark +--- +``` + +{{% warning %}} +Thare are some features not available in Mmark; one example being that shortcodes are not translated when used in an included `.mmark` file ([#3131](https://github.com/gohugoio/hugo/issues/3137)), and `EXTENSION_ABBREVIATION` ([#1970](https://github.com/gohugoio/hugo/issues/1970)) and the aforementioned GFM todo lists ([#2270](https://github.com/gohugoio/hugo/issues/2270)) are not fully supported. Contributions are welcome. +{{% /warning %}} + +## MathJax with Hugo + +[MathJax](http://www.mathjax.org/) is a JavaScript library that allows the display of mathematical expressions described via a LaTeX-style syntax in the HTML (or Markdown) source of a web page. As it is a pure a JavaScript library, getting it to work within Hugo is fairly straightforward, but does have some oddities that will be discussed here. + +This is not an introduction into actually using MathJax to render typeset mathematics on your website. Instead, this page is a collection of tips and hints for one way to get MathJax working on a website built with Hugo. + +### Enable MathJax + +The first step is to enable MathJax on pages that you would like to have typeset math. There are multiple ways to do this (adventurous readers can consult the [Loading and Configuring](http://docs.mathjax.org/en/latest/configuration.html) section of the MathJax documentation for additional methods of including MathJax), but the easiest way is to use the secure MathJax CDN by include a ` +{{< /code >}} + +One way to ensure that this code is included in all pages is to put it in one of the templates that live in the `layouts/partials/` directory. For example, I have included this in the bottom of my template `footer.html` because I know that the footer will be included in every page of my website. + +### Options and Features + +MathJax is a stable open-source library with many features. I encourage the interested reader to view the [MathJax Documentation](http://docs.mathjax.org/en/latest/index.html), specifically the sections on [Basic Usage](http://docs.mathjax.org/en/latest/index.html#basic-usage) and [MathJax Configuration Options](http://docs.mathjax.org/en/latest/index.html#mathjax-configuration-options). + +### Issues with Markdown + +{{% note %}} +The following issues with Markdown assume you are using `.md` for content and BlackFriday for parsing. Using [Mmark](#mmark) as your content format will obviate the need for the following workarounds. + +When using Mmark with MathJax, use `displayMath: [['$$','$$'], ['\\[','\\]']]`. See the [Mmark `README.md`](https://github.com/miekg/mmark/wiki/Syntax#math-blocks) for more information. In addition to MathJax, Mmark has been shown to work well with [KaTeX](https://github.com/Khan/KaTeX). See this [related blog post from a Hugo user](http://nosubstance.me/post/a-great-toolset-for-static-blogging/). +{{% /note %}} + +After enabling MathJax, any math entered between proper markers (see the [MathJax documentation][mathjaxdocs]) will be processed and typeset in the web page. One issue that comes up, however, with Markdown is that the underscore character (`_`) is interpreted by Markdown as a way to wrap text in `emph` blocks while LaTeX (MathJax) interprets the underscore as a way to create a subscript. This "double speak" of the underscore can result in some unexpected and unwanted behavior. + +### Solution + +There are multiple ways to remedy this problem. One solution is to simply escape each underscore in your math code by entering `\_` instead of `_`. This can become quite tedious if the equations you are entering are full of subscripts. + +Another option is to tell Markdown to treat the MathJax code as verbatim code and not process it. One way to do this is to wrap the math expression inside a `
` `
` block. Markdown would ignore these sections and they would get passed directly on to MathJax and processed correctly. This works great for display style mathematics, but for inline math expressions the line break induced by the `
` is not acceptable. The syntax for instructing Markdown to treat inline text as verbatim is by wrapping it in backticks (`` ` ``). You might have noticed, however, that the text included in between backticks is rendered differently than standard text (on this site these are items highlighted in red). To get around this problem, we could create a new CSS entry that would apply standard styling to all inline verbatim text that includes MathJax code. Below I will show the HTML and CSS source that would accomplish this (note this solution was adapted from [this blog post](http://doswa.com/2011/07/20/mathjax-in-markdown.html)---all credit goes to the original author). + +{{< code file="mathjax-markdown-solution.html" >}} + + + +{{< /code >}} + + + +As before, this content should be included in the HTML source of each page that will be using MathJax. The next code snippet contains the CSS that is used to have verbatim MathJax blocks render with the same font style as the body of the page. + +{{< code file="mathjax-style.css" >}} +code.has-jax { + font: inherit; + font-size: 100%; + background: inherit; + border: inherit; + color: #515151; +} +{{< /code >}} + +In the CSS snippet, notice the line `color: #515151;`. `#515151` is the value assigned to the `color` attribute of the `body` class in my CSS. In order for the equations to fit in with the body of a web page, this value should be the same as the color of the body. + +### Usage + +With this setup, everything is in place for a natural usage of MathJax on pages generated using Hugo. In order to include inline mathematics, just put LaTeX code in between `` `$ TeX Code $` `` or `` `\( TeX Code \)` ``. To include display style mathematics, just put LaTeX code in between `
$$TeX Code$$
`. All the math will be properly typeset and displayed within your Hugo generated web page! + +## Additional Formats Through External Helpers + +Hugo has new concept called _external helpers_. It means that you can write your content using [Asciidoc][ascii], [reStructuredText][rest]. If you have files with associated extensions, Hugo will call external commands to generate the content. ([See the Hugo source code for external helpers][helperssource].) + +For example, for Asciidoc files, Hugo will try to call the `asciidoctor` or `asciidoc` command. This means that you will have to install the associated tool on your machine to be able to use these formats. ([See the Asciidoctor docs for installation instructions](http://asciidoctor.org/docs/install-toolchain/)). + +To use these formats, just use the standard extension and the front matter exactly as you would do with natively supported `.md` files. + +{{% warning "Performance of External Helpers" %}} +Because additional formats are external commands generation performance will rely heavily on the performance of the external tool you are using. As this feature is still in its infancy, feedback is welcome. +{{% /warning %}} + +## Learn Markdown + +Markdown syntax is simple enough to learn in a single sitting. The following are excellent resources to get you up and running: + +* [Daring Fireball: Markdown, John Gruber (Creator of Markdown)][fireball] +* [Markdown Cheatsheet, Adam Pritchard][mdcheatsheet] +* [Markdown Tutorial (Interactive), Garen Torikian][mdtutorial] + +[`emojify` function]: /functions/emojify/ +[ascii]: http://asciidoc.org/ +[bfconfig]: /getting-started/configuration/#configuring-blackfriday-rendering +[blackfriday]: https://github.com/russross/blackfriday +[mmark]: https://github.com/miekg/mmark +[config]: /getting-started/configuration/ +[developer tools]: /tools/ +[emojis]: https://www.webpagefx.com/tools/emoji-cheat-sheet/ +[fireball]: https://daringfireball.net/projects/markdown/ +[gfmtasks]: https://guides.github.com/features/mastering-markdown/#syntax +[helperssource]: https://github.com/gohugoio/hugo/blob/77c60a3440806067109347d04eb5368b65ea0fe8/helpers/general.go#L65 +[hl]: /tools/syntax-highlighting/ +[hlsc]: /content-management/shortcodes/#highlight +[hugocss]: /css/style.css +[ietf]: https://tools.ietf.org/html/ +[mathjaxdocs]: https://docs.mathjax.org/en/latest/ +[mdcheatsheet]: https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet +[mdtutorial]: http://www.markdowntutorial.com/ +[Miek Gieben's website]: https://miek.nl/2016/March/05/mmark-syntax-document/ +[mmark]: https://github.com/miekg/mmark +[mmarkgh]: https://github.com/miekg/mmark/wiki/Syntax +[org]: http://orgmode.org/ +[Pygments]: http://pygments.org/ +[rest]: http://docutils.sourceforge.net/rst.html +[sc]: /content-management/shortcodes/ +[sct]: /templates/shortcode-templates/ diff --git a/content/content-management/front-matter.md b/content/content-management/front-matter.md new file mode 100644 index 000000000..22c1eb110 --- /dev/null +++ b/content/content-management/front-matter.md @@ -0,0 +1,198 @@ +--- +title: Front Matter +linktitle: +description: Hugo allows you to add front matter in yaml, toml, or json to your content files. +date: 2017-01-09 +publishdate: 2017-01-09 +lastmod: 2017-02-24 +categories: [content management] +#tags: ["front matter", "yaml", "toml", "json", "metadata", "archetypes"] +menu: + docs: + parent: "content-management" + weight: 30 +weight: 30 #rem +draft: false +aliases: [/content/front-matter/] +toc: true +--- + +**Front matter** allows you to keep metadata attached to an instance of a [content type][]---i.e., embedded inside a content file---and is one of the many features that gives Hugo its strength. + +## Front Matter Formats + +Hugo supports three formats for front matter, each with their own identifying tokens. + +TOML +: identified by opening and closing `+++`. + +YAML +: identified by opening and closing `---`. + +JSON +: a single JSON object surrounded by '`{`' and '`}`', followed by a new line. + +### TOML Example + +``` ++++ +title = "spf13-vim 3.0 release and new website" +description = "spf13-vim is a cross platform distribution of vim plugins and resources for Vim." +tags = [ ".vimrc", "plugins", "spf13-vim", "vim" ] +date = "2012-04-06" +categories = [ + "Development", + "VIM" +] +slug = "spf13-vim-3-0-release-and-new-website" ++++ +``` + +### YAML Example + +``` +--- +title: "spf13-vim 3.0 release and new website" +description: "spf13-vim is a cross platform distribution of vim plugins and resources for Vim." +tags: [ ".vimrc", "plugins", "spf13-vim", "vim" ] +lastmod: 2015-12-23 +date: "2012-04-06" +categories: + - "Development" + - "VIM" +slug: "spf13-vim-3-0-release-and-new-website" +--- +``` + +### JSON Example + +``` +{ + "title": "spf13-vim 3.0 release and new website", + "description": "spf13-vim is a cross platform distribution of vim plugins and resources for Vim.", + "tags": [ ".vimrc", "plugins", "spf13-vim", "vim" ], + "date": "2012-04-06", + "categories": [ + "Development", + "VIM" + ], + "slug": "spf13-vim-3-0-release-and-new-website" +} +``` + +## Front Matter Variables + +### Predefined + +There are a few predefined variables that Hugo is aware of. See [Page Variables][pagevars] for how to call many of these predefined variables in your templates. + +`aliases` +: an array of one or more aliases (e.g., old published paths of renamed content) that will be created in the output directory structure . See [Aliases][aliases] for details. + +`date` +: the datetime at which the content was created; note this value is auto-populated according to Hugo's built-in [archetype][]. + +`description` +: the description for the content. + +`draft` +: if `true`, the content will not be rendered unless the `--buildDrafts` flag is passed to the `hugo` command. + +`expiryDate` +: the datetime at which the content should no longer be published by Hugo; expired content will not be rendered unless the `--buildExpired` flag is passed to the `hugo` command. + +`isCJKLanguage` +: if `true`, Hugo will explicitly treat the content as a CJK language; both `.Summary` and `.WordCount` work properly in CJK languages. + +`keywords` +: the meta keywords for the content. + +`layout` +: the layout Hugo should select from the [lookup order][lookup] when rendering the content. If a `type` is not specified in the front matter, Hugo will look for the layout of the same name in the layout directory that corresponds with a content's section. See ["Defining a Content Type"][definetype] + +`lastmod` +: the datetime at which the content was last modified. + +`linkTitle` +: used for creating links to content; if set, Hugo defaults to using the `linktitle` before the `title`. Hugo can also [order lists of content by `linktitle`][bylinktitle]. + +`markup` +: **experimental**; specify `"rst"` for reStructuredText (requires`rst2html`) or `"md"` (default) for Markdown. + +`outputs` +: allows you to specify output formats specific to the content. See [output formats][outputs]. + +`publishDate` +: if in the future, content will not be rendered unless the `--buildFuture` flag is passed to `hugo`. + +`slug` +: appears as the tail of the output URL. A value specified in front matter will override the segment of the URL based on the filename. + +`taxonomies` +: these will use the field name of the plural form of the index; see the `tags` and `categories` in the above front matter examples. + +`title` +: the title for the content. + +`type` +: the type of the content; this value will be automatically derived from the directory (i.e., the [section][]) if not specified in front matter. + +`url` +: the full path to the content from the web root. It makes no assumptions about the path of the content file. It also ignores any language prefixes of +the multilingual feature. + +`weight` +: used for [ordering your content in lists][ordering]. + +{{% note "Hugo's Default URL Destinations" %}} +If neither `slug` nor `url` is present and [permalinks are not configured otherwise in your site `config` file](/content-management/urls/#permalinks), Hugo will use the filename of your content to create the output URL. See [Content Organization](/content-management/organization) for an explanation of paths in Hugo and [URL Management](/content-management/urls/) for ways to customize Hugo's default behaviors. +{{% /note %}} + +### User-Defined + +You can add fields to your front matter arbitrarily to meet your needs. These user-defined key-values are placed into a single `.Params` variable for use in your templates. + +The following fields can be accessed via `.Params.include_toc` and `.Params.show_comments`, respectively. The [Variables][] section provides more information on using Hugo's page- and site-level variables in your templates. + +``` +include_toc: true +show_comments: false +``` + +These two user-defined fields can then be accessed via `.Params.include_toc` and `.Params.show_comments`, respectively. The [Variables][variables] section provides more information on using Hugo's page- and site-level variables in your templates. + + +## Order Content Through Front Matter + +You can assign content-specific `weight` in the front matter of your content. These values are especially useful for [ordering][ordering] in list views. You can use `weight` for ordering of content and the convention of [`_weight`][taxweight] for ordering content within a taxonomy. See [Ordering and Grouping Hugo Lists][lists] to see how `weight` can be used to organize your content in list views. + +## Override Global Markdown Configuration + +It's possible to set some options for Markdown rendering in a content's front matter as an override to the [BlackFriday rendering options set in your project configuration][config]. + +## Front Matter Format Specs + +* [TOML Spec][toml] +* [YAML Spec][yaml] +* [JSON Spec][json] + +[variables]: /variables/ +[aliases]: /content-management/urls/#aliases/ +[archetype]: /content-management/archetypes/ +[bylinktitle]: /templates/lists/#by-link-title +[config]: /getting-started/configuration/ "Hugo documentation for site configuration" +[content type]: /content-management/types/ +[contentorg]: /content-management/organization/ +[definetype]: /content-management/types/#defining-a-content-type "Learn how to specify a type and a layout in a content's front matter" +[json]: /documents/ecma-404-json-spec.pdf "Specification for JSON, JavaScript Object Notation" +[lists]: /templates/lists/#ordering-content "See how to order content in list pages; for example, templates that look to specific _index.md for content and front matter." +[lookup]: /templates/lookup-order/ "Hugo traverses your templates in a specific order when rendering content to allow for DRYer templating." +[ordering]: /templates/lists/ "Hugo provides multiple ways to sort and order your content in list templates" +[outputs]: /templates/output-formats/ "With the release of v22, you can output your content to any text format using Hugo's familiar templating" +[pagevars]: /variables/page/ +[section]: /content-management/sections/ +[taxweight]: /content-management/taxonomies/ +[toml]: https://github.com/toml-lang/toml "Specification for TOML, Tom's Obvious Minimal Language" +[urls]: /content-management/urls/ +[variables]: /variables/ +[yaml]: http://yaml.org/spec/ "Specification for YAML, YAML Ain't Markup Language" diff --git a/content/content-management/menus.md b/content/content-management/menus.md new file mode 100644 index 000000000..364867c67 --- /dev/null +++ b/content/content-management/menus.md @@ -0,0 +1,179 @@ +--- +title: Menus +linktitle: Menus +description: Hugo has a simple yet powerful menu system. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-03-31 +categories: [content management] +#tags: [menus] +draft: false +menu: + docs: + parent: "content-management" + weight: 120 +weight: 120 #rem +aliases: [/extras/menus/] +toc: true +--- + +{{% note "Lazy Blogger"%}} +If all you want is a simple menu for your sections, see the ["Section Menu for Lazy Bloggers" in Menu Templates](/templates/menu-templates/#section-menu-for-lazy-blogger). +{{% /note %}} + +You can do this: + +* Place content in one or many menus +* Handle nested menus with unlimited depth +* Create menu entries without being attached to any content +* Distinguish active element (and active branch) + +## What is a Menu in Hugo? + +A **menu** is a named array of menu entries accessible by name via the [`.Site.Menus` site variable][sitevars]. For example, you can access your site's `main` menu via `.Site.Menus.main`. + +{{% note "Menus on Multilingual Sites" %}} +If you make use of the [multilingual feature](/content-management/multilingual/), you can define language-independent menus. +{{% /note %}} + +A menu entry has the following properties (i.e., variables) available to it: + +`.URL` +: string + +`.Name` +: string + +`.Menu` +: string + +`.Identifier` +: string + +`.Pre` +: template.HTML + +`.Post` +: template.HTML + +`.Weight` +: int + +`.Parent` +: string + +`.Children` +: Menu + +Note that menus also have the following functions available as well: + +`.HasChildren` +: boolean + +Additionally, there are some relevant functions available to menus on a page: + +`.IsMenuCurrent` +: (menu string, menuEntry *MenuEntry ) boolean + +`.HasMenuCurrent` +: (menu string, menuEntry *MenuEntry) boolean + +## Add content to menus + +Hugo allows you to add content to a menu via the content's [front matter](/content-management/front-matter/). + +### Simple + +If all you need to do is add an entry to a menu, the simple form works well. + +#### A Single Menu + +``` +--- +menu: "main" +--- +``` + +#### Multiple Menus + +``` +--- +menu: ["main", "footer"] +--- +``` + +#### Advanced + + +``` +--- +menu: + docs: + parent: 'extras' + weight: 20 +--- +``` + +## Add Non-content Entries to a Menu + +You can also add entries to menus that aren’t attached to a piece of content. This takes place in your Hugo project's [`config` file][config]. + +Here’s an example snippet pulled from a `config.toml`: + +{{< code file="config.toml" >}} +[[menu.main]] + name = "about hugo" + pre = "" + weight = -110 + identifier = "about" + url = "/about/" +[[menu.main]] + name = "getting started" + pre = "" + weight = -100 + url = "/getting-started/" +{{< /code >}} + +Here's the equivalent snippet in a `config.yaml`: + +{{< code file="config.yml" >}} +--- +menu: + docs: + - Name: "about hugo" + Pre: "" + Weight: -110 + Identifier: "about" + URL: "/about/" + - Name: "getting started" + Pre: "" + Weight: -100 + URL: "/getting-started/" +--- +{{< /code >}} + +{{% note %}} +The URLs must be relative to the context root. If the `baseURL` is `https://example.com/mysite/`, then the URLs in the menu must not include the context root `mysite`. Using an absolute URL will overide the baseURL. If the value used for `URL` in the above example is `https://subdomain.example.com/`, the output will be `https://subdomain.example.com`. +{{% /note %}} + +## Nesting + +All nesting of content is done via the `parent` field. + +The parent of an entry should be the identifier of another entry. The identifier should be unique (within a menu). + +The following order is used to determine an Identifier: + +`.Name > .LinkTitle > .Title` + +This means that `.Title` will be used unless `.LinkTitle` is present, etc. In practice, `.Name` and `.Identifier` are only used to structure relationships and therefore never displayed. + +In this example, the top level of the menu is defined in your [site `config` file][config]). All content entries are attached to one of these entries via the `.Parent` field. + +## Render Menus + +See [Menu Templates](/templates/menu-templates/) for information on how to render your site menus within your templates. + +[config]: /getting-started/configuration/ +[multilingual]: /content-management/multilingual/ +[sitevars]: /variables/ diff --git a/content/content-management/multilingual.md b/content/content-management/multilingual.md new file mode 100644 index 000000000..958634c58 --- /dev/null +++ b/content/content-management/multilingual.md @@ -0,0 +1,294 @@ +--- +title: Multilingual Mode +linktitle: Multilingual and i18n +description: Hugo supports the creation of websites with multiple languages side by side. +date: 2017-01-10 +publishdate: 2017-01-10 +lastmod: 2017-01-10 +categories: [content management] +#tags: [multilingual,i18n, internationalization] +menu: + docs: + parent: "content-management" + weight: 150 +weight: 150 #rem +draft: false +aliases: [/content/multilingual/,/content-management/multilingual/] +toc: true +--- + +You should define the available languages in a `Languages` section in your site configuration. + +## Configure Languages + +The following is an example of a TOML site configuration for a multilingual Hugo project: + +{{< code file="config.toml" download="config.toml" >}} +DefaultContentLanguage = "en" +copyright = "Everything is mine" + +[params.navigation] +help = "Help" + +[Languages] +[Languages.en] +title = "My blog" +weight = 1 +[Languages.en.params] +linkedin = "english-link" + +[Languages.fr] +copyright = "Tout est à moi" +title = "Mon blog" +weight = 2 +[Languages.fr.params] +linkedin = "lien-francais" +[Languages.fr.navigation] +help = "Aide" +{{< /code >}} + +Anything not defined in a `[Languages]` block will fall back to the global +value for that key (e.g., `copyright` for the English [`en`] language). + +With the configuration above, all content, sitemap, RSS feeds, paginations, +and taxonomy pages will be rendered below `/` in English (your default content language) and then below `/fr` in French. + +When working with front matter `Params` in [single page templates][singles], omit the `params` in the key for the translation. + +If you want all of the languages to be put below their respective language code, enable `defaultContentLanguageInSubdir: true`. + +Only the obvious non-global options can be overridden per language. Examples of global options are `baseURL`, `buildDrafts`, etc. + +## Taxonomies and Blackfriday + +Taxonomies and [Blackfriday configuration][config] can also be set per language: + + +{{< code file="bf-config.toml" >}} +[Taxonomies] +tag = "tags" + +[blackfriday] +angledQuotes = true +hrefTargetBlank = true + +[Languages] +[Languages.en] +weight = 1 +title = "English" +[Languages.en.blackfriday] +angledQuotes = false + +[Languages.fr] +weight = 2 +title = "Français" +[Languages.fr.Taxonomies] +plaque = "plaques" +{{< /code >}} + +## Translate Your Content + +Translated articles are identified by the name of the content file. + +### Examples of Translated Articles + +1. `/content/about.en.md` +2. `/content/about.fr.md` + +In this example, the `about.md` will be assigned the configured `defaultContentLanguage`. + +1. `/content/about.md` +2. `/content/about.fr.md` + +This way, you can slowly start to translate your current content without having to rename everything. If left unspecified, the default value for `defaultContentLanguage` is `en`. + +By having the same *base filename*, the content pieces are linked together as translated pieces. + +If you need distinct URLs per language, you can set the slug in the non-default language file. For example, you can define a custom slug for a French translation in the front matter of `content/about.fr.md` as follows: + +``` +slug: "a-propos" + +``` + +At render, Hugo will build both `/about/` and `/a-propos/` as properly linked translated pages. + +{{%note %}} +Hugo currently uses the base filename as the translation key, which can be an issue with identical filenames in different sections. +We will fix this in https://github.com/gohugoio/hugo/issues/2699 +{{% /note %}} +{{< todo >}}Rewrite/remove the above one issue is fixed.{{< /todo >}} + +## Link to Translated Content + +To create a list of links to translated content, use a template similar to the following: + +{{< code file="layouts/partials/i18nlist.html" >}} +{{ if .IsTranslated }} +

{{ i18n "translations" }}

+ +{{ end }} +{{< /code >}} + +The above can be put in a `partial` (i.e., inside `layouts/partials/`) and included in any template, be it for a [single content page][contenttemplate] or the [homepage][]. It will not print anything if there are no translations for a given page, or if there are translations---in the case of the homepage, section listing, etc.---a site with only render one language. + +The above also uses the [`i18n` function][i18func] described in the next section. + +## Translation of Strings + +Hugo uses [go-i18n][] to support string translations. [See the project's source repository][go-i18n-source] to find tools that will help you manage your translation workflows. + +Translations are collected from the `themes//i18n/` folder (built into the theme), as well as translations present in `i18n/` at the root of your project. In the `i18n`, the translations will be merged and take precedence over what is in the theme folder. Language files should be named according to [RFC 5646][] with names such as `en-US.toml`, `fr.toml`, etc. + +From within your templates, use the `i18n` function like this: + +``` +{{ i18n "home" }} +``` + +This uses a definition like this one in `i18n/en-US.toml`: + +``` +[home] +other = "Home" +``` + +Often you will want to use to the page variables in the translations strings. To do that, pass on the "." context when calling `i18n`: + +``` +{{ i18n "wordCount" . }} +``` + +This uses a definition like this one in `i18n/en-US.toml`: + +``` +[wordCount] +other = "This article has {{ .WordCount }} words." +``` +An example of singular and plural form: + +``` +[readingTime] +one = "One minute read" +other = "{{.Count}} minutes read" +``` +And then in the template: + +``` +{{ i18n "readingTime" .ReadingTime }} +``` +To track down missing translation strings, run Hugo with the `--i18n-warnings` flag: + +``` + hugo --i18n-warnings | grep i18n +i18n|MISSING_TRANSLATION|en|wordCount +``` + +## Customize Dates + +At the time of this writing, Golang does not yet have support for internationalized locales, but if you do some work, you can simulate it. For example, if you want to use French month names, you can add a data file like ``data/mois.yaml`` with this content: + +~~~yaml +1: "janvier" +2: "février" +3: "mars" +4: "avril" +5: "mai" +6: "juin" +7: "juillet" +8: "août" +9: "septembre" +10: "octobre" +11: "novembre" +12: "décembre" +~~~ + +... then index the non-English date names in your templates like so: + +~~~html + +~~~ + +This technique extracts the day, month and year by specifying ``.Date.Day``, ``.Date.Month``, and ``.Date.Year``, and uses the month number as a key, when indexing the month name data file. + +## Menus + +You can define your menus for each language independently. The [creation of a menu][menus] works analogous to earlier versions of Hugo, except that they have to be defined in their language-specific block in the configuration file: + +``` +defaultContentLanguage = "en" + +[languages.en] +weight = 0 +languageName = "English" + +[[languages.en.menu.main]] +url = "/" +name = "Home" +weight = 0 + + +[languages.de] +weight = 10 +languageName = "Deutsch" + +[[languages.de.menu.main]] +url = "/" +name = "Startseite" +weight = 0 +``` + +The rendering of the main navigation works as usual. `.Site.Menus` will just contain the menu of the current language. Pay attention to the generation of the menu links. `absLangURL` takes care that you link to the correct locale of your website. Otherwise, both menu entries would link to the English version as the default content language that resides in the root directory. + +``` +
    + {{- $currentPage := . -}} + {{ range .Site.Menus.main -}} +
  • + {{ .Name }} +
  • + {{- end }} +
+ +``` + +## Missing translations + +If a string does not have a translation for the current language, Hugo will use the value from the default language. If no default value is set, an empty string will be shown. + +While translating a Hugo website, it can be handy to have a visual indicator of missing translations. The [`enableMissingTranslationPlaceholders` configuration option][config] will flag all untranslated strings with the placeholder `[i18n] identifier`, where `identifier` is the id of the missing translation. + +{{% note %}} +Hugo will generate your website with these missing translation placeholders. It might not be suited for production environments. +{{% /note %}} + +## Multilingual Themes support + +To support Multilingual mode in your themes, some considerations must be taken for the URLs in the templates. If there is more than one language, URLs must meet the following criteria: + +* Come from the built-in `.Permalink` or `.URL` +* Be constructed with + * The [`relLangURL` template function][rellangurl] or the [`absLangURL` template function][abslangurl] **OR** + * Prefixed with `{{ .LanguagePrefix }}` + +If there is more than one language defined, the `LanguagePrefix` variable will equal `/en` (or whatever your `CurrentLanguage` is). If not enabled, it will be an empty string and is therefore harmless for single-language Hugo websites. + +[abslangurl]: /functions/abslangurl +[config]: /getting-started/configuration/ +[contenttemplate]: /templates/single-page-templates/ +[go-i18n-source]: https://github.com/nicksnyder/go-i18n +[go-i18n]: https://github.com/nicksnyder/go-i18n +[homepage]: /templates/homepage/ +[i18func]: /functions/i18n/ +[menus]: /content-management/menus/ +[rellangurl]: /functions/rellangurl +[RFC 5646]: https://tools.ietf.org/html/rfc5646 +[singles]: /templates/single-page-templates/ diff --git a/content/content-management/organization.md b/content/content-management/organization.md new file mode 100644 index 000000000..95fd3562a --- /dev/null +++ b/content/content-management/organization.md @@ -0,0 +1,241 @@ +--- +title: Content Organization +linktitle: Organization +description: Hugo assumes that the same structure that works to organize your source content is used to organize the rendered site. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +categories: [content management,fundamentals] +#tags: [sections,content,organization] +menu: + docs: + parent: "content-management" + weight: 10 +weight: 10 #rem +draft: false +aliases: [/content/sections/] +toc: true +--- + +{{% note %}} +This section is not updated with the new nested sections support in Hugo 0.24, see https://github.com/gohugoio/hugoDocs/issues/36 +{{% /note %}} +{{% todo %}} +See above +{{% /todo %}} + +## Organization of Content Source + +In Hugo, your content should be organized in a manner that reflects the rendered website. + +While Hugo supports content nested at any level, the top levels (i.e. `content/`) are special in Hugo and are considered the content [sections][]. Without any additional configuration, the following will just work: + +``` +. +└── content + └── about + | └── _index.md // <- https://example.com/about/ + ├── post + | ├── firstpost.md // <- https://example.com/post/firstpost/ + | ├── happy + | | └── ness.md // <- https://example.com/post/happy/ness/ + | └── secondpost.md // <- https://example.com/post/secondpost/ + └── quote + ├── first.md // <- https://example.com/quote/first/ + └── second.md // <- https://example.com/quote/second/ +``` + +## Path Breakdown in Hugo + +The following demonstrates the relationships between your content organization and the output URL structure for your Hugo website when it renders. These examples assume you are [using pretty URLs][pretty], which is the default behavior for Hugo. The examples also assume a key-value of `baseurl = "https://example.com"` in your [site's configuration file][config]. + +### Index Pages: `_index.md` + +`_index.md` has a special role in Hugo. It allows you to add front matter and content to your [list templates][lists] as of v0.18. These templates include those for [section templates][], [taxonomy templates][], [taxonomy terms templates][], and your [homepage template][]. In your templates, you can grab information from `_index.md` using the [`.Site.GetPage` function][getpage]. + +You can keep one `_index.md` for your homepage and one in each of your content sections, taxonomies, and taxonomy terms. The following shows typical placement of an `_index.md` that would contain content and front matter for a `posts` section list page on a Hugo website: + + +``` +. url +. ⊢--^-⊣ +. path slug +. ⊢--^-⊣⊢---^---⊣ +. filepath +. ⊢------^------⊣ +content/posts/_index.md +``` + +At build, this will output to the following destination with the associated values: + +``` + + url ("/posts/") + ⊢-^-⊣ + baseurl section ("posts") +⊢--------^---------⊣⊢-^-⊣ + permalink +⊢----------^-------------⊣ +https://example.com/posts/index.html +``` + +### Single Pages in Sections + +Single content files in each of your sections are going to be rendered as [single page templates][singles]. Here is an example of a single `post` within `posts`: + + +``` + path ("posts/my-first-hugo-post.md") +. ⊢-----------^------------⊣ +. section slug +. ⊢-^-⊣⊢--------^----------⊣ +content/posts/my-first-hugo-post.md +``` + +At the time Hugo builds your site, the content will be output to the following destination: + +``` + + url ("/posts/my-first-hugo-post/") + ⊢------------^----------⊣ + baseurl section slug +⊢--------^--------⊣⊢-^--⊣⊢-------^---------⊣ + permalink +⊢--------------------^---------------------⊣ +https://example.com/posts/my-first-hugo-post/index.html +``` + +### Section with Nested Directories + +To continue the example, the following demonstrates destination paths for a file located at `content/events/chicago/lollapalooza.md` in the same site: + + +``` + section + ⊢--^--⊣ + url + ⊢-------------^------------⊣ + + baseURL path slug +⊢--------^--------⊣ ⊢------^-----⊣⊢----^------⊣ + permalink +⊢----------------------^-----------------------⊣ +https://example.com/events/chicago/lollapalooza/ +``` + +{{% note %}} +As of v0.20, Hugo does not recognize nested sections. While you can nest as many content *directories* as you'd like, any child directory of a section will still be considered the same section as that of its parents. Therefore, in the above example, `{{.Section}}` for `lollapalooza.md` is `events` and *not* `chicago`. See the [related issue on GitHub](https://github.com/gohugoio/hugo/issues/465). +{{% /note %}} + +## Paths Explained + +The following concepts will provide more insight into the relationship between your project's organization and the default behaviors of Hugo when building the output website. + +### `section` + +A default content type is determined by a piece of content's section. `section` is determined by the location within the project's `content` directory. `section` *cannot* be specified or overridden in front matter. + +### `slug` + +A content's `slug` is either `name.extension` or `name/`. The value for `slug` is determined by + +* the name of the content file (e.g., `lollapalooza.md`) OR +* front matter overrides + +### `path` + +A content's `path` is determined by the section's path to the file. The file `path` + +* is based on the path to the content's location AND +* does not include the slug + +### `url` + +The `url` is the relative URL for the piece of content. The `url` + +* is based on the content's location within the directory structure OR +* is defined in front matter and *overrides all the above* + +## Override Destination Paths via Front Matter + +Hugo believes that you organize your content with a purpose. The same structure that works to organize your source content is used to organize the rendered site. As displayed above, the organization of the source content will be mirrored in the destination. + +There are times where you may need more control over your content. In these cases, there are fields that can be specified in the front matter to determine the destination of a specific piece of content. + +The following items are defined in this order for a specific reason: items explained further down in the list will override earlier items, and not all of these items can be defined in front matter: + +### `filename` + +This isn't in the front matter, but is the actual name of the file minus the extension. This will be the name of the file in the destination (e.g., `content/posts/my-post.md` becomes `example.com/posts/my-post/`). + +### `slug` + +When defined in the front matter, the `slug` can take the place of the filename for the destination. + +{{< code file="content/posts/old-post.md" >}} +--- +title: New Post +slug: "new-post" +--- +{{< /code >}} + +This will render to the following destination according to Hugo's default behavior: + +``` +example.com/posts/new-post/ +``` + +### `section` + +`section` is determined by a content's location on disk and *cannot* be specified in the front matter. See [sections][] for more information. + +### `type` + +A content's `type` is also determined by its location on disk but, unlike `section`, it *can* be specified in the front matter. See [types][]. This can come in especially handy when you want a piece of content to render using a different layout. In the following example, you can create a layout at `layouts/new/mylayout.html` that Hugo will use to render this piece of content, even in the midst of many other posts. + +{{< code file="content/posts/my-post.md" >}} +--- +title: My Post +type: new +layout: mylayout +--- +{{< /code >}} + + + + + +### `url` + +A complete URL can be provided. This will override all the above as it pertains to the end destination. This must be the path from the baseURL (starting with a `/`). `url` will be used exactly as it provided in the front matter and will ignore the `--uglyURLs` setting in your site configuration: + +{{< code file="content/posts/old-url.md" >}} +--- +title: Old URL +url: /blog/new-url/ +--- +{{< /code >}} + +Assuming your `baseURL` is [configured][config] to `https://example.com`, the addition of `url` to the front matter will make `old-url.md` render to the following destination: + +``` +https://example.com/blog/new-url/ +``` + +You can see more information on how to control output paths in [URL Management][urls]. + +[config]: /getting-started/configuration/ +[formats]: /content-management/formats/ +[front matter]: /content-management/front-matter/ +[getpage]: /functions/getpage/ +[homepage template]: /templates/homepage/ +[homepage]: /templates/homepage/ +[lists]: /templates/lists/ +[pretty]: /content-management/urls/#pretty-urls +[section templates]: /templates/section-templates/ +[sections]: /content-management/sections/ +[singles]: /templates/single-page-templates/ +[taxonomy templates]: /templates/taxonomy-templates/ +[taxonomy terms templates]: /templates/taxonomy-templates/ +[types]: /content-management/types/ +[urls]: /content-management/urls/ diff --git a/content/content-management/sections.md b/content/content-management/sections.md new file mode 100644 index 000000000..dcb0025d1 --- /dev/null +++ b/content/content-management/sections.md @@ -0,0 +1,73 @@ +--- +title: Content Sections +linktitle: Sections +description: Hugo supports content sections, which according to Hugo's default behavior, will reflect the structure of the rendered website. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +categories: [content management] +#tags: [lists,sections,content types,organization] +menu: + docs: + parent: "content-management" + weight: 50 +weight: 50 #rem +draft: false +aliases: [/content/sections/] +toc: true +--- + +{{% note %}} +This section is not updated with the new nested sections support in Hugo 0.24, see https://github.com/gohugoio/hugoDocs/issues/36 +{{% /note %}} +{{% todo %}} +See above +{{% /todo %}} + +Hugo believes that you organize your content with a purpose. The same structure that works to organize your source content is used to organize the rendered site (see [directory structure][]). + +Following this pattern, Hugo uses the top level of your content organization as the content **section**. + +The following example shows a content directory structure for a website that has three sections: "authors," "events," and "posts": + +``` +. +└── content + ├── authors + | ├── _index.md // <- example.com/authors/ + | ├── john-doe.md // <- example.com/authors/john-doe/ + | └── jane-doe.md // <- example.com/authors/jane-doe/ + └── events + | ├── _index.md // <- example.com/events/ + | ├── event-1.md // <- example.com/events/event-1/ + | ├── event-2.md // <- example.com/events/event-2/ + | └── event-3.md // <- example.com/events/event-3/ + └── posts + | ├── _index.md // <- example.com/posts/ + | ├── event-1.md // <- example.com/posts/event-1/ + | ├── event-2.md // <- example.com/posts/event-2/ + | ├── event-3.md // <- example.com/posts/event-3/ + | ├── event-4.md // <- example.com/posts/event-4/ + | └── event-5.md // <- example.com/posts/event-5/ +``` + +## Content Section Lists + +Hugo will automatically create pages for each section root that list all of the content in that section. See the documentation on [section templates][] for details on customizing the way these pages are rendered. + +As of Hugo v0.18, section pages can also have a content file and front matter. These section content files must be placed in their corresponding section folder and named `_index.md` in order for Hugo to correctly render the front matter and content. + +{{% warning "`index.md` vs `_index.md`" %}} +Hugo themes developed before v0.18 often used an `index.md`(i.e., without the leading underscore [`_`]) in a content section as a hack to emulate the behavior of `_index.md`. The hack may work...*sometimes*; however, the order of page rendering can be unpredictable in Hugo. What works now may fail to render appropriately as your site grows. It is **strongly advised** to use `_index.md` as content for your section index pages. **Note:** `_index.md`'s layout, as representative of a section, is a [list page template](/templates/section-templates/) and *not* a [single page template](/templates/single-page-templates/). If you want to alter the new default behavior for `_index.md`, configure `disableKinds` accordingly in your [site's configuration](/getting-started/configuration/). +{{% /warning %}} + +## Content *Section* vs Content *Type* + +By default, everything created within a section will use the [content type][] that matches the section name. For example, Hugo will assume that `posts/post-1.md` has a `posts` content type. If you are using an [archetype][] for your posts section, Hugo will generate front matter according to what it finds in `archetypes/posts.md`. + +[archetype]: /content-management/archetypes/ +[content type]: /content-management/types/ +[directory structure]: /getting-started/directory-structure/ +[section templates]: /templates/section-templates/ + + diff --git a/content/content-management/shortcodes.md b/content/content-management/shortcodes.md new file mode 100644 index 000000000..a4424a996 --- /dev/null +++ b/content/content-management/shortcodes.md @@ -0,0 +1,395 @@ +--- +title: Shortcodes +linktitle: +description: Shortcodes are simple snippets inside your content files calling built-in or custom templates. +godocref: +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-03-31 +menu: + docs: + parent: "content-management" + weight: 35 +weight: 35 #rem +categories: [content management] +#tags: [markdown,content,shortcodes] +draft: false +aliases: [/extras/shortcodes/] +toc: true +--- + +## What a Shortcode is + +Hugo loves Markdown because of its simple content format, but there are times when Markdown falls short. Often, content authors are forced to add raw HTML (e.g., video ``) to Markdown content. We think this contradicts the beautiful simplicity of Markdown's syntax. + +Hugo created **shortcodes** to circumvent these limitations. + +A shortcode is a simple snippet inside a content file that Hugo will render using a predefined template. Note that shortcodes will not work in template files. If you need the type of drop-in functionality that shortcodes provide but in a template, you most likely want a [partial template][partials] instead. + +In addition to cleaner Markdown, shortcodes can be updated any time to reflect new classes, techniques, or standards. At the point of site generation, Hugo shortcodes will easily merge in your changes. You avoid a possibly complicated search and replace operation. + +## Use Shortcodes + +In your content files, a shortcode can be called by calling `{{%/* shortcodename parameters */%}}`. Shortcode parameters are space delimited, and parameters with internal spaces can be quoted. + +The first word in the shortcode declaration is always the name of the shortcode. Parameters follow the name. Depending upon how the shortcode is defined, the parameters may be named, positional, or both, although you can't mix parameter types in a single call. The format for named parameters models that of HTML with the format `name="value"`. + +Some shortcodes use or require closing shortcodes. Again like HTML, the opening and closing shortcodes match (name only) with the closing declaration, which is prepended with a slash. + +Here are two examples of paired shortcodes: + +``` +{{%/* mdshortcode */%}}Stuff to `process` in the *center*.{{%/* /mdshortcode */%}} +``` + +``` +{{}} A bunch of code here {{}} +``` + +The examples above use two different delimiters, the difference being the `%` character in the first and the `<>` characters in the second. + +### Shortcodes with Markdown + +The `%` character indicates that the shortcode's inner content---called in the [shortcode template][sctemps] with the [`.Inner` variable][scvars]---needs further processing by the page's rendering processor (i.e. markdown via Blackfriday). In the following example, Blackfriday would convert `**World**` to `World`: + +``` +{{%/* myshortcode */%}}Hello **World!**{{%/* /myshortcode */%}} +``` + +### Shortcodes Without Markdown + +The `<` character indicates that the shortcode's inner content does *not* need further rendering. Often shortcodes without markdown include internal HTML: + +``` +{{}}

Hello World!

{{}} +``` + +### Nested Shortcodes + +You can call shortcodes within other shortcodes by creating your own templates that leverage the `.Parent` variable. `.Parent` allows you to check the context in which the shortcode is being called. See [Shortcode templates][sctemps]. + +## Use Hugo's Built-in Shortcodes + +Hugo ships with a set of predefined shortcodes that represent very common usage. These shortcodes are provided for author convenience and to keep your markdown content clean. + +### `figure` + +`figure` is an extension of the image syntax in markdown, which does not provide a shorthand for the more semantic [HTML5 `
` element][figureelement]. + +The `figure` shortcode can use the following named parameters: + +* `src` +* `link` +* `title` +* `caption` +* `class` +* `attr` (i.e., attribution) +* `attrlink` +* `alt` + +#### Example `figure` Input + +{{< code file="figure-input-example.md" >}} +{{}} +{{< /code >}} + +#### Example `figure` Output + +{{< output file="figure-output-example.html" >}} +
+ +
+

Steve Francia

+
+
+{{< /output >}} + +### `gist` + +Bloggers often want to include GitHub gists when writing posts. Let's suppose we want to use the [gist at the following url][examplegist]: + +``` +https://gist.github.com/spf13/7896402 +``` + +We can embed the gist in our content via username and gist ID pulled from the URL: + +``` +{{}} +``` + +#### Example `gist` Input + +If the gist contains several files and you want to quote just one of them, you can pass the filename (quoted) as an optional third argument: + +{{< code file="gist-input.md" >}} +{{}} +{{< /code >}} + +#### Example `gist` Output + +{{< output file="gist-output.html" >}} +{{< gist spf13 7896402 >}} +{{< /output >}} + +#### Example `gist` Display + +To demonstrate the remarkably efficiency of Hugo's shortcode feature, we have embedded the `spf13` `gist` example in this page. The following simulates the experience for visitors to your website. Naturally, the final display will be contingent on your stylesheets and surrounding markup. + +{{< gist spf13 7896402 >}} + +### `highlight` + +This shortcode will convert the source code provided into syntax-highlighted HTML. Read more on [highlighting](/tools/syntax-highlighting/). `highlight` takes exactly one required `language` parameter and requires a closing shortcode. + +#### Example `highlight` Input + +{{< code file="content/tutorials/learn-html.md" >}} +{{}} +
+
+

{{ .Title }}

+ {{ range .Data.Pages }} + {{ .Render "summary"}} + {{ end }} +
+
+{{}} +{{< /code >}} + +#### Example `highlight` Output + +The `highlight` shortcode example above would produce the following HTML when the site is rendered: + +{{< output file="tutorials/learn-html/index.html" >}} +<section id="main"> + <div> + <h1 id="title">{{ .Title }}</h1> + {{ range .Data.Pages }} + {{ .Render "summary"}} + {{ end }} + </div> +</section> +{{< /output >}} + +{{% note "More on Syntax Highlighting" %}} +To see even more options for adding syntax-highlighted code blocks to your website, see [Syntax Highlighting in Developer Tools](/tools/syntax-highlighting/). +{{% /note %}} + +### `instagram` + +If you'd like to embed a photo from [Instagram][], you only need the photo's ID. You can discern an Instagram photo ID from the URL: + +``` +https://www.instagram.com/p/BWNjjyYFxVx/ +``` + +#### Example `instagram` Input + +{{< code file="instagram-input.md" >}} +{{}} +{{< /code >}} + +You also have the option to hide the caption: + +{{< code file="instagram-input-hide-caption.md" >}} +{{}} +{{< /code >}} + +#### Example `instagram` Output + +By adding the preceding `hidecaption` example, the following HTML will be added to your rendered website's markup: + +{{< output file="instagram-hide-caption-output.html" >}} +{{< instagram BWNjjyYFxVx hidecaption >}} +{{< /output >}} + +#### Example `instagram` Display + +Using the preceding `instagram` with hidecaption` example above, the following simulates the displayed experience for visitors to your website. Naturally, the final display will be contingent on your stylesheets and surrounding markup. + +{{< instagram BWNjjyYFxVx hidecaption >}} + + +### `ref` and `relref` + +These shortcodes will look up the pages by their relative path (e.g., `blog/post.md`) or their logical name (`post.md`) and return the permalink (`ref`) or relative permalink (`relref`) for the found page. + +`ref` and `relref` also make it possible to make fragmentary links that work for the header links generated by Hugo. + +{{% note "More on Cross References" %}} +Read a more extensive description of `ref` and `relref` in the [cross references](/content-management/cross-references/) documentation. +{{% /note %}} + +`ref` and `relref` take exactly one required parameter of _reference_, quoted and in position `0`. + +#### Example `ref` and `relref` Input + +``` +[Neat]({{}}) +[Who]({{}}) +``` + +#### Example `ref` and `relref` Output + +Assuming that standard Hugo pretty URLs are turned on. + +``` +Neat +Who +``` + +### `speakerdeck` + +To embed slides from [Speaker Deck][], click on "< /> Embed" (under Share right next to the template on Speaker Deck) and copy the URL: + +``` + +``` + +#### `speakerdeck` Example Input + +Extract the value from the field `data-id` and pass it to the shortcode: + +{{< code file="speakerdeck-example-input.md" >}} +{{}} +{{< /code >}} + +#### `speakerdeck` Example Output + +{{< output file="speakerdeck-example-input.md" >}} +{{< speakerdeck 4e8126e72d853c0060001f97 >}} +{{< /output >}} + +#### `speakerdeck` Example Display + +For the preceding `speakerdeck` example, the following simulates the displayed experience for visitors to your website. Naturally, the final display will be contingent on your stylesheets and surrounding markup. + +{{< speakerdeck 4e8126e72d853c0060001f97 >}} + +### `tweet` + +You want to include a single tweet into your blog post? Everything you need is the URL of the tweet: + +``` +https://twitter.com/spf13/status/877500564405444608 +``` + +#### Example `tweet` Input + +Pass the tweet's ID from the URL as a parameter to the `tweet` shortcode: + +{{< code file="example-tweet-input.md" >}} +{{}} +{{< /code >}} + +#### Example `tweet` Output + +Using the preceding `tweet` example, the following HTML will be added to your rendered website's markup: + +{{< output file="example-tweet-output.html" >}} +{{< tweet 877500564405444608 >}} +{{< /output >}} + +#### Example `tweet` Display + +Using the preceding `tweet` example, the following simulates the displayed experience for visitors to your website. Naturally, the final display will be contingent on your stylesheets and surrounding markup. + +{{< tweet 877500564405444608 >}} + +### `vimeo` + +Adding a video from [Vimeo][] is equivalent to the YouTube shortcode above. + +``` +https://vimeo.com/channels/staffpicks/146022717 +``` + +#### Example `vimeo` Input + +Extract the ID from the video's URL and pass it to the `vimeo` shortcode: + +{{< code file="example-vimeo-input.md" >}} +{{}} +{{< /code >}} + +#### Example `vimeo` Output + +Using the preceding `vimeo` example, the following HTML will be added to your rendered website's markup: + +{{< output file="example-vimeo-output.html" >}} +{{< vimeo 146022717 >}} +{{< /output >}} + +{{% tip %}} +If you want to further customize the visual styling of the YouTube or Vimeo output, add a `class` named parameter when calling the shortcode. The new `class` will be added to the `
` that wraps the ` +
+{{< /code >}} + +{{< code file="youtube-embed.html" copy="false" >}} +
+ +
+{{< /code >}} + +### Single Named Example: `image` + +Let's say you want to create your own `img` shortcode rather than use Hugo's built-in [`figure` shortcode][figure]. Your goal is to be able to call the shortcode as follows in your content files: + +{{< code file="content-image.md" >}} +{{}} +{{< /code >}} + +You have created the shortcode at `/layouts/shortcodes/img.html`, which loads the following shortcode template: + +{{< code file="/layouts/shortcodes/img.html" >}} + +
+ {{ with .Get "link"}}{{ end }} + + {{ if .Get "link"}}{{ end }} + {{ if or (or (.Get "title") (.Get "caption")) (.Get "attr")}} +
{{ if isset .Params "title" }} +

{{ .Get "title" }}

{{ end }} + {{ if or (.Get "caption") (.Get "attr")}}

+ {{ .Get "caption" }} + {{ with .Get "attrlink"}} {{ end }} + {{ .Get "attr" }} + {{ if .Get "attrlink"}} {{ end }} +

{{ end }} +
+ {{ end }} +
+ +{{< /code >}} + +Would be rendered as: + +{{< code file="img-output.html" copy="false" >}} +
+ +
+

Steve Francia

+
+
+{{< /code >}} + +### Single Flexible Example: `vimeo` + +``` +{{}} +{{}} +``` + +Would load the template found at `/layouts/shortcodes/vimeo.html`: + +{{< code file="/layouts/shortcodes/vimeo.html" >}} +{{ if .IsNamedParams }} +
+ +
+{{ else }} +
+ +
+{{ end }} +{{< /code >}} + +Would be rendered as: + +{{< code file="vimeo-iframes.html" copy="false" >}} +
+ +
+
+ +
+{{< /code >}} + +### Paired Example: `highlight` + +The following is taken from `highlight`, which is a [built-in shortcode][] that ships with Hugo. + +{{< code file="highlight-example.md" >}} +{{}} + + This HTML + +{{}} +{{< /code >}} + +The template for the `highlight` shortcode uses the following code, which is already included in Hugo: + +``` +{{ .Get 0 | highlight .Inner }} +``` + +The rendered output of the HTML example code block will be as follows: + +{{< code file="syntax-highlighted.html" copy="false" >}} +
<html>
+    <body> This HTML </body>
+</html>
+
+{{< /code >}} + +{{% note %}} +The preceding shortcode makes use of a Hugo-specific template function called `highlight`, which uses [Pygments](http://pygments.org) to add syntax highlighting to the example HTML code block. See the [developer tools page on syntax highlighting](/tools/syntax-highlighting/) for more information. +{{% /note %}} + +### Nested Shortcode: Image Gallery + +Hugo's [`.Parent` shortcode variable][parent] returns a boolean value depending on whether the shortcode in question is called within the context of a *parent* shortcode. This provides an inheritance model for common shortcode parameters. + +The following example is contrived but demonstrates the concept. Assume you have a `gallery` shortcode that expects one named `class` parameter: + +{{< code file="layouts/shortcodes/gallery.html" >}} +
+ {{.Inner}} +
+{{< /code >}} + +You also have an `image` shortcode with a single named `src` parameter that you want to call inside of `gallery` and other shortcodes so that the parent defines the context of each `image`: + +{{< code file="layouts/shortcodes/image.html" >}} +{{- $src := .Get "src" -}} +{{- with .Parent -}} + +{{- else -}} + +{{- end }} +{{< /code >}} + +You can then call your shortcode in your content as follows: + +``` +{{}} + {{}} + {{}} +{{}} +{{}} +``` + +This will output the following HTML. Note how the first two `image` shortcodes inherit the `class` value of `content-gallery` set with the call to the parent `gallery`, whereas the third `image` only uses `src`: + +``` + + +``` + +## More Shortcode Examples + +More shortcode examples can be found in the [shortcodes directory for spf13.com][spfscs] and the [shortcodes directory for the Hugo docs][docsshortcodes]. + +[basic content files]: /content-management/formats/ "See how Hugo leverages markdown--and other supported formats--to create content for your website." +[built-in shortcode]: /content-management/shortcodes/ +[config]: /getting-started/configuration/ "Learn more about Hugo's built-in configuration variables as well as how to us your site's configuration file to include global key-values that can be used throughout your rendered website." +[Content Management: Shortcodes]: /content-management/shortcodes/#using-hugo-s-built-in-shortcodes "Check this section if you are not familiar with the definition of what a shortcode is or if you are unfamiliar with how to use Hugo's built-in shortcodes in your content files." +[source organization]: /getting-started/directory-structure/#directory-structure-explained "Learn how Hugo scaffolds new sites and what it expects to find in each of your directories." +[docsshortcodes]: https://github.com/gohugoio/hugo/tree/master/docs/layouts/shortcodes "See the shortcode source directory for the documentation site you're currently reading." +[figure]: /content-management/shortcodes/#figure +[hugosc]: /content-management/shortcodes/#using-hugo-s-built-in-shortcodes +[lookup order]: /templates/lookup-order/ "See the order in which Hugo traverses your template files to decide where and how to render your content at build time" +[pagevars]: /variables/page/ "See which variables you can leverage in your templating for page vs list templates." +[parent]: /variables/shortcodes/ +[shortcodesvars]: /variables/shortcodes/ "Certain variables are specific to shortcodes, although most .Page variables can be accessed within your shortcode template." +[spfscs]: https://github.com/spf13/spf13.com/tree/master/layouts/shortcodes "See more examples of shortcodes by visiting the shortcode directory of the source for spf13.com, the blog of Hugo's creator, Steve Francia." +[templates]: /templates/ "The templates section of the Hugo docs." +[vimeoexample]: #single-flexible-example-vimeo +[youtubeshortcode]: /content-management/shortcodes/#youtube "See how to use Hugo's built-in YouTube shortcode." \ No newline at end of file diff --git a/content/templates/single-page-templates.md b/content/templates/single-page-templates.md new file mode 100644 index 000000000..20942e5d5 --- /dev/null +++ b/content/templates/single-page-templates.md @@ -0,0 +1,105 @@ +--- +title: Single Page Templates +linktitle: +description: The primary view of content in Hugo is the single view. Hugo will render every Markdown file provided with a corresponding single template. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-04-06 +categories: [templates] +#tags: [page] +menu: + docs: + parent: "templates" + weight: 60 +weight: 60 +sections_weight: 60 +draft: false +aliases: [/layout/content/] +toc: true +--- + +## Single Page Template Lookup Order + +You can specify a [content's `type`][content type] and `layout` in a single content file's [front matter][]. However, you cannot specify `section` because this is determined based on file location (see [content section][section]). + +Hugo assumes your content section and content type are the same unless you tell Hugo otherwise by providing a `type` directly in the front matter of a content file. This is why #1 and #3 come before #2 and #4, respectively, in the following lookup order. Values in angle brackets (`<>`) are variable. + +1. `/layouts//.html` +2. `/layouts/
>/.html` +3. `/layouts//single.html` +4. `/layouts/
/single.html` +5. `/layouts/_default/single.html` +6. `/themes//layouts//.html` +7. `/themes//layouts/
/.html` +8. `/themes//layouts//single.html` +9. `/themes//layouts/
/single.html` +10. `/themes//layouts/_default/single.html` + +## Example Single Page Templates + +Content pages are of the type `page` and will therefore have all the [page variables][pagevars] and [site variables][] available to use in their templates. + +### `post/single.html` + +This single page template makes use of Hugo [base templates][], the [`.Format` function][] for dates, the [`.WordCount` page variable][pagevars], and ranges through the single content's specific [taxonomies][pagetaxonomy]. [`with`][] is also used to check whether the taxonomies are set in the front matter. + +{{< code file="layouts/post/single.html" download="single.html" >}} +{{ define "main" }} +
+

{{ .Title }}

+
+
+ {{ .Content }} +
+
+
+ +{{ end }} +{{< /code >}} + +To easily generate new instances of a content type (e.g., new `.md` files in a section like `project/`) with preconfigured front matter, use [content archetypes][archetypes]. + +[archetypes]: /content-management/archetypes/ +[base templates]: /templates/base/ +[config]: /getting-started/configuration/ +[content type]: /content-management/types/ +[directory structure]: /getting-started/directory-structure/ +[dry]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself +[`.Format` function]: /functions/format/ +[front matter]: /content-management/front-matter/ +[pagetaxonomy]: /templates/taxonomy-templates/#displaying-a-single-piece-of-content-s-taxonomies +[pagevars]: /variables/page/ +[partials]: /templates/partials/ +[section]: /content-management/sections/ +[site variables]: /variables/site/ +[spf13]: http://spf13.com/ +[`with`]: /functions/with/ \ No newline at end of file diff --git a/content/templates/sitemap-template.md b/content/templates/sitemap-template.md new file mode 100644 index 000000000..43bd7ff39 --- /dev/null +++ b/content/templates/sitemap-template.md @@ -0,0 +1,75 @@ +--- +title: Sitemap Template +# linktitle: Sitemap +description: Hugo ships with a built-in template file observing the v0.9 of the Sitemap Protocol, but you can override this template if needed. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +categories: [templates] +#tags: [sitemap, xml] +menu: + docs: + parent: "templates" + weight: 160 +weight: 160 +sections_weight: 160 +draft: false +aliases: [/layout/sitemap/,/templates/sitemap/] +toc: false +--- + +A single Sitemap template is used to generate the `sitemap.xml` file. +Hugo automatically comes with this template file. *No work is needed on +the users' part unless they want to customize `sitemap.xml`.* + +A sitemap is a `Page` and therefore has all the [page variables][pagevars] available to use in this template along with Sitemap-specific ones: + +`.Sitemap.ChangeFreq` +: The page change frequency + +`.Sitemap.Priority` +: The priority of the page + +`.Sitemap.Filename` +: The sitemap filename + +If provided, Hugo will use `/layouts/sitemap.xml` instead of the internal `sitemap.xml` template that ships with Hugo. + +## Hugo’s sitemap.xml + +This template respects the version 0.9 of the [Sitemap Protocol](http://www.sitemaps.org/protocol.html). + +``` + + {{ range .Data.Pages }} + + {{ .Permalink }}{{ if not .Lastmod.IsZero }} + {{ safeHTML ( .Lastmod.Format "2006-01-02T15:04:05-07:00" ) }}{{ end }}{{ with .Sitemap.ChangeFreq }} + {{ . }}{{ end }}{{ if ge .Sitemap.Priority 0.0 }} + {{ .Sitemap.Priority }}{{ end }} + + {{ end }} + +``` + +{{% note %}} +Hugo will automatically add the following header line to this file +on render. Please don't include this in the template as it's not valid HTML. + +`` +{{% /note %}} + +## Configure `sitemap.xml` + +Defaults for ``, `` and `filename` values can be set in the site's config file, e.g.: + +``` +[sitemap] + changefreq = "monthly" + priority = 0.5 + filename = "sitemap.xml" +``` + +The same fields can be specified in an individual content file's front matter in order to override the value assigned to that piece of content at render time. + +[pagevars]: /variables/page/ \ No newline at end of file diff --git a/content/templates/taxonomy-templates.md b/content/templates/taxonomy-templates.md new file mode 100644 index 000000000..6e5b6c40f --- /dev/null +++ b/content/templates/taxonomy-templates.md @@ -0,0 +1,352 @@ +--- +title: Taxonomy Templates +# linktitle: +description: Taxonomy templating includes taxonomy list pages, taxonomy terms pages, and using taxonomies in your single page templates. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +categories: [templates] +#tags: [taxonomies,metadata,front matter,terms] +menu: + docs: + parent: "templates" + weight: 50 +weight: 50 +sections_weight: 50 +draft: false +aliases: [/taxonomies/displaying/,/templates/terms/,/indexes/displaying/,/taxonomies/templates/,/indexes/ordering/, /templates/taxonomies/, /templates/taxonomy/] +toc: true +--- + + + +Hugo includes support for user-defined groupings of content called **taxonomies**. Taxonomies are classifications that demonstrate logical relationships between content. See [Taxonomies under Content Management](/content-management/taxonomies) if you are unfamiliar with how Hugo leverages this powerful feature. + +Hugo provides multiple ways to use taxonomies throughout your project templates: + +* Order the way content associated with a taxonomy term is displayed in a [taxonomy list template](#taxonomy-list-template) +* Order the way the terms for a taxonomy are displayed in a [taxonomy terms template](#taxonomy-terms-template) +* List a single content's taxonomy terms within a [single page template][] + +## Taxonomy List Templates + +Taxonomy list page templates are lists and therefore have all the variables and methods available to [list pages][lists]. + +### Taxonomy List Template Lookup Order + +A taxonomy will be rendered at /`PLURAL`/`TERM`/ (e.g., http://spf13.com/topics/golang/) according to the following lookup order: + +1. `/layouts/taxonomy/.html` +2. `/layouts/_default/taxonomy.html` +3. `/layouts/_default/list.html` +4. `/themes//layouts/taxonomy/.html` +5. `/themes//layouts/_default/taxonomy.html` +6. `/themes//layouts/_default/list.html` + +## Taxonomy Terms Template + +### Taxonomy Terms Templates Lookup Order + +A taxonomy terms page will be rendered at `example.com/`/ (e.g., http://spf13.com/topics/) according to the following lookup order: + +1. `/layouts/taxonomy/.terms.html` +2. `/layouts/_default/terms.html` +3. `/themes//layouts/taxonomy/.terms.html` +4. `/themes//layouts/_default/terms.html` + +{{% warning "The Taxonomy Terms Template has a Unique Lookup Order" %}} +If Hugo does not find a terms template in `layout/` or `/themes//layouts/`, Hugo will *not* render a taxonomy terms page. +{{% /warning %}} + + +Hugo makes a set of values and methods available on the various Taxonomy structures. + +### Taxonomy Methods + +A Taxonomy is a `map[string]WeightedPages`. + +.Get(term) +: Returns the WeightedPages for a term. + +.Count(term) +: The number of pieces of content assigned to this term. + +.Alphabetical +: Returns an OrderedTaxonomy (slice) ordered by Term. + +.ByCount +: Returns an OrderedTaxonomy (slice) ordered by number of entries. + +### OrderedTaxonomy + +Since Maps are unordered, an OrderedTaxonomy is a special structure that has a defined order. + +``` +[]struct { + Name string + WeightedPages WeightedPages +} +``` + +Each element of the slice has: + +.Term +: The Term used. + +.WeightedPages +: A slice of Weighted Pages. + +.Count +: The number of pieces of content assigned to this term. + +.Pages +: All Pages assigned to this term. All [list methods][renderlists] are available to this. + +## WeightedPages + +WeightedPages is simply a slice of WeightedPage. + +``` +type WeightedPages []WeightedPage +``` + +.Count(term) +: The number of pieces of content assigned to this term. + +.Pages +: Returns a slice of pages, which then can be ordered using any of the [list methods][renderlists]. + +## Displaying custom metadata in Taxonomy Terms Templates + +If you need to display custom metadata for each taxonomy term, you will need to create a page for that term at `/content///_index.md` and add your metadata in it's front matter, [as explained in the taxonomies documentation](/content-management/taxonomies/#add-custom-meta-data-to-a-taxonomy-term). Based on the Actors taxonomy example shown there, within your taxonomy terms template, you may access your custom fields by iterating through the variable `.Data.Pages` as such: + +``` +
    + {{ range .Data.Pages }} +
  • + {{ .Title }} + {{ .Params.wikipedia }} +
  • + {{ end }} +
+``` + + + +## Order Taxonomies + +Taxonomies can be ordered by either alphabetical key or by the number of content pieces assigned to that key. + +### Order Alphabetically Example + +``` +
    + {{ $data := .Data }} + {{ range $key, $value := .Data.Taxonomy.Alphabetical }} +
  • {{ $value.Name }} {{ $value.Count }}
  • + {{ end }} +
+``` + +### Order by Popularity Example + +``` +
    + {{ $data := .Data }} + {{ range $key, $value := .Data.Taxonomy.ByCount }} +
  • {{ $value.Name }} {{ $value.Count }}
  • + {{ end }} +
+``` + + + +## Order Content within Taxonomies + +Hugo uses both `date` and `weight` to order content within taxonomies. + +Each piece of content in Hugo can optionally be assigned a date. It can also be assigned a weight for each taxonomy it is assigned to. + +When iterating over content within taxonomies, the default sort is the same as that used for [section and list pages]() first by weight then by date. This means that if the weights for two pieces of content are the same, than the more recent content will be displayed first. The default weight for any piece of content is 0. + +### Assign Weight + +Content can be assigned weight for each taxonomy that it's assigned to. + +``` ++++ +tags = [ "a", "b", "c" ] +tags_weight = 22 +categories = ["d"] +title = "foo" +categories_weight = 44 ++++ +Front Matter with weighted tags and categories +``` + +The convention is `taxonomyname_weight`. + +In the above example, this piece of content has a weight of 22 which applies to the sorting when rendering the pages assigned to the "a", "b" and "c" values of the 'tag' taxonomy. + +It has also been assigned the weight of 44 when rendering the 'd' category. + +With this the same piece of content can appear in different positions in different taxonomies. + +Currently taxonomies only support the default ordering of content which is weight -> date. + + + +There are two different templates that the use of taxonomies will require you to provide. + +Both templates are covered in detail in the templates section. + +A [list template](/templates/list/) is any template that will be used to render multiple pieces of content in a single html page. This template will be used to generate all the automatically created taxonomy pages. + +A [taxonomy terms template](/templates/terms/) is a template used to +generate the list of terms for a given template. + + + +There are four common ways you can display the data in your +taxonomies in addition to the automatic taxonomy pages created by hugo +using the [list templates](/templates/list/): + +1. For a given piece of content, you can list the terms attached +2. For a given piece of content, you can list other content with the same + term +3. You can list all terms for a taxonomy +4. You can list all taxonomies (with their terms) + +## Display a Single Piece of Content's Taxonomies + +Within your content templates, you may wish to display the taxonomies that piece of content is assigned to. + +Because we are leveraging the front matter system to define taxonomies for content, the taxonomies assigned to each content piece are located in the usual place (i.e., `.Params.`). + +### Example: List Tags in a Single Page Template + +``` +
    + {{ range .Params.tags }} +
  • {{ . }}
  • + {{ end }} +
+``` + +If you want to list taxonomies inline, you will have to take care of optional plural endings in the title (if multiple taxonomies), as well as commas. Let's say we have a taxonomy "directors" such as `directors: [ "Joel Coen", "Ethan Coen" ]` in the TOML-format front matter. + +To list such taxonomies, use the following: + +### Example: Comma-delimit Tags in a Single Page Template + +``` +{{ if .Params.directors }} + Director{{ if gt (len .Params.directors) 1 }}s{{ end }}: + {{ range $index, $director := .Params.directors }}{{ if gt $index 0 }}, {{ end }}{{ . }}{{ end }} +{{ end }} +``` + +Alternatively, you may use the [delimit template function][delimit] as a shortcut if the taxonomies should just be listed with a separator. See {{< gh 2143 >}} on GitHub for discussion. + +## List Content with the Same Taxonomy Term + +If you are using a taxonomy for something like a series of posts, you can list individual pages associated with the same taxonomy. This is also a quick and dirty method for showing related content: + +### Example: Showing Content in Same Series + +``` + +``` + +## List All content in a Given taxonomy + +This would be very useful in a sidebar as “featured content”. You could even have different sections of “featured content” by assigning different terms to the content. + +### Example: Grouping "Featured" Content + +``` + +``` + +## Render a Site's Taxonomies + +If you wish to display the list of all keys for your site's taxonomy, you can retrieve them from the [`.Site` variable][sitevars] available on every page. + +This may take the form of a tag cloud, a menu, or simply a list. + +The following example displays all terms in a site's tags taxonomy: + +### Example: List All Site Tags + +``` +
    + {{ range $name, $taxonomy := .Site.Taxonomies.tags }} +
  • {{ $name }}
  • + {{ end }} +
+``` + +### Example: List All Taxonomies, Terms, and Assigned Content + +This example will list all taxonomies and their terms, as well as all the content assigned to each of the terms. + +{{< code file="layouts/partials/all-taxonomies.html" download="all-taxonomies.html" download="all-taxonomies.html" >}} +
+
    + {{ range $taxonomyname, $taxonomy := .Site.Taxonomies }} +
  • {{ $taxonomyname }} +
      + {{ range $key, $value := $taxonomy }} +
    • {{ $key }}
    • + + {{ end }} +
    +
  • + {{ end }} +
+
+{{< /code >}} + +## `.Site.GetPage` for Taxonomies + +Because taxonomies are lists, the [`.GetPage` function][getpage] can be used to get all the pages associated with a particular taxonomy term using a terse syntax. The following ranges over the full list of tags on your site and links to each of the individual taxonomy pages for each term without having to use the more fragile URL construction of the "List All Site Tags" example above: + +{{< code file="links-to-all-tags" >}} +
    + {{ range ($.Site.GetPage "taxonomyTerm" "tags").Pages }} +
  • {{ .Title}}
  • + {{ end }} +
+{{< /code >}} + + + + +[delimit]: /functions/delimit/ +[getpage]: /functions/getpage/ +[lists]: /templates/lists/ +[renderlists]: /templates/lists/ +[single page template]: /templates/single-page-templates/ +[sitevars]: /variables/site/ diff --git a/content/templates/template-debugging.md b/content/templates/template-debugging.md new file mode 100644 index 000000000..1905f6db6 --- /dev/null +++ b/content/templates/template-debugging.md @@ -0,0 +1,81 @@ +--- +title: Template Debugging +# linktitle: Template Debugging +description: You can use Go templates' `printf` function to debug your Hugo templates. These snippets provide a quick and easy visualization of the variables available to you in different contexts. +godocref: http://golang.org/pkg/fmt/ +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +categories: [templates] +#tags: [debugging,troubleshooting] +menu: + docs: + parent: "templates" + weight: 180 +weight: 180 +sections_weight: 180 +draft: false +aliases: [] +toc: false +--- + +Here are some snippets you can add to your template to answer some common questions. + +These snippets use the `printf` function available in all Go templates. This function is an alias to the Go function, [fmt.Printf](http://golang.org/pkg/fmt/). + +## What Variables are Available in this Context? + +You can use the template syntax, `$.`, to get the top-level template context from anywhere in your template. This will print out all the values under, `.Site`. + +``` +{{ printf "%#v" $.Site }} +``` + +This will print out the value of `.Permalink`: + + +``` +{{ printf "%#v" .Permalink }} +``` + + +This will print out a list of all the variables scoped to the current context +(`.`, aka ["the dot"][tempintro]). + + +``` +{{ printf "%#v" . }} +``` + + +When developing a [homepage][], what does one of the pages you're looping through look like? + +``` +{{ range .Data.Pages }} + {{/* The context, ".", is now each one of the pages as it goes through the loop */}} + {{ printf "%#v" . }} +{{ end }} +``` + +{{% note "`.Data.Pages` on the Homepage" %}} +`.Data.Pages` on the homepage is equivalent to `.Site.Pages`. +{{% /note %}} + +## Why Am I Showing No Defined Variables? + +Check that you are passing variables in the `partial` function: + +``` +{{ partial "header" }} +``` + +This example will render the header partial, but the header partial will not have access to any contextual variables. You need to pass variables explicitly. For example, note the addition of ["the dot"][tempintro]. + +``` +{{ partial "header" . }} +``` + +The dot (`.`) is considered fundamental to understanding Hugo templating. For more information, see [Introduction to Hugo Templating][tempintro]. + +[homepage]: /templates/homepage/ +[tempintro]: /templates/introduction/ \ No newline at end of file diff --git a/content/templates/views.md b/content/templates/views.md new file mode 100644 index 000000000..0383636a1 --- /dev/null +++ b/content/templates/views.md @@ -0,0 +1,121 @@ +--- +title: Content View Templates +# linktitle: Content Views +description: Hugo can render alternative views of your content, which is especially useful in list and summary views. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +categories: [templates] +#tags: [views] +menu: + docs: + parent: "templates" + weight: 70 +weight: 70 +sections_weight: 70 +draft: false +aliases: [] +toc: true +--- + +These alternative **content views** are especially useful in [list templates][lists]. + +The following are common use cases for content views: + +* You want content of every type to be shown on the homepage but only with limited [summary views][summaries]. +* You only want a bulleted list of your content on a [taxonomy list page][taxonomylists]. Views make this very straightforward by delegating the rendering of each different type of content to the content itself. + +## Create a Content View + +To create a new view, create a template in each of your different content type directories with the view name. The following example contains an "li" view and a "summary" view for the `post` and `project` content types. As you can see, these sit next to the [single content view][single] template, `single.html. You can even provide a specific view for a given type and continue to use the `_default/single.html` for the primary view. + +``` + ▾ layouts/ + ▾ post/ + li.html + single.html + summary.html + ▾ project/ + li.html + single.html + summary.html +``` + +Hugo also has support for a default content template to be used in the event that a specific content view template has not been provided for that type. Content views can also be defined in the `_default` directory and will work the same as list and single templates who eventually trickle down to the `_default` directory as a matter of the lookup order. + + +``` +▾ layouts/ + ▾ _default/ + li.html + single.html + summary.html +``` + +## Which Template Will be Rendered? + +The following is the [lookup order][lookup] for content views: + +1. `/layouts//.html` +2. `/layouts/_default/.html` +3. `/themes//layouts//.html` +4. `/themes//layouts/_default/.html` + +## Example: Content View Inside a List + +The following example demonstrates how to use content views inside of your [list templates][lists]. + +### `list.html` + +In this example, `.Render` is passed into the template to call the [render function][render]. `.Render` is a special function that instructs content to render itself with the view template provided as the first argument. In this case, the template is going to render the `summary.html` view that follows: + +{{< code file="layouts/_default/list.html" download="list.html" >}} +
+
+

{{ .Title }}

+ {{ range .Data.Pages }} + {{ .Render "summary"}} + {{ end }} +
+
+{{< /code >}} + +### `summary.html` + +Hugo will pass the entire page object to the following `summary.html` view template. (See [Page Variables][pagevars] for a complete list.) + +{{< code file="layouts/_default/summary.html" download="summary.html" >}} + +{{< /code >}} + +### `li.html` + +Continuing on the previous example, we can change our render function to use a smaller `li.html` view by changing the argument in the call to the `.Render` function (i.e., `{{ .Render "li" }}`). + +{{< code file="layouts/_default/li.html" download="li.html" >}} +
  • + {{ .Title }} +
    {{ .Date.Format "Mon, Jan 2, 2006" }}
    +
  • +{{< /code >}} + +[lists]: /templates/lists/ +[lookup]: /templates/lookup-order/ +[pagevars]: /variables/page/ +[render]: /functions/render/ +[single]: /templates/single-page-templates/ +[spf]: http://spf13.com +[spfsourceli]: https://github.com/spf13/spf13.com/blob/master/layouts/_default/li.html +[spfsourcesection]: https://github.com/spf13/spf13.com/blob/master/layouts/_default/section.html +[spfsourcesummary]: https://github.com/spf13/spf13.com/blob/master/layouts/_default/summary.html +[summaries]: /content-management/summaries/ +[taxonomylists]: /templates/taxonomy-templates/ \ No newline at end of file diff --git a/content/themes/_index.md b/content/themes/_index.md new file mode 100644 index 000000000..10579db1e --- /dev/null +++ b/content/themes/_index.md @@ -0,0 +1,26 @@ +--- +title: Themes +linktitle: Themes Overview +description: Install, use, and create Hugo themes. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +menu: + docs: + parent: "themes" + weight: 01 +weight: 01 +sections_weight: 01 +categories: [themes] +#tags: [themes,introduction,overview] +draft: false +aliases: [/themes/overview/] +toc: false +--- + +Hugo provides a robust theming system that is easy to implement yet feature complete. You can view the themes created by the Hugo community on the [Hugo themes website][hugothemes]. + +Hugo themes are powered by the excellent Go template library and are designed to reduce code duplication. They are easy to both customize and keep in synch with the upstream theme. + +[goprimer]: /templates/introduction/ +[hugothemes]: http://themes.gohugo.io/ diff --git a/content/themes/creating.md b/content/themes/creating.md new file mode 100644 index 000000000..d6477f00d --- /dev/null +++ b/content/themes/creating.md @@ -0,0 +1,87 @@ +--- +title: Create a Theme +linktitle: Create a Theme +description: The `hugo new theme` command will scaffold the beginnings of a new theme for you to get you on your way. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +categories: [themes] +#tags: [themes, source, organization, directories] +menu: + docs: + parent: "themes" + weight: 30 +weight: 30 +sections_weight: 30 +draft: false +aliases: [/themes/creation/,/tutorials/creating-a-new-theme/] +toc: true +wip: true +--- + +{{% warning "Use Relative Links" %}} +If you're creating a theme with plans to share it with the community, use relative URLs since users of your theme may not publish from the root of their website. See [relURL](/functions/relurl) and [absURL](/functions/absurl). +{{% /warning %}} + +Hugo can initialize a new blank theme directory within your existing `themes` using the `hugo new` command: + +``` +hugo new theme [name] +``` + +## Theme Components + +A theme consists of templates and static assets such as javascript and css files. Themes can also provide [archetypes][], which are archetypal content types used by the `hugo new` command to scaffold new content files with preconfigured front matter. + + +{{% note "Use the Hugo Generator Tag" %}} +The [`.Hugo.Generator`](/variables/hugo/) tag is included in all themes featured in the [Hugo Themes Showcase](http://themes.gohugo.io). We ask that you include the generator tag in all sites and themes you create with Hugo to help the core team track Hugo's usage and popularity. +{{% /note %}} + +## Layouts + +Hugo is built around the concept that things should be as simple as possible. +Fundamentally, website content is displayed in two different ways, a single +piece of content and a list of content items. With Hugo, a theme layout starts +with the defaults. As additional layouts are defined, they are used for the +content type or section they apply to. This keeps layouts simple, but permits +a large amount of flexibility. + +## Single Content + +The default single file layout is located at `layouts/_default/single.html`. + +## List of Contents + +The default list file layout is located at `layouts/_default/list.html`. + +## Partial Templates + +Theme creators should liberally use [partial templates](/templates/partials/) +throughout their theme files. Not only is a good DRY practice to include shared +code, but partials are a special template type that enables the themes end user +to be able to overwrite just a small piece of a file or inject code into the +theme from their local /layouts. These partial templates are perfect for easy +injection into the theme with minimal maintenance to ensure future +compatibility. + +## Static + +Everything in the static directory will be copied directly into the final site +when rendered. No structure is provided here to enable complete freedom. It is +common to organize the static content into: + +``` +/css +/js +/img +``` + +The actual structure is entirely up to you, the theme creator, on how you would like to organize your files. + +## Archetypes + +If your theme makes use of specific keys in the front matter, it is a good idea +to provide an archetype for each content type you have. [Read more about archetypes][archetypes]. + +[archetypes]: /content-management/archetypes/ diff --git a/content/themes/customizing.md b/content/themes/customizing.md new file mode 100644 index 000000000..1c82b2b71 --- /dev/null +++ b/content/themes/customizing.md @@ -0,0 +1,80 @@ +--- +title: Customize a Theme +linktitle: Customize a Theme +description: Customize a theme by overriding theme layouts and static assets in your top-level project directories. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +categories: [themes] +#tags: [themes, source, organization, directories] +menu: + docs: + parent: "themes" + weight: 20 +weight: 20 +sections_weight: 20 +draft: false +aliases: [/themes/customize/] +toc: true +wip: true +--- + +The following are key concepts for Hugo site customization with themes. Hugo permits you to supplement *or* override any theme template or static file with files in your working directory. + +{{% note %}} +When you use a theme cloned from its git repository, do not edit the theme's files directly. Instead, theme customization in Hugo is a matter of *overriding* the templates made available to you in a theme. This provides the added flexibility of tweaking a theme to meet your needs while staying current with a theme's upstream. +{{% /note %}} + +## Override Static Files + +There are times where you want to include static assets that differ from versions of the same asset that ships with a theme. + +For example, a theme may use jQuery 1.8 in the following location: + +``` +/themes//static/js/jquery.min.js +``` + +You want to replace the version of jQuery that ships with the theme with the newer `jquery-3.1.1.js`. The easiest way to do this is to replace the file *with a file of the same name* in the same relative path in your project's root. Therefore, change `jquery-3.1.1.js` to `jquery.min.js` so that it is *identical* to the theme's version and place the file here: + +``` +/static/js/jquery.min.js +``` + +## Override Template Files + +Anytime Hugo looks for a matching template, it will first check the working directory before looking in the theme directory. If you would like to modify a template, simply create that template in your local `layouts` directory. + +The [template lookup order][lookup] explains the rules Hugo uses to determine which template to use for a given piece of content. Read and understand these rules carefully. + +This is especially helpful when the theme creator used [partial templates][partials]. These partial templates are perfect for easy injection into the theme with minimal maintenance to ensure future compatibility. + +For example: + +``` +/themes//layouts/_default/single.html +``` + +Would be overwritten by + +``` +/layouts/_default/single.html +``` + +{{% warning %}} +This only works for templates that Hugo "knows about" (i.e., that follow its convention for folder structure and naming). If a theme imports template files in a creatively named directory, Hugo won’t know to look for the local `/layouts` first. +{{% /warning %}} + +## Override Archetypes + +If the archetype that ships with the theme for a given content type (or all content types) doesn’t fit with how you are using the theme, feel free to copy it to your `/archetypes` directory and make modifications as you see fit. + +{{% warning "Beware of `layouts/_default`" %}} +The `_default` directory is a very powerful force in Hugo, especially as it pertains to overwriting theme files. If a default file is located in the local [archetypes](/content-management/archetypes/) or layout directory (i.e., `archetypes/default.md` or `/layouts/_default/*.html`, respectively), it will override the file of the same name in the corresponding theme directory (i.e., `themes//archetypes/default.md` or `themes//layout/_defaults/*.html`, respectively). + +It is usually better to override specific files; i.e. rather than using `layouts/_default/*.html` in your working directory. +{{% /warning %}} + +[archetypes]: /content-management/archetypes/ +[lookup]: /templates/lookup-order/ +[partials]: /templates/partials/ \ No newline at end of file diff --git a/content/themes/installing-and-using-themes.md b/content/themes/installing-and-using-themes.md new file mode 100644 index 000000000..474b7b036 --- /dev/null +++ b/content/themes/installing-and-using-themes.md @@ -0,0 +1,112 @@ +--- +title: Install and Use Themes +linktitle: Install and Use Themes +description: Install and use a theme from the Hugo theme showcase easily through the CLI. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +categories: [themes] +#tags: [install, themes, source, organization, directories,usage] +menu: + docs: + parent: "themes" + weight: 10 +weight: 10 +sections_weight: 10 +draft: false +aliases: [/themes/usage/,/themes/installing/] +toc: true +wip: true +--- + +{{% note "No Default Theme" %}} +Hugo currently doesn’t ship with a “default” theme. This decision is intentional. We leave it up to you to decide which theme best suits your Hugo project. +{{% /note %}} + +## Assumptions + +1. You have already [installed Hugo on your development machine][install]. +2. You have git installed on your machine and you are familiar with basic git usage. + +## Install Themes + +The community-contributed themes featured on [themes.gohugo.io](//themes.gohugo.io/) are hosted in a [centralized GitHub repository][themesrepo]. The Hugo Themes Repo at is really a meta repository that contains pointers to a set of contributed themes. + +{{% warning "Get `git` First" %}} +Without [Git](https://git-scm.com/) installed on your computer, none of the following theme instructions will work. Git tutorials are beyond the scope of the Hugo docs, but [GitHub](https://try.github.io/) and [codecademy](https://www.codecademy.com/learn/learn-git) offer free, interactive courses for beginners. +{{% /warning %}} + +### Install All Themes + +You can install *all* available Hugo themes by cloning the entire [Hugo Theme repository on GitHub][themesrepo] from within your working directory. Depending on your internet connection the download of all themes might take a while. + +``` +git clone --depth 1 --recursive https://github.com/gohugoio/hugoThemes.git themes +``` + +Before you use a theme, remove the .git folder in that theme's root folder. Otherwise, this will cause problem if you deploy using Git. + +### Install a Single Theme + +Change into the `themes` directory and download a theme by replacing `URL_TO_THEME` with the URL of the theme repository: + +``` +cd themes +git clone URL_TO_THEME +``` + +The following example shows how to use the "Hyde" theme, which has its source hosted at : + +{{< code file="clone-theme.sh" >}} +cd themes +git clone https://github.com/spf13/hyde +{{< /code >}} + +Alternatively, you can download the theme as a `.zip` file, unzip the theme contents, and then move the unzipped source into your `themes` directory. + +{{% note "Read the `README`" %}} +Always review the `README.md` file that is shipped with a theme. Often, these files contain further instructions required for theme setup; e.g., copying values from an example configuration file. +{{% /note %}} + +## Theme Placement + +Please make certain you have installed the themes you want to use in the +`/themes` directory. This is the default directory used by Hugo. Hugo comes with the ability to change the themes directory via the [`themesDir` variable in your site configuration][config], but this is not recommended. + +## Use Themes + +Hugo applies the decided theme first and then applies anything that is in the local directory. This allows for easier customization while retaining compatibility with the upstream version of the theme. To learn more, go to [customizing themes][customizethemes]. + +### Command Line + +There are two different approaches to using a theme with your Hugo website: via the Hugo CLI or as part of your + +To change a theme via the Hugo CLI, you can pass the `-t` [flag][] when building your site: + +``` +hugo -t themename +``` + +Likely, you will want to add the theme when running the Hugo local server, especially if you are going to [customize the theme][customizethemes]: + +``` +hugo server -t themename +``` + +### `config` File + +If you've already decided on the theme for your site and do not want to fiddle with the command line, you can add the theme directly to your [site configuration file][config]: + +``` +theme: themename +``` + +{{% note "A Note on `themename`" %}} +The `themename` in the above examples must match the name of the specific theme directory inside `/themes`; i.e., the directory name (likely lowercase and urlized) rather than the name of the theme displayed in the [Themes Showcase site](http://themes.gohugo.io). +{{% /note %}} + +[customizethemes]: /themes/customizing/ +[flag]: /getting-started/usage/ "See the full list of flags in Hugo's basic usage." +[install]: /getting-started/installing/ +[config]: /getting-started/configuration/ "Learn how to customize your Hugo website configuration file in yaml, toml, or json." +[themesrepo]: https://github.com/gohugoio/hugoThemes \ No newline at end of file diff --git a/content/tools/_index.md b/content/tools/_index.md new file mode 100644 index 000000000..7c3e66aa8 --- /dev/null +++ b/content/tools/_index.md @@ -0,0 +1,25 @@ +--- +title: Developer Tools +linktitle: Developer Tools Overview +description: In addition to Hugo's powerful CLI, there is a large number of community-developed tool chains for Hugo developers. +date: 2016-12-05 +publishdate: 2016-12-05 +lastmod: 2017-02-26 +categories: [developer tools] +#tags: [] +menu: + docs: + parent: "tools" + weight: 01 +weight: 01 +sections_weight: 01 +draft: false +aliases: [/tools/] +--- + +One of Hugo's greatest strengths is it's passionate---and always evolving---developer community. With the exception of the `highlight` shortcode mentioned in [Syntax Highlighting][syntax], the tools and other projects featured in this section are offerings from both commercial services and open-source projects, many of which are developed by Hugo developers just like you. + +[See the popularity of Hugo compared with other static site generators.][staticgen] + +[staticgen]: https://staticgen.com +[syntax]: /tools/syntax-highlighting/ diff --git a/content/tools/editors.md b/content/tools/editors.md new file mode 100644 index 000000000..f98a7d36c --- /dev/null +++ b/content/tools/editors.md @@ -0,0 +1,44 @@ +--- +title: Editor Plug-ins for Hugo +linktitle: Editor Plug-ins +description: The Hugo community uses a wide range of preferred tools and has developed plug-ins for some of the most popular text editors to help automate parts of your workflow. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +categories: [developer tools] +#tags: [editor, plug-ins] +menu: + docs: + parent: "tools" + weight: 50 +weight: 50 +sections_weight: 50 +draft: false +aliases: [] +toc: false +--- + +The Hugo community uses a wide range of preferred tools and has developed plug-ins for some of the most popular text editors to help automate parts of your workflow. + +## Sublime Text + +* [Hugofy](https://github.com/akmittal/Hugofy). Hugofy is a plugin for Sublime Text 3 to make life easier to use Hugo static site generator. + +## Visual Studio Code + +* [Hugofy](https://marketplace.visualstudio.com/items?itemName=akmittal.hugofy). Hugofy is a plugin for Visual Studio Code to "make life easier" when developing with Hugo. The source code can be found [here](https://github.com/akmittal/hugofy-vscode). + +## Emacs + +* [hugo.el](https://github.com/yewton/hugo.el). Some helper functions for creating a Website with Hugo. Note that Hugo also supports [Org-mode][formats]. + +## Vim + +* [Vim Hugo Helper](https://github.com/robertbasic/vim-hugo-helper). A small Vim plugin to help me with writing posts with Hugo. + +## Atom + +* [Hugofy](https://atom.io/packages/hugofy). A Hugo Static Website Generator package for Atom. +* [language-hugo](https://atom.io/packages/language-hugo). Adds syntax highlighting to Hugo files. + +[formats]: /content-management/formats/ \ No newline at end of file diff --git a/content/tools/frontends.md b/content/tools/frontends.md new file mode 100644 index 000000000..d0c6b3168 --- /dev/null +++ b/content/tools/frontends.md @@ -0,0 +1,33 @@ +--- +title: Frontend Interfaces with Hugo +linktitle: Frontends +description: Do you prefer a graphical user interface over a text editor? Give these frontends a try. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +categories: [developer tools] +#tags: [frontend,gui] +menu: + docs: + parent: "tools" + weight: 40 +weight: 40 +sections_weight: 40 +draft: false +aliases: [] +toc: false +--- + +* [enwrite](https://github.com/zzamboni/enwrite). Enwrite enables evernote-powered, statically generated blogs and websites. Now posting to your blog or updating your website is as easy as writing a new note in Evernote! +* [caddy-hugo](https://github.com/hacdias/caddy-hugo). `caddy-hugo` is an add-on for [Caddy](https://caddyserver.com/) that delivers a good UI to edit the content of your Hugo website. +* [Lipi](https://github.com/SohanChy/Lipi). Lipi is a native GUI frontend written in Java to manage your Hugo websites. + + +## Commercial Services + +* [Appernetic.io](https://appernetic.io) is a Hugo Static Site Generator as a Service that is easy to use for non-technical users. + * **Features:** inline PageDown editor, visual tree view, image upload and digital asset management with Cloudinary, site preview, continuous integration with GitHub, atomic deploy and hosting, Git and Hugo integration, autosave, custom domain, project syncing, theme cloning and management. Developers have complete control over the source code and can manage it with GitHub’s deceptively simple workflow. +* [DATOCMS](https://www.datocms.com) DatoCMS is a fully customizable administrative area for your static websites. Use your favorite website generator, let your clients publish new content independently, and the host the site anywhere you like. +* [Forestry.io](https://forestry.io/). Forestry is a simple CMS for Jekyll and Hugo websites with support for GitHub, GitLab, and Bitbucket. Every time an update is made via the CMS, Forestry will commit changes back to your repo and will compile/deploy your website to S3, GitHub Pages, FTP, etc. +* [Netlify.com](https://www.netlify.com). Netlify builds, deploys, and hosts your static website or app (Hugo, Jekyll, etc). Netlify offers a drag-and-drop interface and automatic deployments from GitHub or Bitbucket. + * **Features:** global CDN, atomic deploys, ultra-fast DNS, instant cache invalidation, high availability, automated hosting, Git integration, form submission hooks, authentication providers, and custom domains. Developers have complete control over the source code and can manage it with GitHub or Bitbucket's deceptively simple workflow. diff --git a/content/tools/migrations.md b/content/tools/migrations.md new file mode 100644 index 000000000..227bdd222 --- /dev/null +++ b/content/tools/migrations.md @@ -0,0 +1,71 @@ +--- +title: Migrate to Hugo +linktitle: Migrations +description: A list of community-developed tools for migrating from your existing static site generator or content management system to Hugo. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +#tags: [migrations,jekyll,wordpress,drupal,ghost,contentful] +menu: + docs: + parent: "tools" + weight: 10 +weight: 10 +sections_weight: 10 +draft: false +aliases: [/developer-tools/migrations/,/developer-tools/migrated/] +toc: true +--- + +This section highlights some projects around Hugo that are independently developed. These tools try to extend the functionality of our static site generator or help you to get started. + +{{% note %}} +Do you know or maintain a similar project around Hugo? Feel free to open a [pull request](https://github.com/gohugoio/hugo/pulls) on GitHub if you think it should be added. +{{% /note %}} + +Take a look at this list of migration tools if you currently use other logging tools like Jekyll or WordPress but intend to switch to Hugo instead. They'll take care to export your content into Hugo-friendly formats. + +## Jekyll + +Alternatively, you can use the new [Jekyll import command](/commands/hugo_import_jekyll/). + +- [JekyllToHugo](https://github.com/SenjinDarashiva/JekyllToHugo) - A Small script for converting Jekyll blog posts to a Hugo site. +- [ConvertToHugo](https://github.com/coderzh/ConvertToHugo) - Convert your blog from Jekyll to Hugo. + +## Ghost + +- [ghostToHugo](https://github.com/jbarone/ghostToHugo) - Convert Ghost blog posts and export them to Hugo. + +## Octopress + +- [octohug](https://github.com/codebrane/octohug) - Octopress to Hugo migrator. + +## DokuWiki + +- [dokuwiki-to-hugo](https://github.com/wgroeneveld/dokuwiki-to-hugo) - Migrates your dokuwiki source pages from [DokuWiki syntax](https://www.dokuwiki.org/wiki:syntax) to Hugo Markdown syntax. Includes extra's like the TODO plugin. Written with extensibility in mind using python 3. Also generates a TOML header for each page. Designed to copypaste the wiki directory into your /content directory. + +## WordPress + +- [wordpress-to-hugo-exporter](https://github.com/SchumacherFM/wordpress-to-hugo-exporter) - A one-click WordPress plugin that converts all posts, pages, taxonomies, metadata, and settings to Markdown and YAML which can be dropped into Hugo. (Note: If you have trouble using this plugin, you can [export your site for Jekyll](https://wordpress.org/plugins/jekyll-exporter/) and use Hugo's built in Jekyll converter listed above.) + +## Tumblr + +- [tumblr-importr](https://github.com/carlmjohnson/tumblr-importr) - An importer that uses the Tumblr API to create a Hugo static site. +- [tumblr2hugomarkdown](https://github.com/Wysie/tumblr2hugomarkdown) - Export all your Tumblr content to Hugo Markdown files with preserved original formatting. +- [Tumblr to Hugo](https://github.com/jipiboily/tumblr-to-hugo) - A migration tool that converts each of your Tumblr posts to a content file with a proper title and path. Furthermore, "Tumblr to Hugo" creates a CSV file with the original URL and the new path on Hugo, to help you setup the redirections. + +## Drupal + +- [drupal2hugo](https://github.com/danapsimer/drupal2hugo) - Convert a Drupal site to Hugo. + +## Joomla + +- [hugojoomla](https://github.com/davetcc/hugojoomla) - This utility written in Java takes a Joomla database and converts all the content into Markdown files. It changes any URLs that are in Joomla's internal format and converts them to a suitable form. + +## Blogger + +- [blogimport](https://github.com/natefinch/blogimport) - A tool to import from Blogger posts to Hugo. + +## Contentful + +- [contentful2hugo](https://github.com/ArnoNuyts/contentful2hugo) - A tool to create content-files for Hugo from content on [Contentful](https://www.contentful.com/). diff --git a/content/tools/other.md b/content/tools/other.md new file mode 100644 index 000000000..5f3036a9c --- /dev/null +++ b/content/tools/other.md @@ -0,0 +1,28 @@ +--- +title: Other Hugo Community Projects +linktitle: Other Projects +description: Some interesting projects developed by the Hugo community that don't quite fit into our other developer tool categories. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +categories: [developer tools] +#tags: [frontend,gui] +menu: + docs: + parent: "tools" + weight: 70 +weight: 70 +sections_weight: 70 +draft: false +aliases: [] +toc: false +--- + +And for all the other small things around Hugo: + +* [hugo-gallery](https://github.com/icecreammatt/hugo-gallery) lets you create an image gallery for Hugo sites. +* [flickr-hugo-embed](https://github.com/nikhilm/flickr-hugo-embed) prints shortcodes to embed a set of images from an album on Flickr into Hugo. +* [hugo-openapispec-shortcode](https://github.com/tenfourty/hugo-openapispec-shortcode) A shortcode that allows you to include [Open API Spec](https://openapis.org) (formerly known as Swagger Spec) in a page. +* [HugoPhotoSwipe](https://github.com/GjjvdBurg/HugoPhotoSwipe) makes it easy to create image galleries using PhotoSwipe. +* [Hugo SFTP Upload](https://github.com/thomasmey/HugoSftpUpload) Syncs the local build of your Hugo website with your remote webserver via SFTP. +* [Emacs Easy Hugo](https://github.com/masasam/emacs-easy-hugo) Emacs package for writing blog posts in markdown or org-mode and building your site with Hugo. diff --git a/content/tools/search.md b/content/tools/search.md new file mode 100644 index 000000000..dac345a13 --- /dev/null +++ b/content/tools/search.md @@ -0,0 +1,29 @@ +--- +title: Search for your Hugo Website +linktitle: Search +description: See some of the open-source and commercial search options for your newly created Hugo website. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-26 +categories: [developer tools] +#tags: [search,tools] +menu: + docs: + parent: "tools" + weight: 60 +weight: 60 +sections_weight: 60 +draft: false +aliases: [] +toc: true +--- + +A static website with a dynamic search function? Yes. As alternatives to embeddable scripts from Google or other search engines, you can provide your visitors a custom search by indexing your content files directly. + +* [Hugoidx](https://github.com/blevesearch/hugoidx) is an experimental application to create a search index. It's built on top of [Bleve](http://www.blevesearch.com/). +* [GitHub Gist for Hugo Workflow](https://gist.github.com/sebz/efddfc8fdcb6b480f567). This gist contains a simple workflow to create a search index for your static website. It uses a simple Grunt script to index all your content files and [lunr.js](http://lunrjs.com/) to serve the search results. +* [hugo-lunr](https://www.npmjs.com/package/hugo-lunr). A simple way to add site search to your static Hugo site using [lunr.js](http://lunrjs.com/). Hugo-lunr will create an index file of any html and markdown documents in your Hugo project. + +## Commercial Search Services + +* [Algolia](https://www.algolia.com/)'s Search API makes it easy to deliver a great search experience in your apps and websites. Algolia Search provides hosted full-text, numerical, faceted, and geolocalized search. \ No newline at end of file diff --git a/content/tools/starter-kits.md b/content/tools/starter-kits.md new file mode 100644 index 000000000..5d43d4e4d --- /dev/null +++ b/content/tools/starter-kits.md @@ -0,0 +1,38 @@ +--- +title: Starter Kits +linktitle: Starter Kits +description: A list of community-developed projects designed to help you get up and running with Hugo. +date: 2017-02-22 +publishdate: 2017-02-01 +lastmod: 2017-02-22 +#tags: [starters,assets,pipeline] +menu: + docs: + parent: "tools" + weight: 30 +weight: 30 +sections_weight: 30 +draft: false +aliases: [/developer-tools/migrations/,/developer-tools/migrated/] +toc: false +--- + +Know of a Hugo-related starter kit that isn't mentioned here? [Please add it to the list.][addkit] + +{{% note "Starter Kits are Not Maintained by the Hugo Team"%}} +The following starter kits are developed by active members of the Hugo community. If you find yourself having issues with any of the projects, it's best to file an issue directly with the project's maintainer(s). +{{% /note %}} + +* [Victor Hugo][]. Victor Hugo is a Hugo boilerplate for creating truly epic websites using Gulp + Webpack as an asset pipeline. Victor Hugo uses post-css and Babel for CSS and JavaScript, respectively, and is actively maintained. +* [GOHUGO AMP][]. GoHugo AMP is a starter theme that aims to make it easy to adopt [Google's AMP Project][amp]. The starter kit comes with 40+ shortcodes and partials plus automatic structured data. The project also includes a [separate site with extensive documentation][gohugodocs]. +* [Blaupause][]. Blaupause is a developer-friendly Hugo starter kit based on Gulp tasks. It comes ES6-ready with several helpers for SVG and fonts and basic structure for HTML, SCSS, and JavaScript. +* [hugulp][]. hugulp is a tool to optimize the assets of a Hugo website. The main idea is to recreate the famous Ruby on Rails Asset Pipeline, which minifies, concatenates and fingerprints the assets used in your website. + + +[addkit]: https://github.com/gohugoio/hugo/edit/master/docs/content/tools/starter-kits.md +[amp]: https://www.ampproject.org/ +[Blaupause]: https://github.com/fspoettel/blaupause +[GOHUGO AMP]: https://github.com/wildhaber/gohugo-amp +[gohugodocs]: https://gohugo-amp.gohugohq.com/ +[hugulp]: https://github.com/jbrodriguez/hugulp +[Victor Hugo]: https://github.com/netlify/victor-hugo \ No newline at end of file diff --git a/content/tools/syntax-highlighting.md b/content/tools/syntax-highlighting.md new file mode 100644 index 000000000..e6fb4de82 --- /dev/null +++ b/content/tools/syntax-highlighting.md @@ -0,0 +1,237 @@ +--- +title: Syntax Highlighting +linktitle: +description: Hugo provides server-side syntax highlighting via Pygments and, like most static site generators, works very well with client-side (JavaScript) syntax highlighting libraries as well. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +#tags: [highlighting,pygments,code blocks,syntax] +categories: [developer tools] +menu: + docs: + parent: "tools" + weight: 20 +weight: 20 +sections_weight: 20 +draft: false +aliases: [/extras/highlighting/,/extras/highlight/] +toc: true +--- + +Hugo can highlight source code in _two different ways_—either pre-processed server side from your content or to defer the processing to the client side, using a JavaScript library. + +## Server-side + +For the pre-processed approach, highlighting is performed by an external Python-based program called [Pygments](http://pygments.org/) and is triggered via an embedded Hugo shortcode (see [example](#example-highlight-shortcode-input) below). If Pygments is absent from the path, it will silently simply pass the content along without highlighting. + +### Server-side Advantages + +The advantages of server-side syntax highlighting are that it doesn’t depend on a JavaScript library and, consequently, works very well when read from an RSS feed. + +### Pygments + +If you have never worked with Pygments before, here is a brief primer: + ++ Install Python from [python.org](https://www.python.org/downloads/). Version 2.7.x is already sufficient. ++ Run `pip install Pygments` in order to install Pygments. Once installed, Pygments gives you a command `pygmentize`. Make sure it sits in your PATH; otherwise, Hugo will not be able to find and use it. + +On Debian and Ubuntu systems, you may also install Pygments by running `sudo apt-get install python3-pygments`. + +Hugo gives you two options that you can set with the variable `pygmentsuseclasses` (default `false`) in your [site configuration](/getting-started/configuration/). + +1. Color codes for highlighting keywords are directly inserted if `pygmentsuseclasses = false` (default). The color codes depend on your choice of the `pygmentsstyle` (default = `"monokai"`). You can explore the different color styles on [pygments.org](http://pygments.org/) after inserting some example code. +2. If you choose `pygmentsuseclasses = true`, Hugo includes class names in your code instead of color codes. For class-names to be meaningful, you need to include a `.css` file in your website representing your color scheme. You can either generate this `.css` files according to the [description from the Pygments documentation](http://pygments.org/docs/cmdline/) or download the one of the many pre-built color schemes from [Pygment's GitHub css repository](https://github.com/richleland/pygments-css). + +### Server-side Usage + +Highlighting is carried out via the [built-in shortcode](/content-management/shortcodes/) `highlight`. `highlight` takes exactly one required parameter for the programming language to be highlighted and requires a closing shortcode. Note that `highlight` is *not* used for client-side javascript highlighting. + +### Example `highlight` Shortcode Input + +{{< code file="example-highlight-shortcode-input.md" >}} +{{}} +
    +
    +

    {{ .Title }}

    + {{ range .Data.Pages }} + {{ .Render "summary"}} + {{ end }} +
    +
    +{{}} +{{< /code >}} + +### Example `highlight` Shortcode Output + +{{< output file="example-highlight-shortcode-output.html" >}} +<section id="main"> + <div> + <h1 id="title">{{ .Title }}</h1> + {{ range .Data.Pages }} + {{ .Render "summary"}} + {{ end }} + </div> +</section> +{{< /output >}} + +### Options + +Options for controlling highlighting can be added in the second argument as a quoted, comma-separated key-value list. The example below will syntax highlight in `go` with inline line numbers and line numbers 2 and 3 highlighted. + +``` +{{}} +var a string +var b string +var c string +var d string +{{}} +``` + +The `highlight` shortcode includes the following supported keywords: + +* `style` +* `encoding` +* `noclasses` +* `hl_lines` +* `linenos` + +Note that `style` and `noclasses` will override the similar setting in the [global config](/getting-started/configuration/). + +The keywords in the `highlight` shortcode mirror those of Pygments from the command line. See the [Pygments documentation](http://pygments.org/docs/) for more information. + +### Code Fences + +It is also possible to add syntax highlighting with GitHub flavored code fences. To enable this, set the `PygmentsCodeFences` to `true` in Hugo's [configuration file](/getting-started/configuration/); + +``` +``` +
    +
    +

    {{ .Title }}

    + {{ range .Data.Pages }} + {{ .Render "summary"}} + {{ end }} +
    +
    +``` +``` + +{{% note "Disclaimers on Pygments" %}} +* Pygments is relatively slow and _causes a performance hit when building your site_, but Hugo has been designed to cache the results to disk. +* The caching can be turned off by setting the `--ignoreCache` flag to `true`. +* The languages available for highlighting depend on your Pygments installation. +{{% /note %}} + +## Client-side + +Alternatively, code highlighting can be applied to your code blocks in client-side JavaScript. + +Client-side syntax highlighting is very simple to add. You'll need to pick +a library and a corresponding theme. Some popular libraries are: + +- [Highlight.js] +- [Prism] +- [Rainbow] +- [Syntax Highlighter] +- [Google Prettify] + +### Client-side Advantages + +The advantages of client-side syntax highlighting are that it doesn’t cost anything when building your site, and some of the highlighting scripts available cover more languages than Pygments does. + +### Highlight.js Example + +This example uses the popular [Highlight.js] library, hosted by [Yandex], a popular Russian search engine. + +In your `./layouts/partials/` (or `./layouts/chrome/`) folder, depending on your specific theme, there will be a snippet that will be included in every generated HTML page, such as `header.html` or `header.includes.html`. Simply add the css and js to initialize [Highlight.js]: + +``` + + + +``` + +### Prism example + +Prism is another popular highlighter library and is used on some major sites. +The [download section of the prism.js website][prismdownload] is simple to use and affords you a high degree of customization to pick only the languages you'll be using on your site. + +Similar to Highlight.js, you simply load `prism.css` in your `` via whatever Hugo partial template is creating that part of your pages: + +``` +... + +... +``` + +Add `prism.js` near the bottom of your `` tag in whatever Hugo partial template is appropriate for your site or theme. + +``` +... + + +``` + +In this example, the local paths indicate that your downloaded copy of these files are being added to the site, typically under `./static/css/` and `./static/js/`, respectively. + +### Client-side Usage + +To use client-side highlighting, most of these javascript libraries expect your code to be wrapped in semantically correct `` elements with language-specific class attributes. For example, a code block for HTML would have `class="language-html"`. + +The client-side highlighting script therefore looks for programming language classes according to this convention: `language-go`, `language-html`, `language-css`, `language-bash`, etc. If you look at the page's source, you might see something like the following: + +``` +
    +  
    +  body {
    +    font-family: "Noto Sans", sans-serif;
    +  }
    +  
    +
    +``` + +If you are using markdown, your content pages needs to use the following syntax, with the name of the language to be highlighted entered directly after the first "fence." A fenced code block can be noted by opening and closing triple tilde ~ or triple back ticks `: + +{{< nohighlight >}} +~~~css +body { + font-family: "Noto Sans", sans-serif; +} +~~~ +{{< /nohighlight >}} + +Here is the same example but with triple back ticks to denote the fenced code block: + +{{< nohighlight >}} +``` +body { + font-family: "Noto Sans", sans-serif; +} +``` +{{< /nohighlight >}} + +Passing the above examples through the highlighter script would yield the following markup: + +{{< nohighlight >}} +<pre><code class="language-css hljs">;<span class="hljs-selector-tag">body</span> { + <span class="hljs-attribute">font-family</span>: <span class="hljs-string">"Noto Sans"</span>, sans-serif; +} +{{< /nohighlight >}} + +In the case of the coding color scheme used by the Hugo docs, the resulting output would then look like the following to the website's end users: + +``` +body { + font-family: "Noto Sans", sans-serif; +} +``` + +Please see individual libraries' documentation for how to implement each of the JavaScript-based libraries. + +[Prism]: http://prismjs.com +[prismdownload]: http://prismjs.com/download.html +[Highlight.js]: http://highlightjs.org/ +[Rainbow]: http://craig.is/making/rainbows +[Syntax Highlighter]: http://alexgorbatchev.com/SyntaxHighlighter/ +[Google Prettify]: https://github.com/google/code-prettify +[Yandex]: http://yandex.ru/ \ No newline at end of file diff --git a/content/troubleshooting/_index.md b/content/troubleshooting/_index.md new file mode 100644 index 000000000..8900d78e2 --- /dev/null +++ b/content/troubleshooting/_index.md @@ -0,0 +1,26 @@ +--- +title: Troubleshoot +linktitle: Troubleshoot +description: Frequently asked questions and known issues pulled from the Hugo Discuss forum. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +menu: + docs: + parent: "troubleshooting" + weight: 01 +weight: 01 #rem +draft: false +hidesectioncontents: false +slug: +aliases: [/troubleshooting/faqs/,/faqs/] +toc: false +notesforauthors: +--- + +The Troubleshooting section includes known issues, recent workarounds, and FAQs pulled from the [Hugo Discussion Forum][forum]. + + + + +[forum]: https://discourse.gohugo.io diff --git a/content/troubleshooting/accented-characters-in-urls.md b/content/troubleshooting/accented-characters-in-urls.md new file mode 100644 index 000000000..a902377c1 --- /dev/null +++ b/content/troubleshooting/accented-characters-in-urls.md @@ -0,0 +1,60 @@ +--- +title: Accented Characters in URLs +linktitle: Accented Characters in URLs +description: If you're having trouble with special characters in your taxonomies or titles adding odd characters to your URLs. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +#tags: [urls,multilingual,special characters] +categories: [troubleshooting] +menu: + docs: + parent: "troubleshooting" +weight: +draft: false +slug: +aliases: [/troubleshooting/categories-with-accented-characters/] +toc: true +--- + +## Trouble: Categories with accented characters + +> One of my categories is named "Le-carré," but the link ends up being generated like this: +> +> ``` +> categories/le-carr%C3%A9 +> ``` +> +> And not working. Is there an easy fix for this that I'm overlooking? + +## Solution + +Are you a macOS user? If so, you are likely a victim of HFS Plus file system's insistence to store the "é" (U+00E9) character in Normal Form Decomposed (NFD) mode, i.e. as "e" + " ́" (U+0065 U+0301). + +`le-carr%C3%A9` is actually correct, `%C3%A9` being the UTF-8 version of U+00E9 as expected by the web server. The problem is that OS X turns [U+00E9] into [U+0065 U+0301], and thus `le-carr%C3%A9` no longer works. Instead, only `le-carre%CC%81` ending with `e%CC%81` would match that [U+0065 U+0301] at the end. + +This is unique to OS X. The rest of the world does not do this, and most certainly not your web server which is most likely running Linux. This is not a Hugo-specific problem either. Other people have been bitten by this when they have accented characters in their HTML files. + +Note that this problem is not specific to Latin scripts. Japanese Mac users often run into the same issue; e.g., with `だ` decomposing into `た` and `゙`. (Read the [Japanese Perl users article][]). + +Rsync 3.x to the rescue! From [an answer posted on Server Fault][]: + +> You can use rsync's `--iconv` option to convert between UTF-8 NFC & NFD, at least if you're on a Mac. There is a special `utf-8-mac` character set that stands for UTF-8 NFD. So to copy files from your Mac to your web server, you'd need to run something like: +> +> `rsync -a --iconv=utf-8-mac,utf-8 localdir/ mywebserver:remotedir/` +> +> This will convert all the local filenames from UTF-8 NFD to UTF-8 NFC on the remote server. The files' contents won't be affected. - [Server Fault][] + +Please make sure you have the latest version of rsync 3.x installed. The rsync that ships with OS X is outdated. Even the version that comes packaged with 10.10 (Yosemite) is version 2.6.9 protocol version 29. The `--iconv` flag is new in rsync 3.x. + +### Discussion Forum References + +* http://discourse.gohugo.io/t/categories-with-accented-characters/505 +* http://wiki.apache.org/subversion/NonNormalizingUnicodeCompositionAwareness +* https://en.wikipedia.org/wiki/Unicode_equivalence#Example +* http://zaiste.net/2012/07/brand_new_rsync_for_osx/ +* https://gogo244.wordpress.com/2014/09/17/drived-me-crazy-convert-utf-8-mac-to-utf-8/ + +[an Answer posted on Server Fault]: http://serverfault.com/questions/397420/converting-utf-8-nfd-filenames-to-utf-8-nfc-in-either-rsync-or-afpd "Converting UTF-8 NFD filenames to UTF-8 NFC in either rsync or afpd, Server Fault Discussion" +[Japanese Perl users article]: http://perl-users.jp/articles/advent-calendar/2010/english/24 "Encode::UTF8Mac makes you happy while handling file names on MacOSX" +[Server Fault]: http://serverfault.com/questions/397420/converting-utf-8-nfd-filenames-to-utf-8-nfc-in-either-rsync-or-afpd "Converting UTF-8 NFD filenames to UTF-8 NFC in either rsync or afpd, Server Fault Discussion" diff --git a/content/troubleshooting/build-performance.md b/content/troubleshooting/build-performance.md new file mode 100644 index 000000000..89225cbc5 --- /dev/null +++ b/content/troubleshooting/build-performance.md @@ -0,0 +1,23 @@ +--- +title: Build Performance +linktitle: Build Performance +description: +date: 2017-03-12 +publishdate: 2017-03-12 +lastmod: 2017-03-12 +#tags: [performance, build] +categories: [troubleshooting] +menu: + docs: + parent: "troubleshooting" +weight: +draft: true +slug: +aliases: [] +toc: true +wip: true +--- + + + + diff --git a/content/troubleshooting/eof-error.md b/content/troubleshooting/eof-error.md new file mode 100644 index 000000000..da1b827fe --- /dev/null +++ b/content/troubleshooting/eof-error.md @@ -0,0 +1,49 @@ +--- +title: EOF Error +linktitle: EOF Error +description: If you find yourself seeing an EOF error in the console whenever you create a new content file from Hugo's archetype feature. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +categories: [troubleshooting] +menu: + docs: + parent: "troubleshooting" +#tags: [eof, end of file, error, faqs] +draft: false +weight: +aliases: [/troubleshooting/strange-eof-error/] +toc: true +--- + +## Trouble: `hugo new` Aborts with EOF error + +> I'm running into an issue where I cannot get archetypes working, when running `hugo new showcase/test.md`, for example, I see an `EOF` error thrown by Hugo. +> +> When I run Hugo with v0.12 via `hugo new -v showcase/test.md`, I see the following output: +> +> ``` +> INFO: 2015/01/04 Using config file: /private/tmp/test/config.toml +> INFO: 2015/01/04 attempting to create showcase/test.md of showcase +> INFO: 2015/01/04 curpath: /private/tmp/test/archetypes/showcase.md +> ERROR: 2015/01/04 EOF +> ``` +> +> Is there something that I am blatantly missing? + +## Solution: Carriage Returns + +The solution is to add a final newline (i.e., `EOL`) to the end of your default.md archetype file of your theme. You can do this by adding a carriage return after the closing `+++` or `---` of your TOML or YAML front matter, respectively. + +{{% note "Final EOL Unnecessary in v0.13+" %}} +As of v0.13, Hugo's parser has been enhanced to accommodate archetype files without final EOL thanks to the great work by [@tatsushid](https://github.com/tatsushid). +{{% /note %}} + +## Discussion Forum References + +* http://discourse.gohugo.io/t/archetypes-not-properly-working-in-0-12/544 +* http://discourse.gohugo.io/t/eol-f-in-archetype-files/554 + +## Related Hugo Issues + +* https://github.com/gohugoio/hugo/issues/776 diff --git a/content/variables/_index.md b/content/variables/_index.md new file mode 100644 index 000000000..8e53af553 --- /dev/null +++ b/content/variables/_index.md @@ -0,0 +1,23 @@ +--- +title: Variables and Params +linktitle: Variables Overview +description: Page-, file-, taxonomy-, and site-level variables and parameters available in templates. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +categories: [variables and params] +#tags: [variables,params,values,globals] +draft: false +menu: + docs: + parent: "variables" + weight: 1 +weight: 01 #rem +sections_weight: 01 +aliases: [/templates/variables/] +toc: false +--- + +Hugo's templates are context aware and make a large number of values available to you as you're creating views for your website. + +[Go templates]: /templates/introduction/ "Understand context in Go templates by learning the language's fundamental templating functions." diff --git a/content/variables/files.md b/content/variables/files.md new file mode 100644 index 000000000..dd6c6efc2 --- /dev/null +++ b/content/variables/files.md @@ -0,0 +1,48 @@ +--- +title: File Variables +linktitle: +description: "You can access filesystem-related data for a content file in the `.File` variable." +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +categories: [variables and params] +#tags: [files] +draft: false +menu: + docs: + parent: "variables" + weight: 40 +weight: 40 +sections_weight: 40 +aliases: [/variables/file-variables/] +toc: false +--- + +{{% note "Rendering Local Files" %}} +For information on creating shortcodes and templates that tap into Hugo's file-related feature set, see [Local File Templates](/templates/files/). +{{% /note %}} + +The `.File` object contains the following fields: + +`.File.Path` +: the original relative path of the page (e.g., `content/posts/foo.en.md`) + +`.File.LogicalName` +: the name of the content file that represents a page (e.g., `foo.en.md`) + +`.File.TranslationBaseName` +: the filename without extension or optional language identifier (e.g., `foo`) + +`.File.BaseFileName` +: the filename without extension (e.g., `foo.en`) + +`.File.Ext` +: the file extension of the content file (e.g., `md`); this can also be called using `.File.Extension` as well. Note that it is *only* the extension without `.`. + +`.File.Lang` +: the language associated with the given file if Hugo's [Multilingual features][multilingual] are enabled (e.g., `en`) + +`.File.Dir` +: given the path `content/posts/dir1/dir2/`, the relative directory path of the content file will be returned (e.g., `posts/dir1/dir2/`) + +[Multilingual]: /content-management/multilingual/ \ No newline at end of file diff --git a/content/variables/git.md b/content/variables/git.md new file mode 100644 index 000000000..97ecb3c61 --- /dev/null +++ b/content/variables/git.md @@ -0,0 +1,54 @@ +--- +title: Git Info Variables +linktitle: Git Variables +description: Get the last Git revision information for every content file. +date: 2017-03-12 +publishdate: 2017-03-12 +lastmod: 2017-03-12 +categories: [variables and params] +#tags: [git] +draft: false +menu: + docs: + parent: "variables" + weight: 70 +weight: 70 +sections_weight: 70 +aliases: [/extras/gitinfo/] +toc: false +wip: false +--- + +{{% note "`.GitInfo` Performance Considerations" %}} +Hugo's Git integrations should be fairly performant but *can* increase your build time. This will depend on the size of your Git history. +{{% /note %}} + +## `.GitInfo` Prerequisites + +1. The Hugo site must be in a Git-enabled directory. +2. The Git executable must be installed and in your system `PATH`. +3. The `.GitInfo` feature must be enabled in your Hugo project by passing `--enableGitInfo` flag on the command line or by setting `enableGitInfo` to `true` in your [site's configuration file][configuration]. + +## The `.GitInfo` Object + +The `GitInfo` object contains the following fields: + +`.AbbreviatedHash` +: the abbreviated commit hash (e.g., `866cbcc`) + +`.AuthorName` +: the author's name, respecting `.mailmap` + +`.AuthorEmail` +: the author's email address, respecting `.mailmap` + +`.AuthorDate` +: the author date + +`.Hash` +: the commit hash (e.g., `866cbccdab588b9908887ffd3b4f2667e94090c3`) + +`.Subject` +: commit message subject (e.g., `tpl: Add custom index function`) + +[configuration]: /getting-started/configuration/ diff --git a/content/variables/hugo.md b/content/variables/hugo.md new file mode 100644 index 000000000..da83a91f7 --- /dev/null +++ b/content/variables/hugo.md @@ -0,0 +1,39 @@ +--- +title: Hugo-specific Variables +linktitle: Hugo Variables +description: The `.Hugo` variable provides easy access to Hugo-related data. +date: 2017-03-12 +publishdate: 2017-03-12 +lastmod: 2017-03-12 +categories: [variables and params] +#tags: [hugo,generator] +draft: false +menu: + docs: + parent: "variables" + weight: 60 +weight: 60 +sections_weight: 60 +aliases: [] +toc: false +wip: false +--- + +It contains the following fields: + +`.Hugo.Generator` +: `` tag for the version of Hugo that generated the site. `.Hugo.Generator` outputs a *complete* HTML tag; e.g. `` + +`.Hugo.Version` +: the current version of the Hugo binary you are using e.g. `0.13-DEV`
    + +`.Hugo.CommitHash` +: the git commit hash of the current Hugo binary e.g. `0e8bed9ccffba0df554728b46c5bbf6d78ae5247` + +`.Hugo.BuildDate` +: the compile date of the current Hugo binary formatted with RFC 3339 e.g. `2002-10-02T10:00:00-05:00`
    + +{{% note "Use the Hugo Generator Tag" %}} +We highly recommend using `.Hugo.Generator` in your website's ``. `.Hugo.Generator` is included by default in all themes hosted on [themes.gohugo.io](http://themes.gohugo.io). The generator tag allows the Hugo team to track the usage and popularity of Hugo. +{{% /note %}} + diff --git a/content/variables/menus.md b/content/variables/menus.md new file mode 100644 index 000000000..3f3f8fb33 --- /dev/null +++ b/content/variables/menus.md @@ -0,0 +1,50 @@ +--- +title: Menu Variables +linktitle: Menu Variables +description: A menu entry in a menu template has specific variables and functions to make menu management easier. +date: 2017-03-12 +publishdate: 2017-03-12 +lastmod: 2017-03-12 +categories: [variables and params] +#tags: [menus] +draft: false +menu: + docs: + parent: "variables" + weight: 50 +weight: 50 +sections_weight: 50 +aliases: [/variables/menu/] +toc: false +--- + +The [menu template][] has the following properties: + +`.URL` +: string + +`.Name` +: string + +`.Menu` +: string + +`.Identifier` +: string + +`.Pre` +: template.HTML + +`.Post` +: template.HTML + +`.Weight` +: int + +`.Parent` +: string + +`.Children` +: Menu + +[menu template]: /templates/menu-templates/ \ No newline at end of file diff --git a/content/variables/page.md b/content/variables/page.md new file mode 100644 index 000000000..58c4d2110 --- /dev/null +++ b/content/variables/page.md @@ -0,0 +1,268 @@ +--- +title: Page Variables +linktitle: +description: Page-level variables are defined in a content file's front matter, derived from the content's file location, or extracted from the content body itself. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +categories: [variables and params] +#tags: [pages] +draft: false +menu: + docs: + parent: "variables" + weight: 20 +weight: 20 +sections_weight: 20 +aliases: [/variables/page/] +toc: true +--- + +The following is a list of page-level variables. Many of these will be defined in the front matter, derived from file location, or extracted from the content itself. + +{{% note "`.Scratch`" %}} +See [`.Scratch`](/functions/scratch/) for page-scoped, writable variables. +{{% /note %}} + +## Page Variables + +`.AlternativeOutputFormats` +: contains all alternative formats for a given page; this variable is especially useful `link rel` list in your site's ``. (See [Output Formats](/templates/output-formats/).) + +`.Content` +: the content itself, defined below the front matter. + +`.Data` +: the data specific to this type of page. + +`.Date` +: the date associated with the page; `.Date` pulls from the `date` field in a content's front matter. See also `.ExpiryDate`, `.PublishDate`, and `.Lastmod`. + +`.Description` +: the description for the page. + +`.Draft` +: a boolean, `true` if the content is marked as a draft in the front matter. + +`.ExpiryDate` +: the date on which the content is scheduled to expire; `.ExpiryDate` pulls from the `expirydate` field in a content's front matter. See also `.PublishDate`, `.Date`, and `.Lastmod`. + +`.FuzzyWordCount` +: the approximate number of words in the content. + +`.Hugo` +: see [Hugo Variables](/variables/hugo/). + +`.IsHome` +: `true` in the context of the [homepage](/templates/homepage/). + +`.IsNode` +: always `false` for regular content pages. + +`.IsPage` +: always `true` for regular content pages. + +`.IsTranslated` +: `true` if there are translations to display. + +`.Keywords` +: the meta keywords for the content. + +`.Kind` +: the page's *kind*. Possible return values are `page`, `home`, `section`, `taxonomy`, or `taxonomyTerm`. Note that there are also `RSS`, `sitemap`, `robotsTXT`, and `404` kinds, but these are only available during the rendering of each of these respective page's kind and therefore *not* available in any of the `Pages` collections. + +`.Lang` +: language taken from the language extension notation. + +`.Language` +: a language object that points to the language's definition in the site +`config`. + +`.Lastmod` +: the date the content was last modified; `.Lastmod` pulls from the `lastmod` field in a content's front matter. If `lastmod` is not set, Hugo will default to the `date` field. See also `.ExpiryDate`, `.Date`, and `.PublishDate`. + +`.LinkTitle` +: access when creating links to the content. If set, Hugo will use the `linktitle` from the front matter before `title`. + +`.Next` +: pointer to the following content (based on the `publishdate` field in front matter). + +`.NextInSection` +: pointer to the following content within the same section (based on `publishdate` field in front matter). + +`.OutputFormats` +: contains all formats, including the current format, for a given page. Can be combined the with [`.Get` function](/functions/get/) to grab a specific format. (See [Output Formats](/templates/output-formats/).) + +`.Pages` +: a collection of associated pages. This value will be `nil` for regular content pages. `.Pages` is an alias for `.Data.Pages`. + +`.Permalink` +: the Permanent link for this page; see [Permalinks](/content-management/urls/) + +`.Plain` +: the Page content stripped of HTML tags and presented as a string. + +`.PlainWords` +: the Page content stripped of HTML as a `[]string` using Go's [`strings.Fields`](https://golang.org/pkg/strings/#Fields) to split `.Plain` into a slice. + +`.Prev` +: Pointer to the previous content (based on `publishdate` in front matter). + +`.PrevInSection` +: Pointer to the previous content within the same section (based on `publishdate` in front matter). For example, `{{if .PrevInSection}}{{.PrevInSection.Permalink}}{{end}}`. + +`.PublishDate` +: the date on which the content was or will be published; `.Publishdate` pulls from the `publishdate` field in a content's front matter. See also `.ExpiryDate`, `.Date`, and `.Lastmod`. + +`.RSSLink` +: link to the taxonomies' RSS link. + +`.RawContent` +: raw markdown content without the front matter. Useful with [remarkjs.com]( +http://remarkjs.com) + +`.ReadingTime` +: the estimated time, in minutes, it takes to read the content. + +`.Ref` +: returns the permalink for a given reference (e.g., `.Ref "sample.md"`). `.Ref` does *not* handle in-page fragments correctly. See [Cross References](/content-management/cross-references/). + +`.RelPermalink` +: the relative permanent link for this page. + +`.RelRef` +: returns the relative permalink for a given reference (e.g., `RelRef +"sample.md"`). `.RelRef` does *not* handle in-page fragments correctly. See [Cross References](/content-management/cross-references/). + +`.Section` +: the [section](/content-management/sections/) this content belongs to. + +`.Site` +: see [Site Variables](/variables/site/). + +`.Summary` +: a generated summary of the content for easily showing a snippet in a summary view. The breakpoint can be set manually by inserting <!--more--> at the appropriate place in the content page. See [Content Summaries](/content-management/summaries/) for more details. + +`.TableOfContents` +: the rendered [table of contents](/content-management/toc/) for the page. + +`.Title` +: the title for this page. + +`.Translations` +: a list of translated versions of the current page. See [Multilingual Mode](/content-management/multilingual/) for more information. + +`.Truncated` +: a boolean, `true` if the `.Summary` is truncated. Useful for showing a "Read more..." link only when necessary. See [Summaries](/content-management/summaries/) for more information. + +`.Type` +: the [content type](/content-management/types/) of the content (e.g., `post`). + +`.URL` +: the URL for the page relative to the web root. Note that a `url` set directly in front matter overrides the default relative URL for the rendered page. + +`.UniqueID` +: the MD5-checksum of the content file's path. + +`.Weight` +: assigned weight (in the front matter) to this content, used in sorting. + +`.WordCount` +: the number of words in the content. + +## Page-level Params + +Any other value defined in the front matter in a content file, including taxonomies, will be made available as part of the `.Params` variable. + +``` +--- +title: My First Post +date: date: 2017-02-20T15:26:23-06:00 +categories: [one] +tags: [two,three,four] +``` + +With the above front matter, the `tags` and `categories` taxonomies are accessible via the following: + +* `.Params.tags` +* `.Params.categories` + +{{% note "Casing of Params" %}} +Page-level `.Params` are *only* accessible in lowercase. +{{% /note %}} + +The `.Params` variable is particularly useful for the introduction of user-defined front matter fields in content files. For example, a Hugo website on book reviews could have the following front matter in `/content/review/book01.md`: + +``` +--- +... +affiliatelink: "http://www.my-book-link.here" +recommendedby: "My Mother" +... +--- +``` + +These fields would then be accessible to the `/themes/yourtheme/layouts/review/single.html` template through `.Params.affiliatelink` and `.Params.recommendedby`, respectively. + +Two common situations where this type of front matter field could be introduced is as a value of a certain attribute like `href=""` or by itself to be displayed as text to the website's visitors. + +{{< code file="/themes/yourtheme/layouts/review/single.html" >}} +

    Buy this book

    +

    It was recommended by {{ .Params.recommendedby }}.

    +{{< /code >}} + +This template would render as follows, assuming you've set [`uglyURLs`](/content-management/urls/) to `false` in your [site `config`](/getting-started/configuration/): + +{{< output file="yourbaseurl/review/book01/index.html" >}} +

    Buy this book

    +

    It was recommended by my Mother.

    +{{< /output >}} + +{{% note %}} +See [Archetypes](/content-management/archetypes/) for consistency of `Params` across pieces of content. +{{% /note %}} + +### The `.Param` Method + +In Hugo, you can declare params in individual pages and globally for your entire website. A common use case is to have a general value for the site param and a more specific value for some of the pages (i.e., a header image): + +``` +{{ $.Param "header_image" }} +``` + +The `.Param` method provides a way to resolve a single value according to it's definition in a page parameter (i.e. in the content's front matter) or a site parameter (i.e., in your `config`). + +### Access Nested Fields in Front Matter + +When front matter contains nested fields like the following: + +``` +--- +author: + given_name: John + family_name: Feminella + display_name: John Feminella +--- +``` +`.Param` can access these fields by concatenating the field names together with a dot: + +``` +{{ $.Param "author.display_name" }} +``` + +If your front matter contains a top-level key that is ambiguous with a nested key, as in the following case: + +``` +--- +favorites.flavor: vanilla +favorites: + flavor: chocolate +--- +``` + +The top-level key will be preferred. Therefore, the following method, when applied to the previous example, will print `vanilla` and not `chocolate`: + +``` +{{ $.Param "favorites.flavor" }} +=> vanilla +``` diff --git a/content/variables/shortcodes.md b/content/variables/shortcodes.md new file mode 100644 index 000000000..c3a3f920b --- /dev/null +++ b/content/variables/shortcodes.md @@ -0,0 +1,36 @@ +--- +title: Shortcode Variables +linktitle: Shortcode Variables +description: Shortcodes can access page variables and also have their own specific built-in variables. +date: 2017-03-12 +publishdate: 2017-03-12 +lastmod: 2017-03-12 +categories: [variables and params] +#tags: [shortcodes] +draft: false +menu: + docs: + parent: "variables" + weight: 20 +weight: 20 +sections_weight: 20 +aliases: [] +toc: false +--- + +[Shortcodes][shortcodes] have access to parameters delimited in the shortcode declaration via [`.Get`][getfunction], page- and site-level variables, and also the following shortcode-specific fields: + +`.Parent` +: provides access to the parent shortcode context in nested shortcodes. This can be very useful for inheritance of common shortcode parameters from the root. + +`.IsNamedParams` +: boolean that returns `true` when the shortcode in question uses [named rather than positional parameters][shortcodes] + +`.Inner` +: represents the content between the opening and closing shortcode tags when a [closing shortcode][markdownshortcode] is used + +[getfunction]: /functions/get/ +[markdownshortcode]: /content-management/shortcodes/#shortcodes-with-markdown +[shortcodes]: /templates/shortcode-templates/ + + diff --git a/content/variables/site.md b/content/variables/site.md new file mode 100644 index 000000000..447a21fc3 --- /dev/null +++ b/content/variables/site.md @@ -0,0 +1,125 @@ +--- +title: Site Variables +linktitle: Site Variables +description: Many, but not all, site-wide variables are defined in your site's configuration. However, Hugo provides a number of built-in variables for convenient access to global values in your templates. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +categories: [variables and params] +#tags: [global,site] +draft: false +menu: + docs: + parent: "variables" + weight: 10 +weight: 10 +sections_weight: 10 +aliases: [/variables/site-variables/] +toc: true +--- + +The following is a list of site-level (aka "global") variables. Many of these variables are defined in your site's [configuration file][config], whereas others are built into Hugo's core for convenient usage in your templates. + +## Site Variables List + +`.Site.AllPages` +: array of all pages, regardless of their translation. + +`.Site.Author` +: a map of the authors as defined in the site configuration. + +`.Site.BaseURL` +: the base URL for the site as defined in the site configuration. + +`.Site.BuildDrafts` +: a boolean (default: `false`) to indicate whether to build drafts as defined in the site configuration. + +`.Site.Copyright` +: a string representing the copyright of your website as defined in the site configuration. + +`.Site.Data` +: custom data, see [Data Templates](/templates/data-templates/). + +`.Site.DisqusShortname` +: a string representing the shortname of the Disqus shortcode as defined in the site configuration. + +`.Site.Files` +: all source files for the Hugo website. + +`.Site.GoogleAnalytics` +: a string representing your tracking code for Google Analytics as defined in the site configuration. + +`.Site.IsMultiLingual` +: whether there are more than one language in this site. See [Multilingual](/content-management/multilingual/) for more information. + +`.Site.Language.Lang` +: the language code of the current locale (e.g., `en`). + +`.Site.Language.LanguageName` +: the full language name (e.g. `English`). + +`.Site.Language.Weight` +: the weight that defines the order in the `.Site.Languages` list. + +`.Site.Language` +: indicates the language currently being used to render the website. This object's attributes are set in site configurations' language definition. + +`.Site.LanguageCode` +: a string representing the language as defined in the site configuration. This is mostly used to populate the RSS feeds with the right language code. + +`.Site.LanguagePrefix` +: this can be used to prefix URLs to point to the correct language. It will even work when only one defined language. See also the functions [absLangURL](/functions/abslangurl/) and [relLangURL](/functions/rellangurl). + +`.Site.Languages` +: an ordered list (ordered by defined weight) of languages. + +`.Site.LastChange` +: a string representing the date/time of the most recent change to your site. This string is based on the [`date` variable in the front matter](/content-management/front-matter) of your content pages. + +`.Site.Menus` +: all of the menus in the site. + +`.Site.Pages` +: array of all content ordered by Date with the newest first. This array contains only the pages in the current language. + +`.Site.Permalinks` +: a string to override the default [permalink](/content-management/urls/) format as defined in the site configuration. + +`.Site.RegularPages` +: a shortcut to the *regular* page collection. `.Site.RegularPages` is equivalent to `where .Site.Pages "Kind" "page"`. + +`.Site.RSSLink` +: the URL for the site RSS. + +`.Site.Sections` +: top-level directories of the site. + +`.Site.Taxonomies` +: the [taxonomies](/taxonomies/usage/) for the entire site. Replaces the now-obsolete `.Site.Indexes` since v0.11. Also see section [Taxonomies elsewhere](#taxonomies-elsewhere). + +`.Site.Title` +: a string representing the title of the site. + +## The `.Site.Params` Variable + +`.Site.Params` is a container holding the values from the `params` section of your site configuration. + +### Example: `.Site.Params` + +The following `config.toml` defines a site-wide param for `description`: + +``` +baseURL = "https://yoursite.example.com/" + +[params] + description = "Tesla's Awesome Hugo Site" + author = "Nikola Tesla" +``` + +You can use `.Site.Params` in a [partial template](/templates/partials/) to call the default site description: + +{{< code file="layouts/partials/head.html" >}} + +{{< /code >}} + +[config]: /getting-started/configuration/ \ No newline at end of file diff --git a/content/variables/sitemap.md b/content/variables/sitemap.md new file mode 100644 index 000000000..da4c12e74 --- /dev/null +++ b/content/variables/sitemap.md @@ -0,0 +1,32 @@ +--- +title: Sitemap Variables +linktitle: Sitemap Variables +description: +date: 2017-03-12 +publishdate: 2017-03-12 +lastmod: 2017-03-12 +categories: [variables and params] +#tags: [sitemap] +draft: false +menu: + docs: + parent: "variables" + weight: 80 +weight: 80 +sections_weight: 80 +aliases: [] +toc: false +--- + +A sitemap is a `Page` and therefore has all the [page variables][pagevars] available to use sitemap templates. They also have the following sitemap-specific variables available to them: + +`.Sitemap.ChangeFreq` +: the page change frequency + +`.Sitemap.Priority` +: the priority of the page + +`.Sitemap.Filename` +: the sitemap filename + +[pagevars]: /variables/page/ \ No newline at end of file diff --git a/content/variables/taxonomy.md b/content/variables/taxonomy.md new file mode 100644 index 000000000..ef20156b4 --- /dev/null +++ b/content/variables/taxonomy.md @@ -0,0 +1,84 @@ +--- +title: Taxonomy Variables +linktitle: +description: Taxonomy pages are of type `Page` and have all page-, site-, and list-level variables available to them. However, taxonomy terms templates have additional variables available to their templates. +date: 2017-02-01 +publishdate: 2017-02-01 +lastmod: 2017-02-01 +categories: [variables and params] +#tags: [taxonomies,terms] +draft: false +menu: + docs: + parent: "variables" + weight: 30 +weight: 30 +sections_weight: 30 +aliases: [] +toc: true +--- + +## Taxonomy Terms Page Variables + +[Taxonomy terms pages][taxonomytemplates] are of the type `Page` and have the following additional variables. + +For example, the following fields would be available in `layouts/_defaults/terms.html`, depending on how you organize your [taxonomy templates][taxonomytemplates]: + +`.Data.Singular` +: The singular name of the taxonomy (e.g., `tags => `tag`) + +`.Data.Plural` +: The plural name of the taxonomy (e.g., `tags => tags`) + +`.Data.Pages` +: The list of pages in the taxonomy + +`.Data.Terms` +: The taxonomy itself + +`.Data.Terms.Alphabetical` +: The taxonomy terms alphabetized + +`.Data.Terms.ByCount` +: The Terms ordered by popularity + +Note that `.Data.Terms.Alphabetical` and `.Data.Terms.ByCount` can also be reversed: + +* `.Data.Terms.Alphabetical.Reverse` +* `.Data.Terms.ByCount.Reverse` + +## Use `.Site.Taxonomies` Outside of Taxonomy Templates + +The `.Site.Taxonomies` variable holds all the taxonomies defined site-wide. `.Site.Taxonomies` is a map of the taxonomy name to a list of its values (e.g., `"tags" -> ["tag1", "tag2", "tag3"]``). Each value, though, is not a string but rather a *Taxonomy variable*. + +## The `.Taxonomy` Variable + +The `.Taxonomy` variable, available, for example, as `.Site.Taxonomies.tags`, contains the list of tags (values) and, for each tag, their corresponding content pages. + +### Example Usage of `.Site.Taxonomies` + +The following [partial template][partials] will list all your site's taxonomies, each of their keys, and all the content assigned to each of the keys. For more examples of how to order and render your taxonomies, see [Taxonomy Templates][taxonomytemplates]. + +{{< code file="all-taxonomies-keys-and-pages.html" download="all-taxonomies-keys-and-pages.html" >}} +
    +
      + {{ range $taxonomyname, $taxonomy := .Site.Taxonomies }} +
    • {{ $taxonomyname }} +
        + {{ range $key, $value := $taxonomy }} +
      • {{ $key }}
      • + + {{ end }} +
      +
    • + {{ end }} +
    +
    +{{< /code >}} + +[partials]: /templates/partials/ +[taxonomytemplates]: /templates/taxonomy-templates/ \ No newline at end of file diff --git a/create/content.go b/create/content.go deleted file mode 100644 index 8af417294..000000000 --- a/create/content.go +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright 2016 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package create provides functions to create new content. -package create - -import ( - "bytes" - "os" - "os/exec" - "path/filepath" - - "github.com/gohugoio/hugo/helpers" - "github.com/gohugoio/hugo/hugolib" - jww "github.com/spf13/jwalterweatherman" -) - -// NewContent creates a new content file in the content directory based upon the -// given kind, which is used to lookup an archetype. -func NewContent( - ps *helpers.PathSpec, - siteFactory func(filename string, siteUsed bool) (*hugolib.Site, error), kind, targetPath string) error { - ext := helpers.Ext(targetPath) - - jww.INFO.Printf("attempting to create %q of %q of ext %q", targetPath, kind, ext) - - archetypeFilename := findArchetype(ps, kind, ext) - - // Building the sites can be expensive, so only do it if really needed. - siteUsed := false - - if archetypeFilename != "" { - f, err := ps.Fs.Source.Open(archetypeFilename) - if err != nil { - return err - } - defer f.Close() - - if helpers.ReaderContains(f, []byte(".Site")) { - siteUsed = true - } - } - - s, err := siteFactory(targetPath, siteUsed) - if err != nil { - return err - } - - var content []byte - - content, err = executeArcheTypeAsTemplate(s, kind, targetPath, archetypeFilename) - if err != nil { - return err - } - - contentPath := s.PathSpec.AbsPathify(filepath.Join(s.Cfg.GetString("contentDir"), targetPath)) - - if err := helpers.SafeWriteToDisk(contentPath, bytes.NewReader(content), s.Fs.Source); err != nil { - return err - } - - jww.FEEDBACK.Println(contentPath, "created") - - editor := s.Cfg.GetString("newContentEditor") - if editor != "" { - jww.FEEDBACK.Printf("Editing %s with %q ...\n", targetPath, editor) - - cmd := exec.Command(editor, contentPath) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - return cmd.Run() - } - - return nil -} - -// FindArchetype takes a given kind/archetype of content and returns an output -// path for that archetype. If no archetype is found, an empty string is -// returned. -func findArchetype(ps *helpers.PathSpec, kind, ext string) (outpath string) { - search := []string{ps.AbsPathify(ps.Cfg.GetString("archetypeDir"))} - - if ps.Cfg.GetString("theme") != "" { - themeDir := filepath.Join(ps.AbsPathify(ps.Cfg.GetString("themesDir")+"/"+ps.Cfg.GetString("theme")), "/archetypes/") - if _, err := ps.Fs.Source.Stat(themeDir); os.IsNotExist(err) { - jww.ERROR.Printf("Unable to find archetypes directory for theme %q at %q", ps.Cfg.GetString("theme"), themeDir) - } else { - search = append(search, themeDir) - } - } - - for _, x := range search { - // If the new content isn't in a subdirectory, kind == "". - // Therefore it should be excluded otherwise `is a directory` - // error will occur. github.com/gohugoio/hugo/issues/411 - var pathsToCheck = []string{"default"} - - if ext != "" { - if kind != "" { - pathsToCheck = append([]string{kind + ext, "default" + ext}, pathsToCheck...) - } else { - pathsToCheck = append([]string{"default" + ext}, pathsToCheck...) - } - } - - for _, p := range pathsToCheck { - curpath := filepath.Join(x, p) - jww.DEBUG.Println("checking", curpath, "for archetypes") - if exists, _ := helpers.Exists(curpath, ps.Fs.Source); exists { - jww.INFO.Println("curpath: " + curpath) - return curpath - } - } - } - - return "" -} diff --git a/create/content_template_handler.go b/create/content_template_handler.go deleted file mode 100644 index d73c52a24..000000000 --- a/create/content_template_handler.go +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright 2017 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package create - -import ( - "bytes" - "fmt" - "strings" - "time" - - "github.com/gohugoio/hugo/helpers" - "github.com/gohugoio/hugo/source" - - "github.com/gohugoio/hugo/hugolib" - "github.com/gohugoio/hugo/tpl" - "github.com/spf13/afero" -) - -// ArchetypeFileData represents the data available to an archetype template. -type ArchetypeFileData struct { - // The archetype content type, either given as --kind option or extracted - // from the target path's section, i.e. "blog/mypost.md" will resolve to - // "blog". - Type string - - // The current date and time as a RFC3339 formatted string, suitable for use in front matter. - Date string - - // The Site, fully equipped with all the pages etc. Note: This will only be set if it is actually - // used in the archetype template. Also, if this is a multilingual setup, - // this site is the site that best matches the target content file, based - // on the presence of language code in the filename. - Site *hugolib.Site - - // The target content file. Note that the .Content will be empty, as that - // has not been created yet. - *source.File -} - -const ( - // ArchetypeTemplateTemplate is used as initial template when adding an archetype template. - ArchetypeTemplateTemplate = `--- -title: "{{ replace .TranslationBaseName "-" " " | title }}" -date: {{ .Date }} -draft: true ---- - -` -) - -var ( - archetypeShortcodeReplacementsPre = strings.NewReplacer( - "{{<", "{x{<", - "{{%", "{x{%", - ">}}", ">}x}", - "%}}", "%}x}") - - archetypeShortcodeReplacementsPost = strings.NewReplacer( - "{x{<", "{{<", - "{x{%", "{{%", - ">}x}", ">}}", - "%}x}", "%}}") -) - -func executeArcheTypeAsTemplate(s *hugolib.Site, kind, targetPath, archetypeFilename string) ([]byte, error) { - - var ( - archetypeContent []byte - archetypeTemplate []byte - err error - ) - - sp := source.NewSourceSpec(s.Deps.Cfg, s.Deps.Fs) - f := sp.NewFile(targetPath) - - data := ArchetypeFileData{ - Type: kind, - Date: time.Now().Format(time.RFC3339), - File: f, - Site: s, - } - - if archetypeFilename == "" { - // TODO(bep) archetype revive the issue about wrong tpl funcs arg order - archetypeTemplate = []byte(ArchetypeTemplateTemplate) - } else { - archetypeTemplate, err = afero.ReadFile(s.Fs.Source, archetypeFilename) - if err != nil { - return nil, fmt.Errorf("Failed to read archetype file %q: %s", archetypeFilename, err) - } - - } - - // The archetype template may contain shortcodes, and these does not play well - // with the Go templates. Need to set some temporary delimiters. - archetypeTemplate = []byte(archetypeShortcodeReplacementsPre.Replace(string(archetypeTemplate))) - - // Reuse the Hugo template setup to get the template funcs properly set up. - templateHandler := s.Deps.Tmpl.(tpl.TemplateHandler) - templateName := "_text/" + helpers.Filename(archetypeFilename) - if err := templateHandler.AddTemplate(templateName, string(archetypeTemplate)); err != nil { - return nil, fmt.Errorf("Failed to parse archetype file %q: %s", archetypeFilename, err) - } - - templ := templateHandler.Lookup(templateName) - - var buff bytes.Buffer - if err := templ.Execute(&buff, data); err != nil { - return nil, fmt.Errorf("Failed to process archetype file %q: %s", archetypeFilename, err) - } - - archetypeContent = []byte(archetypeShortcodeReplacementsPost.Replace(buff.String())) - - if !bytes.Contains(archetypeContent, []byte("date")) || !bytes.Contains(archetypeContent, []byte("title")) { - // TODO(bep) remove some time in the future. - s.Log.FEEDBACK.Println(fmt.Sprintf(`WARNING: date and/or title missing from archetype file %q. -From Hugo 0.24 this must be provided in the archetype file itself, if needed. Example: -%s -`, archetypeFilename, ArchetypeTemplateTemplate)) - - } - - return archetypeContent, nil - -} diff --git a/create/content_test.go b/create/content_test.go deleted file mode 100644 index 914759164..000000000 --- a/create/content_test.go +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright 2016 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package create_test - -import ( - "os" - "path/filepath" - "strings" - "testing" - - "github.com/gohugoio/hugo/deps" - - "github.com/gohugoio/hugo/hugolib" - - "fmt" - - "github.com/gohugoio/hugo/hugofs" - - "github.com/gohugoio/hugo/create" - "github.com/gohugoio/hugo/helpers" - "github.com/spf13/afero" - "github.com/spf13/viper" - "github.com/stretchr/testify/require" -) - -func TestNewContent(t *testing.T) { - v := viper.New() - initViper(v) - - cases := []struct { - kind string - path string - expected []string - }{ - {"post", "post/sample-1.md", []string{`title = "Post Arch title"`, `test = "test1"`, "date = \"2015-01-12T19:20:04-07:00\""}}, - {"post", "post/org-1.org", []string{`#+title: ORG-1`}}, - {"emptydate", "post/sample-ed.md", []string{`title = "Empty Date Arch title"`, `test = "test1"`}}, - {"stump", "stump/sample-2.md", []string{`title: "Sample 2"`}}, // no archetype file - {"", "sample-3.md", []string{`title: "Sample 3"`}}, // no archetype - {"product", "product/sample-4.md", []string{`title = "SAMPLE-4"`}}, // empty archetype front matter - {"shortcodes", "shortcodes/go.md", []string{ - `title = "GO"`, - "{{< myshortcode >}}", - "{{% myshortcode %}}", - "{{}}\n{{%/* comment */%}}"}}, // shortcodes - } - - for _, c := range cases { - cfg, fs := newTestCfg() - ps, err := helpers.NewPathSpec(fs, cfg) - require.NoError(t, err) - h, err := hugolib.NewHugoSites(deps.DepsCfg{Cfg: cfg, Fs: fs}) - require.NoError(t, err) - require.NoError(t, initFs(fs)) - - siteFactory := func(filename string, siteUsed bool) (*hugolib.Site, error) { - return h.Sites[0], nil - } - - require.NoError(t, create.NewContent(ps, siteFactory, c.kind, c.path)) - - fname := filepath.Join("content", filepath.FromSlash(c.path)) - content := readFileFromFs(t, fs.Source, fname) - for i, v := range c.expected { - found := strings.Contains(content, v) - if !found { - t.Errorf("[%d] %q missing from output:\n%q", i, v, content) - } - } - } -} - -func initViper(v *viper.Viper) { - v.Set("metaDataFormat", "toml") - v.Set("archetypeDir", "archetypes") - v.Set("contentDir", "content") - v.Set("themesDir", "themes") - v.Set("layoutDir", "layouts") - v.Set("i18nDir", "i18n") - v.Set("theme", "sample") -} - -func initFs(fs *hugofs.Fs) error { - perm := os.FileMode(0755) - var err error - - // create directories - dirs := []string{ - "archetypes", - "content", - filepath.Join("themes", "sample", "archetypes"), - } - for _, dir := range dirs { - err = fs.Source.Mkdir(dir, perm) - if err != nil { - return err - } - } - - // create files - for _, v := range []struct { - path string - content string - }{ - { - path: filepath.Join("archetypes", "post.md"), - content: "+++\ndate = \"2015-01-12T19:20:04-07:00\"\ntitle = \"Post Arch title\"\ntest = \"test1\"\n+++\n", - }, - { - path: filepath.Join("archetypes", "post.org"), - content: "#+title: {{ .BaseFileName | upper }}", - }, - { - path: filepath.Join("archetypes", "product.md"), - content: `+++ -title = "{{ .BaseFileName | upper }}" -+++`, - }, - { - path: filepath.Join("archetypes", "emptydate.md"), - content: "+++\ndate =\"\"\ntitle = \"Empty Date Arch title\"\ntest = \"test1\"\n+++\n", - }, - // #3623x - { - path: filepath.Join("archetypes", "shortcodes.md"), - content: `+++ -title = "{{ .BaseFileName | upper }}" -+++ - -{{< myshortcode >}} - -Some text. - -{{% myshortcode %}} -{{}} -{{%/* comment */%}} - - -`, - }, - } { - f, err := fs.Source.Create(v.path) - if err != nil { - return err - } - defer f.Close() - - _, err = f.Write([]byte(v.content)) - if err != nil { - return err - } - } - - return nil -} - -// TODO(bep) extract common testing package with this and some others -func readFileFromFs(t *testing.T, fs afero.Fs, filename string) string { - filename = filepath.FromSlash(filename) - b, err := afero.ReadFile(fs, filename) - if err != nil { - // Print some debug info - root := strings.Split(filename, helpers.FilePathSeparator)[0] - afero.Walk(fs, root, func(path string, info os.FileInfo, err error) error { - if info != nil && !info.IsDir() { - fmt.Println(" ", path) - } - - return nil - }) - t.Fatalf("Failed to read file: %s", err) - } - return string(b) -} - -func newTestCfg() (*viper.Viper, *hugofs.Fs) { - - v := viper.New() - fs := hugofs.NewMem(v) - - v.SetFs(fs.Source) - - initViper(v) - - return v, fs - -} diff --git a/data/articles.toml b/data/articles.toml new file mode 100644 index 000000000..379ae5562 --- /dev/null +++ b/data/articles.toml @@ -0,0 +1,731 @@ +[[article]] + title = "A visit to the Workshop: Hugo/Unix/Vim integration" + url = "https://blog.afoolishmanifesto.com/posts/hugo-unix-vim-integration/" + author = "fREW Schmidt" + date = "2017-07-22" + +[[article]] + title = "Hugo Easy Gallery - Automagical PhotoSwipe image gallery with a one-line shortcode" + url = "https://www.liwen.id.au/heg/" + author = "Li-Wen Yip" + date = "2017-03-25" + +[[article]] + title = "Automagical Image Gallery in Hugo with PhotoSwipe and jQuery" + url = "https://www.liwen.id.au/photoswipe/" + author = "Li-Wen Yip" + date = "2017-03-04" + +[[article]] + title = "Adding Isso Comments to Hugo" + url = "https://stiobhart.net/2017-02-24-isso-comments/" + author = "Stíobhart Matulevicz" + date = "2017-02-24" + +[[article]] + title = "Hugo Tutorial: How to Build & Host a (Very Fast) Static E-Commerce Site" + url = "https://snipcart.com/blog/hugo-tutorial-static-site-ecommerce" + author = "Snipcart" + date = "2017-02-23" + +[[article]] + title = "How to Password Protect a Hugo Site" + url = "https://www.aerobatic.com/blog/password-protect-a-hugo-site/" + author = "Aerobatic" + date = "2017-02-19" + +[[article]] + title = "Switching from Wordpress to Hugo" + url = "http://schnuddelhuddel.de/switching-from-wordpress-to-hugo/" + author = "Mario Martelli" + date = "2017-02-19" + +[[article]] + title = "Zero to HTTP/2 with AWS and Hugo" + url = "https://habd.as/zero-to-http-2-aws-hugo/" + author = "Josh Habdas" + date = "2017-02-16" + +[[article]] + title = "Deploy a Hugo site to Aerobatic with CircleCI" + url = "https://www.aerobatic.com/blog/hugo-github-circleci/" + author = "Aerobatic" + date = "2017-02-14" + +[[article]] + title = "NPM scripts for building and deploying Hugo site" + url = "https://www.aerobatic.com/blog/hugo-npm-buildtool-setup/" + author = "Aerobatic" + date = "2017-02-12" + +[[article]] + title = "Getting started with Hugo and the plain-blog theme, on NearlyFreeSpeech.Net" + url = "https://www.penwatch.net/cms/get_started_plain_blog/" + author = "Li-aung “Lewis” Yip" + date = "2017-02-12" + +[[article]] + title = "Choose Hugo over Jekyll" + url = "https://habd.as/choose-hugo-over-jekyll/" + author = "Josh Habdas" + date = "2017-02-10" + +[[article]] + title = "Build a Hugo site using Cloud9 IDE and host on App Engine" + url = "https://loyall.ch/lab/2017/01/build-a-static-website-with-cloud9-hugo-and-app-engine/" + author = "Pascal Aubort" + date = "2017-02-05" + +[[article]] + title = "Hugo Continuous Deployment with Bitbucket Pipelines and Aerobatic" + url = "https://www.aerobatic.com/blog/hugo-bitbucket-pipelines/" + author = "Aerobatic" + date = "2017-02-04" + +[[article]] + title = "How to use Firebase to host a Hugo site" + url = "https://www.m0d3rnc0ad.com/post/static-site-firebase/" + author = "Andrew Cuga" + date= "2017-02-04" + +[[article]] + title = "A publishing workflow for teams using static site generators" + url = "https://www.keybits.net/post/publishing-workflow-for-teams-using-static-site-generators/" + author = "Tom Atkins" + date = "2017-01-02" + +[[article]] + title = "How To Dynamically Use Google Fonts In A Hugo Website" + url = "https://stoned.io/web-development/hugo/How-To-Dynamically-Use-Google-Fonts-In-A-Hugo-Website/" + author = "Hash Borgir" + date = "2016-10-27" + +[[article]] + title = "Embedding Facebook In A Hugo Template" + url = "https://stoned.io/web-development/hugo/Embedding-Facebook-In-A-Hugo-Template/" + author = "Hash Borgir" + date = "2016-10-22" + +[[article]] + title = "通过 Gitlab-cl 将 Hugo blog 自动部署至 GitHub (Chinese, Continious integration)" + url = "https://zetaoyang.github.io/post/2016/10/17/gitlab-cl.html" + author = "Zetao Yang" + date = "2016-10-17" + +[[article]] + title = "A Step-by-Step Guide: Hugo on Netlify" + url = "https://www.netlify.com/blog/2016/09/21/a-step-by-step-guide-hugo-on-netlify/" + author = "Eli Williamson" + date = "2016-09-21" + +[[article]] + title = "Building our site: From Django & Wordpress to a static generator (Part I)" + url = "https://tryolabs.com/blog/2016/09/20/building-our-site-django-wordpress-to-static-part-i/" + author = "Alan Descoins" + date = "2016-09-20" + +[[article]] + title = "Webseitenmaschine - Statische Websites mit Hugo erzeugen (German, $)" + url = "http://www.heise.de/ct/ausgabe/2016-12-Statische-Websites-mit-Hugo-erzeugen-3211704.html" + author = "Christian Helmbold" + date = "2016-05-27" + +[[article]] + title = "Cómo hacer sitios web estáticos con Hugo y Go - Platzi (Video tutorial)" + url = "https://www.youtube.com/watch?v=qaXXpdiCHXE" + author = "Verónica López" + date = "2016-04-06" + +[[article]] + title = "CDNOverview: A CDN comparison site made with Hugo" + url = "https://www.cloakfusion.com/cdnoverview-cdn-comparison-site-made-hugo/" + author = "Thijs de Zoete" + date = "2016-02-23" + +[[article]] + title = "Hugo: A Modern WebSite Engine That Just Works" + url = "https://github.com/shekhargulati/52-technologies-in-2016/blob/master/07-hugo/README.md" + author = "Shekhar Gulati" + date = "2016-02-14" + +[[article]] + title = "Minify Hugo Generated HTML" + url = "http://ratson.name/blog/minify-hugo-generated-html/" + author = "Ratson" + date = "2016-02-02" + +[[article]] + title = "HugoのデプロイをWerckerからCircle CIに変更した - log" + url = "http://log.deprode.net/logs/2016-01-17/" + author = "Deprode" + date = "2016-01-17" + +[[article]] + title = "Static site generators: el futuro de las webs estáticas
    (Hugo, Jekyll, Flask y otros)" + url = "http://sitelabs.es/static-site-generators-futuro-las-webs-estaticas/" + author = "Eneko Sarasola" + date = "2016-01-09" + +[[article]] + title = "Writing a Lambda Function for Hugo" + url = "https://blog.jolexa.net/post/writing-a-lambda-function-for-hugo/" + author = "Jeremy Olexa" + date = "2016-01-01" + +[[article]] + title = "Ein Blog mit Hugo erstellen - Tutorial (Deutsch/German)" + url = "http://privat.albicker.org/tags/hugo.html" + author = "Bernhard Albicker" + date = "2015-12-30" + +[[article]] + title = "How to host Hugo static website generator on AWS Lambda" + url = "http://bezdelev.com/post/hugo-aws-lambda-static-website/" + author = "Ilya Bezdelev" + date = "2015-12-15" + +[[article]] + title = "Migrating from Pelican to Hugo" + url = "http://www.softinio.com/post/migrating-from-pelican-to-hugo/" + author = "Salar Rahmanian" + date = "2015-11-29" + +[[article]] + title = "Static Website Generators Reviewed: Jekyll, Middleman, Roots, Hugo" + url = "http://www.smashingmagazine.com/2015/11/static-website-generators-jekyll-middleman-roots-hugo-review/" + author = "Mathias Biilmann Christensen" + date = "2015-11-16" + +[[article]] + title = "How To Deploy a Hugo Site to Production with Git Hooks on Ubuntu 14.04" + url = "https://www.digitalocean.com/community/tutorials/how-to-deploy-a-hugo-site-to-production-with-git-hooks-on-ubuntu-14-04" + author = "Justin Ellingwood" + date = "2015-11-12" + +[[article]] + title = "How To Install and Use Hugo, a Static Site Generator, on Ubuntu 14.04" + url = "https://www.digitalocean.com/community/tutorials/how-to-install-and-use-hugo-a-static-site-generator-on-ubuntu-14-04" + author = "Justin Ellingwood" + date = "2015-11-09" + +[[article]] + title = "Switching from Wordpress to Hugo" + url = "http://justinfx.com/2015/11/08/switching-from-wordpress-to-hugo/" + author = "Justin Israel" + date = "2015-11-08" + +[[article]] + title = "Hands-on Experience with Hugo as a Static Site Generator" + url = "http://usersnap.com/blog/hands-on-experience-with-hugo-static-site-generator/" + author = "Thomas Peham" + date = "2015-10-15" + +[[article]] + title = "Statische Webseites mit Hugo erstellen/Vortrag mit Foliensatz (deutsch)" + url = "http://sfd.koelnerlinuxtreffen.de/2015/HaraldWeidner/" + author = "Harald Weidner" + date = "2015-09-19" + +[[article]] + title = "Moving from WordPress to Hugo" + url = "http://abhipandey.com/2015/09/moving-to-hugo/" + author = "Abhishek Pandey" + date = "2015-09-15" + +[[article]] + title = "通过webhook将Hugo自动部署至GitHub Pages和GitCafe Pages (Automated deployment)" + url = "http://blog.coderzh.com/2015/09/13/use-webhook-automated-deploy-hugo/" + author = "CoderZh" + date = "2015-09-13" + +[[article]] + title = "使用hugo搭建个人博客站点 (Using Hugo to build a personal blog site)" + url = "http://blog.coderzh.com/2015/08/29/hugo/" + author = "CoderZh" + date = "2015-08-29" + +[[article]] + title = "Good-Bye Wordpress, Hello Hugo! (German)" + url = "http://blog.arminhanisch.de/2015/08/blog-migration-zu-hugo/" + author = "Armin Hanisch" + date = "2015-08-18" + +[[article]] + title = "Générer votre site web statique avec Hugo (Generate your static site with Hugo)" + url = "http://www.linux-pratique.com/?p=191" + author = "Benoît Benedetti" + date = "2015-06-26" + +[[article]] + title = "Hugo向けの新しいテーマを作った (I created a new theme for Hugo)" + url = "https://yet.unresolved.xyz/blog/2016/10/03/how-to-make-of-hugo-theme/" + author = "Daisuke Tsuji" + date = "2015-06-20" + +[[article]] + title = "Hugo - Gerando um site com conteúdo estático. (Portuguese Brazil)" + url = "http://blog.ffrizzo.com/posts/hugo/" + author = "Fabiano Frizzo" + date = "2015-06-02" + +[[article]] + title = "An Introduction to Static Site Generators" + url = "http://davidwalsh.name/introduction-static-site-generators" + author = "Eduardo Bouças" + date = "2015-05-20" + +[[article]] + title = "Hugo Still Rules" + url = "http://cheekycoder.com/2015/05/hugo-still-rules/" + author = "Cheeky Coder" + date = "2015-05-18" + +[[article]] + title = "hugo - Static Site Generator" + url = "http://gscacco.github.io/post/hugo/" + author = "G Scaccoio" + date = "2015-05-04" + +[[article]] + title = "WindowsでHugoを使う" + url = "http://ureta.net/2015/05/hugo-on-windows/" + author = "うれ太郎" + date = "2015-05-01" + +[[article]] + title = "Hugoのshortcodesを用いてサイトにスライドなどを埋め込む" + url = "http://blog.yucchiy.com/2015/04/29/hugo-shortcode/" + author = "Yucchiy" + date = "2015-04-29" + +[[article]] + title = "HugoとCircleCIでGitHub PagesにBlogを公開してみたら超簡単だった" + url = "http://hori-ryota.github.io/blog/create-blog-with-hugo-and-circleci/" + author = "Hori Ryota" + date = "2015-04-17" + +[[article]] + title = "10 Best Static Site Generators" + url = "http://beebom.com/2015/04/best-static-site-generators" + author = "Aniruddha Mysore" + date = "2015-04-06" + +[[article]] + title = "Goodbye WordPress; Hello Hugo" + url = "http://willwarren.com/2015/04/05/goodbye-wordpress-hello-hugo/" + author = "Will Warren" + date = "2015-04-05" + +[[article]] + title = "Static Websites with Hugo on Google Cloud Storage" + url = "http://www.moxie.io/post/static-websites-with-hugo-on-google-cloud-storage/" + author = "Moxie Input/Output" + date = "2015-04-02" + +[[article]] + title = "De nuevo iniciando un blog" + url = "https://alvarolizama.net/" + author = "Alvaro Lizama" + date = "2015-03-29" + +[[article]] + title = "We moved our blog from Posthaven to Hugo after only three posts. Why?" + url = "http://blog.hypriot.com/post/moved-from-posthaven-to-hugo/" + author = "Hypriot" + date = "2015-03-27" + +[[article]] + title = "Top Static Site Generators in 2015" + url = "http://superdevresources.com/static-site-generators-2015/" + author = "Kanishk Kunal" + date = "2015-03-12" + +[[article]] + title = "Moving to Hugo" + url = "http://abiosoft.com/moving-to-hugo/" + author = "Abiola Ibrahim" + date = "2015-03-08" + +[[article]] + title = "Migrating a blog (yes, this one!) from Wordpress to Hugo" + url = "http://justindunham.net/migrating-from-wordpress-to-hugo/" + author = "Justin Dunham" + date = "2015-02-13" + +[[article]] + title = "blogをoctopressからHugoに乗り換えたメモ" + url = "http://blog.jigyakkuma.org/2015/02/11/hugo/" + author = "jigyakkuma" + date = "2015-02-11" + +[[article]] + title = "Hugoでブログをつくった" + url = "http://porgy13.github.io/post/new-hugo-blog/" + author = "porgy13" + date = "2015-02-07" + +[[article]] + title = "Hugoにブログを移行した" + url = "http://keichi.net/post/first/" + author = "Keichi Takahashi" + date = "2015-02-04" + +[[article]] + title = "Hugo静态网站生成器中文教程" + url = "http://nanshu.wang/post/2015-01-31/" + author = "Nanshu Wang" + date = "2015-01-31" + +[[article]] + title = "Hugo + Github Pages + Wercker CI = ¥0(無料)
    でコマンド 1 発(自動化)でサイト
    ・ブログを公開・運営・分析・収益化
    " + url = "http://qiita.com/yoheimuta/items/8a619cac356bed89a4c9" + author = "Yohei Yoshimuta" + date = "2015-01-31" + +[[article]] + title = "Running Hugo websites on anynines" + url = "http://blog.anynines.com/running-hugo-websites-on-anynines/" + author = "Julian Weber" + date = "2015-01-30" + +[[article]] + title = "MiddlemanからHugoへ移行した" + url = "http://re-dzine.net/2015/01/hugo/" + author = "Haruki Konishi" + date = "2015-01-21" + +[[article]] + title = "WordPress から Hugo に乗り換えました" + url = "http://rakuishi.com/archives/wordpress-to-hugo/" + author = "rakuishi" + date = "2015-01-20" + +[[article]] + title = "HUGOを使ってサイトを立ち上げる方法" + url = "http://qiita.com/syui/items/869538099551f24acbbf" + author = "Syui" + date = "2015-01-17" + +[[article]] + title = "Jekyllが許されるのは小学生までだよね" + url = "http://t32k.me/mol/log/hugo/" + author = "Ishimoto Koji" + date = "2015-01-16" + +[[article]] + title = "Getting started with Hugo" + url = "http://anthonyfok.org/post/getting-started-with-hugo/" + author = "Anthony Fok" + date = "2015-01-12" + +[[article]] + title = "把这个博客静态化了 (Migrate to Hugo)" + url = "http://lich-eng.com/2015/01/03/migrate-to-hugo/" + author = "Li Cheng" + date = "2015-01-03" + +[[article]] + title = "Porting my blog with Hugo" + url = "http://blog.srackham.com/posts/porting-my-blog-with-hugo/" + author = "Stuart Rackham" + date = "2014-12-30" + +[[article]] + title = "Hugoを使ってみたときのメモ" + url = "http://machortz.github.io/posts/usinghugo/" + author = "Machortz" + date = "2014-12-29" + +[[article]] + title = "OctopressからHugoへ移行した" + url = "http://deeeet.com/writing/2014/12/25/hugo/" + author = "Taichi Nakashima" + date = "2014-12-25" + +[[article]] + title = "Migrating to Hugo From Octopress" + url = "http://nathanleclaire.com/blog/2014/12/22/migrating-to-hugo-from-octopress/" + author = "Nathan LeClaire" + date = "2014-12-22" + +[[article]] + title = "Dynamic Pages with GoHugo.io" + url = "http://cyrillschumacher.com/2014/12/21/dynamic-pages-with-gohugo.io/" + author = "Cyrill Schumacher" + date = "2014-12-21" + +[[article]] + title = "6 Static Blog Generators That Aren’t Jekyll" + url = "http://www.sitepoint.com/6-static-blog-generators-arent-jekyll/" + author = "David Turnbull" + date = "2014-12-08" + +[[article]] + title = "Travel Blogging Setup" + url = "http://www.stou.dk/2014/11/travel-blogging-setup/" + author = "Rasmus Stougaard" + date = "2014-11-23" + +[[article]] + title = "Hosting A Hugo Website Behind Nginx" + url = "http://www.bigbeeconsultants.co.uk/blog/hosting-hugo-website-behind-nginx" + author = "Rick Beton" + date = "2014-11-20" + +[[article]] + title = "使用Hugo搭建免费个人Blog (How to use Hugo)" + url = "http://ulricqin.com/post/how-to-use-hugo/" + author = "Ulric Qin 秦晓辉" + date = "2014-11-11" + +[[article]] + title = "Built in Speed and Built for Speed by Hugo" + url = "http://cheekycoder.com/2014/10/built-for-speed-by-hugo/" + author = "Cheeky Coder" + date = "2014-10-30" + +[[article]] + title = "Hugo para crear sitios web estáticos" + url = "http://www.webbizarro.com/noticias/1076/hugo-para-crear-sitios-web-estaticos/" + author = "Web Bizarro" + date = "2014-08-19" + +[[article]] + title = "Going with hugo" + url = "http://www.markuseliasson.se/article/going-with-hugo/" + author = "Markus Eliasson" + date = "2014-08-18" + +[[article]] + title = "Benchmarking Jekyll, Hugo and Wintersmith" + url = "http://fredrikloch.me/post/2014-08-12-Jekyll-and-its-alternatives-from-a-site-generation-point-of-view/" + author = "Fredrik Loch" + date = "2014-08-12" + +[[article]] + title = "Goodbye Octopress, Hello Hugo!" + url = "http://andreimihu.com/blog/2014/08/11/goodbye-octopress-hello-hugo/" + author = "Andrei Mihu" + date = "2014-08-11" + +[[article]] + title = "Beautiful sites for Open Source projects" + url = "http://beautifulopen.com/2014/08/09/hugo/" + author = "Beautiful Open" + date = "2014-08-09" + +[[article]] + title = "Hugo: Beyond the Defaults" + url = "http://npf.io/2014/08/hugo-beyond-the-defaults/" + author = "Nate Finch" + date = "2014-08-08" + +[[article]] + title = "First Impressions of Hugo" + url = "https://peteraba.com/blog/first-impressions-of-hugo/" + author = "Peter Aba" + date = "2014-06-06" + +[[article]] + title = "New Site Workflow" + url = "http://vurt.co.uk/post/new_website/" + author = "Giles Paterson" + date = "2014-08-05" + +[[article]] + title = "How I Learned to Stop Worrying and Love the (Static) Web" + url = "http://cognition.ca/post/about-hugo/" + author = "Joshua McKenty" + date = "2014-08-04" + +[[article]] + title = "Hugo - Static Site Generator" + url = "http://kenwoo.io/blog/hugo---static-site-generator/" + author = "Kenny Woo" + date = "2014-08-03" + +[[article]] + title = "Hugo Is Friggin' Awesome" + url = "http://npf.io/2014/08/hugo-is-awesome/" + author = "Nate Finch" + date = "2014-08-01" + +[[article]] + title = "再次搬家 (Move from WordPress to Hugo)" + url = "http://www.chingli.com/misc/move-from-wordpress-to-hugo/" + author = "青砾 (chingli)" + date = "2014-07-12" + +[[article]] + title = "Embedding Gists in Hugo" + url = "http://danmux.com/posts/embedded_gists/" + author = "Dan Mull" + date = "2014-07-05" + +[[article]] + title = "An Introduction To Hugo" + url = "http://www.cirrushosting.com/web-hosting-blog/an-introduction-to-hugo/" + author = "Dan Silber" + date = "2014-07-01" + +[[article]] + title = "Moving to Hugo" + url = "http://danmux.com/posts/hugo_based_blog/" + author = "Dan Mull" + date = "2014-05-29" + +[[article]] + title = "开源之静态站点生成器排行榜
    (Leaderboard of open-source static website generators)" + url = "http://code.csdn.net/news/2819909" + author = "CSDN.net" + date = "2014-05-23" + +[[article]] + title = "Finally, a satisfying and effective blog setup" + url = "http://michaelwhatcott.com/now-powered-by-hugo/" + author = "Michael Whatcott" + date = "2014-05-20" + +[[article]] + title = "Hugo from scratch" + url = "http://zackofalltrades.com/notes/2014/05/hugo-from-scratch/" + author = "Zack Williams" + date = "2014-05-18" + +[[article]] + title = "Why I switched away from Jekyll" + url = "http://www.jakejanuzelli.com/why-I-switched-away-from-jekyll/" + author = "Jake Januzelli" + date = "2014-05-10" + +[[article]] + title = "Welcome our new blog" + url = "http://blog.ninya.io/posts/welcome-our-new-blog/" + author = "Ninya.io" + date = "2014-04-11" + +[[article]] + title = "Mission Not Accomplished" + url = "http://johnsto.co.uk/blog/mission-not-accomplished/" + author = "Dave Johnston" + date = "2014-04-03" + +[[article]] + title = "Hugo - A Static Site Builder in Go" + url = "http://deepfriedcode.com/post/hugo/" + author = "Deep Fried Code" + date = "2014-03-30" + +[[article]] + title = "Adventures in Angular Podcast" + url = "http://devchat.tv/adventures-in-angular/003-aia-gdes" + author = "Matias Niemela" + date = "2014-03-28" + +[[article]] + title = "Hugo" + url = "http://bra.am/post/hugo/" + author = "bra.am" + date = "2014-03-23" + +[[article]] + title = "Converting Blogger To Markdown" + url = "http://trishagee.github.io/project/atom-to-hugo/" + author = "Trisha Gee" + date = "2014-03-20" + +[[article]] + title = "Moving to Hugo Static Web Pages" + url = "http://tepid.org/tech/hugo-web/" + author = "Tobias Weingartner" + date = "2014-03-16" + +[[article]] + title = "New Blog Engine: Hugo" + url = "https://blog.afoolishmanifesto.com/posts/hugo/" + author = "fREW Schmidt" + date = "2014-03-15" + +[[article]] + title = "Hugo + gulp.js = Huggle" + url = "http://ktmud.github.io/huggle/en/intro/)" + author = "Jesse Yang 杨建超" + date = "2014-03-08" + +[[article]] + title = "Powered by Hugo" + url = "http://kieranhealy.org/blog/archives/2014/02/24/powered-by-hugo/" + author = "Kieran Healy" + date = "2014-02-24" + +[[article]] + title = "静的サイトを素早く構築するために
    GoLangで作られたジェネレータHugo
    " + url = "http://hamasyou.com/blog/2014/02/21/hugo/" + author = "
    Shogo Hamada
    濱田章吾
    " + date = "2014-02-21" + +[[article]] + title = "Latest Roundup of Useful Tools For Developers" + url = "http://codegeekz.com/latest-roundup-of-useful-tools-for-developers/" + author = "CodeGeekz" + date = "2014-02-13" + +[[article]] + title = "Hugo: Static Site Generator written in Go" + url = "http://www.braveterry.com/2014/02/06/hugo-static-site-generator-written-in-go/" + author = "Brave Terry" + date = "2014-02-06" + +[[article]] + title = "10 Useful HTML5 Tools for Web Designers and Developers" + url = "http://designdizzy.com/10-useful-html5-tools-for-web-designers-and-developers/" + author = "Design Dizzy" + date = "2014-02-04" + +[[article]] + title = "Hugo – Fast, Flexible Static Site Generator" + url = "http://cube3x.com/hugo-fast-flexible-static-site-generator/" + author = "Joby Joseph" + date = "2014-01-18" + +[[article]] + title = "Hugo: A new way to build static website" + url = "http://www.w3update.com/opensource/hugo-a-new-way-to-build-static-website.html" + author = "w3update" + date = "2014-01-17" + +[[article]] + title = "Xaprb now uses Hugo" + url = "http://xaprb.com/blog/2014/01/15/using-hugo/" + author = "Baron Schwartz" + date = "2014-01-15" + +[[article]] + title = "New jQuery Plugins And Resources That Web Designers Need" + url = "http://www.designyourway.net/blog/resources/new-jquery-plugins-and-resources-that-web-designers-need/" + author = "Design Your Way" + date = "2014-01-01" + +[[article]] + title = "On Blog Construction" + url = "http://alexla.sh/post/on-blog-construction/" + author = "Alexander Lash" + date = "2013-12-27" + +[[article]] + title = "Hugo" + url = "http://onethingwell.org/post/69070926608/hugo" + author = "One Thing Well" + date = "2013-12-05" + +[[article]] + title = "In Praise Of Hugo" + url = "http://sound-guru.com/blog/post/hello-world/" + author = "sound-guru.com" + date = "2013-10-19" + +[[article]] + title = "Hosting a blog on S3 and Cloudfront" + url = "http://www.danesparza.net/2013/07/hosting-a-blog-on-s3-and-cloudfront/" + author = "Dan Esparza" + date = "2013-07-24" diff --git a/data/docs.json b/data/docs.json new file mode 100644 index 000000000..59ab28281 --- /dev/null +++ b/data/docs.json @@ -0,0 +1,1778 @@ +{ + "media": { + "types": [ + { + "type": "application/javascript", + "string": "application/javascript+js", + "mainType": "application", + "subType": "javascript", + "suffix": "js", + "delimiter": "." + }, + { + "type": "application/json", + "string": "application/json+json", + "mainType": "application", + "subType": "json", + "suffix": "json", + "delimiter": "." + }, + { + "type": "application/rss", + "string": "application/rss+xml", + "mainType": "application", + "subType": "rss", + "suffix": "xml", + "delimiter": "." + }, + { + "type": "application/xml", + "string": "application/xml+xml", + "mainType": "application", + "subType": "xml", + "suffix": "xml", + "delimiter": "." + }, + { + "type": "text/calendar", + "string": "text/calendar+ics", + "mainType": "text", + "subType": "calendar", + "suffix": "ics", + "delimiter": "." + }, + { + "type": "text/css", + "string": "text/css+css", + "mainType": "text", + "subType": "css", + "suffix": "css", + "delimiter": "." + }, + { + "type": "text/csv", + "string": "text/csv+csv", + "mainType": "text", + "subType": "csv", + "suffix": "csv", + "delimiter": "." + }, + { + "type": "text/html", + "string": "text/html+html", + "mainType": "text", + "subType": "html", + "suffix": "html", + "delimiter": "." + }, + { + "type": "text/plain", + "string": "text/plain+txt", + "mainType": "text", + "subType": "plain", + "suffix": "txt", + "delimiter": "." + } + ] + }, + "output": { + "formats": [ + { + "MediaType": "text/html+html", + "name": "AMP", + "mediaType": { + "type": "text/html", + "string": "text/html+html", + "mainType": "text", + "subType": "html", + "suffix": "html", + "delimiter": "." + }, + "path": "amp", + "baseName": "index", + "rel": "amphtml", + "protocol": "", + "isPlainText": false, + "isHTML": true, + "noUgly": false, + "notAlternative": false + }, + { + "MediaType": "text/css+css", + "name": "CSS", + "mediaType": { + "type": "text/css", + "string": "text/css+css", + "mainType": "text", + "subType": "css", + "suffix": "css", + "delimiter": "." + }, + "path": "", + "baseName": "styles", + "rel": "stylesheet", + "protocol": "", + "isPlainText": true, + "isHTML": false, + "noUgly": false, + "notAlternative": true + }, + { + "MediaType": "text/csv+csv", + "name": "CSV", + "mediaType": { + "type": "text/csv", + "string": "text/csv+csv", + "mainType": "text", + "subType": "csv", + "suffix": "csv", + "delimiter": "." + }, + "path": "", + "baseName": "index", + "rel": "alternate", + "protocol": "", + "isPlainText": true, + "isHTML": false, + "noUgly": false, + "notAlternative": false + }, + { + "MediaType": "text/calendar+ics", + "name": "Calendar", + "mediaType": { + "type": "text/calendar", + "string": "text/calendar+ics", + "mainType": "text", + "subType": "calendar", + "suffix": "ics", + "delimiter": "." + }, + "path": "", + "baseName": "index", + "rel": "alternate", + "protocol": "webcal://", + "isPlainText": true, + "isHTML": false, + "noUgly": false, + "notAlternative": false + }, + { + "MediaType": "text/html+html", + "name": "HTML", + "mediaType": { + "type": "text/html", + "string": "text/html+html", + "mainType": "text", + "subType": "html", + "suffix": "html", + "delimiter": "." + }, + "path": "", + "baseName": "index", + "rel": "canonical", + "protocol": "", + "isPlainText": false, + "isHTML": true, + "noUgly": false, + "notAlternative": false + }, + { + "MediaType": "application/json+json", + "name": "JSON", + "mediaType": { + "type": "application/json", + "string": "application/json+json", + "mainType": "application", + "subType": "json", + "suffix": "json", + "delimiter": "." + }, + "path": "", + "baseName": "index", + "rel": "alternate", + "protocol": "", + "isPlainText": true, + "isHTML": false, + "noUgly": false, + "notAlternative": false + }, + { + "MediaType": "application/rss+xml", + "name": "RSS", + "mediaType": { + "type": "application/rss", + "string": "application/rss+xml", + "mainType": "application", + "subType": "rss", + "suffix": "xml", + "delimiter": "." + }, + "path": "", + "baseName": "index", + "rel": "alternate", + "protocol": "", + "isPlainText": false, + "isHTML": false, + "noUgly": true, + "notAlternative": false + } + ], + "layouts": [ + { + "Example": "AMP home, with theme \"demoTheme\".", + "OutputFormat": "AMP", + "Suffix": "html", + "Template Lookup Order": [ + "layouts/index.amp.html", + "layouts/index.html", + "layouts/_default/list.amp.html", + "layouts/_default/list.html", + "demoTheme/layouts/index.amp.html", + "demoTheme/layouts/index.html", + "demoTheme/layouts/_default/list.amp.html", + "demoTheme/layouts/_default/list.html" + ] + }, + { + "Example": "AMP home, French language\".", + "OutputFormat": "AMP", + "Suffix": "html", + "Template Lookup Order": [ + "layouts/index.fr.amp.html", + "layouts/index.amp.html", + "layouts/index.fr.html", + "layouts/index.html", + "layouts/_default/list.fr.amp.html", + "layouts/_default/list.amp.html", + "layouts/_default/list.fr.html", + "layouts/_default/list.html" + ] + }, + { + "Example": "RSS home, no theme.", + "OutputFormat": "RSS", + "Suffix": "xml", + "Template Lookup Order": [ + "layouts/rss.xml", + "layouts/_default/rss.xml", + "layouts/_internal/_default/rss.xml" + ] + }, + { + "Example": "JSON home, no theme.", + "OutputFormat": "JSON", + "Suffix": "json", + "Template Lookup Order": [ + "layouts/index.json.json", + "layouts/index.json", + "layouts/_default/list.json.json", + "layouts/_default/list.json" + ] + }, + { + "Example": "CSV regular, \"layout: demolayout\" in front matter.", + "OutputFormat": "CSV", + "Suffix": "csv", + "Template Lookup Order": [ + "layouts/_default/demolayout.csv.csv", + "layouts/_default/demolayout.csv" + ] + }, + { + "Example": "JSON regular, \"type: demotype\" in front matter.", + "OutputFormat": "JSON", + "Suffix": "json", + "Template Lookup Order": [ + "layouts/demotype/single.json.json", + "layouts/demotype/single.json", + "layouts/_default/single.json.json", + "layouts/_default/single.json" + ] + }, + { + "Example": "HTML regular.", + "OutputFormat": "HTML", + "Suffix": "html", + "Template Lookup Order": [ + "layouts/_default/single.html.html", + "layouts/_default/single.html" + ] + }, + { + "Example": "AMP regular.", + "OutputFormat": "AMP", + "Suffix": "html", + "Template Lookup Order": [ + "layouts/_default/single.amp.html", + "layouts/_default/single.html" + ] + }, + { + "Example": "Calendar blog section.", + "OutputFormat": "Calendar", + "Suffix": "ics", + "Template Lookup Order": [ + "layouts/section/blog.calendar.ics", + "layouts/section/blog.ics", + "layouts/blog/list.calendar.ics", + "layouts/blog/list.ics", + "layouts/_default/section.calendar.ics", + "layouts/_default/section.ics", + "layouts/_default/list.calendar.ics", + "layouts/_default/list.ics" + ] + }, + { + "Example": "Calendar taxonomy list.", + "OutputFormat": "Calendar", + "Suffix": "ics", + "Template Lookup Order": [ + "layouts/taxonomy/tag.calendar.ics", + "layouts/taxonomy/tag.ics", + "layouts/_default/taxonomy.calendar.ics", + "layouts/_default/taxonomy.ics", + "layouts/_default/list.calendar.ics", + "layouts/_default/list.ics" + ] + }, + { + "Example": "Calendar taxonomy term.", + "OutputFormat": "Calendar", + "Suffix": "ics", + "Template Lookup Order": [ + "layouts/taxonomy/tag.terms.calendar.ics", + "layouts/taxonomy/tag.terms.ics", + "layouts/_default/terms.calendar.ics", + "layouts/_default/terms.ics" + ] + } + ] + }, + "tpl": { + "funcs": { + "cast": { + "ToInt": { + "Description": "ToInt converts the given value to an int.", + "Args": [ + "v" + ], + "Aliases": [ + "int" + ], + "Examples": [ + [ + "{{ \"1234\" | int | printf \"%T\" }}", + "int" + ] + ] + }, + "ToString": { + "Description": "ToString converts the given value to a string.", + "Args": [ + "v" + ], + "Aliases": [ + "string" + ], + "Examples": [ + [ + "{{ 1234 | string | printf \"%T\" }}", + "string" + ] + ] + } + }, + "compare": { + "Default": { + "Description": "Default checks whether a given value is set and returns a default value if it\nis not. \"Set\" in this context means non-zero for numeric types and times;\nnon-zero length for strings, arrays, slices, and maps;\nany boolean or struct value; or non-nil for any other types.", + "Args": [ + "dflt", + "given" + ], + "Aliases": [ + "default" + ], + "Examples": [ + [ + "{{ \"Hugo Rocks!\" | default \"Hugo Rules!\" }}", + "Hugo Rocks!" + ], + [ + "{{ \"\" | default \"Hugo Rules!\" }}", + "Hugo Rules!" + ] + ] + }, + "Eq": { + "Description": "Eq returns the boolean truth of arg1 == arg2.", + "Args": [ + "x", + "y" + ], + "Aliases": [ + "eq" + ], + "Examples": [ + [ + "{{ if eq .Section \"blog\" }}current{{ end }}", + "current" + ] + ] + }, + "Ge": { + "Description": "Ge returns the boolean truth of arg1 \u003e= arg2.", + "Args": [ + "a", + "b" + ], + "Aliases": [ + "ge" + ], + "Examples": [] + }, + "Gt": { + "Description": "Gt returns the boolean truth of arg1 \u003e arg2.", + "Args": [ + "a", + "b" + ], + "Aliases": [ + "gt" + ], + "Examples": [] + }, + "Le": { + "Description": "Le returns the boolean truth of arg1 \u003c= arg2.", + "Args": [ + "a", + "b" + ], + "Aliases": [ + "le" + ], + "Examples": [] + }, + "Lt": { + "Description": "Lt returns the boolean truth of arg1 \u003c arg2.", + "Args": [ + "a", + "b" + ], + "Aliases": [ + "lt" + ], + "Examples": [] + }, + "Ne": { + "Description": "Ne returns the boolean truth of arg1 != arg2.", + "Args": [ + "x", + "y" + ], + "Aliases": [ + "ne" + ], + "Examples": [] + } + }, + "collections": { + "After": { + "Description": "After returns all the items after the first N in a rangeable list.", + "Args": [ + "index", + "seq" + ], + "Aliases": [ + "after" + ], + "Examples": [] + }, + "Apply": { + "Description": "Apply takes a map, array, or slice and returns a new slice with the function fname applied over it.", + "Args": [ + "seq", + "fname", + "args" + ], + "Aliases": [ + "apply" + ], + "Examples": [] + }, + "Delimit": { + "Description": "Delimit takes a given sequence and returns a delimited HTML string.\nIf last is passed to the function, it will be used as the final delimiter.", + "Args": [ + "seq", + "delimiter", + "last" + ], + "Aliases": [ + "delimit" + ], + "Examples": [ + [ + "{{ delimit (slice \"A\" \"B\" \"C\") \", \" \" and \" }}", + "A, B and C" + ] + ] + }, + "Dictionary": { + "Description": "Dictionary creates a map[string]interface{} from the given parameters by\nwalking the parameters and treating them as key-value pairs. The number\nof parameters must be even.", + "Args": [ + "values" + ], + "Aliases": [ + "dict" + ], + "Examples": [] + }, + "EchoParam": { + "Description": "EchoParam returns a given value if it is set; otherwise, it returns an\nempty string.", + "Args": [ + "a", + "key" + ], + "Aliases": [ + "echoParam" + ], + "Examples": [ + [ + "{{ echoParam .Params \"langCode\" }}", + "en" + ] + ] + }, + "First": { + "Description": "First returns the first N items in a rangeable list.", + "Args": [ + "limit", + "seq" + ], + "Aliases": [ + "first" + ], + "Examples": [] + }, + "In": { + "Description": "In returns whether v is in the set l. l may be an array or slice.", + "Args": [ + "l", + "v" + ], + "Aliases": [ + "in" + ], + "Examples": [ + [ + "{{ if in \"this string contains a substring\" \"substring\" }}Substring found!{{ end }}", + "Substring found!" + ] + ] + }, + "Index": { + "Description": "Index returns the result of indexing its first argument by the following\narguments. Thus \"index x 1 2 3\" is, in Go syntax, x[1][2][3]. Each\nindexed item must be a map, slice, or array.\n\nCopied from Go stdlib src/text/template/funcs.go.\n\nWe deviate from the stdlib due to https://github.com/golang/go/issues/14751.\n\nTODO(moorereason): merge upstream changes.", + "Args": [ + "item", + "indices" + ], + "Aliases": [ + "index" + ], + "Examples": [] + }, + "Intersect": { + "Description": "Intersect returns the common elements in the given sets, l1 and l2. l1 and\nl2 must be of the same type and may be either arrays or slices.", + "Args": [ + "l1", + "l2" + ], + "Aliases": [ + "intersect" + ], + "Examples": [] + }, + "IsSet": { + "Description": "IsSet returns whether a given array, channel, slice, or map has a key\ndefined.", + "Args": [ + "a", + "key" + ], + "Aliases": [ + "isSet", + "isset" + ], + "Examples": [] + }, + "Last": { + "Description": "Last returns the last N items in a rangeable list.", + "Args": [ + "limit", + "seq" + ], + "Aliases": [ + "last" + ], + "Examples": [] + }, + "Querify": { + "Description": "Querify encodes the given parameters in URL-encoded form (\"bar=baz\u0026foo=quux\") sorted by key.", + "Args": [ + "params" + ], + "Aliases": [ + "querify" + ], + "Examples": [ + [ + "{{ (querify \"foo\" 1 \"bar\" 2 \"baz\" \"with spaces\" \"qux\" \"this\u0026that=those\") | safeHTML }}", + "bar=2\u0026baz=with+spaces\u0026foo=1\u0026qux=this%26that%3Dthose" + ], + [ + "\u003ca href=\"https://www.google.com?{{ (querify \"q\" \"test\" \"page\" 3) | safeURL }}\"\u003eSearch\u003c/a\u003e", + "\u003ca href=\"https://www.google.com?page=3\u0026amp;q=test\"\u003eSearch\u003c/a\u003e" + ] + ] + }, + "Seq": { + "Description": "Seq creates a sequence of integers. It's named and used as GNU's seq.\n\nExamples:\n 3 =\u003e 1, 2, 3\n 1 2 4 =\u003e 1, 3\n -3 =\u003e -1, -2, -3\n 1 4 =\u003e 1, 2, 3, 4\n 1 -2 =\u003e 1, 0, -1, -2", + "Args": [ + "args" + ], + "Aliases": [ + "seq" + ], + "Examples": [ + [ + "{{ seq 3 }}", + "[1 2 3]" + ] + ] + }, + "Shuffle": { + "Description": "Shuffle returns the given rangeable list in a randomised order.", + "Args": [ + "seq" + ], + "Aliases": [ + "shuffle" + ], + "Examples": [] + }, + "Slice": { + "Description": "Slice returns a slice of all passed arguments.", + "Args": [ + "args" + ], + "Aliases": [ + "slice" + ], + "Examples": [ + [ + "{{ slice \"B\" \"C\" \"A\" | sort }}", + "[A B C]" + ] + ] + }, + "Sort": { + "Description": "Sort returns a sorted sequence.", + "Args": [ + "seq", + "args" + ], + "Aliases": [ + "sort" + ], + "Examples": [] + }, + "Union": { + "Description": "Union returns the union of the given sets, l1 and l2. l1 and\nl2 must be of the same type and may be either arrays or slices.\nIf l1 and l2 aren't of the same type then l1 will be returned.\nIf either l1 or l2 is nil then the non-nil list will be returned.", + "Args": [ + "l1", + "l2" + ], + "Aliases": [ + "union" + ], + "Examples": [ + [ + "{{ union (slice 1 2 3) (slice 3 4 5) }}", + "[1 2 3 4 5]" + ] + ] + }, + "Uniq": { + "Description": "Uniq takes in a slice or array and returns a slice with subsequent\nduplicate elements removed.", + "Args": [ + "l" + ], + "Aliases": [ + "uniq" + ], + "Examples": [ + [ + "{{ slice 1 2 3 2 | uniq }}", + "[1 2 3]" + ] + ] + }, + "Where": { + "Description": "Where returns a filtered subset of a given data type.", + "Args": [ + "seq", + "key", + "args" + ], + "Aliases": [ + "where" + ], + "Examples": [] + } + }, + "crypto": { + "MD5": { + "Description": "MD5 hashes the given input and returns its MD5 checksum.", + "Args": [ + "in" + ], + "Aliases": [ + "md5" + ], + "Examples": [ + [ + "{{ md5 \"Hello world, gophers!\" }}", + "b3029f756f98f79e7f1b7f1d1f0dd53b" + ], + [ + "{{ crypto.MD5 \"Hello world, gophers!\" }}", + "b3029f756f98f79e7f1b7f1d1f0dd53b" + ] + ] + }, + "SHA1": { + "Description": "SHA1 hashes the given input and returns its SHA1 checksum.", + "Args": [ + "in" + ], + "Aliases": [ + "sha1" + ], + "Examples": [ + [ + "{{ sha1 \"Hello world, gophers!\" }}", + "c8b5b0e33d408246e30f53e32b8f7627a7a649d4" + ] + ] + }, + "SHA256": { + "Description": "SHA256 hashes the given input and returns its SHA256 checksum.", + "Args": [ + "in" + ], + "Aliases": [ + "sha256" + ], + "Examples": [ + [ + "{{ sha256 \"Hello world, gophers!\" }}", + "6ec43b78da9669f50e4e422575c54bf87536954ccd58280219c393f2ce352b46" + ] + ] + } + }, + "data": { + "GetCSV": { + "Description": "GetCSV expects a data separator and one or n-parts of a URL to a resource which\ncan either be a local or a remote one.\nThe data separator can be a comma, semi-colon, pipe, etc, but only one character.\nIf you provide multiple parts for the URL they will be joined together to the final URL.\nGetCSV returns nil or a slice slice to use in a short code.", + "Args": [ + "sep", + "urlParts" + ], + "Aliases": [ + "getCSV" + ], + "Examples": [] + }, + "GetJSON": { + "Description": "GetJSON expects one or n-parts of a URL to a resource which can either be a local or a remote one.\nIf you provide multiple parts they will be joined together to the final URL.\nGetJSON returns nil or parsed JSON to use in a short code.", + "Args": [ + "urlParts" + ], + "Aliases": [ + "getJSON" + ], + "Examples": [] + } + }, + "encoding": { + "Base64Decode": { + "Description": "Base64Decode returns the base64 decoding of the given content.", + "Args": [ + "content" + ], + "Aliases": [ + "base64Decode" + ], + "Examples": [ + [ + "{{ \"SGVsbG8gd29ybGQ=\" | base64Decode }}", + "Hello world" + ], + [ + "{{ 42 | base64Encode | base64Decode }}", + "42" + ] + ] + }, + "Base64Encode": { + "Description": "Base64Encode returns the base64 encoding of the given content.", + "Args": [ + "content" + ], + "Aliases": [ + "base64Encode" + ], + "Examples": [ + [ + "{{ \"Hello world\" | base64Encode }}", + "SGVsbG8gd29ybGQ=" + ] + ] + }, + "Jsonify": { + "Description": "Jsonify encodes a given object to JSON.", + "Args": [ + "v" + ], + "Aliases": [ + "jsonify" + ], + "Examples": [ + [ + "{{ (slice \"A\" \"B\" \"C\") | jsonify }}", + "[\"A\",\"B\",\"C\"]" + ] + ] + } + }, + "fmt": { + "Print": { + "Description": "", + "Args": [ + "a" + ], + "Aliases": [ + "print" + ], + "Examples": [ + [ + "{{ print \"works!\" }}", + "works!" + ] + ] + }, + "Printf": { + "Description": "", + "Args": [ + "format", + "a" + ], + "Aliases": [ + "printf" + ], + "Examples": [ + [ + "{{ printf \"%s!\" \"works\" }}", + "works!" + ] + ] + }, + "Println": { + "Description": "", + "Args": [ + "a" + ], + "Aliases": [ + "println" + ], + "Examples": [ + [ + "{{ println \"works!\" }}", + "works!\n" + ] + ] + } + }, + "images": { + "Config": { + "Description": "Config returns the image.Config for the specified path relative to the\nworking directory.", + "Args": [ + "path" + ], + "Aliases": [ + "imageConfig" + ], + "Examples": [] + } + }, + "inflect": { + "Humanize": { + "Description": "Humanize returns the humanized form of a single parameter.\n\nIf the parameter is either an integer or a string containing an integer\nvalue, the behavior is to add the appropriate ordinal.\n\n Example: \"my-first-post\" -\u003e \"My first post\"\n Example: \"103\" -\u003e \"103rd\"\n Example: 52 -\u003e \"52nd\"", + "Args": [ + "in" + ], + "Aliases": [ + "humanize" + ], + "Examples": [ + [ + "{{ humanize \"my-first-post\" }}", + "My first post" + ], + [ + "{{ humanize \"myCamelPost\" }}", + "My camel post" + ], + [ + "{{ humanize \"52\" }}", + "52nd" + ], + [ + "{{ humanize 103 }}", + "103rd" + ] + ] + }, + "Pluralize": { + "Description": "Pluralize returns the plural form of a single word.", + "Args": [ + "in" + ], + "Aliases": [ + "pluralize" + ], + "Examples": [ + [ + "{{ \"cat\" | pluralize }}", + "cats" + ] + ] + }, + "Singularize": { + "Description": "Singularize returns the singular form of a single word.", + "Args": [ + "in" + ], + "Aliases": [ + "singularize" + ], + "Examples": [ + [ + "{{ \"cats\" | singularize }}", + "cat" + ] + ] + } + }, + "lang": { + "NumFmt": { + "Description": "NumFmt formats a number with the given precision using the\nnegative, decimal, and grouping options. The `options`\nparameter is a string consisting of `\u003cnegative\u003e \u003cdecimal\u003e \u003cgrouping\u003e`. The\ndefault `options` value is `- . ,`.\n\nNote that numbers are rounded up at 5 or greater.\nSo, with precision set to 0, 1.5 becomes `2`, and 1.4 becomes `1`.", + "Args": [ + "precision", + "number", + "options" + ], + "Aliases": null, + "Examples": [ + [ + "{{ lang.NumFmt 2 12345.6789 }}", + "12,345.68" + ], + [ + "{{ lang.NumFmt 2 12345.6789 \"- , .\" }}", + "12.345,68" + ], + [ + "{{ lang.NumFmt 6 -12345.6789 \"- .\" }}", + "-12345.678900" + ], + [ + "{{ lang.NumFmt 0 -12345.6789 \"- . ,\" }}", + "-12,346" + ], + [ + "{{ -98765.4321 | lang.NumFmt 2 }}", + "-98,765.43" + ] + ] + }, + "Translate": { + "Description": "Translate ...", + "Args": [ + "id", + "args" + ], + "Aliases": [ + "i18n", + "T" + ], + "Examples": [] + } + }, + "math": { + "Add": { + "Description": "", + "Args": [ + "a", + "b" + ], + "Aliases": [ + "add" + ], + "Examples": [ + [ + "{{add 1 2}}", + "3" + ] + ] + }, + "Div": { + "Description": "", + "Args": [ + "a", + "b" + ], + "Aliases": [ + "div" + ], + "Examples": [ + [ + "{{div 6 3}}", + "2" + ] + ] + }, + "Log": { + "Description": "", + "Args": [ + "a" + ], + "Aliases": null, + "Examples": [ + [ + "{{math.Log 1}}", + "0" + ] + ] + }, + "Mod": { + "Description": "Mod returns a % b.", + "Args": [ + "a", + "b" + ], + "Aliases": [ + "mod" + ], + "Examples": [ + [ + "{{mod 15 3}}", + "0" + ] + ] + }, + "ModBool": { + "Description": "ModBool returns the boolean of a % b. If a % b == 0, return true.", + "Args": [ + "a", + "b" + ], + "Aliases": [ + "modBool" + ], + "Examples": [ + [ + "{{modBool 15 3}}", + "true" + ] + ] + }, + "Mul": { + "Description": "", + "Args": [ + "a", + "b" + ], + "Aliases": [ + "mul" + ], + "Examples": [ + [ + "{{mul 2 3}}", + "6" + ] + ] + }, + "Sub": { + "Description": "", + "Args": [ + "a", + "b" + ], + "Aliases": [ + "sub" + ], + "Examples": [ + [ + "{{sub 3 2}}", + "1" + ] + ] + } + }, + "os": { + "Getenv": { + "Description": "Getenv retrieves the value of the environment variable named by the key.\nIt returns the value, which will be empty if the variable is not present.", + "Args": [ + "key" + ], + "Aliases": [ + "getenv" + ], + "Examples": [] + }, + "ReadDir": { + "Description": "ReadDir lists the directory contents relative to the configured WorkingDir.", + "Args": [ + "i" + ], + "Aliases": [ + "readDir" + ], + "Examples": [ + [ + "{{ range (readDir \".\") }}{{ .Name }}{{ end }}", + "README.txt" + ] + ] + }, + "ReadFile": { + "Description": "ReadFilereads the file named by filename relative to the configured\nWorkingDir. It returns the contents as a string. There is a upper size\nlimit set at 1 megabytes.", + "Args": [ + "i" + ], + "Aliases": [ + "readFile" + ], + "Examples": [ + [ + "{{ readFile \"README.txt\" }}", + "Hugo Rocks!" + ] + ] + } + }, + "partials": { + "Include": { + "Description": "Include executes the named partial and returns either a string,\nwhen the partial is a text/template, or template.HTML when html/template.", + "Args": [ + "name", + "contextList" + ], + "Aliases": [ + "partial" + ], + "Examples": [ + [ + "{{ partial \"header.html\" . }}", + "\u003ctitle\u003eHugo Rocks!\u003c/title\u003e" + ] + ] + } + }, + "safe": { + "CSS": { + "Description": "CSS returns a given string as html/template CSS content.", + "Args": [ + "a" + ], + "Aliases": [ + "safeCSS" + ], + "Examples": [ + [ + "{{ \"Bat\u0026Man\" | safeCSS | safeCSS }}", + "Bat\u0026amp;Man" + ] + ] + }, + "HTML": { + "Description": "HTML returns a given string as html/template HTML content.", + "Args": [ + "a" + ], + "Aliases": [ + "safeHTML" + ], + "Examples": [ + [ + "{{ \"Bat\u0026Man\" | safeHTML | safeHTML }}", + "Bat\u0026Man" + ], + [ + "{{ \"Bat\u0026Man\" | safeHTML }}", + "Bat\u0026Man" + ] + ] + }, + "HTMLAttr": { + "Description": "HTMLAttr returns a given string as html/template HTMLAttr content.", + "Args": [ + "a" + ], + "Aliases": [ + "safeHTMLAttr" + ], + "Examples": [] + }, + "JS": { + "Description": "JS returns the given string as a html/template JS content.", + "Args": [ + "a" + ], + "Aliases": [ + "safeJS" + ], + "Examples": [ + [ + "{{ \"(1*2)\" | safeJS | safeJS }}", + "(1*2)" + ] + ] + }, + "JSStr": { + "Description": "JSStr returns the given string as a html/template JSStr content.", + "Args": [ + "a" + ], + "Aliases": [ + "safeJSStr" + ], + "Examples": [] + }, + "SanitizeURL": { + "Description": "SanitizeURL returns a given string as html/template URL content.", + "Args": [ + "a" + ], + "Aliases": [ + "sanitizeURL", + "sanitizeurl" + ], + "Examples": [] + }, + "URL": { + "Description": "URL returns a given string as html/template URL content.", + "Args": [ + "a" + ], + "Aliases": [ + "safeURL" + ], + "Examples": [ + [ + "{{ \"http://gohugo.io\" | safeURL | safeURL }}", + "http://gohugo.io" + ] + ] + } + }, + "strings": { + "Chomp": { + "Description": "Chomp returns a copy of s with all trailing newline characters removed.", + "Args": [ + "s" + ], + "Aliases": [ + "chomp" + ], + "Examples": [ + [ + "{{chomp \"\u003cp\u003eBlockhead\u003c/p\u003e\\n\" }}", + "\u003cp\u003eBlockhead\u003c/p\u003e" + ] + ] + }, + "Contains": { + "Description": "", + "Args": null, + "Aliases": null, + "Examples": null + }, + "ContainsAny": { + "Description": "", + "Args": null, + "Aliases": null, + "Examples": null + }, + "CountRunes": { + "Description": "CountRunes returns the number of runes in s, excluding whitepace.", + "Args": [ + "s" + ], + "Aliases": [ + "countrunes" + ], + "Examples": [] + }, + "CountWords": { + "Description": "CountWords returns the approximate word count in s.", + "Args": [ + "s" + ], + "Aliases": [ + "countwords" + ], + "Examples": [] + }, + "FindRE": { + "Description": "FindRE returns a list of strings that match the regular expression. By default all matches\nwill be included. The number of matches can be limited with an optional third parameter.", + "Args": [ + "expr", + "content", + "limit" + ], + "Aliases": [ + "findRE" + ], + "Examples": [ + [ + "{{ findRE \"[G|g]o\" \"Hugo is a static side generator written in Go.\" \"1\" }}", + "[go]" + ] + ] + }, + "HasPrefix": { + "Description": "HasPrefix tests whether the input s begins with prefix.", + "Args": [ + "s", + "prefix" + ], + "Aliases": [ + "hasPrefix" + ], + "Examples": [ + [ + "{{ hasPrefix \"Hugo\" \"Hu\" }}", + "true" + ], + [ + "{{ hasPrefix \"Hugo\" \"Fu\" }}", + "false" + ] + ] + }, + "HasSuffix": { + "Description": "", + "Args": null, + "Aliases": null, + "Examples": null + }, + "Replace": { + "Description": "Replace returns a copy of the string s with all occurrences of old replaced\nwith new.", + "Args": [ + "s", + "old", + "new" + ], + "Aliases": [ + "replace" + ], + "Examples": [ + [ + "{{ replace \"Batman and Robin\" \"Robin\" \"Catwoman\" }}", + "Batman and Catwoman" + ] + ] + }, + "ReplaceRE": { + "Description": "ReplaceRE returns a copy of s, replacing all matches of the regular\nexpression pattern with the replacement text repl.", + "Args": [ + "pattern", + "repl", + "s" + ], + "Aliases": [ + "replaceRE" + ], + "Examples": [] + }, + "SliceString": { + "Description": "SliceString slices a string by specifying a half-open range with\ntwo indices, start and end. 1 and 4 creates a slice including elements 1 through 3.\nThe end index can be omitted, it defaults to the string's length.", + "Args": [ + "a", + "startEnd" + ], + "Aliases": [ + "slicestr" + ], + "Examples": [ + [ + "{{slicestr \"BatMan\" 0 3}}", + "Bat" + ], + [ + "{{slicestr \"BatMan\" 3}}", + "Man" + ] + ] + }, + "Split": { + "Description": "Split slices an input string into all substrings separated by delimiter.", + "Args": [ + "a", + "delimiter" + ], + "Aliases": [ + "split" + ], + "Examples": [] + }, + "Substr": { + "Description": "Substr extracts parts of a string, beginning at the character at the specified\nposition, and returns the specified number of characters.\n\nIt normally takes two parameters: start and length.\nIt can also take one parameter: start, i.e. length is omitted, in which case\nthe substring starting from start until the end of the string will be returned.\n\nTo extract characters from the end of the string, use a negative start number.\n\nIn addition, borrowing from the extended behavior described at http://php.net/substr,\nif length is given and is negative, then that many characters will be omitted from\nthe end of string.", + "Args": [ + "a", + "nums" + ], + "Aliases": [ + "substr" + ], + "Examples": [ + [ + "{{substr \"BatMan\" 0 -3}}", + "Bat" + ], + [ + "{{substr \"BatMan\" 3 3}}", + "Man" + ] + ] + }, + "Title": { + "Description": "Title returns a copy of the input s with all Unicode letters that begin words\nmapped to their title case.", + "Args": [ + "s" + ], + "Aliases": [ + "title" + ], + "Examples": [ + [ + "{{title \"Bat man\"}}", + "Bat Man" + ] + ] + }, + "ToLower": { + "Description": "ToLower returns a copy of the input s with all Unicode letters mapped to their\nlower case.", + "Args": [ + "s" + ], + "Aliases": [ + "lower" + ], + "Examples": [ + [ + "{{lower \"BatMan\"}}", + "batman" + ] + ] + }, + "ToUpper": { + "Description": "ToUpper returns a copy of the input s with all Unicode letters mapped to their\nupper case.", + "Args": [ + "s" + ], + "Aliases": [ + "upper" + ], + "Examples": [ + [ + "{{upper \"BatMan\"}}", + "BATMAN" + ] + ] + }, + "Trim": { + "Description": "Trim returns a string with all leading and trailing characters defined\ncontained in cutset removed.", + "Args": [ + "s", + "cutset" + ], + "Aliases": [ + "trim" + ], + "Examples": [ + [ + "{{ trim \"++Batman--\" \"+-\" }}", + "Batman" + ] + ] + }, + "TrimPrefix": { + "Description": "", + "Args": null, + "Aliases": null, + "Examples": null + }, + "TrimSuffix": { + "Description": "", + "Args": null, + "Aliases": null, + "Examples": null + }, + "Truncate": { + "Description": "", + "Args": [ + "a", + "options" + ], + "Aliases": [ + "truncate" + ], + "Examples": [ + [ + "{{ \"this is a very long text\" | truncate 10 \" ...\" }}", + "this is a ..." + ], + [ + "{{ \"With [Markdown](/markdown) inside.\" | markdownify | truncate 14 }}", + "With \u003ca href=\"/markdown\"\u003eMarkdown …\u003c/a\u003e" + ] + ] + } + }, + "time": { + "AsTime": { + "Description": "AsTime converts the textual representation of the datetime string into\na time.Time interface.", + "Args": [ + "v" + ], + "Aliases": null, + "Examples": [ + [ + "{{ (time \"2015-01-21\").Year }}", + "2015" + ] + ] + }, + "Format": { + "Description": "Format converts the textual representation of the datetime string into\nthe other form or returns it of the time.Time value. These are formatted\nwith the layout string", + "Args": [ + "layout", + "v" + ], + "Aliases": [ + "dateFormat" + ], + "Examples": [ + [ + "dateFormat: {{ dateFormat \"Monday, Jan 2, 2006\" \"2015-01-21\" }}", + "dateFormat: Wednesday, Jan 21, 2015" + ] + ] + }, + "Now": { + "Description": "Now returns the current local time.", + "Args": null, + "Aliases": [ + "now" + ], + "Examples": [] + } + }, + "transform": { + "Emojify": { + "Description": "Emojify returns a copy of s with all emoji codes replaced with actual emojis.\n\nSee http://www.emoji-cheat-sheet.com/", + "Args": [ + "s" + ], + "Aliases": [ + "emojify" + ], + "Examples": [ + [ + "{{ \"I :heart: Hugo\" | emojify }}", + "I ❤️ Hugo" + ] + ] + }, + "HTMLEscape": { + "Description": "HTMLEscape returns a copy of s with reserved HTML characters escaped.", + "Args": [ + "s" + ], + "Aliases": [ + "htmlEscape" + ], + "Examples": [ + [ + "{{ htmlEscape \"Cathal Garvey \u0026 The Sunshine Band \u003ccathal@foo.bar\u003e\" | safeHTML}}", + "Cathal Garvey \u0026amp; The Sunshine Band \u0026lt;cathal@foo.bar\u0026gt;" + ], + [ + "{{ htmlEscape \"Cathal Garvey \u0026 The Sunshine Band \u003ccathal@foo.bar\u003e\"}}", + "Cathal Garvey \u0026amp;amp; The Sunshine Band \u0026amp;lt;cathal@foo.bar\u0026amp;gt;" + ], + [ + "{{ htmlEscape \"Cathal Garvey \u0026 The Sunshine Band \u003ccathal@foo.bar\u003e\" | htmlUnescape | safeHTML }}", + "Cathal Garvey \u0026 The Sunshine Band \u003ccathal@foo.bar\u003e" + ] + ] + }, + "HTMLUnescape": { + "Description": "HTMLUnescape returns a copy of with HTML escape requences converted to plain\ntext.", + "Args": [ + "s" + ], + "Aliases": [ + "htmlUnescape" + ], + "Examples": [ + [ + "{{ htmlUnescape \"Cathal Garvey \u0026amp; The Sunshine Band \u0026lt;cathal@foo.bar\u0026gt;\" | safeHTML}}", + "Cathal Garvey \u0026 The Sunshine Band \u003ccathal@foo.bar\u003e" + ], + [ + "{{\"Cathal Garvey \u0026amp;amp; The Sunshine Band \u0026amp;lt;cathal@foo.bar\u0026amp;gt;\" | htmlUnescape | htmlUnescape | safeHTML}}", + "Cathal Garvey \u0026 The Sunshine Band \u003ccathal@foo.bar\u003e" + ], + [ + "{{\"Cathal Garvey \u0026amp;amp; The Sunshine Band \u0026amp;lt;cathal@foo.bar\u0026amp;gt;\" | htmlUnescape | htmlUnescape }}", + "Cathal Garvey \u0026amp; The Sunshine Band \u0026lt;cathal@foo.bar\u0026gt;" + ], + [ + "{{ htmlUnescape \"Cathal Garvey \u0026amp; The Sunshine Band \u0026lt;cathal@foo.bar\u0026gt;\" | htmlEscape | safeHTML }}", + "Cathal Garvey \u0026amp; The Sunshine Band \u0026lt;cathal@foo.bar\u0026gt;" + ] + ] + }, + "Highlight": { + "Description": "Highlight returns a copy of s as an HTML string with syntax\nhighlighting applied.", + "Args": [ + "s", + "lang", + "opts" + ], + "Aliases": [ + "highlight" + ], + "Examples": [] + }, + "Markdownify": { + "Description": "Markdownify renders a given input from Markdown to HTML.", + "Args": [ + "s" + ], + "Aliases": [ + "markdownify" + ], + "Examples": [ + [ + "{{ .Title | markdownify}}", + "\u003cstrong\u003eBatMan\u003c/strong\u003e" + ] + ] + }, + "Plainify": { + "Description": "Plainify returns a copy of s with all HTML tags removed.", + "Args": [ + "s" + ], + "Aliases": [ + "plainify" + ], + "Examples": [ + [ + "{{ plainify \"Hello \u003cstrong\u003eworld\u003c/strong\u003e, gophers!\" }}", + "Hello world, gophers!" + ] + ] + } + }, + "urls": { + "AbsLangURL": { + "Description": "AbsLangURL takes a given string and converts it to an absolute URL according\nto a page's position in the project directory structure and the current\nlanguage.", + "Args": [ + "a" + ], + "Aliases": [ + "absLangURL" + ], + "Examples": [] + }, + "AbsURL": { + "Description": "AbsURL takes a given string and converts it to an absolute URL.", + "Args": [ + "a" + ], + "Aliases": [ + "absURL" + ], + "Examples": [] + }, + "Ref": { + "Description": "Ref returns the absolute URL path to a given content item.", + "Args": [ + "in", + "refs" + ], + "Aliases": [ + "ref" + ], + "Examples": [] + }, + "RelLangURL": { + "Description": "RelLangURL takes a given string and prepends the relative path according to a\npage's position in the project directory structure and the current language.", + "Args": [ + "a" + ], + "Aliases": [ + "relLangURL" + ], + "Examples": [] + }, + "RelRef": { + "Description": "RelRef returns the relative URL path to a given content item.", + "Args": [ + "in", + "refs" + ], + "Aliases": [ + "relref" + ], + "Examples": [] + }, + "RelURL": { + "Description": "RelURL takes a given string and prepends the relative path according to a\npage's position in the project directory structure.", + "Args": [ + "a" + ], + "Aliases": [ + "relURL" + ], + "Examples": [] + }, + "URLize": { + "Description": "", + "Args": [ + "a" + ], + "Aliases": [ + "urlize" + ], + "Examples": [] + } + } + } + } +} diff --git a/data/homepagetweets.toml b/data/homepagetweets.toml new file mode 100644 index 000000000..67bf7b4bd --- /dev/null +++ b/data/homepagetweets.toml @@ -0,0 +1,258 @@ +[[tweet]] +name = "STOQE" +twitter_handle = "@STOQE" +quote = "I fear @GoHugoIO v0.22 might be so fast it creates a code vortex that time-warps me back to a time I used Wordpress. #gasp" +link = "https://twitter.com/STOQE/status/874184881701494784" +date = 2017-06-12T00:00:00Z + +[[tweet]] +name = "Christophe Diericx" +twitter_handle = "@spcrngr_" +quote = "The more I use gohugo.io, the more I really like it. Super intuitive/powerful static site generator...great job @GoHugoIO" +link = "https://twitter.com/spcrngr_/status/870863020905435136" +date = 2017-06-03T00:00:00Z + +[[tweet]] +name = "marcoscan" +twitter_handle = "@marcoscan" +quote = "Blog migrated from @WordPress to @GoHugoIO, with a little refresh of my theme, Vim shortcuts and a full featured deploy script #gohugo" +link = "https://twitter.com/marcoscan/status/869661175960752129" +date = 2017-05-30T00:00:00Z + +[[tweet]] +name = "Sandra Kuipers" +twitter_handle = "@SKuipersDesign" +quote = "Who knew static site building could be fun 🤔 Learning #gohugo today" +link = "https://twitter.com/SKuipersDesign/status/868796256902029312" +date = 2017-05-28T00:00:00Z + +[[tweet]] +name = "Netlify" +twitter_handle = "@Netlify" +quote = "Top Ten Static Site Generators of 2017. Congrats to the top 3: 1. @Jekyllrb 2. @GoHugoIO 3. @hexojs" +link = "https://twitter.com/Netlify/status/868122279221362688" +date = 2017-05-26T00:00:00Z + +[[tweet]] +name = "Phil Hawksworth" +twitter_handle = "@philhawksworth" +quote = "I've been keen on #JAMStack for some time, but @GoHugoIO is wooing me all over again. Great fun to build with. And speeeeedy." +link = "https://twitter.com/philhawksworth/status/866684170512326657" +date = 2017-05-22T00:00:00Z + +[[tweet]] +name = "Aras Pranckevicius" +twitter_handle = "@aras_p" +quote = "I've probably said it before...but having Hugo rebuild the whole website in 300ms is amazing. gohugo.io, #gohugo" +link = "https://twitter.com/aras_p/status/861157286823288832" +date = 2017-05-07T00:00:00Z + +[[tweet]] +name = "Hans Beck" +twitter_handle = "@EnrichedGamesHB" +quote = "Diving deeper into @GoHugoIO. A lot of docs there, top work! But I've the impressed that #gohugo is far easier than its feels from the docs!" +link = "https://twitter.com/EnrichedGamesHB/status/836854762440130560" +date = 2017-03-01T00:00:00Z + +[[tweet]] +name = "Alan Richardson" +twitter_handle = "@eviltester" +quote = "I migrated the @BlackOpsTesting .com website from docpad to Hugo last weekend. http://gohugo.io/ Super Fast HTML Generation @spf13 " +link = "https://twitter.com/eviltester/status/553520335115808768" +date = 2015-01-09T00:00:00Z + +[[tweet]] +name = "Janez Čadež‏" +twitter_handle = "@jamziSLO" +quote = "Building @garazaFRI website in #hugo. This static site generator is soooo damn fast! #gohugo #golang" +link = "https://twitter.com/jamziSLO/status/817720283977183234" +date = 2017-01-07T00:00:00Z + +[[tweet]] +name = "Execute‏‏" +twitter_handle = "@executerun" +quote = "Hah, #gohugo. I was working with #gohugo on #linux but now I realised how easy is to set-up it on #windows. Just need to add binary to #path!" +link = "https://twitter.com/executerun/status/809753145270272005" +date = 2016-12-16T00:00:00Z + +[[tweet]] +name = "Baron Schwartz" +twitter_handle = "@xaprb" +quote = "Hugo is impressively capable. It's a static site generator by @spf13 written in #golang . Just upgraded to latest release; very powerful. " +link = "https://twitter.com/xaprb/status/556894866488455169" +date = 2015-01-18T00:00:00Z + +[[tweet]] +name = "Dave Cottlehuber" +twitter_handle = "@dch__" +quote = "I just fell in love with #hugo, a static site/blog engine written by @spf13 in #golang + stellar docs" +link = "https://twitter.com/dch__/status/460158115498176512" +date = 2014-04-26T00:00:00Z + +[[tweet]] +name = "David Caunt" +twitter_handle = "@dcaunt" +quote = "I had a play with Hugo and it was good, uses Markdown files for content" +link = "https://twitter.com/dcaunt/statuses/406466996277374976" +date = 2013-11-29T00:00:00Z + +[[tweet]] +name = "David Gay" +twitter_handle = "@oddshocks" +quote = "Hugo is super-rad." +link = "https://twitter.com/oddshocks/statuses/405083217893421056" +date = 2013-11-25T00:00:00Z + +[[tweet]] +name = "Diti" +twitter_handle = "@DitiPengi" +quote = "The dev version of Hugo is AWESOME! <3 I promise, I will try to learn go ASAP and help contribute to the project! Just too great!" +link = "https://twitter.com/DitiPengi/status/472470974051676160" +date = 2014-05-30T00:00:00Z + +[[tweet]] +name = "Douglas Stephen " +twitter_handle = "@DougStephenJr" +quote = "Even as a long-time Octopress fan, I’ve gotta admit that this project Hugo looks very very cool" +link = "https://twitter.com/DougStephenJr/statuses/364512471660249088" +date = 2013-08-05T00:00:00Z + +[[tweet]] +name = "Hugo Rodger-Brown" +twitter_handle = "@hugorodgerbrown" +quote = "Finally someone builds me my own static site generator" +link = "https://twitter.com/hugorodgerbrown/statuses/364417910153818112" +date = 2013-05-08T00:00:00Z + +[[tweet]] +name = "Hugo Roy" +twitter_handle = "@hugoroyd" +quote = "Finally the answer to the question my parents have been asking: What does Hugo do?" +link = "https://twitter.com/hugoroyd/status/501704796727173120" +date = 2014-08-19T00:00:00Z + +[[tweet]] +name = "Daniel Miessler" +twitter_handle = "@DanielMiessler" +quote = "Websites for named vulnerabilities should run on static site generator platforms like Hugo. Read-only + burst traffic = static." +link = "https://twitter.com/DanielMiessler/status/704703841673957376" +date = 2016-03-01T00:00:00Z + +[[tweet]] +name = "Javier Segura" +twitter_handle = "@jsegura" +quote = "Another site generated with Hugo here! I'm getting in love with it." +link = "https://twitter.com/jsegura/status/465978434154659841" +date = 2014-05-12T00:00:00Z + +[[tweet]] +name = "Jim Biancolo" +twitter_handle = "@jimbiancolo" +quote = "I’m loving the static site generator renaissance we are currently enjoying. Hugo is new, looks great, written in Go" +link = "https://twitter.com/jimbiancolo/statuses/408678420348813314" +date = 2013-05-12T00:00:00Z + +[[tweet]] +name = "Jip J. Dekker" +twitter_handle = "@jipjdekker" +quote = "Building a personal website in Hugo. Works like a charm. And written in @golang!" +link = "https://twitter.com/jipjdekker/status/413783548735152131" +date = 2013-12-19T00:00:00Z + +[[tweet]] +name = "Jose Gonzalvo" +twitter_handle = "@jgonzalvo" +quote = "Checking out Hugo; Loving it so far. Like Jekyll but not so blog-oriented and written in go" +link = "https://twitter.com/jgonzalvo/statuses/408177855819173888" +date = 2013-12-04T00:00:00Z + +[[tweet]] +name = "Josh Matz" +twitter_handle = "@joshmatz" +quote = "A static site generator without the long build times? Yes, please!" +link = "https://twitter.com/joshmatz/statuses/364437436870696960" +date = 2013-08-05T00:00:00Z + +[[tweet]] +name = "Kieran Healy" +twitter_handle = "@kjhealy" +quote = "OK, so in today's speed battle of static site generators, @spf13's hugo is kicking everyone's ass, by miles." +link = "https://twitter.com/kjhealy/status/437349384809115648" +date = 2014-02-22T00:00:00Z + +[[tweet]] +name = "Ludovic Chabant" +twitter_handle = "@ludovicchabant" +quote = "Good work on Hugo, I’m impressed with the speed!" +link = "https://twitter.com/ludovicchabant/statuses/408806199602053120" +date = 2013-12-06T00:00:00Z + +[[tweet]] +name = "Luke Holder" +twitter_handle = "@lukeholder" +quote = "this is AWESOME. a single little executable and so fast." +link = "https://twitter.com/lukeholder/status/430352287936946176" +date = 2014-02-03T00:00:00Z + +[[tweet]] +name = "Markus Eliasson" +twitter_handle = "@markuseliasson" +quote = "Hugo is fast, dead simple to setup and well documented" +link = "https://twitter.com/markuseliasson/status/501594865877008384" +date = 2014-08-19T00:00:00Z + +[[tweet]] +name = "mercime" +twitter_handle = "@mercime_one" +quote = "Hugo: Makes the Web Fun Again" +link = "https://twitter.com/mercime_one/status/500547145087205377" +date = 2014-08-16T00:00:00Z + +[[tweet]] +name = "Michael Whatcott" +twitter_handle = "@mdwhatcott" +quote = "One more satisfied #Hugo blogger. Thanks @spf13 and friends!" +link = "https://twitter.com/mdwhatcott/status/469980686531571712" +date = 2014-05-23T00:00:00Z + +[[tweet]] +name = "Nathan Toups" +twitter_handle = "@rojoroboto" +quote = "I love Hugo! My site is generated with it now http://rjrbt.io" +link = "https://twitter.com/rojoroboto/status/423439915620106242" +date = 2014-01-15T00:00:00Z + +[[tweet]] +name = "Ruben Solvang" +twitter_handle = "@messo85" +quote = "#Hugo is the new @jekyllrb / @middlemanapp! Faster, easier and runs everywhere." +link = "https://twitter.com/messo85/status/472825062027182081" +date = 2014-05-31T00:00:00Z + +[[tweet]] +name = "Ryan Martinsen" +twitter_handle = "@popthestack" +quote = "Also, I re-launched my blog (it looks the same as before) using Hugo, a *fast* static engine. Very happy with it. gohugo.io" +link = "https://twitter.com/popthestack/status/549972754125307904" +date = 2014-12-30T00:00:00Z + +[[tweet]] +name = "The Lone Cuber" +twitter_handle = "@TheLoneCuber" +quote = "Jekyll is dead to me these days though... long live Hugo! Hugo is *by far* the best in its field. Thanks for making it happen." +link = "https://twitter.com/TheLoneCuber/status/495716684456398848" +date = 2014-08-02T00:00:00Z + +[[tweet]] +name = "The Lone Cuber" +twitter_handle = "@TheLoneCuber" +quote = "Finally, a publishing platform that's a joy to use. #NoMoreBarriers" +link = "https://twitter.com/TheLoneCuber/status/495731334711488512" +date = 2014-08-02T00:00:00Z + +[[tweet]] +name = "WorkHTML" +twitter_handle = "@workhtml" +quote = " #Hugo A very good alternative for #wordpress !!! A fast and modern static website engine gohugo.io " +link = "https://twitter.com/workhtml/status/563064361301053440" +date = 2015-02-04T00:00:00Z diff --git a/data/references.toml b/data/references.toml new file mode 100644 index 000000000..84d8c5935 --- /dev/null +++ b/data/references.toml @@ -0,0 +1,211 @@ + +[[quotes]] +name = "Execute‏‏" +twitter_handle = "@executerun" +quote = "Hah, #gohugo. I was working with #gohugo on #linux but now I realised how easy is to set-up it on #windows. Just need to add binary to #path!" +link = "https://twitter.com/executerun/status/809753145270272005" +date = 2016-12-16T00:00:00Z + + +[[quotes]] +name = "Janez Čadež‏" +twitter_handle = "@jamziSLO" +quote = "Building @garazaFRI website in #hugo. This static site generator is soooo damn fast!" +link = "https://twitter.com/jamziSLO/status/817720283977183234" +date = 2017-01-07T00:00:00Z + +[[quotes]] +name = "Hans Beck" +twitter_handle = "@EnrichedGamesHB" +quote = "Diving deeper into @GoHugoIO . A lot of docs there, top work! But I've the impressed that #gohugo is far easier than its feels from the docs!" +link = "https://twitter.com/EnrichedGamesHB/status/836854762440130560" +date = 2017-03-01T00:00:00Z + +[[quotes]] +name = "Alan Richardson" +twitter_handle = "@eviltester" +quote = "I migrated the @BlackOpsTesting .com website from docpad to Hugo last weekend. http://gohugo.io/ Super Fast HTML Generation @spf13 " +link = "https://twitter.com/eviltester/status/553520335115808768" +date = 2015-01-09T00:00:00Z + +[[quotes]] +name = "Baron Schwartz" +twitter_handle = "@xaprb" +quote = "Hugo is impressively capable. It's a static site generator by @spf13 written in #golang . Just upgraded to latest release; very powerful. " +link = "https://twitter.com/xaprb/status/556894866488455169" +date = 2015-01-18T00:00:00Z + +[[quotes]] +name = "Dave Cottlehuber" +twitter_handle = "@dch__" +quote = "I just fell in love with #hugo, a static site/blog engine written by @spf13 in #golang + stellar docs" +link = "https://twitter.com/dch__/status/460158115498176512" +date = 2014-04-26T00:00:00Z + +[[quotes]] +name = "David Caunt" +twitter_handle = "@dcaunt" +quote = "I had a play with Hugo and it was good, uses Markdown files for content" +link = "https://twitter.com/dcaunt/statuses/406466996277374976" +date = 2013-11-29T00:00:00Z + +[[quotes]] +name = "David Gay" +twitter_handle = "@oddshocks" +quote = "Hugo is super-rad." +link = "https://twitter.com/oddshocks/statuses/405083217893421056" +date = 2013-11-25T00:00:00Z + +[[quotes]] +name = "Diti" +twitter_handle = "@DitiPengi" +quote = "The dev version of Hugo is AWESOME! <3 I promise, I will try to learn go ASAP and help contribute to the project! Just too great!" +link = "https://twitter.com/DitiPengi/status/472470974051676160" +date = 2014-05-30T00:00:00Z + +[[quotes]] +name = "Douglas Stephen " +twitter_handle = "@DougStephenJr" +quote = "Even as a long-time Octopress fan, I’ve gotta admit that this project Hugo looks very very cool" +link = "https://twitter.com/DougStephenJr/statuses/364512471660249088" +date = 2013-08-05T00:00:00Z + +[[quotes]] +name = "Hugo Rodger-Brown" +twitter_handle = "@hugorodgerbrown" +quote = "Finally someone builds me my own static site generator" +link = "https://twitter.com/hugorodgerbrown/statuses/364417910153818112" +date = 2013-05-08T00:00:00Z + +[[quotes]] +name = "Hugo Roy" +twitter_handle = "@hugoroyd" +quote = "Finally the answer to the question my parents have been asking: What does Hugo do?" +link = "https://twitter.com/hugoroyd/status/501704796727173120" +date = 2014-08-19T00:00:00Z + +[[quotes]] +name = "Daniel Miessler" +twitter_handle = "@DanielMiessler" +quote = "Websites for named vulnerabilities should run on static site generator platforms like Hugo. Read-only + burst traffic = static." +link = "https://twitter.com/DanielMiessler/status/704703841673957376" +date = 2016-03-01T00:00:00Z + +[[quotes]] +name = "Javier Segura" +twitter_handle = "@jsegura" +quote = "Another site generated with Hugo here! I'm getting in love with it." +link = "https://twitter.com/jsegura/status/465978434154659841" +date = 2014-05-12T00:00:00Z + +[[quotes]] +name = "Jim Biancolo" +twitter_handle = "@jimbiancolo" +quote = "I’m loving the static site generator renaissance we are currently enjoying. Hugo is new, looks great, written in Go" +link = "https://twitter.com/jimbiancolo/statuses/408678420348813314" +date = 2013-05-12T00:00:00Z + +[[quotes]] +name = "Jip J. Dekker" +twitter_handle = "@jipjdekker" +quote = "Building a personal website in Hugo. Works like a charm. And written in @golang!" +link = "https://twitter.com/jipjdekker/status/413783548735152131" +date = 2013-12-19T00:00:00Z + +[[quotes]] +name = "Jose Gonzalvo" +twitter_handle = "@jgonzalvo" +quote = "Checking out Hugo; Loving it so far. Like Jekyll but not so blog-oriented and written in go" +link = "https://twitter.com/jgonzalvo/statuses/408177855819173888" +date = 2013-12-04T00:00:00Z + +[[quotes]] +name = "Josh Matz" +twitter_handle = "@joshmatz" +quote = "A static site generator without the long build times? Yes, please!" +link = "https://twitter.com/joshmatz/statuses/364437436870696960" +date = 2013-08-05T00:00:00Z + +[[quotes]] +name = "Kieran Healy" +twitter_handle = "@kjhealy" +quote = "OK, so in today's speed battle of static site generators, @spf13's hugo is kicking everyone's ass, by miles." +link = "https://twitter.com/kjhealy/status/437349384809115648" +date = 2014-02-22T00:00:00Z + +[[quotes]] +name = "Ludovic Chabant" +twitter_handle = "@ludovicchabant" +quote = "Good work on Hugo, I’m impressed with the speed!" +link = "https://twitter.com/ludovicchabant/statuses/408806199602053120" +date = 2013-12-06T00:00:00Z + +[[quotes]] +name = "Luke Holder" +twitter_handle = "@lukeholder" +quote = "this is AWESOME. a single little executable and so fast." +link = "https://twitter.com/lukeholder/status/430352287936946176" +date = 2014-02-03T00:00:00Z + +[[quotes]] +name = "Markus Eliasson" +twitter_handle = "@markuseliasson" +quote = "Hugo is fast, dead simple to setup and well documented" +link = "https://twitter.com/markuseliasson/status/501594865877008384" +date = 2014-08-19T00:00:00Z + +[[quotes]] +name = "mercime" +twitter_handle = "@mercime_one" +quote = "Hugo: Makes the Web Fun Again" +link = "https://twitter.com/mercime_one/status/500547145087205377" +date = 2014-08-16T00:00:00Z + +[[quotes]] +name = "Michael Whatcott" +twitter_handle = "@mdwhatcott" +quote = "One more satisfied #Hugo blogger. Thanks @spf13 and friends!" +link = "https://twitter.com/mdwhatcott/status/469980686531571712" +date = 2014-05-23T00:00:00Z + +[[quotes]] +name = "Nathan Toups" +twitter_handle = "@rojoroboto" +quote = "I love Hugo! My site is generated with it now http://rjrbt.io" +link = "https://twitter.com/rojoroboto/status/423439915620106242" +date = 2014-01-15T00:00:00Z + +[[quotes]] +name = "Ruben Solvang" +twitter_handle = "@messo85" +quote = "#Hugo is the new @jekyllrb / @middlemanapp! Faster, easier and runs everywhere." +link = "https://twitter.com/messo85/status/472825062027182081" +date = 2014-05-31T00:00:00Z + +[[quotes]] +name = "Ryan Martinsen" +twitter_handle = "@popthestack" +quote = "Also, I re-launched my blog (it looks the same as before) using Hugo, a *fast* static engine. Very happy with it. gohugo.io" +link = "https://twitter.com/popthestack/status/549972754125307904" +date = 2014-12-30T00:00:00Z + +[[quotes]] +name = "The Lone Cuber" +twitter_handle = "@TheLoneCuber" +quote = "Jekyll is dead to me these days though... long live Hugo! Hugo is *by far* the best in its field. Thanks for making it happen." +link = "https://twitter.com/TheLoneCuber/status/495716684456398848" +date = 2014-08-02T00:00:00Z + +[[quotes]] +name = "The Lone Cuber" +twitter_handle = "@TheLoneCuber" +quote = "Finally, a publishing platform that's a joy to use. #NoMoreBarriers" +link = "https://twitter.com/TheLoneCuber/status/495731334711488512" +date = 2014-08-02T00:00:00Z + +[[quotes]] +name = "WorkHTML" +twitter_handle = "@workhtml" +quote = " #Hugo A very good alternative for #wordpress !!! A fast and modern static website engine gohugo.io " +link = "https://twitter.com/workhtml/status/563064361301053440" +date = 2015-02-04T00:00:00Z diff --git a/data/titles.toml b/data/titles.toml new file mode 100644 index 000000000..2348c8561 --- /dev/null +++ b/data/titles.toml @@ -0,0 +1,2 @@ +[Showcase] +title = "Site Showcase" diff --git a/deps/deps.go b/deps/deps.go deleted file mode 100644 index ed073c5d3..000000000 --- a/deps/deps.go +++ /dev/null @@ -1,181 +0,0 @@ -package deps - -import ( - "io/ioutil" - "log" - "os" - - "github.com/gohugoio/hugo/config" - "github.com/gohugoio/hugo/helpers" - "github.com/gohugoio/hugo/hugofs" - "github.com/gohugoio/hugo/output" - "github.com/gohugoio/hugo/tpl" - jww "github.com/spf13/jwalterweatherman" -) - -// Deps holds dependencies used by many. -// There will be normally only one instance of deps in play -// at a given time, i.e. one per Site built. -type Deps struct { - // The logger to use. - Log *jww.Notepad `json:"-"` - - // The templates to use. This will usually implement the full tpl.TemplateHandler. - Tmpl tpl.TemplateFinder `json:"-"` - - // The file systems to use. - Fs *hugofs.Fs `json:"-"` - - // The PathSpec to use - *helpers.PathSpec `json:"-"` - - // The ContentSpec to use - *helpers.ContentSpec `json:"-"` - - // The configuration to use - Cfg config.Provider `json:"-"` - - // The translation func to use - Translate func(translationID string, args ...interface{}) string `json:"-"` - - Language *helpers.Language - - // All the output formats available for the current site. - OutputFormatsConfig output.Formats - - templateProvider ResourceProvider - WithTemplate func(templ tpl.TemplateHandler) error `json:"-"` - - translationProvider ResourceProvider -} - -// ResourceProvider is used to create and refresh, and clone resources needed. -type ResourceProvider interface { - Update(deps *Deps) error - Clone(deps *Deps) error -} - -// TemplateHandler returns the used tpl.TemplateFinder as tpl.TemplateHandler. -func (d *Deps) TemplateHandler() tpl.TemplateHandler { - return d.Tmpl.(tpl.TemplateHandler) -} - -// LoadResources loads translations and templates. -func (d *Deps) LoadResources() error { - // Note that the translations need to be loaded before the templates. - if err := d.translationProvider.Update(d); err != nil { - return err - } - - if err := d.templateProvider.Update(d); err != nil { - return err - } - - if th, ok := d.Tmpl.(tpl.TemplateHandler); ok { - th.PrintErrors() - } - - return nil -} - -// New initializes a Dep struct. -// Defaults are set for nil values, -// but TemplateProvider, TranslationProvider and Language are always required. -func New(cfg DepsCfg) (*Deps, error) { - var ( - logger = cfg.Logger - fs = cfg.Fs - ) - - if cfg.TemplateProvider == nil { - panic("Must have a TemplateProvider") - } - - if cfg.TranslationProvider == nil { - panic("Must have a TranslationProvider") - } - - if cfg.Language == nil { - panic("Must have a Language") - } - - if logger == nil { - logger = jww.NewNotepad(jww.LevelError, jww.LevelError, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime) - } - - if fs == nil { - // Default to the production file system. - fs = hugofs.NewDefault(cfg.Language) - } - - ps, err := helpers.NewPathSpec(fs, cfg.Language) - - if err != nil { - return nil, err - } - - d := &Deps{ - Fs: fs, - Log: logger, - templateProvider: cfg.TemplateProvider, - translationProvider: cfg.TranslationProvider, - WithTemplate: cfg.WithTemplate, - PathSpec: ps, - ContentSpec: helpers.NewContentSpec(cfg.Language), - Cfg: cfg.Language, - Language: cfg.Language, - } - - return d, nil -} - -// ForLanguage creates a copy of the Deps with the language dependent -// parts switched out. -func (d Deps) ForLanguage(l *helpers.Language) (*Deps, error) { - var err error - - d.PathSpec, err = helpers.NewPathSpec(d.Fs, l) - if err != nil { - return nil, err - } - - d.ContentSpec = helpers.NewContentSpec(l) - d.Cfg = l - d.Language = l - - if err := d.translationProvider.Clone(&d); err != nil { - return nil, err - } - - if err := d.templateProvider.Clone(&d); err != nil { - return nil, err - } - - return &d, nil - -} - -// DepsCfg contains configuration options that can be used to configure Hugo -// on a global level, i.e. logging etc. -// Nil values will be given default values. -type DepsCfg struct { - - // The Logger to use. - Logger *jww.Notepad - - // The file systems to use - Fs *hugofs.Fs - - // The language to use. - Language *helpers.Language - - // The configuration to use. - Cfg config.Provider - - // Template handling. - TemplateProvider ResourceProvider - WithTemplate func(templ tpl.TemplateHandler) error - - // i18n handling. - TranslationProvider ResourceProvider -} diff --git a/docshelper/docs.go b/docshelper/docs.go deleted file mode 100644 index 94cb70dec..000000000 --- a/docshelper/docs.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2017-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package docshelper provides some helpers for the Hugo documentation, and -// is of limited interest for the general Hugo user. -package docshelper - -import ( - "encoding/json" -) - -// DocProviders contains all DocProviders added to the system. -var DocProviders = make(map[string]DocProvider) - -// AddDocProvider adds or updates the DocProvider for a given name. -func AddDocProvider(name string, provider DocProvider) { - DocProviders[name] = provider -} - -// DocProvider is used to save arbitrary JSON data -// used for the generation of the documentation. -type DocProvider func() map[string]interface{} - -// MarshalJSON returns a JSON representation of the DocProvider. -func (d DocProvider) MarshalJSON() ([]byte, error) { - return json.MarshalIndent(d(), "", " ") -} diff --git a/examples/blog/.gitignore b/examples/blog/.gitignore deleted file mode 100644 index 958340e12..000000000 --- a/examples/blog/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -# Hugo default output directory -/public - -## OS Files -# Windows -Thumbs.db -ehthumbs.db -Desktop.ini -$RECYCLE.BIN/ - -# OSX -.DS_Store diff --git a/examples/blog/README.md b/examples/blog/README.md deleted file mode 100644 index cfdff3c91..000000000 --- a/examples/blog/README.md +++ /dev/null @@ -1,42 +0,0 @@ -Hugo Example Blog -================= - -This repository provides a fully-working example of a [Hugo](https://github.com/gohugoio/hugo)-powered blog. Many -Hugo-specific features are used as a way to see them in action, and hopefully ease the learning curve for creating your -very own site with Hugo. - -Features --------- - -- Recent Posts at main index -- Indexes for `tags` and `categories` -- Post information block, with links for all `tags` and `categories` post belongs to -- [Bootstrap 3](http://getbootstrap.com/) ready - - Currently using the [Yeti](http://bootswatch.com/yeti/) theme from http://bootswatch.com/ - -Common things that should be added in the near future *(pull requests are welcome!)*: - -- Disqus integration -- More content types to demonstrate different layout methods - - About Me - - Contact - -Getting Started ---------------- - -To get started, you should simply fork or clone this repository! That's definitely an important first step. - -[Install Hugo](http://gohugo.io/overview/installing) in a way that best suits your environment and comfort level. - -Edit `config.toml` and change the default properties to suit your own information. This is not required to run the -example, but this is the global configuration file and you're going to need to use it eventually. Start here! - -In a command prompt or terminal, navigate to the path that contains your `config.toml` file and run `hugo`. That's it! -You should now have a `public` directory with a complete blog! Open `public/index.html` in your browser and bask. - -If that wasn't amazing enough, from the same terminal, run `hugo server`. This will watch your directories for changes -and rebuild the site immediately, *and* it will make these changes available at http://localhost:1313/ so you can view -your finished site in your browser. Go on, try it. This is one of the best ways to preview your site while working on it. - -To further learn Hugo and learn more, read through the Hugo [documentation](http://gohugo.io/overview/introduction) -or browse around the files in this repository. Have fun! diff --git a/examples/blog/config.toml b/examples/blog/config.toml deleted file mode 100644 index b402f2c7f..000000000 --- a/examples/blog/config.toml +++ /dev/null @@ -1,4 +0,0 @@ -baseURL = "http://blog.hugoexample.com/" -languageCode = "en-us" -title = "Hugo Example Blog" -canonifyURLs = true diff --git a/examples/blog/content/post/another-post.md b/examples/blog/content/post/another-post.md deleted file mode 100644 index 057c2d27b..000000000 --- a/examples/blog/content/post/another-post.md +++ /dev/null @@ -1,57 +0,0 @@ -+++ -title = "Another Hugo Post" -description = "Nothing special, but one post is boring." -date = "2014-09-02" -categories = [ "example", "configuration" ] -tags = [ - "example", - "hugo", - "toml" -] -+++ - -TOML, YAML, JSON --- Oh my! -------------------------- - -One of the nifty Hugo features we should cover: flexible configuration and front matter formats! This entry has front -matter in `toml`, unlike the last one which used `yaml`, and `json` is also available if that's your preference. - - - -The `toml` front matter used on this entry: - -``` -+++ -title = "Another Hugo Post" -description = "Nothing special, but one post is boring." -date = "2014-09-02" -categories = [ "example", "configuration" ] -tags = [ - "example", - "hugo", - "toml" -] -+++ -``` - -This flexibility also extends to your site's global configuration file. You're free to use any format you prefer::simply -name the file `config.yaml`, `config.toml` or `config.json`, and go on your merry way. - -JSON Example ------------- - -How would this entry's front matter look in `json`? That's easy enough to demonstrate: - -``` -{ - "title": "Another Hugo Post", - "description": "Nothing special, but one post is boring.", - "date": "2014-09-02", - "categories": [ "example", "configuration" ], - "tags": [ - "example", - "hugo", - "toml" - ], -} -``` diff --git a/examples/blog/content/post/hello-hugo.md b/examples/blog/content/post/hello-hugo.md deleted file mode 100644 index f58886ee8..000000000 --- a/examples/blog/content/post/hello-hugo.md +++ /dev/null @@ -1,61 +0,0 @@ ---- -title: "Hello Hugo!" -description: "Saying 'Hello' from Hugo" -date: "2014-09-01" -categories: - - "example" - - "hello" -tags: - - "example" - - "hugo" - - "blog" ---- - -Hello from Hugo! If you're reading this in your browser, good job! The file `content/post/hello-hugo.md` has been -converted into a complete HTML document by Hugo. Isn't that pretty nifty? - -A Section ---------- - -Here's a simple titled section where you can place whatever information you want. - -You can use inline HTML if you want, but really there's not much that Markdown can't do. - -Showing off with Markdown -------------------------- - -A full cheat sheet can be found [here](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) -or through [Google](https://google.com/). - -There are some *easy* examples for styling, though. I can't **emphasize** that enough. -Creating [links](https://google.com/) or `inline code` blocks are very straightforward. - -``` -There are some *easy* examples for styling, though. I can't **emphasize** that enough. -Creating [links](https://google.com/) or `inline code` blocks are very straightforward. -``` - -Front Matter for Fun --------------------- - -This is the meta data for this post. It is located at the top of the `content/post/hello-hugo.md` markdown file. - -``` ---- -title: "Hello Hugo!" -description: "Saying 'Hello' from Hugo" -date: "2014-09-01" -categories: - - "example" - - "hello" -tags: - - "example" - - "hugo" - - "blog" ---- -``` - -This section, called 'Front Matter', is what tells Hugo about the content in this file: the `title` of the item, the -`description`, and the `date` it was posted. In our example, we've added two custom bits of data too. The `categories` and -`tags` sections are used in this example for indexing/grouping content. You will learn more about what that means by -examining the code in this example and through reading the Hugo [documentation](http://gohugo.io/overview/introduction). diff --git a/examples/blog/layouts/_default/single.html b/examples/blog/layouts/_default/single.html deleted file mode 100644 index 13a53f666..000000000 --- a/examples/blog/layouts/_default/single.html +++ /dev/null @@ -1,21 +0,0 @@ -{{ partial "header.html" . }} - -{{ partial "navbar.html" . }} -
    -
    -
    -
    -

    {{ .Title }}
    {{ .Description }}

    -
    - {{ .Content }} -
    -
    - - -
    - {{ partial "menu.html" . }} -
    -
    -{{ partial "footer.copyright.html" . }} -
    -{{ partial "footer.html" . }} diff --git a/examples/blog/layouts/index.html b/examples/blog/layouts/index.html deleted file mode 100644 index a69100409..000000000 --- a/examples/blog/layouts/index.html +++ /dev/null @@ -1,19 +0,0 @@ -{{ partial "header.html" . }} - -{{ partial "navbar.html" . }} -
    -
    -
    - {{ range first 10 .Data.Pages }} - {{ .Render "summary" }} - {{ end }} -
    - - -
    - {{ partial "menu.html" . }} -
    -
    -{{ partial "footer.copyright.html" . }} -
    -{{ partial "footer.html" . }} diff --git a/examples/blog/layouts/indexes/category.html b/examples/blog/layouts/indexes/category.html deleted file mode 100644 index 653d81964..000000000 --- a/examples/blog/layouts/indexes/category.html +++ /dev/null @@ -1,24 +0,0 @@ -{{ partial "header.html" . }} - -{{ partial "navbar.html" . }} -
    -
    -
    -
    - Items in category {{ .Title | lower }} -
      - {{ range .Data.Pages }} - {{ .Render "li" }} - {{ end}} -
    -
    -
    - - -
    - {{ partial "menu.html" . }} -
    -
    -{{ partial "footer.copyright.html" . }} -
    -{{ partial "footer.html" . }} diff --git a/examples/blog/layouts/indexes/post.html b/examples/blog/layouts/indexes/post.html deleted file mode 100644 index b3a835ccd..000000000 --- a/examples/blog/layouts/indexes/post.html +++ /dev/null @@ -1,24 +0,0 @@ -{{ partial "header.html" . }} - -{{ partial "navbar.html" . }} -
    -
    -
    -
    - Blog Post Archive -
      - {{ range .Data.Pages }} - {{ .Render "li" }} - {{ end}} -
    -
    -
    - - -
    - {{ partial "menu.html" . }} -
    -
    -{{ partial "footer.copyright.html" . }} -
    -{{ partial "footer.html" . }} diff --git a/examples/blog/layouts/indexes/tag.html b/examples/blog/layouts/indexes/tag.html deleted file mode 100644 index f59b76715..000000000 --- a/examples/blog/layouts/indexes/tag.html +++ /dev/null @@ -1,24 +0,0 @@ -{{ partial "header.html" . }} - -{{ partial "navbar.html" . }} -
    -
    -
    -
    - Items with tag {{ .Title | lower }} -
      - {{ range .Data.Pages }} - {{ .Render "li" }} - {{ end}} -
    -
    -
    - - -
    - {{ partial "menu.html" . }} -
    -
    -{{ partial "footer.copyright.html" . }} -
    -{{ partial "footer.html" . }} diff --git a/examples/blog/layouts/partials/footer.copyright.html b/examples/blog/layouts/partials/footer.copyright.html deleted file mode 100644 index 64c9353ea..000000000 --- a/examples/blog/layouts/partials/footer.copyright.html +++ /dev/null @@ -1,9 +0,0 @@ -
    -
    -
    -
    -

    © Enthusiastic Hugo User {{ .Now.Format "2006" }} · - Built with Hugo

    -
    -
    -
    \ No newline at end of file diff --git a/examples/blog/layouts/partials/footer.html b/examples/blog/layouts/partials/footer.html deleted file mode 100644 index 8945fa4ed..000000000 --- a/examples/blog/layouts/partials/footer.html +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/examples/blog/layouts/partials/header.html b/examples/blog/layouts/partials/header.html deleted file mode 100644 index 24500a483..000000000 --- a/examples/blog/layouts/partials/header.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - {{ partial "meta.html" . }} - - {{ .Title }} - {{ .Site.BaseURL }} - - {{ partial "header.includes.html" . }} - {{ if .RSSLink }}{{ end }} - diff --git a/examples/blog/layouts/partials/header.includes.html b/examples/blog/layouts/partials/header.includes.html deleted file mode 100644 index 767e3eee1..000000000 --- a/examples/blog/layouts/partials/header.includes.html +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/examples/blog/layouts/partials/menu.html b/examples/blog/layouts/partials/menu.html deleted file mode 100644 index 61ce0c6b5..000000000 --- a/examples/blog/layouts/partials/menu.html +++ /dev/null @@ -1,15 +0,0 @@ -
    -
    -

    Connect. Socialize.

    -
    -
    - - - -
    - - Hey, listen!
    - You should modify the layouts/partials/menu.html template and include your own profile links. -
    -
    -
    diff --git a/examples/blog/layouts/partials/meta.html b/examples/blog/layouts/partials/meta.html deleted file mode 100644 index 95fd2a711..000000000 --- a/examples/blog/layouts/partials/meta.html +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/examples/blog/layouts/partials/navbar.html b/examples/blog/layouts/partials/navbar.html deleted file mode 100644 index b15c24630..000000000 --- a/examples/blog/layouts/partials/navbar.html +++ /dev/null @@ -1,22 +0,0 @@ - diff --git a/examples/blog/layouts/post/li.html b/examples/blog/layouts/post/li.html deleted file mode 100644 index d57be9c80..000000000 --- a/examples/blog/layouts/post/li.html +++ /dev/null @@ -1,4 +0,0 @@ -
  • -
    {{ .Title}}
    - posted on {{ .Date.Format "January 2, 2006" }}
    -
  • \ No newline at end of file diff --git a/examples/blog/layouts/post/single.html b/examples/blog/layouts/post/single.html deleted file mode 100644 index 4b792ebe2..000000000 --- a/examples/blog/layouts/post/single.html +++ /dev/null @@ -1,35 +0,0 @@ -{{ partial "header.html" . }} - -{{ partial "navbar.html" . }} -
    -
    -
    -
    -

    {{ .Title }}
    {{ .Description }}

    -
    - {{ .Content }} -
    -
    - - -
    -
    -

    {{ .Date.Format "January 2, 2006" }}
    - {{ .WordCount }} words

    -
    - Categories -
      - {{ range .Params.categories }} -
    • {{ . }}
    • - {{ end }} -
    -
    - Tags
    - {{ range .Params.tags }}{{ . }} {{ end }} -
    - {{ partial "menu.html" . }} -
    -
    -{{ partial "footer.copyright.html" . }} -
    -{{ partial "footer.html" . }} diff --git a/examples/blog/layouts/post/summary.html b/examples/blog/layouts/post/summary.html deleted file mode 100644 index f70b6827a..000000000 --- a/examples/blog/layouts/post/summary.html +++ /dev/null @@ -1,9 +0,0 @@ -
    -

    - {{ .Title }} Posted on {{ .Date.Format "Jan 2, 2006" }}
    - {{ .Description }} -

    -
    -

    {{ .Summary }}

    - Read More -
    \ No newline at end of file diff --git a/examples/blog/static/css/bootstrap.min.css b/examples/blog/static/css/bootstrap.min.css deleted file mode 100644 index 70829d0d3..000000000 --- a/examples/blog/static/css/bootstrap.min.css +++ /dev/null @@ -1,11 +0,0 @@ -@import url("https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,700italic,400,300,700");/*! - * bootswatch v3.3.6 - * Homepage: http://bootswatch.com - * Copyright 2012-2015 Thomas Park - * Licensed under MIT - * Based on Bootstrap -*//*! - * Bootstrap v3.3.6 (http://getbootstrap.com) - * Copyright 2011-2015 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,*:before,*:after{background:transparent !important;color:#000 !important;-webkit-box-shadow:none !important;box-shadow:none !important;text-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}@font-face{font-family:'Glyphicons Halflings';src:url('../fonts/glyphicons-halflings-regular.eot');src:url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'),url('../fonts/glyphicons-halflings-regular.woff') format('woff'),url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'),url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-euro:before,.glyphicon-eur:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#222222;background-color:#ffffff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#008cba;text-decoration:none}a:hover,a:focus{color:#008cba;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img,.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:0}.img-thumbnail{padding:4px;line-height:1.4;background-color:#ffffff;border:1px solid #dddddd;border-radius:0;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:21px;margin-bottom:21px;border:0;border-top:1px solid #dddddd}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role="button"]{cursor:pointer}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:300;line-height:1.1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:normal;line-height:1;color:#999999}h1,.h1,h2,.h2,h3,.h3{margin-top:21px;margin-bottom:10.5px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10.5px;margin-bottom:10.5px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:39px}h2,.h2{font-size:32px}h3,.h3{font-size:26px}h4,.h4{font-size:19px}h5,.h5{font-size:15px}h6,.h6{font-size:13px}p{margin:0 0 10.5px}.lead{margin-bottom:21px;font-size:17px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:22.5px}}small,.small{font-size:80%}mark,.mark{background-color:#fcf8e3;padding:.2em}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#999999}.text-primary{color:#008cba}a.text-primary:hover,a.text-primary:focus{color:#006687}.text-success{color:#43ac6a}a.text-success:hover,a.text-success:focus{color:#358753}.text-info{color:#5bc0de}a.text-info:hover,a.text-info:focus{color:#31b0d5}.text-warning{color:#e99002}a.text-warning:hover,a.text-warning:focus{color:#b67102}.text-danger{color:#f04124}a.text-danger:hover,a.text-danger:focus{color:#d32a0e}.bg-primary{color:#fff;background-color:#008cba}a.bg-primary:hover,a.bg-primary:focus{background-color:#006687}.bg-success{background-color:#dff0d8}a.bg-success:hover,a.bg-success:focus{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover,a.bg-info:focus{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover,a.bg-warning:focus{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover,a.bg-danger:focus{background-color:#e4b9b9}.page-header{padding-bottom:9.5px;margin:42px 0 21px;border-bottom:1px solid #dddddd}ul,ol{margin-top:0;margin-bottom:10.5px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-top:0;margin-bottom:21px}dt,dd{line-height:1.4}dt{font-weight:bold}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999999}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10.5px 21px;margin:0 0 21px;font-size:18.75px;border-left:5px solid #dddddd}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.4;color:#6f6f6f}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #dddddd;border-left:0;text-align:right}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}address{margin-bottom:21px;font-style:normal;line-height:1.4}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:0}kbd{padding:2px 4px;font-size:90%;color:#ffffff;background-color:#333333;border-radius:0;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}kbd kbd{padding:0;font-size:100%;font-weight:bold;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:10px;margin:0 0 10.5px;font-size:14px;line-height:1.4;word-break:break-all;word-wrap:break-word;color:#333333;background-color:#f5f5f5;border:1px solid #cccccc;border-radius:0}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.row{margin-left:-15px;margin-right:-15px}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0%}@media (min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0%}}@media (min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0%}}@media (min-width:1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0%}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#999999;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:21px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.4;vertical-align:top;border-top:1px solid #dddddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #dddddd}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #dddddd}.table .table{background-color:#ffffff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #dddddd}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #dddddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*="col-"]{position:static;float:none;display:table-column}table td[class*="col-"],table th[class*="col-"]{position:static;float:none;display:table-cell}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#d9edf7}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr.info:hover>th{background-color:#c4e3f3}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}.table-responsive{overflow-x:auto;min-height:0.01%}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15.75px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #dddddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0;min-width:0}legend{display:block;width:100%;padding:0;margin-bottom:21px;font-size:22.5px;line-height:inherit;color:#333333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:bold}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="file"]{display:block}input[type="range"]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:9px;font-size:15px;line-height:1.4;color:#6f6f6f}.form-control{display:block;width:100%;height:39px;padding:8px 12px;font-size:15px;line-height:1.4;color:#6f6f6f;background-color:#ffffff;background-image:none;border:1px solid #cccccc;border-radius:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(102,175,233,0.6)}.form-control::-moz-placeholder{color:#999999;opacity:1}.form-control:-ms-input-placeholder{color:#999999}.form-control::-webkit-input-placeholder{color:#999999}.form-control::-ms-expand{border:0;background-color:transparent}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eeeeee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type="search"]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type="date"].form-control,input[type="time"].form-control,input[type="datetime-local"].form-control,input[type="month"].form-control{line-height:39px}input[type="date"].input-sm,input[type="time"].input-sm,input[type="datetime-local"].input-sm,input[type="month"].input-sm,.input-group-sm input[type="date"],.input-group-sm input[type="time"],.input-group-sm input[type="datetime-local"],.input-group-sm input[type="month"]{line-height:36px}input[type="date"].input-lg,input[type="time"].input-lg,input[type="datetime-local"].input-lg,input[type="month"].input-lg,.input-group-lg input[type="date"],.input-group-lg input[type="time"],.input-group-lg input[type="datetime-local"],.input-group-lg input[type="month"]{line-height:60px}}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;margin-top:10px;margin-bottom:10px}.radio label,.checkbox label{min-height:21px;padding-left:20px;margin-bottom:0;font-weight:normal;cursor:pointer}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{position:absolute;margin-left:-20px;margin-top:4px \9}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:normal;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"].disabled,input[type="checkbox"].disabled,fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"]{cursor:not-allowed}.radio-inline.disabled,.checkbox-inline.disabled,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio.disabled label,.checkbox.disabled label,fieldset[disabled] .radio label,fieldset[disabled] .checkbox label{cursor:not-allowed}.form-control-static{padding-top:9px;padding-bottom:9px;margin-bottom:0;min-height:36px}.form-control-static.input-lg,.form-control-static.input-sm{padding-left:0;padding-right:0}.input-sm{height:36px;padding:8px 12px;font-size:12px;line-height:1.5;border-radius:0}select.input-sm{height:36px;line-height:36px}textarea.input-sm,select[multiple].input-sm{height:auto}.form-group-sm .form-control{height:36px;padding:8px 12px;font-size:12px;line-height:1.5;border-radius:0}.form-group-sm select.form-control{height:36px;line-height:36px}.form-group-sm textarea.form-control,.form-group-sm select[multiple].form-control{height:auto}.form-group-sm .form-control-static{height:36px;min-height:33px;padding:9px 12px;font-size:12px;line-height:1.5}.input-lg{height:60px;padding:16px 20px;font-size:19px;line-height:1.3333333;border-radius:0}select.input-lg{height:60px;line-height:60px}textarea.input-lg,select[multiple].input-lg{height:auto}.form-group-lg .form-control{height:60px;padding:16px 20px;font-size:19px;line-height:1.3333333;border-radius:0}.form-group-lg select.form-control{height:60px;line-height:60px}.form-group-lg textarea.form-control,.form-group-lg select[multiple].form-control{height:auto}.form-group-lg .form-control-static{height:60px;min-height:40px;padding:17px 20px;font-size:19px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:48.75px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:39px;height:39px;line-height:39px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback,.input-group-lg+.form-control-feedback,.form-group-lg .form-control+.form-control-feedback{width:60px;height:60px;line-height:60px}.input-sm+.form-control-feedback,.input-group-sm+.form-control-feedback,.form-group-sm .form-control+.form-control-feedback{width:36px;height:36px;line-height:36px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label{color:#43ac6a}.has-success .form-control{border-color:#43ac6a;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#358753;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #85d0a1;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #85d0a1}.has-success .input-group-addon{color:#43ac6a;border-color:#43ac6a;background-color:#dff0d8}.has-success .form-control-feedback{color:#43ac6a}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label{color:#e99002}.has-warning .form-control{border-color:#e99002;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#b67102;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #febc53;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #febc53}.has-warning .input-group-addon{color:#e99002;border-color:#e99002;background-color:#fcf8e3}.has-warning .form-control-feedback{color:#e99002}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label{color:#f04124}.has-error .form-control{border-color:#f04124;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#d32a0e;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #f79483;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #f79483}.has-error .input-group-addon{color:#f04124;border-color:#f04124;background-color:#f2dede}.has-error .form-control-feedback{color:#f04124}.has-feedback label~.form-control-feedback{top:26px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#626262}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.form-inline .input-group .form-control{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .radio label,.form-inline .checkbox label{padding-left:0}.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:0;margin-bottom:0;padding-top:9px}.form-horizontal .radio,.form-horizontal .checkbox{min-height:30px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}@media (min-width:768px){.form-horizontal .control-label{text-align:right;margin-bottom:0;padding-top:9px}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:17px;font-size:19px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:9px;font-size:12px}}.btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:8px 12px;font-size:15px;line-height:1.4;border-radius:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn:active:focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn.active.focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus,.btn.focus{color:#333333;text-decoration:none}.btn:active,.btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333333;background-color:#e7e7e7;border-color:#cccccc}.btn-default:focus,.btn-default.focus{color:#333333;background-color:#cecece;border-color:#8c8c8c}.btn-default:hover{color:#333333;background-color:#cecece;border-color:#adadad}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{color:#333333;background-color:#cecece;border-color:#adadad}.btn-default:active:hover,.btn-default.active:hover,.open>.dropdown-toggle.btn-default:hover,.btn-default:active:focus,.btn-default.active:focus,.open>.dropdown-toggle.btn-default:focus,.btn-default:active.focus,.btn-default.active.focus,.open>.dropdown-toggle.btn-default.focus{color:#333333;background-color:#bcbcbc;border-color:#8c8c8c}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled.focus,.btn-default[disabled].focus,fieldset[disabled] .btn-default.focus{background-color:#e7e7e7;border-color:#cccccc}.btn-default .badge{color:#e7e7e7;background-color:#333333}.btn-primary{color:#ffffff;background-color:#008cba;border-color:#0079a1}.btn-primary:focus,.btn-primary.focus{color:#ffffff;background-color:#006687;border-color:#001921}.btn-primary:hover{color:#ffffff;background-color:#006687;border-color:#004b63}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{color:#ffffff;background-color:#006687;border-color:#004b63}.btn-primary:active:hover,.btn-primary.active:hover,.open>.dropdown-toggle.btn-primary:hover,.btn-primary:active:focus,.btn-primary.active:focus,.open>.dropdown-toggle.btn-primary:focus,.btn-primary:active.focus,.btn-primary.active.focus,.open>.dropdown-toggle.btn-primary.focus{color:#ffffff;background-color:#004b63;border-color:#001921}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled.focus,.btn-primary[disabled].focus,fieldset[disabled] .btn-primary.focus{background-color:#008cba;border-color:#0079a1}.btn-primary .badge{color:#008cba;background-color:#ffffff}.btn-success{color:#ffffff;background-color:#43ac6a;border-color:#3c9a5f}.btn-success:focus,.btn-success.focus{color:#ffffff;background-color:#358753;border-color:#183e26}.btn-success:hover{color:#ffffff;background-color:#358753;border-color:#2b6e44}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{color:#ffffff;background-color:#358753;border-color:#2b6e44}.btn-success:active:hover,.btn-success.active:hover,.open>.dropdown-toggle.btn-success:hover,.btn-success:active:focus,.btn-success.active:focus,.open>.dropdown-toggle.btn-success:focus,.btn-success:active.focus,.btn-success.active.focus,.open>.dropdown-toggle.btn-success.focus{color:#ffffff;background-color:#2b6e44;border-color:#183e26}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled.focus,.btn-success[disabled].focus,fieldset[disabled] .btn-success.focus{background-color:#43ac6a;border-color:#3c9a5f}.btn-success .badge{color:#43ac6a;background-color:#ffffff}.btn-info{color:#ffffff;background-color:#5bc0de;border-color:#46b8da}.btn-info:focus,.btn-info.focus{color:#ffffff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#ffffff;background-color:#31b0d5;border-color:#269abc}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{color:#ffffff;background-color:#31b0d5;border-color:#269abc}.btn-info:active:hover,.btn-info.active:hover,.open>.dropdown-toggle.btn-info:hover,.btn-info:active:focus,.btn-info.active:focus,.open>.dropdown-toggle.btn-info:focus,.btn-info:active.focus,.btn-info.active.focus,.open>.dropdown-toggle.btn-info.focus{color:#ffffff;background-color:#269abc;border-color:#1b6d85}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled.focus,.btn-info[disabled].focus,fieldset[disabled] .btn-info.focus{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#ffffff}.btn-warning{color:#ffffff;background-color:#e99002;border-color:#d08002}.btn-warning:focus,.btn-warning.focus{color:#ffffff;background-color:#b67102;border-color:#513201}.btn-warning:hover{color:#ffffff;background-color:#b67102;border-color:#935b01}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{color:#ffffff;background-color:#b67102;border-color:#935b01}.btn-warning:active:hover,.btn-warning.active:hover,.open>.dropdown-toggle.btn-warning:hover,.btn-warning:active:focus,.btn-warning.active:focus,.open>.dropdown-toggle.btn-warning:focus,.btn-warning:active.focus,.btn-warning.active.focus,.open>.dropdown-toggle.btn-warning.focus{color:#ffffff;background-color:#935b01;border-color:#513201}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled.focus,.btn-warning[disabled].focus,fieldset[disabled] .btn-warning.focus{background-color:#e99002;border-color:#d08002}.btn-warning .badge{color:#e99002;background-color:#ffffff}.btn-danger{color:#ffffff;background-color:#f04124;border-color:#ea2f10}.btn-danger:focus,.btn-danger.focus{color:#ffffff;background-color:#d32a0e;border-color:#731708}.btn-danger:hover{color:#ffffff;background-color:#d32a0e;border-color:#b1240c}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{color:#ffffff;background-color:#d32a0e;border-color:#b1240c}.btn-danger:active:hover,.btn-danger.active:hover,.open>.dropdown-toggle.btn-danger:hover,.btn-danger:active:focus,.btn-danger.active:focus,.open>.dropdown-toggle.btn-danger:focus,.btn-danger:active.focus,.btn-danger.active.focus,.open>.dropdown-toggle.btn-danger.focus{color:#ffffff;background-color:#b1240c;border-color:#731708}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled.focus,.btn-danger[disabled].focus,fieldset[disabled] .btn-danger.focus{background-color:#f04124;border-color:#ea2f10}.btn-danger .badge{color:#f04124;background-color:#ffffff}.btn-link{color:#008cba;font-weight:normal;border-radius:0}.btn-link,.btn-link:active,.btn-link.active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#008cba;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#999999;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:16px 20px;font-size:19px;line-height:1.3333333;border-radius:0}.btn-sm,.btn-group-sm>.btn{padding:8px 12px;font-size:12px;line-height:1.5;border-radius:0}.btn-xs,.btn-group-xs>.btn{padding:4px 6px;font-size:12px;line-height:1.5;border-radius:0}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-property:height, visibility;-o-transition-property:height, visibility;transition-property:height, visibility;-webkit-transition-duration:0.35s;-o-transition-duration:0.35s;transition-duration:0.35s;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid \9;border-right:4px solid transparent;border-left:4px solid transparent}.dropup,.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:15px;text-align:left;background-color:#ffffff;border:1px solid #cccccc;border:1px solid rgba(0,0,0,0.15);border-radius:0;-webkit-box-shadow:0 6px 12px rgba(0,0,0,0.175);box-shadow:0 6px 12px rgba(0,0,0,0.175);-webkit-background-clip:padding-box;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9.5px 0;overflow:hidden;background-color:rgba(0,0,0,0.2)}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:1.4;color:#555555;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{text-decoration:none;color:#262626;background-color:#eeeeee}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#ffffff;text-decoration:none;outline:0;background-color:#008cba}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999999}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);cursor:not-allowed}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{left:auto;right:0}.dropdown-menu-left{left:0;right:auto}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.4;color:#999999;white-space:nowrap}.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px dashed;border-bottom:4px solid \9;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{left:auto;right:0}.navbar-right .dropdown-menu-left{left:0;right:auto}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-left:12px;padding-right:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:0;border-top-left-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-right-radius:0;border-top-left-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle="buttons"]>.btn input[type="radio"],[data-toggle="buttons"]>.btn-group>.btn input[type="radio"],[data-toggle="buttons"]>.btn input[type="checkbox"],[data-toggle="buttons"]>.btn-group>.btn input[type="checkbox"]{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*="col-"]{float:none;padding-left:0;padding-right:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:60px;padding:16px 20px;font-size:19px;line-height:1.3333333;border-radius:0}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:60px;line-height:60px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:36px;padding:8px 12px;font-size:12px;line-height:1.5;border-radius:0}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:36px;line-height:36px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:8px 12px;font-size:15px;font-weight:normal;line-height:1;color:#6f6f6f;text-align:center;background-color:#eeeeee;border:1px solid #cccccc;border-radius:0}.input-group-addon.input-sm{padding:8px 12px;font-size:12px;border-radius:0}.input-group-addon.input-lg{padding:16px 20px;font-size:19px;border-radius:0}.input-group-addon input[type="radio"],.input-group-addon input[type="checkbox"]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{margin-bottom:0;padding-left:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eeeeee}.nav>li.disabled>a{color:#999999}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#999999;text-decoration:none;background-color:transparent;cursor:not-allowed}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eeeeee;border-color:#008cba}.nav .nav-divider{height:1px;margin:9.5px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #dddddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.4;border:1px solid transparent;border-radius:0 0 0 0}.nav-tabs>li>a:hover{border-color:#eeeeee #eeeeee #dddddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#6f6f6f;background-color:#ffffff;border:1px solid #dddddd;border-bottom-color:transparent;cursor:default}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #dddddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #dddddd;border-radius:0 0 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#ffffff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:0}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#ffffff;background-color:#008cba}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #dddddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #dddddd;border-radius:0 0 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#ffffff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:45px;margin-bottom:21px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:0}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block !important;height:auto !important;padding-bottom:0;overflow:visible !important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-left:0;padding-right:0}}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;padding:12px 15px;font-size:19px;line-height:21px;height:45px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:5.5px;margin-bottom:5.5px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:0}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:6px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:21px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:21px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:12px;padding-bottom:12px}}.navbar-form{margin-left:-15px;margin-right:-15px;padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);margin-top:3px;margin-bottom:3px}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn,.navbar-form .input-group .form-control{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .radio label,.navbar-form .checkbox label{padding-left:0}.navbar-form .radio input[type="radio"],.navbar-form .checkbox input[type="checkbox"]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-right-radius:0;border-top-left-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:3px;margin-bottom:3px}.navbar-btn.btn-sm{margin-top:4.5px;margin-bottom:4.5px}.navbar-btn.btn-xs{margin-top:11.5px;margin-bottom:11.5px}.navbar-text{margin-top:12px;margin-bottom:12px}@media (min-width:768px){.navbar-text{float:left;margin-left:15px;margin-right:15px}}@media (min-width:768px){.navbar-left{float:left !important}.navbar-right{float:right !important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#333333;border-color:#222222}.navbar-default .navbar-brand{color:#ffffff}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#ffffff;background-color:transparent}.navbar-default .navbar-text{color:#ffffff}.navbar-default .navbar-nav>li>a{color:#ffffff}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#ffffff;background-color:#272727}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#ffffff;background-color:#272727}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#cccccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:transparent}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:transparent}.navbar-default .navbar-toggle .icon-bar{background-color:#ffffff}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#222222}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{background-color:#272727;color:#ffffff}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#ffffff}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#ffffff;background-color:#272727}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#ffffff;background-color:#272727}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#cccccc;background-color:transparent}}.navbar-default .navbar-link{color:#ffffff}.navbar-default .navbar-link:hover{color:#ffffff}.navbar-default .btn-link{color:#ffffff}.navbar-default .btn-link:hover,.navbar-default .btn-link:focus{color:#ffffff}.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:hover,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:focus{color:#cccccc}.navbar-inverse{background-color:#008cba;border-color:#006687}.navbar-inverse .navbar-brand{color:#ffffff}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#ffffff;background-color:transparent}.navbar-inverse .navbar-text{color:#ffffff}.navbar-inverse .navbar-nav>li>a{color:#ffffff}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#ffffff;background-color:#006687}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#ffffff;background-color:#006687}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:transparent}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:transparent}.navbar-inverse .navbar-toggle .icon-bar{background-color:#ffffff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#007196}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{background-color:#006687;color:#ffffff}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#006687}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#006687}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#ffffff}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#ffffff;background-color:#006687}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#ffffff;background-color:#006687}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444444;background-color:transparent}}.navbar-inverse .navbar-link{color:#ffffff}.navbar-inverse .navbar-link:hover{color:#ffffff}.navbar-inverse .btn-link{color:#ffffff}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#ffffff}.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:focus{color:#444444}.breadcrumb{padding:8px 15px;margin-bottom:21px;list-style:none;background-color:#f5f5f5;border-radius:0}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{content:"/\00a0";padding:0 5px;color:#999999}.breadcrumb>.active{color:#333333}.pagination{display:inline-block;padding-left:0;margin:21px 0;border-radius:0}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:8px 12px;line-height:1.4;text-decoration:none;color:#008cba;background-color:transparent;border:1px solid transparent;margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:0;border-top-left-radius:0}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:0;border-top-right-radius:0}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{z-index:2;color:#008cba;background-color:#eeeeee;border-color:transparent}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:3;color:#ffffff;background-color:#008cba;border-color:transparent;cursor:default}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#999999;background-color:#ffffff;border-color:transparent;cursor:not-allowed}.pagination-lg>li>a,.pagination-lg>li>span{padding:16px 20px;font-size:19px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:0;border-top-left-radius:0}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:0;border-top-right-radius:0}.pagination-sm>li>a,.pagination-sm>li>span{padding:8px 12px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:0;border-top-left-radius:0}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:0;border-top-right-radius:0}.pager{padding-left:0;margin:21px 0;list-style:none;text-align:center}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:transparent;border:1px solid transparent;border-radius:3px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eeeeee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999999;background-color:transparent;cursor:not-allowed}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:bold;line-height:1;color:#ffffff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:hover,a.label:focus{color:#ffffff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#999999}.label-default[href]:hover,.label-default[href]:focus{background-color:#808080}.label-primary{background-color:#008cba}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#006687}.label-success{background-color:#43ac6a}.label-success[href]:hover,.label-success[href]:focus{background-color:#358753}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.label-warning{background-color:#e99002}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#b67102}.label-danger{background-color:#f04124}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#d32a0e}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:bold;color:#ffffff;line-height:1;vertical-align:middle;white-space:nowrap;text-align:center;background-color:#008cba;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge,.btn-group-xs>.btn .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#ffffff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#008cba;background-color:#ffffff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#fafafa}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:23px;font-weight:200}.jumbotron>hr{border-top-color:#e1e1e1}.container .jumbotron,.container-fluid .jumbotron{border-radius:0;padding-left:15px;padding-right:15px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-left:60px;padding-right:60px}.jumbotron h1,.jumbotron .h1{font-size:68px}}.thumbnail{display:block;padding:4px;margin-bottom:21px;line-height:1.4;background-color:#ffffff;border:1px solid #dddddd;border-radius:0;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-left:auto;margin-right:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#008cba}.thumbnail .caption{padding:9px;color:#222222}.alert{padding:15px;margin-bottom:21px;border:1px solid transparent;border-radius:0}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:bold}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{background-color:#43ac6a;border-color:#3c9a5f;color:#ffffff}.alert-success hr{border-top-color:#358753}.alert-success .alert-link{color:#e6e6e6}.alert-info{background-color:#5bc0de;border-color:#3db5d8;color:#ffffff}.alert-info hr{border-top-color:#2aabd2}.alert-info .alert-link{color:#e6e6e6}.alert-warning{background-color:#e99002;border-color:#d08002;color:#ffffff}.alert-warning hr{border-top-color:#b67102}.alert-warning .alert-link{color:#e6e6e6}.alert-danger{background-color:#f04124;border-color:#ea2f10;color:#ffffff}.alert-danger hr{border-top-color:#d32a0e}.alert-danger .alert-link{color:#e6e6e6}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:21px;margin-bottom:21px;background-color:#f5f5f5;border-radius:0;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress-bar{float:left;width:0%;height:100%;font-size:12px;line-height:21px;color:#ffffff;text-align:center;background-color:#008cba;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease}.progress-striped .progress-bar,.progress-bar-striped{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress.active .progress-bar,.progress-bar.active{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#43ac6a}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-warning{background-color:#e99002}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.progress-bar-danger{background-color:#f04124}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{zoom:1;overflow:hidden}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-left,.media-right,.media-body{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{margin-bottom:20px;padding-left:0}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#ffffff;border:1px solid #dddddd}.list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}a.list-group-item,button.list-group-item{color:#555555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333333}a.list-group-item:hover,button.list-group-item:hover,a.list-group-item:focus,button.list-group-item:focus{text-decoration:none;color:#555555;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{background-color:#eeeeee;color:#999999;cursor:not-allowed}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text{color:#999999}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#ffffff;background-color:#008cba;border-color:#008cba}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>.small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#87e1ff}.list-group-item-success{color:#43ac6a;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#43ac6a}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,button.list-group-item-success:hover,a.list-group-item-success:focus,button.list-group-item-success:focus{color:#43ac6a;background-color:#d0e9c6}a.list-group-item-success.active,button.list-group-item-success.active,a.list-group-item-success.active:hover,button.list-group-item-success.active:hover,a.list-group-item-success.active:focus,button.list-group-item-success.active:focus{color:#fff;background-color:#43ac6a;border-color:#43ac6a}.list-group-item-info{color:#5bc0de;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#5bc0de}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,button.list-group-item-info:hover,a.list-group-item-info:focus,button.list-group-item-info:focus{color:#5bc0de;background-color:#c4e3f3}a.list-group-item-info.active,button.list-group-item-info.active,a.list-group-item-info.active:hover,button.list-group-item-info.active:hover,a.list-group-item-info.active:focus,button.list-group-item-info.active:focus{color:#fff;background-color:#5bc0de;border-color:#5bc0de}.list-group-item-warning{color:#e99002;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#e99002}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,button.list-group-item-warning:hover,a.list-group-item-warning:focus,button.list-group-item-warning:focus{color:#e99002;background-color:#faf2cc}a.list-group-item-warning.active,button.list-group-item-warning.active,a.list-group-item-warning.active:hover,button.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus,button.list-group-item-warning.active:focus{color:#fff;background-color:#e99002;border-color:#e99002}.list-group-item-danger{color:#f04124;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#f04124}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,button.list-group-item-danger:hover,a.list-group-item-danger:focus,button.list-group-item-danger:focus{color:#f04124;background-color:#ebcccc}a.list-group-item-danger.active,button.list-group-item-danger.active,a.list-group-item-danger.active:hover,button.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus,button.list-group-item-danger.active:focus{color:#fff;background-color:#f04124;border-color:#f04124}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:21px;background-color:#ffffff;border:1px solid transparent;border-radius:0;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:-1;border-top-left-radius:-1}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:17px;color:inherit}.panel-title>a,.panel-title>small,.panel-title>.small,.panel-title>small>a,.panel-title>.small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #dddddd;border-bottom-right-radius:-1;border-bottom-left-radius:-1}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-right-radius:-1;border-top-left-radius:-1}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:-1;border-bottom-left-radius:-1}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.table,.panel>.table-responsive>.table,.panel>.panel-collapse>.table{margin-bottom:0}.panel>.table caption,.panel>.table-responsive>.table caption,.panel>.panel-collapse>.table caption{padding-left:15px;padding-right:15px}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-right-radius:-1;border-top-left-radius:-1}.panel>.table:first-child>thead:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child{border-top-left-radius:-1;border-top-right-radius:-1}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:-1}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:-1}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:-1;border-bottom-left-radius:-1}.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-left-radius:-1;border-bottom-right-radius:-1}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:-1}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:-1}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #dddddd}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{border:0;margin-bottom:0}.panel-group{margin-bottom:21px}.panel-group .panel{margin-bottom:0;border-radius:0}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.panel-body,.panel-group .panel-heading+.panel-collapse>.list-group{border-top:1px solid #dddddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #dddddd}.panel-default{border-color:#dddddd}.panel-default>.panel-heading{color:#333333;background-color:#f5f5f5;border-color:#dddddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#dddddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#dddddd}.panel-primary{border-color:#008cba}.panel-primary>.panel-heading{color:#ffffff;background-color:#008cba;border-color:#008cba}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#008cba}.panel-primary>.panel-heading .badge{color:#008cba;background-color:#ffffff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#008cba}.panel-success{border-color:#3c9a5f}.panel-success>.panel-heading{color:#ffffff;background-color:#43ac6a;border-color:#3c9a5f}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#3c9a5f}.panel-success>.panel-heading .badge{color:#43ac6a;background-color:#ffffff}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#3c9a5f}.panel-info{border-color:#3db5d8}.panel-info>.panel-heading{color:#ffffff;background-color:#5bc0de;border-color:#3db5d8}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#3db5d8}.panel-info>.panel-heading .badge{color:#5bc0de;background-color:#ffffff}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#3db5d8}.panel-warning{border-color:#d08002}.panel-warning>.panel-heading{color:#ffffff;background-color:#e99002;border-color:#d08002}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d08002}.panel-warning>.panel-heading .badge{color:#e99002;background-color:#ffffff}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d08002}.panel-danger{border-color:#ea2f10}.panel-danger>.panel-heading{color:#ffffff;background-color:#f04124;border-color:#ea2f10}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ea2f10}.panel-danger>.panel-heading .badge{color:#f04124;background-color:#ffffff}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ea2f10}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive iframe,.embed-responsive embed,.embed-responsive object,.embed-responsive video{position:absolute;top:0;left:0;bottom:0;height:100%;width:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#fafafa;border:1px solid #e8e8e8;border-radius:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-lg{padding:24px;border-radius:0}.well-sm{padding:9px;border-radius:0}.close{float:right;font-size:22.5px;font-weight:bold;line-height:1;color:#ffffff;text-shadow:0 1px 0 #ffffff;opacity:0.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#ffffff;text-decoration:none;cursor:pointer;opacity:0.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{display:none;overflow:hidden;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0, -25%);-ms-transform:translate(0, -25%);-o-transform:translate(0, -25%);transform:translate(0, -25%);-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0, 0);-ms-transform:translate(0, 0);-o-transform:translate(0, 0);transform:translate(0, 0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#ffffff;border:1px solid #999999;border:1px solid rgba(0,0,0,0.2);border-radius:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,0.5);box-shadow:0 3px 9px rgba(0,0,0,0.5);-webkit-background-clip:padding-box;background-clip:padding-box;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:0.5;filter:alpha(opacity=50)}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.4}.modal-body{position:relative;padding:20px}.modal-footer{padding:20px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,0.5);box-shadow:0 5px 15px rgba(0,0,0,0.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:normal;letter-spacing:normal;line-break:auto;line-height:1.4;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;word-wrap:normal;font-size:12px;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:0.9;filter:alpha(opacity=90)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin-left:-3px;padding:0 5px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#ffffff;text-align:center;background-color:#333333;border-radius:0}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#333333}.tooltip.top-left .tooltip-arrow{bottom:0;right:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#333333}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#333333}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#333333}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#333333}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#333333}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#333333}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#333333}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:normal;letter-spacing:normal;line-break:auto;line-height:1.4;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;word-wrap:normal;font-size:15px;background-color:#333333;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #333333;border:1px solid transparent;border-radius:0;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{margin:0;padding:8px 14px;font-size:15px;background-color:#333333;border-bottom:1px solid #262626;border-radius:-1 -1 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{border-width:10px;content:""}.popover.top>.arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#000000;border-top-color:rgba(0,0,0,0.05);bottom:-11px}.popover.top>.arrow:after{content:" ";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#333333}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#000000;border-right-color:rgba(0,0,0,0.05)}.popover.right>.arrow:after{content:" ";left:1px;bottom:-10px;border-left-width:0;border-right-color:#333333}.popover.bottom>.arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#000000;border-bottom-color:rgba(0,0,0,0.05);top:-11px}.popover.bottom>.arrow:after{content:" ";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#333333}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#000000;border-left-color:rgba(0,0,0,0.05)}.popover.left>.arrow:after{content:" ";right:1px;border-right-width:0;border-left-color:#333333;bottom:-10px}.carousel{position:relative}.carousel-inner{position:relative;overflow:hidden;width:100%}.carousel-inner>.item{display:none;position:relative;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.next,.carousel-inner>.item.active.right{-webkit-transform:translate3d(100%, 0, 0);transform:translate3d(100%, 0, 0);left:0}.carousel-inner>.item.prev,.carousel-inner>.item.active.left{-webkit-transform:translate3d(-100%, 0, 0);transform:translate3d(-100%, 0, 0);left:0}.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right,.carousel-inner>.item.active{-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);left:0}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;left:0;bottom:0;width:15%;opacity:0.5;filter:alpha(opacity=50);font-size:20px;color:#ffffff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6);background-color:rgba(0,0,0,0)}.carousel-control.left{background-image:-webkit-linear-gradient(left, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);background-image:-o-linear-gradient(left, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);background-image:-webkit-gradient(linear, left top, right top, from(rgba(0,0,0,0.5)), to(rgba(0,0,0,0.0001)));background-image:linear-gradient(to right, rgba(0,0,0,0.5) 0, rgba(0,0,0,0.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1)}.carousel-control.right{left:auto;right:0;background-image:-webkit-linear-gradient(left, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);background-image:-o-linear-gradient(left, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);background-image:-webkit-gradient(linear, left top, right top, from(rgba(0,0,0,0.0001)), to(rgba(0,0,0,0.5)));background-image:linear-gradient(to right, rgba(0,0,0,0.0001) 0, rgba(0,0,0,0.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1)}.carousel-control:hover,.carousel-control:focus{outline:0;color:#ffffff;text-decoration:none;opacity:0.9;filter:alpha(opacity=90)}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;margin-top:-10px;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%;margin-left:-10px}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%;margin-right:-10px}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;line-height:1;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;margin-left:-30%;padding-left:0;list-style:none;text-align:center}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;border:1px solid #ffffff;border-radius:10px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0)}.carousel-indicators .active{margin:0;width:12px;height:12px;background-color:#ffffff}.carousel-caption{position:absolute;left:15%;right:15%;bottom:20px;z-index:10;padding-top:20px;padding-bottom:20px;color:#ffffff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,0.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{left:20%;right:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after,.dl-horizontal dd:before,.dl-horizontal dd:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after,.modal-header:before,.modal-header:after,.modal-footer:before,.modal-footer:after{content:" ";display:table}.clearfix:after,.dl-horizontal dd:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-header:after,.modal-footer:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right !important}.pull-left{float:left !important}.hide{display:none !important}.show{display:block !important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none !important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none !important}.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none !important}@media (max-width:767px){.visible-xs{display:block !important}table.visible-xs{display:table !important}tr.visible-xs{display:table-row !important}th.visible-xs,td.visible-xs{display:table-cell !important}}@media (max-width:767px){.visible-xs-block{display:block !important}}@media (max-width:767px){.visible-xs-inline{display:inline !important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block !important}table.visible-sm{display:table !important}tr.visible-sm{display:table-row !important}th.visible-sm,td.visible-sm{display:table-cell !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block !important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block !important}table.visible-md{display:table !important}tr.visible-md{display:table-row !important}th.visible-md,td.visible-md{display:table-cell !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block !important}}@media (min-width:1200px){.visible-lg{display:block !important}table.visible-lg{display:table !important}tr.visible-lg{display:table-row !important}th.visible-lg,td.visible-lg{display:table-cell !important}}@media (min-width:1200px){.visible-lg-block{display:block !important}}@media (min-width:1200px){.visible-lg-inline{display:inline !important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block !important}}@media (max-width:767px){.hidden-xs{display:none !important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none !important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none !important}}@media (min-width:1200px){.hidden-lg{display:none !important}}.visible-print{display:none !important}@media print{.visible-print{display:block !important}table.visible-print{display:table !important}tr.visible-print{display:table-row !important}th.visible-print,td.visible-print{display:table-cell !important}}.visible-print-block{display:none !important}@media print{.visible-print-block{display:block !important}}.visible-print-inline{display:none !important}@media print{.visible-print-inline{display:inline !important}}.visible-print-inline-block{display:none !important}@media print{.visible-print-inline-block{display:inline-block !important}}@media print{.hidden-print{display:none !important}}.navbar{border:none;font-size:13px;font-weight:300}.navbar .navbar-toggle:hover .icon-bar{background-color:#b3b3b3}.navbar-collapse{border-top-color:rgba(0,0,0,0.2);-webkit-box-shadow:none;box-shadow:none}.navbar .btn{padding-top:6px;padding-bottom:6px}.navbar-form{margin-top:7px;margin-bottom:5px}.navbar-form .form-control{height:auto;padding:4px 6px}.navbar .dropdown-menu{border:none}.navbar .dropdown-menu>li>a,.navbar .dropdown-menu>li>a:focus{background-color:transparent;font-size:13px;font-weight:300}.navbar .dropdown-header{color:rgba(255,255,255,0.5)}.navbar-default .dropdown-menu{background-color:#333333}.navbar-default .dropdown-menu>li>a,.navbar-default .dropdown-menu>li>a:focus{color:#ffffff}.navbar-default .dropdown-menu>li>a:hover,.navbar-default .dropdown-menu>.active>a,.navbar-default .dropdown-menu>.active>a:hover{background-color:#272727}.navbar-inverse .dropdown-menu{background-color:#008cba}.navbar-inverse .dropdown-menu>li>a,.navbar-inverse .dropdown-menu>li>a:focus{color:#ffffff}.navbar-inverse .dropdown-menu>li>a:hover,.navbar-inverse .dropdown-menu>.active>a,.navbar-inverse .dropdown-menu>.active>a:hover{background-color:#006687}.btn{padding:8px 12px}.btn-lg{padding:16px 20px}.btn-sm{padding:8px 12px}.btn-xs{padding:4px 6px}.btn-group .btn~.dropdown-toggle{padding-left:16px;padding-right:16px}.btn-group .dropdown-menu{border-top-width:0}.btn-group.dropup .dropdown-menu{border-top-width:1px;border-bottom-width:0;margin-bottom:0}.btn-group .dropdown-toggle.btn-default~.dropdown-menu{background-color:#e7e7e7;border-color:#cccccc}.btn-group .dropdown-toggle.btn-default~.dropdown-menu>li>a{color:#333333}.btn-group .dropdown-toggle.btn-default~.dropdown-menu>li>a:hover{background-color:#d3d3d3}.btn-group .dropdown-toggle.btn-primary~.dropdown-menu{background-color:#008cba;border-color:#0079a1}.btn-group .dropdown-toggle.btn-primary~.dropdown-menu>li>a{color:#ffffff}.btn-group .dropdown-toggle.btn-primary~.dropdown-menu>li>a:hover{background-color:#006d91}.btn-group .dropdown-toggle.btn-success~.dropdown-menu{background-color:#43ac6a;border-color:#3c9a5f}.btn-group .dropdown-toggle.btn-success~.dropdown-menu>li>a{color:#ffffff}.btn-group .dropdown-toggle.btn-success~.dropdown-menu>li>a:hover{background-color:#388f58}.btn-group .dropdown-toggle.btn-info~.dropdown-menu{background-color:#5bc0de;border-color:#46b8da}.btn-group .dropdown-toggle.btn-info~.dropdown-menu>li>a{color:#ffffff}.btn-group .dropdown-toggle.btn-info~.dropdown-menu>li>a:hover{background-color:#39b3d7}.btn-group .dropdown-toggle.btn-warning~.dropdown-menu{background-color:#e99002;border-color:#d08002}.btn-group .dropdown-toggle.btn-warning~.dropdown-menu>li>a{color:#ffffff}.btn-group .dropdown-toggle.btn-warning~.dropdown-menu>li>a:hover{background-color:#c17702}.btn-group .dropdown-toggle.btn-danger~.dropdown-menu{background-color:#f04124;border-color:#ea2f10}.btn-group .dropdown-toggle.btn-danger~.dropdown-menu>li>a{color:#ffffff}.btn-group .dropdown-toggle.btn-danger~.dropdown-menu>li>a:hover{background-color:#dc2c0f}.lead{color:#6f6f6f}cite{font-style:italic}blockquote{border-left-width:1px;color:#6f6f6f}blockquote.pull-right{border-right-width:1px}blockquote small{font-size:12px;font-weight:300}table{font-size:12px}label,.control-label,.help-block,.checkbox,.radio{font-size:12px;font-weight:normal}input[type="radio"],input[type="checkbox"]{margin-top:1px}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{border-color:transparent}.nav-tabs>li>a{background-color:#e7e7e7;color:#222222}.nav-tabs .caret{border-top-color:#222222;border-bottom-color:#222222}.nav-pills{font-weight:300}.breadcrumb{border:1px solid #dddddd;border-radius:3px;font-size:10px;font-weight:300;text-transform:uppercase}.pagination{font-size:12px;font-weight:300;color:#999999}.pagination>li>a,.pagination>li>span{margin-left:4px;color:#999999}.pagination>.active>a,.pagination>.active>span{color:#fff}.pagination>li>a,.pagination>li:first-child>a,.pagination>li:last-child>a,.pagination>li>span,.pagination>li:first-child>span,.pagination>li:last-child>span{border-radius:3px}.pagination-lg>li>a,.pagination-lg>li>span{padding-left:22px;padding-right:22px}.pagination-sm>li>a,.pagination-sm>li>span{padding:0 5px}.pager{font-size:12px;font-weight:300;color:#999999}.list-group{font-size:12px;font-weight:300}.close{opacity:0.4;text-decoration:none;text-shadow:none}.close:hover,.close:focus{opacity:1}.alert{font-size:12px;font-weight:300}.alert .alert-link{font-weight:normal;color:#fff;text-decoration:underline}.label{padding-left:1em;padding-right:1em;border-radius:0;font-weight:300}.label-default{background-color:#e7e7e7;color:#333333}.badge{font-weight:300}.progress{height:22px;padding:2px;background-color:#f6f6f6;border:1px solid #ccc;-webkit-box-shadow:none;box-shadow:none}.dropdown-menu{padding:0;margin-top:0;font-size:12px}.dropdown-menu>li>a{padding:12px 15px}.dropdown-header{padding-left:15px;padding-right:15px;font-size:9px;text-transform:uppercase}.popover{color:#fff;font-size:12px;font-weight:300}.panel-heading,.panel-footer{border-top-right-radius:0;border-top-left-radius:0}.panel-default .close{color:#222222}.modal .close{color:#222222} \ No newline at end of file diff --git a/examples/blog/static/css/custom.css b/examples/blog/static/css/custom.css deleted file mode 100644 index a9bb3c03b..000000000 --- a/examples/blog/static/css/custom.css +++ /dev/null @@ -1,7 +0,0 @@ -body { - margin-top: 75px; /* 100px is double the height of the navbar - I made it a big larger for some more space - keep it at 50px at least if you want to use the fixed top nav */ -} - -footer { - margin: 50px 0; -} \ No newline at end of file diff --git a/examples/blog/static/css/font-awesome.css b/examples/blog/static/css/font-awesome.css deleted file mode 100644 index b2a5fe2f2..000000000 --- a/examples/blog/static/css/font-awesome.css +++ /dev/null @@ -1,2086 +0,0 @@ -/*! - * Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome - * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */ -/* FONT PATH - * -------------------------- */ -@font-face { - font-family: 'FontAwesome'; - src: url('../fonts/fontawesome-webfont.eot?v=4.5.0'); - src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.5.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.5.0') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.5.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.5.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.5.0#fontawesomeregular') format('svg'); - font-weight: normal; - font-style: normal; -} -.fa { - display: inline-block; - font: normal normal normal 14px/1 FontAwesome; - font-size: inherit; - text-rendering: auto; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} -/* makes the font 33% larger relative to the icon container */ -.fa-lg { - font-size: 1.33333333em; - line-height: 0.75em; - vertical-align: -15%; -} -.fa-2x { - font-size: 2em; -} -.fa-3x { - font-size: 3em; -} -.fa-4x { - font-size: 4em; -} -.fa-5x { - font-size: 5em; -} -.fa-fw { - width: 1.28571429em; - text-align: center; -} -.fa-ul { - padding-left: 0; - margin-left: 2.14285714em; - list-style-type: none; -} -.fa-ul > li { - position: relative; -} -.fa-li { - position: absolute; - left: -2.14285714em; - width: 2.14285714em; - top: 0.14285714em; - text-align: center; -} -.fa-li.fa-lg { - left: -1.85714286em; -} -.fa-border { - padding: .2em .25em .15em; - border: solid 0.08em #eeeeee; - border-radius: .1em; -} -.fa-pull-left { - float: left; -} -.fa-pull-right { - float: right; -} -.fa.fa-pull-left { - margin-right: .3em; -} -.fa.fa-pull-right { - margin-left: .3em; -} -/* Deprecated as of 4.4.0 */ -.pull-right { - float: right; -} -.pull-left { - float: left; -} -.fa.pull-left { - margin-right: .3em; -} -.fa.pull-right { - margin-left: .3em; -} -.fa-spin { - -webkit-animation: fa-spin 2s infinite linear; - animation: fa-spin 2s infinite linear; -} -.fa-pulse { - -webkit-animation: fa-spin 1s infinite steps(8); - animation: fa-spin 1s infinite steps(8); -} -@-webkit-keyframes fa-spin { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(359deg); - transform: rotate(359deg); - } -} -@keyframes fa-spin { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(359deg); - transform: rotate(359deg); - } -} -.fa-rotate-90 { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); - -webkit-transform: rotate(90deg); - -ms-transform: rotate(90deg); - transform: rotate(90deg); -} -.fa-rotate-180 { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); - -webkit-transform: rotate(180deg); - -ms-transform: rotate(180deg); - transform: rotate(180deg); -} -.fa-rotate-270 { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); - -webkit-transform: rotate(270deg); - -ms-transform: rotate(270deg); - transform: rotate(270deg); -} -.fa-flip-horizontal { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1); - -webkit-transform: scale(-1, 1); - -ms-transform: scale(-1, 1); - transform: scale(-1, 1); -} -.fa-flip-vertical { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1); - -webkit-transform: scale(1, -1); - -ms-transform: scale(1, -1); - transform: scale(1, -1); -} -:root .fa-rotate-90, -:root .fa-rotate-180, -:root .fa-rotate-270, -:root .fa-flip-horizontal, -:root .fa-flip-vertical { - filter: none; -} -.fa-stack { - position: relative; - display: inline-block; - width: 2em; - height: 2em; - line-height: 2em; - vertical-align: middle; -} -.fa-stack-1x, -.fa-stack-2x { - position: absolute; - left: 0; - width: 100%; - text-align: center; -} -.fa-stack-1x { - line-height: inherit; -} -.fa-stack-2x { - font-size: 2em; -} -.fa-inverse { - color: #ffffff; -} -/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen - readers do not read off random characters that represent icons */ -.fa-glass:before { - content: "\f000"; -} -.fa-music:before { - content: "\f001"; -} -.fa-search:before { - content: "\f002"; -} -.fa-envelope-o:before { - content: "\f003"; -} -.fa-heart:before { - content: "\f004"; -} -.fa-star:before { - content: "\f005"; -} -.fa-star-o:before { - content: "\f006"; -} -.fa-user:before { - content: "\f007"; -} -.fa-film:before { - content: "\f008"; -} -.fa-th-large:before { - content: "\f009"; -} -.fa-th:before { - content: "\f00a"; -} -.fa-th-list:before { - content: "\f00b"; -} -.fa-check:before { - content: "\f00c"; -} -.fa-remove:before, -.fa-close:before, -.fa-times:before { - content: "\f00d"; -} -.fa-search-plus:before { - content: "\f00e"; -} -.fa-search-minus:before { - content: "\f010"; -} -.fa-power-off:before { - content: "\f011"; -} -.fa-signal:before { - content: "\f012"; -} -.fa-gear:before, -.fa-cog:before { - content: "\f013"; -} -.fa-trash-o:before { - content: "\f014"; -} -.fa-home:before { - content: "\f015"; -} -.fa-file-o:before { - content: "\f016"; -} -.fa-clock-o:before { - content: "\f017"; -} -.fa-road:before { - content: "\f018"; -} -.fa-download:before { - content: "\f019"; -} -.fa-arrow-circle-o-down:before { - content: "\f01a"; -} -.fa-arrow-circle-o-up:before { - content: "\f01b"; -} -.fa-inbox:before { - content: "\f01c"; -} -.fa-play-circle-o:before { - content: "\f01d"; -} -.fa-rotate-right:before, -.fa-repeat:before { - content: "\f01e"; -} -.fa-refresh:before { - content: "\f021"; -} -.fa-list-alt:before { - content: "\f022"; -} -.fa-lock:before { - content: "\f023"; -} -.fa-flag:before { - content: "\f024"; -} -.fa-headphones:before { - content: "\f025"; -} -.fa-volume-off:before { - content: "\f026"; -} -.fa-volume-down:before { - content: "\f027"; -} -.fa-volume-up:before { - content: "\f028"; -} -.fa-qrcode:before { - content: "\f029"; -} -.fa-barcode:before { - content: "\f02a"; -} -.fa-tag:before { - content: "\f02b"; -} -.fa-tags:before { - content: "\f02c"; -} -.fa-book:before { - content: "\f02d"; -} -.fa-bookmark:before { - content: "\f02e"; -} -.fa-print:before { - content: "\f02f"; -} -.fa-camera:before { - content: "\f030"; -} -.fa-font:before { - content: "\f031"; -} -.fa-bold:before { - content: "\f032"; -} -.fa-italic:before { - content: "\f033"; -} -.fa-text-height:before { - content: "\f034"; -} -.fa-text-width:before { - content: "\f035"; -} -.fa-align-left:before { - content: "\f036"; -} -.fa-align-center:before { - content: "\f037"; -} -.fa-align-right:before { - content: "\f038"; -} -.fa-align-justify:before { - content: "\f039"; -} -.fa-list:before { - content: "\f03a"; -} -.fa-dedent:before, -.fa-outdent:before { - content: "\f03b"; -} -.fa-indent:before { - content: "\f03c"; -} -.fa-video-camera:before { - content: "\f03d"; -} -.fa-photo:before, -.fa-image:before, -.fa-picture-o:before { - content: "\f03e"; -} -.fa-pencil:before { - content: "\f040"; -} -.fa-map-marker:before { - content: "\f041"; -} -.fa-adjust:before { - content: "\f042"; -} -.fa-tint:before { - content: "\f043"; -} -.fa-edit:before, -.fa-pencil-square-o:before { - content: "\f044"; -} -.fa-share-square-o:before { - content: "\f045"; -} -.fa-check-square-o:before { - content: "\f046"; -} -.fa-arrows:before { - content: "\f047"; -} -.fa-step-backward:before { - content: "\f048"; -} -.fa-fast-backward:before { - content: "\f049"; -} -.fa-backward:before { - content: "\f04a"; -} -.fa-play:before { - content: "\f04b"; -} -.fa-pause:before { - content: "\f04c"; -} -.fa-stop:before { - content: "\f04d"; -} -.fa-forward:before { - content: "\f04e"; -} -.fa-fast-forward:before { - content: "\f050"; -} -.fa-step-forward:before { - content: "\f051"; -} -.fa-eject:before { - content: "\f052"; -} -.fa-chevron-left:before { - content: "\f053"; -} -.fa-chevron-right:before { - content: "\f054"; -} -.fa-plus-circle:before { - content: "\f055"; -} -.fa-minus-circle:before { - content: "\f056"; -} -.fa-times-circle:before { - content: "\f057"; -} -.fa-check-circle:before { - content: "\f058"; -} -.fa-question-circle:before { - content: "\f059"; -} -.fa-info-circle:before { - content: "\f05a"; -} -.fa-crosshairs:before { - content: "\f05b"; -} -.fa-times-circle-o:before { - content: "\f05c"; -} -.fa-check-circle-o:before { - content: "\f05d"; -} -.fa-ban:before { - content: "\f05e"; -} -.fa-arrow-left:before { - content: "\f060"; -} -.fa-arrow-right:before { - content: "\f061"; -} -.fa-arrow-up:before { - content: "\f062"; -} -.fa-arrow-down:before { - content: "\f063"; -} -.fa-mail-forward:before, -.fa-share:before { - content: "\f064"; -} -.fa-expand:before { - content: "\f065"; -} -.fa-compress:before { - content: "\f066"; -} -.fa-plus:before { - content: "\f067"; -} -.fa-minus:before { - content: "\f068"; -} -.fa-asterisk:before { - content: "\f069"; -} -.fa-exclamation-circle:before { - content: "\f06a"; -} -.fa-gift:before { - content: "\f06b"; -} -.fa-leaf:before { - content: "\f06c"; -} -.fa-fire:before { - content: "\f06d"; -} -.fa-eye:before { - content: "\f06e"; -} -.fa-eye-slash:before { - content: "\f070"; -} -.fa-warning:before, -.fa-exclamation-triangle:before { - content: "\f071"; -} -.fa-plane:before { - content: "\f072"; -} -.fa-calendar:before { - content: "\f073"; -} -.fa-random:before { - content: "\f074"; -} -.fa-comment:before { - content: "\f075"; -} -.fa-magnet:before { - content: "\f076"; -} -.fa-chevron-up:before { - content: "\f077"; -} -.fa-chevron-down:before { - content: "\f078"; -} -.fa-retweet:before { - content: "\f079"; -} -.fa-shopping-cart:before { - content: "\f07a"; -} -.fa-folder:before { - content: "\f07b"; -} -.fa-folder-open:before { - content: "\f07c"; -} -.fa-arrows-v:before { - content: "\f07d"; -} -.fa-arrows-h:before { - content: "\f07e"; -} -.fa-bar-chart-o:before, -.fa-bar-chart:before { - content: "\f080"; -} -.fa-twitter-square:before { - content: "\f081"; -} -.fa-facebook-square:before { - content: "\f082"; -} -.fa-camera-retro:before { - content: "\f083"; -} -.fa-key:before { - content: "\f084"; -} -.fa-gears:before, -.fa-cogs:before { - content: "\f085"; -} -.fa-comments:before { - content: "\f086"; -} -.fa-thumbs-o-up:before { - content: "\f087"; -} -.fa-thumbs-o-down:before { - content: "\f088"; -} -.fa-star-half:before { - content: "\f089"; -} -.fa-heart-o:before { - content: "\f08a"; -} -.fa-sign-out:before { - content: "\f08b"; -} -.fa-linkedin-square:before { - content: "\f08c"; -} -.fa-thumb-tack:before { - content: "\f08d"; -} -.fa-external-link:before { - content: "\f08e"; -} -.fa-sign-in:before { - content: "\f090"; -} -.fa-trophy:before { - content: "\f091"; -} -.fa-github-square:before { - content: "\f092"; -} -.fa-upload:before { - content: "\f093"; -} -.fa-lemon-o:before { - content: "\f094"; -} -.fa-phone:before { - content: "\f095"; -} -.fa-square-o:before { - content: "\f096"; -} -.fa-bookmark-o:before { - content: "\f097"; -} -.fa-phone-square:before { - content: "\f098"; -} -.fa-twitter:before { - content: "\f099"; -} -.fa-facebook-f:before, -.fa-facebook:before { - content: "\f09a"; -} -.fa-github:before { - content: "\f09b"; -} -.fa-unlock:before { - content: "\f09c"; -} -.fa-credit-card:before { - content: "\f09d"; -} -.fa-feed:before, -.fa-rss:before { - content: "\f09e"; -} -.fa-hdd-o:before { - content: "\f0a0"; -} -.fa-bullhorn:before { - content: "\f0a1"; -} -.fa-bell:before { - content: "\f0f3"; -} -.fa-certificate:before { - content: "\f0a3"; -} -.fa-hand-o-right:before { - content: "\f0a4"; -} -.fa-hand-o-left:before { - content: "\f0a5"; -} -.fa-hand-o-up:before { - content: "\f0a6"; -} -.fa-hand-o-down:before { - content: "\f0a7"; -} -.fa-arrow-circle-left:before { - content: "\f0a8"; -} -.fa-arrow-circle-right:before { - content: "\f0a9"; -} -.fa-arrow-circle-up:before { - content: "\f0aa"; -} -.fa-arrow-circle-down:before { - content: "\f0ab"; -} -.fa-globe:before { - content: "\f0ac"; -} -.fa-wrench:before { - content: "\f0ad"; -} -.fa-tasks:before { - content: "\f0ae"; -} -.fa-filter:before { - content: "\f0b0"; -} -.fa-briefcase:before { - content: "\f0b1"; -} -.fa-arrows-alt:before { - content: "\f0b2"; -} -.fa-group:before, -.fa-users:before { - content: "\f0c0"; -} -.fa-chain:before, -.fa-link:before { - content: "\f0c1"; -} -.fa-cloud:before { - content: "\f0c2"; -} -.fa-flask:before { - content: "\f0c3"; -} -.fa-cut:before, -.fa-scissors:before { - content: "\f0c4"; -} -.fa-copy:before, -.fa-files-o:before { - content: "\f0c5"; -} -.fa-paperclip:before { - content: "\f0c6"; -} -.fa-save:before, -.fa-floppy-o:before { - content: "\f0c7"; -} -.fa-square:before { - content: "\f0c8"; -} -.fa-navicon:before, -.fa-reorder:before, -.fa-bars:before { - content: "\f0c9"; -} -.fa-list-ul:before { - content: "\f0ca"; -} -.fa-list-ol:before { - content: "\f0cb"; -} -.fa-strikethrough:before { - content: "\f0cc"; -} -.fa-underline:before { - content: "\f0cd"; -} -.fa-table:before { - content: "\f0ce"; -} -.fa-magic:before { - content: "\f0d0"; -} -.fa-truck:before { - content: "\f0d1"; -} -.fa-pinterest:before { - content: "\f0d2"; -} -.fa-pinterest-square:before { - content: "\f0d3"; -} -.fa-google-plus-square:before { - content: "\f0d4"; -} -.fa-google-plus:before { - content: "\f0d5"; -} -.fa-money:before { - content: "\f0d6"; -} -.fa-caret-down:before { - content: "\f0d7"; -} -.fa-caret-up:before { - content: "\f0d8"; -} -.fa-caret-left:before { - content: "\f0d9"; -} -.fa-caret-right:before { - content: "\f0da"; -} -.fa-columns:before { - content: "\f0db"; -} -.fa-unsorted:before, -.fa-sort:before { - content: "\f0dc"; -} -.fa-sort-down:before, -.fa-sort-desc:before { - content: "\f0dd"; -} -.fa-sort-up:before, -.fa-sort-asc:before { - content: "\f0de"; -} -.fa-envelope:before { - content: "\f0e0"; -} -.fa-linkedin:before { - content: "\f0e1"; -} -.fa-rotate-left:before, -.fa-undo:before { - content: "\f0e2"; -} -.fa-legal:before, -.fa-gavel:before { - content: "\f0e3"; -} -.fa-dashboard:before, -.fa-tachometer:before { - content: "\f0e4"; -} -.fa-comment-o:before { - content: "\f0e5"; -} -.fa-comments-o:before { - content: "\f0e6"; -} -.fa-flash:before, -.fa-bolt:before { - content: "\f0e7"; -} -.fa-sitemap:before { - content: "\f0e8"; -} -.fa-umbrella:before { - content: "\f0e9"; -} -.fa-paste:before, -.fa-clipboard:before { - content: "\f0ea"; -} -.fa-lightbulb-o:before { - content: "\f0eb"; -} -.fa-exchange:before { - content: "\f0ec"; -} -.fa-cloud-download:before { - content: "\f0ed"; -} -.fa-cloud-upload:before { - content: "\f0ee"; -} -.fa-user-md:before { - content: "\f0f0"; -} -.fa-stethoscope:before { - content: "\f0f1"; -} -.fa-suitcase:before { - content: "\f0f2"; -} -.fa-bell-o:before { - content: "\f0a2"; -} -.fa-coffee:before { - content: "\f0f4"; -} -.fa-cutlery:before { - content: "\f0f5"; -} -.fa-file-text-o:before { - content: "\f0f6"; -} -.fa-building-o:before { - content: "\f0f7"; -} -.fa-hospital-o:before { - content: "\f0f8"; -} -.fa-ambulance:before { - content: "\f0f9"; -} -.fa-medkit:before { - content: "\f0fa"; -} -.fa-fighter-jet:before { - content: "\f0fb"; -} -.fa-beer:before { - content: "\f0fc"; -} -.fa-h-square:before { - content: "\f0fd"; -} -.fa-plus-square:before { - content: "\f0fe"; -} -.fa-angle-double-left:before { - content: "\f100"; -} -.fa-angle-double-right:before { - content: "\f101"; -} -.fa-angle-double-up:before { - content: "\f102"; -} -.fa-angle-double-down:before { - content: "\f103"; -} -.fa-angle-left:before { - content: "\f104"; -} -.fa-angle-right:before { - content: "\f105"; -} -.fa-angle-up:before { - content: "\f106"; -} -.fa-angle-down:before { - content: "\f107"; -} -.fa-desktop:before { - content: "\f108"; -} -.fa-laptop:before { - content: "\f109"; -} -.fa-tablet:before { - content: "\f10a"; -} -.fa-mobile-phone:before, -.fa-mobile:before { - content: "\f10b"; -} -.fa-circle-o:before { - content: "\f10c"; -} -.fa-quote-left:before { - content: "\f10d"; -} -.fa-quote-right:before { - content: "\f10e"; -} -.fa-spinner:before { - content: "\f110"; -} -.fa-circle:before { - content: "\f111"; -} -.fa-mail-reply:before, -.fa-reply:before { - content: "\f112"; -} -.fa-github-alt:before { - content: "\f113"; -} -.fa-folder-o:before { - content: "\f114"; -} -.fa-folder-open-o:before { - content: "\f115"; -} -.fa-smile-o:before { - content: "\f118"; -} -.fa-frown-o:before { - content: "\f119"; -} -.fa-meh-o:before { - content: "\f11a"; -} -.fa-gamepad:before { - content: "\f11b"; -} -.fa-keyboard-o:before { - content: "\f11c"; -} -.fa-flag-o:before { - content: "\f11d"; -} -.fa-flag-checkered:before { - content: "\f11e"; -} -.fa-terminal:before { - content: "\f120"; -} -.fa-code:before { - content: "\f121"; -} -.fa-mail-reply-all:before, -.fa-reply-all:before { - content: "\f122"; -} -.fa-star-half-empty:before, -.fa-star-half-full:before, -.fa-star-half-o:before { - content: "\f123"; -} -.fa-location-arrow:before { - content: "\f124"; -} -.fa-crop:before { - content: "\f125"; -} -.fa-code-fork:before { - content: "\f126"; -} -.fa-unlink:before, -.fa-chain-broken:before { - content: "\f127"; -} -.fa-question:before { - content: "\f128"; -} -.fa-info:before { - content: "\f129"; -} -.fa-exclamation:before { - content: "\f12a"; -} -.fa-superscript:before { - content: "\f12b"; -} -.fa-subscript:before { - content: "\f12c"; -} -.fa-eraser:before { - content: "\f12d"; -} -.fa-puzzle-piece:before { - content: "\f12e"; -} -.fa-microphone:before { - content: "\f130"; -} -.fa-microphone-slash:before { - content: "\f131"; -} -.fa-shield:before { - content: "\f132"; -} -.fa-calendar-o:before { - content: "\f133"; -} -.fa-fire-extinguisher:before { - content: "\f134"; -} -.fa-rocket:before { - content: "\f135"; -} -.fa-maxcdn:before { - content: "\f136"; -} -.fa-chevron-circle-left:before { - content: "\f137"; -} -.fa-chevron-circle-right:before { - content: "\f138"; -} -.fa-chevron-circle-up:before { - content: "\f139"; -} -.fa-chevron-circle-down:before { - content: "\f13a"; -} -.fa-html5:before { - content: "\f13b"; -} -.fa-css3:before { - content: "\f13c"; -} -.fa-anchor:before { - content: "\f13d"; -} -.fa-unlock-alt:before { - content: "\f13e"; -} -.fa-bullseye:before { - content: "\f140"; -} -.fa-ellipsis-h:before { - content: "\f141"; -} -.fa-ellipsis-v:before { - content: "\f142"; -} -.fa-rss-square:before { - content: "\f143"; -} -.fa-play-circle:before { - content: "\f144"; -} -.fa-ticket:before { - content: "\f145"; -} -.fa-minus-square:before { - content: "\f146"; -} -.fa-minus-square-o:before { - content: "\f147"; -} -.fa-level-up:before { - content: "\f148"; -} -.fa-level-down:before { - content: "\f149"; -} -.fa-check-square:before { - content: "\f14a"; -} -.fa-pencil-square:before { - content: "\f14b"; -} -.fa-external-link-square:before { - content: "\f14c"; -} -.fa-share-square:before { - content: "\f14d"; -} -.fa-compass:before { - content: "\f14e"; -} -.fa-toggle-down:before, -.fa-caret-square-o-down:before { - content: "\f150"; -} -.fa-toggle-up:before, -.fa-caret-square-o-up:before { - content: "\f151"; -} -.fa-toggle-right:before, -.fa-caret-square-o-right:before { - content: "\f152"; -} -.fa-euro:before, -.fa-eur:before { - content: "\f153"; -} -.fa-gbp:before { - content: "\f154"; -} -.fa-dollar:before, -.fa-usd:before { - content: "\f155"; -} -.fa-rupee:before, -.fa-inr:before { - content: "\f156"; -} -.fa-cny:before, -.fa-rmb:before, -.fa-yen:before, -.fa-jpy:before { - content: "\f157"; -} -.fa-ruble:before, -.fa-rouble:before, -.fa-rub:before { - content: "\f158"; -} -.fa-won:before, -.fa-krw:before { - content: "\f159"; -} -.fa-bitcoin:before, -.fa-btc:before { - content: "\f15a"; -} -.fa-file:before { - content: "\f15b"; -} -.fa-file-text:before { - content: "\f15c"; -} -.fa-sort-alpha-asc:before { - content: "\f15d"; -} -.fa-sort-alpha-desc:before { - content: "\f15e"; -} -.fa-sort-amount-asc:before { - content: "\f160"; -} -.fa-sort-amount-desc:before { - content: "\f161"; -} -.fa-sort-numeric-asc:before { - content: "\f162"; -} -.fa-sort-numeric-desc:before { - content: "\f163"; -} -.fa-thumbs-up:before { - content: "\f164"; -} -.fa-thumbs-down:before { - content: "\f165"; -} -.fa-youtube-square:before { - content: "\f166"; -} -.fa-youtube:before { - content: "\f167"; -} -.fa-xing:before { - content: "\f168"; -} -.fa-xing-square:before { - content: "\f169"; -} -.fa-youtube-play:before { - content: "\f16a"; -} -.fa-dropbox:before { - content: "\f16b"; -} -.fa-stack-overflow:before { - content: "\f16c"; -} -.fa-instagram:before { - content: "\f16d"; -} -.fa-flickr:before { - content: "\f16e"; -} -.fa-adn:before { - content: "\f170"; -} -.fa-bitbucket:before { - content: "\f171"; -} -.fa-bitbucket-square:before { - content: "\f172"; -} -.fa-tumblr:before { - content: "\f173"; -} -.fa-tumblr-square:before { - content: "\f174"; -} -.fa-long-arrow-down:before { - content: "\f175"; -} -.fa-long-arrow-up:before { - content: "\f176"; -} -.fa-long-arrow-left:before { - content: "\f177"; -} -.fa-long-arrow-right:before { - content: "\f178"; -} -.fa-apple:before { - content: "\f179"; -} -.fa-windows:before { - content: "\f17a"; -} -.fa-android:before { - content: "\f17b"; -} -.fa-linux:before { - content: "\f17c"; -} -.fa-dribbble:before { - content: "\f17d"; -} -.fa-skype:before { - content: "\f17e"; -} -.fa-foursquare:before { - content: "\f180"; -} -.fa-trello:before { - content: "\f181"; -} -.fa-female:before { - content: "\f182"; -} -.fa-male:before { - content: "\f183"; -} -.fa-gittip:before, -.fa-gratipay:before { - content: "\f184"; -} -.fa-sun-o:before { - content: "\f185"; -} -.fa-moon-o:before { - content: "\f186"; -} -.fa-archive:before { - content: "\f187"; -} -.fa-bug:before { - content: "\f188"; -} -.fa-vk:before { - content: "\f189"; -} -.fa-weibo:before { - content: "\f18a"; -} -.fa-renren:before { - content: "\f18b"; -} -.fa-pagelines:before { - content: "\f18c"; -} -.fa-stack-exchange:before { - content: "\f18d"; -} -.fa-arrow-circle-o-right:before { - content: "\f18e"; -} -.fa-arrow-circle-o-left:before { - content: "\f190"; -} -.fa-toggle-left:before, -.fa-caret-square-o-left:before { - content: "\f191"; -} -.fa-dot-circle-o:before { - content: "\f192"; -} -.fa-wheelchair:before { - content: "\f193"; -} -.fa-vimeo-square:before { - content: "\f194"; -} -.fa-turkish-lira:before, -.fa-try:before { - content: "\f195"; -} -.fa-plus-square-o:before { - content: "\f196"; -} -.fa-space-shuttle:before { - content: "\f197"; -} -.fa-slack:before { - content: "\f198"; -} -.fa-envelope-square:before { - content: "\f199"; -} -.fa-wordpress:before { - content: "\f19a"; -} -.fa-openid:before { - content: "\f19b"; -} -.fa-institution:before, -.fa-bank:before, -.fa-university:before { - content: "\f19c"; -} -.fa-mortar-board:before, -.fa-graduation-cap:before { - content: "\f19d"; -} -.fa-yahoo:before { - content: "\f19e"; -} -.fa-google:before { - content: "\f1a0"; -} -.fa-reddit:before { - content: "\f1a1"; -} -.fa-reddit-square:before { - content: "\f1a2"; -} -.fa-stumbleupon-circle:before { - content: "\f1a3"; -} -.fa-stumbleupon:before { - content: "\f1a4"; -} -.fa-delicious:before { - content: "\f1a5"; -} -.fa-digg:before { - content: "\f1a6"; -} -.fa-pied-piper:before { - content: "\f1a7"; -} -.fa-pied-piper-alt:before { - content: "\f1a8"; -} -.fa-drupal:before { - content: "\f1a9"; -} -.fa-joomla:before { - content: "\f1aa"; -} -.fa-language:before { - content: "\f1ab"; -} -.fa-fax:before { - content: "\f1ac"; -} -.fa-building:before { - content: "\f1ad"; -} -.fa-child:before { - content: "\f1ae"; -} -.fa-paw:before { - content: "\f1b0"; -} -.fa-spoon:before { - content: "\f1b1"; -} -.fa-cube:before { - content: "\f1b2"; -} -.fa-cubes:before { - content: "\f1b3"; -} -.fa-behance:before { - content: "\f1b4"; -} -.fa-behance-square:before { - content: "\f1b5"; -} -.fa-steam:before { - content: "\f1b6"; -} -.fa-steam-square:before { - content: "\f1b7"; -} -.fa-recycle:before { - content: "\f1b8"; -} -.fa-automobile:before, -.fa-car:before { - content: "\f1b9"; -} -.fa-cab:before, -.fa-taxi:before { - content: "\f1ba"; -} -.fa-tree:before { - content: "\f1bb"; -} -.fa-spotify:before { - content: "\f1bc"; -} -.fa-deviantart:before { - content: "\f1bd"; -} -.fa-soundcloud:before { - content: "\f1be"; -} -.fa-database:before { - content: "\f1c0"; -} -.fa-file-pdf-o:before { - content: "\f1c1"; -} -.fa-file-word-o:before { - content: "\f1c2"; -} -.fa-file-excel-o:before { - content: "\f1c3"; -} -.fa-file-powerpoint-o:before { - content: "\f1c4"; -} -.fa-file-photo-o:before, -.fa-file-picture-o:before, -.fa-file-image-o:before { - content: "\f1c5"; -} -.fa-file-zip-o:before, -.fa-file-archive-o:before { - content: "\f1c6"; -} -.fa-file-sound-o:before, -.fa-file-audio-o:before { - content: "\f1c7"; -} -.fa-file-movie-o:before, -.fa-file-video-o:before { - content: "\f1c8"; -} -.fa-file-code-o:before { - content: "\f1c9"; -} -.fa-vine:before { - content: "\f1ca"; -} -.fa-codepen:before { - content: "\f1cb"; -} -.fa-jsfiddle:before { - content: "\f1cc"; -} -.fa-life-bouy:before, -.fa-life-buoy:before, -.fa-life-saver:before, -.fa-support:before, -.fa-life-ring:before { - content: "\f1cd"; -} -.fa-circle-o-notch:before { - content: "\f1ce"; -} -.fa-ra:before, -.fa-rebel:before { - content: "\f1d0"; -} -.fa-ge:before, -.fa-empire:before { - content: "\f1d1"; -} -.fa-git-square:before { - content: "\f1d2"; -} -.fa-git:before { - content: "\f1d3"; -} -.fa-y-combinator-square:before, -.fa-yc-square:before, -.fa-hacker-news:before { - content: "\f1d4"; -} -.fa-tencent-weibo:before { - content: "\f1d5"; -} -.fa-qq:before { - content: "\f1d6"; -} -.fa-wechat:before, -.fa-weixin:before { - content: "\f1d7"; -} -.fa-send:before, -.fa-paper-plane:before { - content: "\f1d8"; -} -.fa-send-o:before, -.fa-paper-plane-o:before { - content: "\f1d9"; -} -.fa-history:before { - content: "\f1da"; -} -.fa-circle-thin:before { - content: "\f1db"; -} -.fa-header:before { - content: "\f1dc"; -} -.fa-paragraph:before { - content: "\f1dd"; -} -.fa-sliders:before { - content: "\f1de"; -} -.fa-share-alt:before { - content: "\f1e0"; -} -.fa-share-alt-square:before { - content: "\f1e1"; -} -.fa-bomb:before { - content: "\f1e2"; -} -.fa-soccer-ball-o:before, -.fa-futbol-o:before { - content: "\f1e3"; -} -.fa-tty:before { - content: "\f1e4"; -} -.fa-binoculars:before { - content: "\f1e5"; -} -.fa-plug:before { - content: "\f1e6"; -} -.fa-slideshare:before { - content: "\f1e7"; -} -.fa-twitch:before { - content: "\f1e8"; -} -.fa-yelp:before { - content: "\f1e9"; -} -.fa-newspaper-o:before { - content: "\f1ea"; -} -.fa-wifi:before { - content: "\f1eb"; -} -.fa-calculator:before { - content: "\f1ec"; -} -.fa-paypal:before { - content: "\f1ed"; -} -.fa-google-wallet:before { - content: "\f1ee"; -} -.fa-cc-visa:before { - content: "\f1f0"; -} -.fa-cc-mastercard:before { - content: "\f1f1"; -} -.fa-cc-discover:before { - content: "\f1f2"; -} -.fa-cc-amex:before { - content: "\f1f3"; -} -.fa-cc-paypal:before { - content: "\f1f4"; -} -.fa-cc-stripe:before { - content: "\f1f5"; -} -.fa-bell-slash:before { - content: "\f1f6"; -} -.fa-bell-slash-o:before { - content: "\f1f7"; -} -.fa-trash:before { - content: "\f1f8"; -} -.fa-copyright:before { - content: "\f1f9"; -} -.fa-at:before { - content: "\f1fa"; -} -.fa-eyedropper:before { - content: "\f1fb"; -} -.fa-paint-brush:before { - content: "\f1fc"; -} -.fa-birthday-cake:before { - content: "\f1fd"; -} -.fa-area-chart:before { - content: "\f1fe"; -} -.fa-pie-chart:before { - content: "\f200"; -} -.fa-line-chart:before { - content: "\f201"; -} -.fa-lastfm:before { - content: "\f202"; -} -.fa-lastfm-square:before { - content: "\f203"; -} -.fa-toggle-off:before { - content: "\f204"; -} -.fa-toggle-on:before { - content: "\f205"; -} -.fa-bicycle:before { - content: "\f206"; -} -.fa-bus:before { - content: "\f207"; -} -.fa-ioxhost:before { - content: "\f208"; -} -.fa-angellist:before { - content: "\f209"; -} -.fa-cc:before { - content: "\f20a"; -} -.fa-shekel:before, -.fa-sheqel:before, -.fa-ils:before { - content: "\f20b"; -} -.fa-meanpath:before { - content: "\f20c"; -} -.fa-buysellads:before { - content: "\f20d"; -} -.fa-connectdevelop:before { - content: "\f20e"; -} -.fa-dashcube:before { - content: "\f210"; -} -.fa-forumbee:before { - content: "\f211"; -} -.fa-leanpub:before { - content: "\f212"; -} -.fa-sellsy:before { - content: "\f213"; -} -.fa-shirtsinbulk:before { - content: "\f214"; -} -.fa-simplybuilt:before { - content: "\f215"; -} -.fa-skyatlas:before { - content: "\f216"; -} -.fa-cart-plus:before { - content: "\f217"; -} -.fa-cart-arrow-down:before { - content: "\f218"; -} -.fa-diamond:before { - content: "\f219"; -} -.fa-ship:before { - content: "\f21a"; -} -.fa-user-secret:before { - content: "\f21b"; -} -.fa-motorcycle:before { - content: "\f21c"; -} -.fa-street-view:before { - content: "\f21d"; -} -.fa-heartbeat:before { - content: "\f21e"; -} -.fa-venus:before { - content: "\f221"; -} -.fa-mars:before { - content: "\f222"; -} -.fa-mercury:before { - content: "\f223"; -} -.fa-intersex:before, -.fa-transgender:before { - content: "\f224"; -} -.fa-transgender-alt:before { - content: "\f225"; -} -.fa-venus-double:before { - content: "\f226"; -} -.fa-mars-double:before { - content: "\f227"; -} -.fa-venus-mars:before { - content: "\f228"; -} -.fa-mars-stroke:before { - content: "\f229"; -} -.fa-mars-stroke-v:before { - content: "\f22a"; -} -.fa-mars-stroke-h:before { - content: "\f22b"; -} -.fa-neuter:before { - content: "\f22c"; -} -.fa-genderless:before { - content: "\f22d"; -} -.fa-facebook-official:before { - content: "\f230"; -} -.fa-pinterest-p:before { - content: "\f231"; -} -.fa-whatsapp:before { - content: "\f232"; -} -.fa-server:before { - content: "\f233"; -} -.fa-user-plus:before { - content: "\f234"; -} -.fa-user-times:before { - content: "\f235"; -} -.fa-hotel:before, -.fa-bed:before { - content: "\f236"; -} -.fa-viacoin:before { - content: "\f237"; -} -.fa-train:before { - content: "\f238"; -} -.fa-subway:before { - content: "\f239"; -} -.fa-medium:before { - content: "\f23a"; -} -.fa-yc:before, -.fa-y-combinator:before { - content: "\f23b"; -} -.fa-optin-monster:before { - content: "\f23c"; -} -.fa-opencart:before { - content: "\f23d"; -} -.fa-expeditedssl:before { - content: "\f23e"; -} -.fa-battery-4:before, -.fa-battery-full:before { - content: "\f240"; -} -.fa-battery-3:before, -.fa-battery-three-quarters:before { - content: "\f241"; -} -.fa-battery-2:before, -.fa-battery-half:before { - content: "\f242"; -} -.fa-battery-1:before, -.fa-battery-quarter:before { - content: "\f243"; -} -.fa-battery-0:before, -.fa-battery-empty:before { - content: "\f244"; -} -.fa-mouse-pointer:before { - content: "\f245"; -} -.fa-i-cursor:before { - content: "\f246"; -} -.fa-object-group:before { - content: "\f247"; -} -.fa-object-ungroup:before { - content: "\f248"; -} -.fa-sticky-note:before { - content: "\f249"; -} -.fa-sticky-note-o:before { - content: "\f24a"; -} -.fa-cc-jcb:before { - content: "\f24b"; -} -.fa-cc-diners-club:before { - content: "\f24c"; -} -.fa-clone:before { - content: "\f24d"; -} -.fa-balance-scale:before { - content: "\f24e"; -} -.fa-hourglass-o:before { - content: "\f250"; -} -.fa-hourglass-1:before, -.fa-hourglass-start:before { - content: "\f251"; -} -.fa-hourglass-2:before, -.fa-hourglass-half:before { - content: "\f252"; -} -.fa-hourglass-3:before, -.fa-hourglass-end:before { - content: "\f253"; -} -.fa-hourglass:before { - content: "\f254"; -} -.fa-hand-grab-o:before, -.fa-hand-rock-o:before { - content: "\f255"; -} -.fa-hand-stop-o:before, -.fa-hand-paper-o:before { - content: "\f256"; -} -.fa-hand-scissors-o:before { - content: "\f257"; -} -.fa-hand-lizard-o:before { - content: "\f258"; -} -.fa-hand-spock-o:before { - content: "\f259"; -} -.fa-hand-pointer-o:before { - content: "\f25a"; -} -.fa-hand-peace-o:before { - content: "\f25b"; -} -.fa-trademark:before { - content: "\f25c"; -} -.fa-registered:before { - content: "\f25d"; -} -.fa-creative-commons:before { - content: "\f25e"; -} -.fa-gg:before { - content: "\f260"; -} -.fa-gg-circle:before { - content: "\f261"; -} -.fa-tripadvisor:before { - content: "\f262"; -} -.fa-odnoklassniki:before { - content: "\f263"; -} -.fa-odnoklassniki-square:before { - content: "\f264"; -} -.fa-get-pocket:before { - content: "\f265"; -} -.fa-wikipedia-w:before { - content: "\f266"; -} -.fa-safari:before { - content: "\f267"; -} -.fa-chrome:before { - content: "\f268"; -} -.fa-firefox:before { - content: "\f269"; -} -.fa-opera:before { - content: "\f26a"; -} -.fa-internet-explorer:before { - content: "\f26b"; -} -.fa-tv:before, -.fa-television:before { - content: "\f26c"; -} -.fa-contao:before { - content: "\f26d"; -} -.fa-500px:before { - content: "\f26e"; -} -.fa-amazon:before { - content: "\f270"; -} -.fa-calendar-plus-o:before { - content: "\f271"; -} -.fa-calendar-minus-o:before { - content: "\f272"; -} -.fa-calendar-times-o:before { - content: "\f273"; -} -.fa-calendar-check-o:before { - content: "\f274"; -} -.fa-industry:before { - content: "\f275"; -} -.fa-map-pin:before { - content: "\f276"; -} -.fa-map-signs:before { - content: "\f277"; -} -.fa-map-o:before { - content: "\f278"; -} -.fa-map:before { - content: "\f279"; -} -.fa-commenting:before { - content: "\f27a"; -} -.fa-commenting-o:before { - content: "\f27b"; -} -.fa-houzz:before { - content: "\f27c"; -} -.fa-vimeo:before { - content: "\f27d"; -} -.fa-black-tie:before { - content: "\f27e"; -} -.fa-fonticons:before { - content: "\f280"; -} -.fa-reddit-alien:before { - content: "\f281"; -} -.fa-edge:before { - content: "\f282"; -} -.fa-credit-card-alt:before { - content: "\f283"; -} -.fa-codiepie:before { - content: "\f284"; -} -.fa-modx:before { - content: "\f285"; -} -.fa-fort-awesome:before { - content: "\f286"; -} -.fa-usb:before { - content: "\f287"; -} -.fa-product-hunt:before { - content: "\f288"; -} -.fa-mixcloud:before { - content: "\f289"; -} -.fa-scribd:before { - content: "\f28a"; -} -.fa-pause-circle:before { - content: "\f28b"; -} -.fa-pause-circle-o:before { - content: "\f28c"; -} -.fa-stop-circle:before { - content: "\f28d"; -} -.fa-stop-circle-o:before { - content: "\f28e"; -} -.fa-shopping-bag:before { - content: "\f290"; -} -.fa-shopping-basket:before { - content: "\f291"; -} -.fa-hashtag:before { - content: "\f292"; -} -.fa-bluetooth:before { - content: "\f293"; -} -.fa-bluetooth-b:before { - content: "\f294"; -} -.fa-percent:before { - content: "\f295"; -} diff --git a/examples/blog/static/js/bootstrap.js b/examples/blog/static/js/bootstrap.js deleted file mode 100644 index 01fbbcbaa..000000000 --- a/examples/blog/static/js/bootstrap.js +++ /dev/null @@ -1,2363 +0,0 @@ -/*! - * Bootstrap v3.3.6 (http://getbootstrap.com) - * Copyright 2011-2015 Twitter, Inc. - * Licensed under the MIT license - */ - -if (typeof jQuery === 'undefined') { - throw new Error('Bootstrap\'s JavaScript requires jQuery') -} - -+function ($) { - 'use strict'; - var version = $.fn.jquery.split(' ')[0].split('.') - if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] > 2)) { - throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher, but lower than version 3') - } -}(jQuery); - -/* ======================================================================== - * Bootstrap: transition.js v3.3.6 - * http://getbootstrap.com/javascript/#transitions - * ======================================================================== - * Copyright 2011-2015 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * ======================================================================== */ - - -+function ($) { - 'use strict'; - - // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/) - // ============================================================ - - function transitionEnd() { - var el = document.createElement('bootstrap') - - var transEndEventNames = { - WebkitTransition : 'webkitTransitionEnd', - MozTransition : 'transitionend', - OTransition : 'oTransitionEnd otransitionend', - transition : 'transitionend' - } - - for (var name in transEndEventNames) { - if (el.style[name] !== undefined) { - return { end: transEndEventNames[name] } - } - } - - return false // explicit for ie8 ( ._.) - } - - // http://blog.alexmaccaw.com/css-transitions - $.fn.emulateTransitionEnd = function (duration) { - var called = false - var $el = this - $(this).one('bsTransitionEnd', function () { called = true }) - var callback = function () { if (!called) $($el).trigger($.support.transition.end) } - setTimeout(callback, duration) - return this - } - - $(function () { - $.support.transition = transitionEnd() - - if (!$.support.transition) return - - $.event.special.bsTransitionEnd = { - bindType: $.support.transition.end, - delegateType: $.support.transition.end, - handle: function (e) { - if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments) - } - } - }) - -}(jQuery); - -/* ======================================================================== - * Bootstrap: alert.js v3.3.6 - * http://getbootstrap.com/javascript/#alerts - * ======================================================================== - * Copyright 2011-2015 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * ======================================================================== */ - - -+function ($) { - 'use strict'; - - // ALERT CLASS DEFINITION - // ====================== - - var dismiss = '[data-dismiss="alert"]' - var Alert = function (el) { - $(el).on('click', dismiss, this.close) - } - - Alert.VERSION = '3.3.6' - - Alert.TRANSITION_DURATION = 150 - - Alert.prototype.close = function (e) { - var $this = $(this) - var selector = $this.attr('data-target') - - if (!selector) { - selector = $this.attr('href') - selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 - } - - var $parent = $(selector) - - if (e) e.preventDefault() - - if (!$parent.length) { - $parent = $this.closest('.alert') - } - - $parent.trigger(e = $.Event('close.bs.alert')) - - if (e.isDefaultPrevented()) return - - $parent.removeClass('in') - - function removeElement() { - // detach from parent, fire event then clean up data - $parent.detach().trigger('closed.bs.alert').remove() - } - - $.support.transition && $parent.hasClass('fade') ? - $parent - .one('bsTransitionEnd', removeElement) - .emulateTransitionEnd(Alert.TRANSITION_DURATION) : - removeElement() - } - - - // ALERT PLUGIN DEFINITION - // ======================= - - function Plugin(option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.alert') - - if (!data) $this.data('bs.alert', (data = new Alert(this))) - if (typeof option == 'string') data[option].call($this) - }) - } - - var old = $.fn.alert - - $.fn.alert = Plugin - $.fn.alert.Constructor = Alert - - - // ALERT NO CONFLICT - // ================= - - $.fn.alert.noConflict = function () { - $.fn.alert = old - return this - } - - - // ALERT DATA-API - // ============== - - $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) - -}(jQuery); - -/* ======================================================================== - * Bootstrap: button.js v3.3.6 - * http://getbootstrap.com/javascript/#buttons - * ======================================================================== - * Copyright 2011-2015 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * ======================================================================== */ - - -+function ($) { - 'use strict'; - - // BUTTON PUBLIC CLASS DEFINITION - // ============================== - - var Button = function (element, options) { - this.$element = $(element) - this.options = $.extend({}, Button.DEFAULTS, options) - this.isLoading = false - } - - Button.VERSION = '3.3.6' - - Button.DEFAULTS = { - loadingText: 'loading...' - } - - Button.prototype.setState = function (state) { - var d = 'disabled' - var $el = this.$element - var val = $el.is('input') ? 'val' : 'html' - var data = $el.data() - - state += 'Text' - - if (data.resetText == null) $el.data('resetText', $el[val]()) - - // push to event loop to allow forms to submit - setTimeout($.proxy(function () { - $el[val](data[state] == null ? this.options[state] : data[state]) - - if (state == 'loadingText') { - this.isLoading = true - $el.addClass(d).attr(d, d) - } else if (this.isLoading) { - this.isLoading = false - $el.removeClass(d).removeAttr(d) - } - }, this), 0) - } - - Button.prototype.toggle = function () { - var changed = true - var $parent = this.$element.closest('[data-toggle="buttons"]') - - if ($parent.length) { - var $input = this.$element.find('input') - if ($input.prop('type') == 'radio') { - if ($input.prop('checked')) changed = false - $parent.find('.active').removeClass('active') - this.$element.addClass('active') - } else if ($input.prop('type') == 'checkbox') { - if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false - this.$element.toggleClass('active') - } - $input.prop('checked', this.$element.hasClass('active')) - if (changed) $input.trigger('change') - } else { - this.$element.attr('aria-pressed', !this.$element.hasClass('active')) - this.$element.toggleClass('active') - } - } - - - // BUTTON PLUGIN DEFINITION - // ======================== - - function Plugin(option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.button') - var options = typeof option == 'object' && option - - if (!data) $this.data('bs.button', (data = new Button(this, options))) - - if (option == 'toggle') data.toggle() - else if (option) data.setState(option) - }) - } - - var old = $.fn.button - - $.fn.button = Plugin - $.fn.button.Constructor = Button - - - // BUTTON NO CONFLICT - // ================== - - $.fn.button.noConflict = function () { - $.fn.button = old - return this - } - - - // BUTTON DATA-API - // =============== - - $(document) - .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) { - var $btn = $(e.target) - if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') - Plugin.call($btn, 'toggle') - if (!($(e.target).is('input[type="radio"]') || $(e.target).is('input[type="checkbox"]'))) e.preventDefault() - }) - .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) { - $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type)) - }) - -}(jQuery); - -/* ======================================================================== - * Bootstrap: carousel.js v3.3.6 - * http://getbootstrap.com/javascript/#carousel - * ======================================================================== - * Copyright 2011-2015 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * ======================================================================== */ - - -+function ($) { - 'use strict'; - - // CAROUSEL CLASS DEFINITION - // ========================= - - var Carousel = function (element, options) { - this.$element = $(element) - this.$indicators = this.$element.find('.carousel-indicators') - this.options = options - this.paused = null - this.sliding = null - this.interval = null - this.$active = null - this.$items = null - - this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this)) - - this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element - .on('mouseenter.bs.carousel', $.proxy(this.pause, this)) - .on('mouseleave.bs.carousel', $.proxy(this.cycle, this)) - } - - Carousel.VERSION = '3.3.6' - - Carousel.TRANSITION_DURATION = 600 - - Carousel.DEFAULTS = { - interval: 5000, - pause: 'hover', - wrap: true, - keyboard: true - } - - Carousel.prototype.keydown = function (e) { - if (/input|textarea/i.test(e.target.tagName)) return - switch (e.which) { - case 37: this.prev(); break - case 39: this.next(); break - default: return - } - - e.preventDefault() - } - - Carousel.prototype.cycle = function (e) { - e || (this.paused = false) - - this.interval && clearInterval(this.interval) - - this.options.interval - && !this.paused - && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) - - return this - } - - Carousel.prototype.getItemIndex = function (item) { - this.$items = item.parent().children('.item') - return this.$items.index(item || this.$active) - } - - Carousel.prototype.getItemForDirection = function (direction, active) { - var activeIndex = this.getItemIndex(active) - var willWrap = (direction == 'prev' && activeIndex === 0) - || (direction == 'next' && activeIndex == (this.$items.length - 1)) - if (willWrap && !this.options.wrap) return active - var delta = direction == 'prev' ? -1 : 1 - var itemIndex = (activeIndex + delta) % this.$items.length - return this.$items.eq(itemIndex) - } - - Carousel.prototype.to = function (pos) { - var that = this - var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active')) - - if (pos > (this.$items.length - 1) || pos < 0) return - - if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid" - if (activeIndex == pos) return this.pause().cycle() - - return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos)) - } - - Carousel.prototype.pause = function (e) { - e || (this.paused = true) - - if (this.$element.find('.next, .prev').length && $.support.transition) { - this.$element.trigger($.support.transition.end) - this.cycle(true) - } - - this.interval = clearInterval(this.interval) - - return this - } - - Carousel.prototype.next = function () { - if (this.sliding) return - return this.slide('next') - } - - Carousel.prototype.prev = function () { - if (this.sliding) return - return this.slide('prev') - } - - Carousel.prototype.slide = function (type, next) { - var $active = this.$element.find('.item.active') - var $next = next || this.getItemForDirection(type, $active) - var isCycling = this.interval - var direction = type == 'next' ? 'left' : 'right' - var that = this - - if ($next.hasClass('active')) return (this.sliding = false) - - var relatedTarget = $next[0] - var slideEvent = $.Event('slide.bs.carousel', { - relatedTarget: relatedTarget, - direction: direction - }) - this.$element.trigger(slideEvent) - if (slideEvent.isDefaultPrevented()) return - - this.sliding = true - - isCycling && this.pause() - - if (this.$indicators.length) { - this.$indicators.find('.active').removeClass('active') - var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)]) - $nextIndicator && $nextIndicator.addClass('active') - } - - var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid" - if ($.support.transition && this.$element.hasClass('slide')) { - $next.addClass(type) - $next[0].offsetWidth // force reflow - $active.addClass(direction) - $next.addClass(direction) - $active - .one('bsTransitionEnd', function () { - $next.removeClass([type, direction].join(' ')).addClass('active') - $active.removeClass(['active', direction].join(' ')) - that.sliding = false - setTimeout(function () { - that.$element.trigger(slidEvent) - }, 0) - }) - .emulateTransitionEnd(Carousel.TRANSITION_DURATION) - } else { - $active.removeClass('active') - $next.addClass('active') - this.sliding = false - this.$element.trigger(slidEvent) - } - - isCycling && this.cycle() - - return this - } - - - // CAROUSEL PLUGIN DEFINITION - // ========================== - - function Plugin(option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.carousel') - var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) - var action = typeof option == 'string' ? option : options.slide - - if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) - if (typeof option == 'number') data.to(option) - else if (action) data[action]() - else if (options.interval) data.pause().cycle() - }) - } - - var old = $.fn.carousel - - $.fn.carousel = Plugin - $.fn.carousel.Constructor = Carousel - - - // CAROUSEL NO CONFLICT - // ==================== - - $.fn.carousel.noConflict = function () { - $.fn.carousel = old - return this - } - - - // CAROUSEL DATA-API - // ================= - - var clickHandler = function (e) { - var href - var $this = $(this) - var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7 - if (!$target.hasClass('carousel')) return - var options = $.extend({}, $target.data(), $this.data()) - var slideIndex = $this.attr('data-slide-to') - if (slideIndex) options.interval = false - - Plugin.call($target, options) - - if (slideIndex) { - $target.data('bs.carousel').to(slideIndex) - } - - e.preventDefault() - } - - $(document) - .on('click.bs.carousel.data-api', '[data-slide]', clickHandler) - .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler) - - $(window).on('load', function () { - $('[data-ride="carousel"]').each(function () { - var $carousel = $(this) - Plugin.call($carousel, $carousel.data()) - }) - }) - -}(jQuery); - -/* ======================================================================== - * Bootstrap: collapse.js v3.3.6 - * http://getbootstrap.com/javascript/#collapse - * ======================================================================== - * Copyright 2011-2015 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * ======================================================================== */ - - -+function ($) { - 'use strict'; - - // COLLAPSE PUBLIC CLASS DEFINITION - // ================================ - - var Collapse = function (element, options) { - this.$element = $(element) - this.options = $.extend({}, Collapse.DEFAULTS, options) - this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' + - '[data-toggle="collapse"][data-target="#' + element.id + '"]') - this.transitioning = null - - if (this.options.parent) { - this.$parent = this.getParent() - } else { - this.addAriaAndCollapsedClass(this.$element, this.$trigger) - } - - if (this.options.toggle) this.toggle() - } - - Collapse.VERSION = '3.3.6' - - Collapse.TRANSITION_DURATION = 350 - - Collapse.DEFAULTS = { - toggle: true - } - - Collapse.prototype.dimension = function () { - var hasWidth = this.$element.hasClass('width') - return hasWidth ? 'width' : 'height' - } - - Collapse.prototype.show = function () { - if (this.transitioning || this.$element.hasClass('in')) return - - var activesData - var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing') - - if (actives && actives.length) { - activesData = actives.data('bs.collapse') - if (activesData && activesData.transitioning) return - } - - var startEvent = $.Event('show.bs.collapse') - this.$element.trigger(startEvent) - if (startEvent.isDefaultPrevented()) return - - if (actives && actives.length) { - Plugin.call(actives, 'hide') - activesData || actives.data('bs.collapse', null) - } - - var dimension = this.dimension() - - this.$element - .removeClass('collapse') - .addClass('collapsing')[dimension](0) - .attr('aria-expanded', true) - - this.$trigger - .removeClass('collapsed') - .attr('aria-expanded', true) - - this.transitioning = 1 - - var complete = function () { - this.$element - .removeClass('collapsing') - .addClass('collapse in')[dimension]('') - this.transitioning = 0 - this.$element - .trigger('shown.bs.collapse') - } - - if (!$.support.transition) return complete.call(this) - - var scrollSize = $.camelCase(['scroll', dimension].join('-')) - - this.$element - .one('bsTransitionEnd', $.proxy(complete, this)) - .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize]) - } - - Collapse.prototype.hide = function () { - if (this.transitioning || !this.$element.hasClass('in')) return - - var startEvent = $.Event('hide.bs.collapse') - this.$element.trigger(startEvent) - if (startEvent.isDefaultPrevented()) return - - var dimension = this.dimension() - - this.$element[dimension](this.$element[dimension]())[0].offsetHeight - - this.$element - .addClass('collapsing') - .removeClass('collapse in') - .attr('aria-expanded', false) - - this.$trigger - .addClass('collapsed') - .attr('aria-expanded', false) - - this.transitioning = 1 - - var complete = function () { - this.transitioning = 0 - this.$element - .removeClass('collapsing') - .addClass('collapse') - .trigger('hidden.bs.collapse') - } - - if (!$.support.transition) return complete.call(this) - - this.$element - [dimension](0) - .one('bsTransitionEnd', $.proxy(complete, this)) - .emulateTransitionEnd(Collapse.TRANSITION_DURATION) - } - - Collapse.prototype.toggle = function () { - this[this.$element.hasClass('in') ? 'hide' : 'show']() - } - - Collapse.prototype.getParent = function () { - return $(this.options.parent) - .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]') - .each($.proxy(function (i, element) { - var $element = $(element) - this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element) - }, this)) - .end() - } - - Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) { - var isOpen = $element.hasClass('in') - - $element.attr('aria-expanded', isOpen) - $trigger - .toggleClass('collapsed', !isOpen) - .attr('aria-expanded', isOpen) - } - - function getTargetFromTrigger($trigger) { - var href - var target = $trigger.attr('data-target') - || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7 - - return $(target) - } - - - // COLLAPSE PLUGIN DEFINITION - // ========================== - - function Plugin(option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.collapse') - var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) - - if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false - if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) - if (typeof option == 'string') data[option]() - }) - } - - var old = $.fn.collapse - - $.fn.collapse = Plugin - $.fn.collapse.Constructor = Collapse - - - // COLLAPSE NO CONFLICT - // ==================== - - $.fn.collapse.noConflict = function () { - $.fn.collapse = old - return this - } - - - // COLLAPSE DATA-API - // ================= - - $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) { - var $this = $(this) - - if (!$this.attr('data-target')) e.preventDefault() - - var $target = getTargetFromTrigger($this) - var data = $target.data('bs.collapse') - var option = data ? 'toggle' : $this.data() - - Plugin.call($target, option) - }) - -}(jQuery); - -/* ======================================================================== - * Bootstrap: dropdown.js v3.3.6 - * http://getbootstrap.com/javascript/#dropdowns - * ======================================================================== - * Copyright 2011-2015 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * ======================================================================== */ - - -+function ($) { - 'use strict'; - - // DROPDOWN CLASS DEFINITION - // ========================= - - var backdrop = '.dropdown-backdrop' - var toggle = '[data-toggle="dropdown"]' - var Dropdown = function (element) { - $(element).on('click.bs.dropdown', this.toggle) - } - - Dropdown.VERSION = '3.3.6' - - function getParent($this) { - var selector = $this.attr('data-target') - - if (!selector) { - selector = $this.attr('href') - selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 - } - - var $parent = selector && $(selector) - - return $parent && $parent.length ? $parent : $this.parent() - } - - function clearMenus(e) { - if (e && e.which === 3) return - $(backdrop).remove() - $(toggle).each(function () { - var $this = $(this) - var $parent = getParent($this) - var relatedTarget = { relatedTarget: this } - - if (!$parent.hasClass('open')) return - - if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return - - $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget)) - - if (e.isDefaultPrevented()) return - - $this.attr('aria-expanded', 'false') - $parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget)) - }) - } - - Dropdown.prototype.toggle = function (e) { - var $this = $(this) - - if ($this.is('.disabled, :disabled')) return - - var $parent = getParent($this) - var isActive = $parent.hasClass('open') - - clearMenus() - - if (!isActive) { - if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { - // if mobile we use a backdrop because click events don't delegate - $(document.createElement('div')) - .addClass('dropdown-backdrop') - .insertAfter($(this)) - .on('click', clearMenus) - } - - var relatedTarget = { relatedTarget: this } - $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget)) - - if (e.isDefaultPrevented()) return - - $this - .trigger('focus') - .attr('aria-expanded', 'true') - - $parent - .toggleClass('open') - .trigger($.Event('shown.bs.dropdown', relatedTarget)) - } - - return false - } - - Dropdown.prototype.keydown = function (e) { - if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return - - var $this = $(this) - - e.preventDefault() - e.stopPropagation() - - if ($this.is('.disabled, :disabled')) return - - var $parent = getParent($this) - var isActive = $parent.hasClass('open') - - if (!isActive && e.which != 27 || isActive && e.which == 27) { - if (e.which == 27) $parent.find(toggle).trigger('focus') - return $this.trigger('click') - } - - var desc = ' li:not(.disabled):visible a' - var $items = $parent.find('.dropdown-menu' + desc) - - if (!$items.length) return - - var index = $items.index(e.target) - - if (e.which == 38 && index > 0) index-- // up - if (e.which == 40 && index < $items.length - 1) index++ // down - if (!~index) index = 0 - - $items.eq(index).trigger('focus') - } - - - // DROPDOWN PLUGIN DEFINITION - // ========================== - - function Plugin(option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.dropdown') - - if (!data) $this.data('bs.dropdown', (data = new Dropdown(this))) - if (typeof option == 'string') data[option].call($this) - }) - } - - var old = $.fn.dropdown - - $.fn.dropdown = Plugin - $.fn.dropdown.Constructor = Dropdown - - - // DROPDOWN NO CONFLICT - // ==================== - - $.fn.dropdown.noConflict = function () { - $.fn.dropdown = old - return this - } - - - // APPLY TO STANDARD DROPDOWN ELEMENTS - // =================================== - - $(document) - .on('click.bs.dropdown.data-api', clearMenus) - .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() }) - .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle) - .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown) - .on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown) - -}(jQuery); - -/* ======================================================================== - * Bootstrap: modal.js v3.3.6 - * http://getbootstrap.com/javascript/#modals - * ======================================================================== - * Copyright 2011-2015 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * ======================================================================== */ - - -+function ($) { - 'use strict'; - - // MODAL CLASS DEFINITION - // ====================== - - var Modal = function (element, options) { - this.options = options - this.$body = $(document.body) - this.$element = $(element) - this.$dialog = this.$element.find('.modal-dialog') - this.$backdrop = null - this.isShown = null - this.originalBodyPad = null - this.scrollbarWidth = 0 - this.ignoreBackdropClick = false - - if (this.options.remote) { - this.$element - .find('.modal-content') - .load(this.options.remote, $.proxy(function () { - this.$element.trigger('loaded.bs.modal') - }, this)) - } - } - - Modal.VERSION = '3.3.6' - - Modal.TRANSITION_DURATION = 300 - Modal.BACKDROP_TRANSITION_DURATION = 150 - - Modal.DEFAULTS = { - backdrop: true, - keyboard: true, - show: true - } - - Modal.prototype.toggle = function (_relatedTarget) { - return this.isShown ? this.hide() : this.show(_relatedTarget) - } - - Modal.prototype.show = function (_relatedTarget) { - var that = this - var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget }) - - this.$element.trigger(e) - - if (this.isShown || e.isDefaultPrevented()) return - - this.isShown = true - - this.checkScrollbar() - this.setScrollbar() - this.$body.addClass('modal-open') - - this.escape() - this.resize() - - this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) - - this.$dialog.on('mousedown.dismiss.bs.modal', function () { - that.$element.one('mouseup.dismiss.bs.modal', function (e) { - if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true - }) - }) - - this.backdrop(function () { - var transition = $.support.transition && that.$element.hasClass('fade') - - if (!that.$element.parent().length) { - that.$element.appendTo(that.$body) // don't move modals dom position - } - - that.$element - .show() - .scrollTop(0) - - that.adjustDialog() - - if (transition) { - that.$element[0].offsetWidth // force reflow - } - - that.$element.addClass('in') - - that.enforceFocus() - - var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) - - transition ? - that.$dialog // wait for modal to slide in - .one('bsTransitionEnd', function () { - that.$element.trigger('focus').trigger(e) - }) - .emulateTransitionEnd(Modal.TRANSITION_DURATION) : - that.$element.trigger('focus').trigger(e) - }) - } - - Modal.prototype.hide = function (e) { - if (e) e.preventDefault() - - e = $.Event('hide.bs.modal') - - this.$element.trigger(e) - - if (!this.isShown || e.isDefaultPrevented()) return - - this.isShown = false - - this.escape() - this.resize() - - $(document).off('focusin.bs.modal') - - this.$element - .removeClass('in') - .off('click.dismiss.bs.modal') - .off('mouseup.dismiss.bs.modal') - - this.$dialog.off('mousedown.dismiss.bs.modal') - - $.support.transition && this.$element.hasClass('fade') ? - this.$element - .one('bsTransitionEnd', $.proxy(this.hideModal, this)) - .emulateTransitionEnd(Modal.TRANSITION_DURATION) : - this.hideModal() - } - - Modal.prototype.enforceFocus = function () { - $(document) - .off('focusin.bs.modal') // guard against infinite focus loop - .on('focusin.bs.modal', $.proxy(function (e) { - if (this.$element[0] !== e.target && !this.$element.has(e.target).length) { - this.$element.trigger('focus') - } - }, this)) - } - - Modal.prototype.escape = function () { - if (this.isShown && this.options.keyboard) { - this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) { - e.which == 27 && this.hide() - }, this)) - } else if (!this.isShown) { - this.$element.off('keydown.dismiss.bs.modal') - } - } - - Modal.prototype.resize = function () { - if (this.isShown) { - $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this)) - } else { - $(window).off('resize.bs.modal') - } - } - - Modal.prototype.hideModal = function () { - var that = this - this.$element.hide() - this.backdrop(function () { - that.$body.removeClass('modal-open') - that.resetAdjustments() - that.resetScrollbar() - that.$element.trigger('hidden.bs.modal') - }) - } - - Modal.prototype.removeBackdrop = function () { - this.$backdrop && this.$backdrop.remove() - this.$backdrop = null - } - - Modal.prototype.backdrop = function (callback) { - var that = this - var animate = this.$element.hasClass('fade') ? 'fade' : '' - - if (this.isShown && this.options.backdrop) { - var doAnimate = $.support.transition && animate - - this.$backdrop = $(document.createElement('div')) - .addClass('modal-backdrop ' + animate) - .appendTo(this.$body) - - this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) { - if (this.ignoreBackdropClick) { - this.ignoreBackdropClick = false - return - } - if (e.target !== e.currentTarget) return - this.options.backdrop == 'static' - ? this.$element[0].focus() - : this.hide() - }, this)) - - if (doAnimate) this.$backdrop[0].offsetWidth // force reflow - - this.$backdrop.addClass('in') - - if (!callback) return - - doAnimate ? - this.$backdrop - .one('bsTransitionEnd', callback) - .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : - callback() - - } else if (!this.isShown && this.$backdrop) { - this.$backdrop.removeClass('in') - - var callbackRemove = function () { - that.removeBackdrop() - callback && callback() - } - $.support.transition && this.$element.hasClass('fade') ? - this.$backdrop - .one('bsTransitionEnd', callbackRemove) - .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : - callbackRemove() - - } else if (callback) { - callback() - } - } - - // these following methods are used to handle overflowing modals - - Modal.prototype.handleUpdate = function () { - this.adjustDialog() - } - - Modal.prototype.adjustDialog = function () { - var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight - - this.$element.css({ - paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '', - paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : '' - }) - } - - Modal.prototype.resetAdjustments = function () { - this.$element.css({ - paddingLeft: '', - paddingRight: '' - }) - } - - Modal.prototype.checkScrollbar = function () { - var fullWindowWidth = window.innerWidth - if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8 - var documentElementRect = document.documentElement.getBoundingClientRect() - fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left) - } - this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth - this.scrollbarWidth = this.measureScrollbar() - } - - Modal.prototype.setScrollbar = function () { - var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10) - this.originalBodyPad = document.body.style.paddingRight || '' - if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth) - } - - Modal.prototype.resetScrollbar = function () { - this.$body.css('padding-right', this.originalBodyPad) - } - - Modal.prototype.measureScrollbar = function () { // thx walsh - var scrollDiv = document.createElement('div') - scrollDiv.className = 'modal-scrollbar-measure' - this.$body.append(scrollDiv) - var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth - this.$body[0].removeChild(scrollDiv) - return scrollbarWidth - } - - - // MODAL PLUGIN DEFINITION - // ======================= - - function Plugin(option, _relatedTarget) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.modal') - var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option) - - if (!data) $this.data('bs.modal', (data = new Modal(this, options))) - if (typeof option == 'string') data[option](_relatedTarget) - else if (options.show) data.show(_relatedTarget) - }) - } - - var old = $.fn.modal - - $.fn.modal = Plugin - $.fn.modal.Constructor = Modal - - - // MODAL NO CONFLICT - // ================= - - $.fn.modal.noConflict = function () { - $.fn.modal = old - return this - } - - - // MODAL DATA-API - // ============== - - $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) { - var $this = $(this) - var href = $this.attr('href') - var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7 - var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data()) - - if ($this.is('a')) e.preventDefault() - - $target.one('show.bs.modal', function (showEvent) { - if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown - $target.one('hidden.bs.modal', function () { - $this.is(':visible') && $this.trigger('focus') - }) - }) - Plugin.call($target, option, this) - }) - -}(jQuery); - -/* ======================================================================== - * Bootstrap: tooltip.js v3.3.6 - * http://getbootstrap.com/javascript/#tooltip - * Inspired by the original jQuery.tipsy by Jason Frame - * ======================================================================== - * Copyright 2011-2015 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * ======================================================================== */ - - -+function ($) { - 'use strict'; - - // TOOLTIP PUBLIC CLASS DEFINITION - // =============================== - - var Tooltip = function (element, options) { - this.type = null - this.options = null - this.enabled = null - this.timeout = null - this.hoverState = null - this.$element = null - this.inState = null - - this.init('tooltip', element, options) - } - - Tooltip.VERSION = '3.3.6' - - Tooltip.TRANSITION_DURATION = 150 - - Tooltip.DEFAULTS = { - animation: true, - placement: 'top', - selector: false, - template: '', - trigger: 'hover focus', - title: '', - delay: 0, - html: false, - container: false, - viewport: { - selector: 'body', - padding: 0 - } - } - - Tooltip.prototype.init = function (type, element, options) { - this.enabled = true - this.type = type - this.$element = $(element) - this.options = this.getOptions(options) - this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport)) - this.inState = { click: false, hover: false, focus: false } - - if (this.$element[0] instanceof document.constructor && !this.options.selector) { - throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!') - } - - var triggers = this.options.trigger.split(' ') - - for (var i = triggers.length; i--;) { - var trigger = triggers[i] - - if (trigger == 'click') { - this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this)) - } else if (trigger != 'manual') { - var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin' - var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout' - - this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this)) - this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this)) - } - } - - this.options.selector ? - (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) : - this.fixTitle() - } - - Tooltip.prototype.getDefaults = function () { - return Tooltip.DEFAULTS - } - - Tooltip.prototype.getOptions = function (options) { - options = $.extend({}, this.getDefaults(), this.$element.data(), options) - - if (options.delay && typeof options.delay == 'number') { - options.delay = { - show: options.delay, - hide: options.delay - } - } - - return options - } - - Tooltip.prototype.getDelegateOptions = function () { - var options = {} - var defaults = this.getDefaults() - - this._options && $.each(this._options, function (key, value) { - if (defaults[key] != value) options[key] = value - }) - - return options - } - - Tooltip.prototype.enter = function (obj) { - var self = obj instanceof this.constructor ? - obj : $(obj.currentTarget).data('bs.' + this.type) - - if (!self) { - self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) - $(obj.currentTarget).data('bs.' + this.type, self) - } - - if (obj instanceof $.Event) { - self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true - } - - if (self.tip().hasClass('in') || self.hoverState == 'in') { - self.hoverState = 'in' - return - } - - clearTimeout(self.timeout) - - self.hoverState = 'in' - - if (!self.options.delay || !self.options.delay.show) return self.show() - - self.timeout = setTimeout(function () { - if (self.hoverState == 'in') self.show() - }, self.options.delay.show) - } - - Tooltip.prototype.isInStateTrue = function () { - for (var key in this.inState) { - if (this.inState[key]) return true - } - - return false - } - - Tooltip.prototype.leave = function (obj) { - var self = obj instanceof this.constructor ? - obj : $(obj.currentTarget).data('bs.' + this.type) - - if (!self) { - self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) - $(obj.currentTarget).data('bs.' + this.type, self) - } - - if (obj instanceof $.Event) { - self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false - } - - if (self.isInStateTrue()) return - - clearTimeout(self.timeout) - - self.hoverState = 'out' - - if (!self.options.delay || !self.options.delay.hide) return self.hide() - - self.timeout = setTimeout(function () { - if (self.hoverState == 'out') self.hide() - }, self.options.delay.hide) - } - - Tooltip.prototype.show = function () { - var e = $.Event('show.bs.' + this.type) - - if (this.hasContent() && this.enabled) { - this.$element.trigger(e) - - var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0]) - if (e.isDefaultPrevented() || !inDom) return - var that = this - - var $tip = this.tip() - - var tipId = this.getUID(this.type) - - this.setContent() - $tip.attr('id', tipId) - this.$element.attr('aria-describedby', tipId) - - if (this.options.animation) $tip.addClass('fade') - - var placement = typeof this.options.placement == 'function' ? - this.options.placement.call(this, $tip[0], this.$element[0]) : - this.options.placement - - var autoToken = /\s?auto?\s?/i - var autoPlace = autoToken.test(placement) - if (autoPlace) placement = placement.replace(autoToken, '') || 'top' - - $tip - .detach() - .css({ top: 0, left: 0, display: 'block' }) - .addClass(placement) - .data('bs.' + this.type, this) - - this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element) - this.$element.trigger('inserted.bs.' + this.type) - - var pos = this.getPosition() - var actualWidth = $tip[0].offsetWidth - var actualHeight = $tip[0].offsetHeight - - if (autoPlace) { - var orgPlacement = placement - var viewportDim = this.getPosition(this.$viewport) - - placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' : - placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' : - placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' : - placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' : - placement - - $tip - .removeClass(orgPlacement) - .addClass(placement) - } - - var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight) - - this.applyPlacement(calculatedOffset, placement) - - var complete = function () { - var prevHoverState = that.hoverState - that.$element.trigger('shown.bs.' + that.type) - that.hoverState = null - - if (prevHoverState == 'out') that.leave(that) - } - - $.support.transition && this.$tip.hasClass('fade') ? - $tip - .one('bsTransitionEnd', complete) - .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : - complete() - } - } - - Tooltip.prototype.applyPlacement = function (offset, placement) { - var $tip = this.tip() - var width = $tip[0].offsetWidth - var height = $tip[0].offsetHeight - - // manually read margins because getBoundingClientRect includes difference - var marginTop = parseInt($tip.css('margin-top'), 10) - var marginLeft = parseInt($tip.css('margin-left'), 10) - - // we must check for NaN for ie 8/9 - if (isNaN(marginTop)) marginTop = 0 - if (isNaN(marginLeft)) marginLeft = 0 - - offset.top += marginTop - offset.left += marginLeft - - // $.fn.offset doesn't round pixel values - // so we use setOffset directly with our own function B-0 - $.offset.setOffset($tip[0], $.extend({ - using: function (props) { - $tip.css({ - top: Math.round(props.top), - left: Math.round(props.left) - }) - } - }, offset), 0) - - $tip.addClass('in') - - // check to see if placing tip in new offset caused the tip to resize itself - var actualWidth = $tip[0].offsetWidth - var actualHeight = $tip[0].offsetHeight - - if (placement == 'top' && actualHeight != height) { - offset.top = offset.top + height - actualHeight - } - - var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight) - - if (delta.left) offset.left += delta.left - else offset.top += delta.top - - var isVertical = /top|bottom/.test(placement) - var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight - var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight' - - $tip.offset(offset) - this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical) - } - - Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) { - this.arrow() - .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%') - .css(isVertical ? 'top' : 'left', '') - } - - Tooltip.prototype.setContent = function () { - var $tip = this.tip() - var title = this.getTitle() - - $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title) - $tip.removeClass('fade in top bottom left right') - } - - Tooltip.prototype.hide = function (callback) { - var that = this - var $tip = $(this.$tip) - var e = $.Event('hide.bs.' + this.type) - - function complete() { - if (that.hoverState != 'in') $tip.detach() - that.$element - .removeAttr('aria-describedby') - .trigger('hidden.bs.' + that.type) - callback && callback() - } - - this.$element.trigger(e) - - if (e.isDefaultPrevented()) return - - $tip.removeClass('in') - - $.support.transition && $tip.hasClass('fade') ? - $tip - .one('bsTransitionEnd', complete) - .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : - complete() - - this.hoverState = null - - return this - } - - Tooltip.prototype.fixTitle = function () { - var $e = this.$element - if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') { - $e.attr('data-original-title', $e.attr('title') || '').attr('title', '') - } - } - - Tooltip.prototype.hasContent = function () { - return this.getTitle() - } - - Tooltip.prototype.getPosition = function ($element) { - $element = $element || this.$element - - var el = $element[0] - var isBody = el.tagName == 'BODY' - - var elRect = el.getBoundingClientRect() - if (elRect.width == null) { - // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093 - elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top }) - } - var elOffset = isBody ? { top: 0, left: 0 } : $element.offset() - var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() } - var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null - - return $.extend({}, elRect, scroll, outerDims, elOffset) - } - - Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) { - return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } : - placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } : - placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } : - /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width } - - } - - Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) { - var delta = { top: 0, left: 0 } - if (!this.$viewport) return delta - - var viewportPadding = this.options.viewport && this.options.viewport.padding || 0 - var viewportDimensions = this.getPosition(this.$viewport) - - if (/right|left/.test(placement)) { - var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll - var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight - if (topEdgeOffset < viewportDimensions.top) { // top overflow - delta.top = viewportDimensions.top - topEdgeOffset - } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow - delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset - } - } else { - var leftEdgeOffset = pos.left - viewportPadding - var rightEdgeOffset = pos.left + viewportPadding + actualWidth - if (leftEdgeOffset < viewportDimensions.left) { // left overflow - delta.left = viewportDimensions.left - leftEdgeOffset - } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow - delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset - } - } - - return delta - } - - Tooltip.prototype.getTitle = function () { - var title - var $e = this.$element - var o = this.options - - title = $e.attr('data-original-title') - || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title) - - return title - } - - Tooltip.prototype.getUID = function (prefix) { - do prefix += ~~(Math.random() * 1000000) - while (document.getElementById(prefix)) - return prefix - } - - Tooltip.prototype.tip = function () { - if (!this.$tip) { - this.$tip = $(this.options.template) - if (this.$tip.length != 1) { - throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!') - } - } - return this.$tip - } - - Tooltip.prototype.arrow = function () { - return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')) - } - - Tooltip.prototype.enable = function () { - this.enabled = true - } - - Tooltip.prototype.disable = function () { - this.enabled = false - } - - Tooltip.prototype.toggleEnabled = function () { - this.enabled = !this.enabled - } - - Tooltip.prototype.toggle = function (e) { - var self = this - if (e) { - self = $(e.currentTarget).data('bs.' + this.type) - if (!self) { - self = new this.constructor(e.currentTarget, this.getDelegateOptions()) - $(e.currentTarget).data('bs.' + this.type, self) - } - } - - if (e) { - self.inState.click = !self.inState.click - if (self.isInStateTrue()) self.enter(self) - else self.leave(self) - } else { - self.tip().hasClass('in') ? self.leave(self) : self.enter(self) - } - } - - Tooltip.prototype.destroy = function () { - var that = this - clearTimeout(this.timeout) - this.hide(function () { - that.$element.off('.' + that.type).removeData('bs.' + that.type) - if (that.$tip) { - that.$tip.detach() - } - that.$tip = null - that.$arrow = null - that.$viewport = null - }) - } - - - // TOOLTIP PLUGIN DEFINITION - // ========================= - - function Plugin(option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.tooltip') - var options = typeof option == 'object' && option - - if (!data && /destroy|hide/.test(option)) return - if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options))) - if (typeof option == 'string') data[option]() - }) - } - - var old = $.fn.tooltip - - $.fn.tooltip = Plugin - $.fn.tooltip.Constructor = Tooltip - - - // TOOLTIP NO CONFLICT - // =================== - - $.fn.tooltip.noConflict = function () { - $.fn.tooltip = old - return this - } - -}(jQuery); - -/* ======================================================================== - * Bootstrap: popover.js v3.3.6 - * http://getbootstrap.com/javascript/#popovers - * ======================================================================== - * Copyright 2011-2015 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * ======================================================================== */ - - -+function ($) { - 'use strict'; - - // POPOVER PUBLIC CLASS DEFINITION - // =============================== - - var Popover = function (element, options) { - this.init('popover', element, options) - } - - if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js') - - Popover.VERSION = '3.3.6' - - Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, { - placement: 'right', - trigger: 'click', - content: '', - template: '' - }) - - - // NOTE: POPOVER EXTENDS tooltip.js - // ================================ - - Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype) - - Popover.prototype.constructor = Popover - - Popover.prototype.getDefaults = function () { - return Popover.DEFAULTS - } - - Popover.prototype.setContent = function () { - var $tip = this.tip() - var title = this.getTitle() - var content = this.getContent() - - $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title) - $tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events - this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text' - ](content) - - $tip.removeClass('fade top bottom left right in') - - // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do - // this manually by checking the contents. - if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide() - } - - Popover.prototype.hasContent = function () { - return this.getTitle() || this.getContent() - } - - Popover.prototype.getContent = function () { - var $e = this.$element - var o = this.options - - return $e.attr('data-content') - || (typeof o.content == 'function' ? - o.content.call($e[0]) : - o.content) - } - - Popover.prototype.arrow = function () { - return (this.$arrow = this.$arrow || this.tip().find('.arrow')) - } - - - // POPOVER PLUGIN DEFINITION - // ========================= - - function Plugin(option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.popover') - var options = typeof option == 'object' && option - - if (!data && /destroy|hide/.test(option)) return - if (!data) $this.data('bs.popover', (data = new Popover(this, options))) - if (typeof option == 'string') data[option]() - }) - } - - var old = $.fn.popover - - $.fn.popover = Plugin - $.fn.popover.Constructor = Popover - - - // POPOVER NO CONFLICT - // =================== - - $.fn.popover.noConflict = function () { - $.fn.popover = old - return this - } - -}(jQuery); - -/* ======================================================================== - * Bootstrap: scrollspy.js v3.3.6 - * http://getbootstrap.com/javascript/#scrollspy - * ======================================================================== - * Copyright 2011-2015 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * ======================================================================== */ - - -+function ($) { - 'use strict'; - - // SCROLLSPY CLASS DEFINITION - // ========================== - - function ScrollSpy(element, options) { - this.$body = $(document.body) - this.$scrollElement = $(element).is(document.body) ? $(window) : $(element) - this.options = $.extend({}, ScrollSpy.DEFAULTS, options) - this.selector = (this.options.target || '') + ' .nav li > a' - this.offsets = [] - this.targets = [] - this.activeTarget = null - this.scrollHeight = 0 - - this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this)) - this.refresh() - this.process() - } - - ScrollSpy.VERSION = '3.3.6' - - ScrollSpy.DEFAULTS = { - offset: 10 - } - - ScrollSpy.prototype.getScrollHeight = function () { - return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight) - } - - ScrollSpy.prototype.refresh = function () { - var that = this - var offsetMethod = 'offset' - var offsetBase = 0 - - this.offsets = [] - this.targets = [] - this.scrollHeight = this.getScrollHeight() - - if (!$.isWindow(this.$scrollElement[0])) { - offsetMethod = 'position' - offsetBase = this.$scrollElement.scrollTop() - } - - this.$body - .find(this.selector) - .map(function () { - var $el = $(this) - var href = $el.data('target') || $el.attr('href') - var $href = /^#./.test(href) && $(href) - - return ($href - && $href.length - && $href.is(':visible') - && [[$href[offsetMethod]().top + offsetBase, href]]) || null - }) - .sort(function (a, b) { return a[0] - b[0] }) - .each(function () { - that.offsets.push(this[0]) - that.targets.push(this[1]) - }) - } - - ScrollSpy.prototype.process = function () { - var scrollTop = this.$scrollElement.scrollTop() + this.options.offset - var scrollHeight = this.getScrollHeight() - var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height() - var offsets = this.offsets - var targets = this.targets - var activeTarget = this.activeTarget - var i - - if (this.scrollHeight != scrollHeight) { - this.refresh() - } - - if (scrollTop >= maxScroll) { - return activeTarget != (i = targets[targets.length - 1]) && this.activate(i) - } - - if (activeTarget && scrollTop < offsets[0]) { - this.activeTarget = null - return this.clear() - } - - for (i = offsets.length; i--;) { - activeTarget != targets[i] - && scrollTop >= offsets[i] - && (offsets[i + 1] === undefined || scrollTop < offsets[i + 1]) - && this.activate(targets[i]) - } - } - - ScrollSpy.prototype.activate = function (target) { - this.activeTarget = target - - this.clear() - - var selector = this.selector + - '[data-target="' + target + '"],' + - this.selector + '[href="' + target + '"]' - - var active = $(selector) - .parents('li') - .addClass('active') - - if (active.parent('.dropdown-menu').length) { - active = active - .closest('li.dropdown') - .addClass('active') - } - - active.trigger('activate.bs.scrollspy') - } - - ScrollSpy.prototype.clear = function () { - $(this.selector) - .parentsUntil(this.options.target, '.active') - .removeClass('active') - } - - - // SCROLLSPY PLUGIN DEFINITION - // =========================== - - function Plugin(option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.scrollspy') - var options = typeof option == 'object' && option - - if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options))) - if (typeof option == 'string') data[option]() - }) - } - - var old = $.fn.scrollspy - - $.fn.scrollspy = Plugin - $.fn.scrollspy.Constructor = ScrollSpy - - - // SCROLLSPY NO CONFLICT - // ===================== - - $.fn.scrollspy.noConflict = function () { - $.fn.scrollspy = old - return this - } - - - // SCROLLSPY DATA-API - // ================== - - $(window).on('load.bs.scrollspy.data-api', function () { - $('[data-spy="scroll"]').each(function () { - var $spy = $(this) - Plugin.call($spy, $spy.data()) - }) - }) - -}(jQuery); - -/* ======================================================================== - * Bootstrap: tab.js v3.3.6 - * http://getbootstrap.com/javascript/#tabs - * ======================================================================== - * Copyright 2011-2015 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * ======================================================================== */ - - -+function ($) { - 'use strict'; - - // TAB CLASS DEFINITION - // ==================== - - var Tab = function (element) { - // jscs:disable requireDollarBeforejQueryAssignment - this.element = $(element) - // jscs:enable requireDollarBeforejQueryAssignment - } - - Tab.VERSION = '3.3.6' - - Tab.TRANSITION_DURATION = 150 - - Tab.prototype.show = function () { - var $this = this.element - var $ul = $this.closest('ul:not(.dropdown-menu)') - var selector = $this.data('target') - - if (!selector) { - selector = $this.attr('href') - selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 - } - - if ($this.parent('li').hasClass('active')) return - - var $previous = $ul.find('.active:last a') - var hideEvent = $.Event('hide.bs.tab', { - relatedTarget: $this[0] - }) - var showEvent = $.Event('show.bs.tab', { - relatedTarget: $previous[0] - }) - - $previous.trigger(hideEvent) - $this.trigger(showEvent) - - if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return - - var $target = $(selector) - - this.activate($this.closest('li'), $ul) - this.activate($target, $target.parent(), function () { - $previous.trigger({ - type: 'hidden.bs.tab', - relatedTarget: $this[0] - }) - $this.trigger({ - type: 'shown.bs.tab', - relatedTarget: $previous[0] - }) - }) - } - - Tab.prototype.activate = function (element, container, callback) { - var $active = container.find('> .active') - var transition = callback - && $.support.transition - && ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length) - - function next() { - $active - .removeClass('active') - .find('> .dropdown-menu > .active') - .removeClass('active') - .end() - .find('[data-toggle="tab"]') - .attr('aria-expanded', false) - - element - .addClass('active') - .find('[data-toggle="tab"]') - .attr('aria-expanded', true) - - if (transition) { - element[0].offsetWidth // reflow for transition - element.addClass('in') - } else { - element.removeClass('fade') - } - - if (element.parent('.dropdown-menu').length) { - element - .closest('li.dropdown') - .addClass('active') - .end() - .find('[data-toggle="tab"]') - .attr('aria-expanded', true) - } - - callback && callback() - } - - $active.length && transition ? - $active - .one('bsTransitionEnd', next) - .emulateTransitionEnd(Tab.TRANSITION_DURATION) : - next() - - $active.removeClass('in') - } - - - // TAB PLUGIN DEFINITION - // ===================== - - function Plugin(option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.tab') - - if (!data) $this.data('bs.tab', (data = new Tab(this))) - if (typeof option == 'string') data[option]() - }) - } - - var old = $.fn.tab - - $.fn.tab = Plugin - $.fn.tab.Constructor = Tab - - - // TAB NO CONFLICT - // =============== - - $.fn.tab.noConflict = function () { - $.fn.tab = old - return this - } - - - // TAB DATA-API - // ============ - - var clickHandler = function (e) { - e.preventDefault() - Plugin.call($(this), 'show') - } - - $(document) - .on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler) - .on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler) - -}(jQuery); - -/* ======================================================================== - * Bootstrap: affix.js v3.3.6 - * http://getbootstrap.com/javascript/#affix - * ======================================================================== - * Copyright 2011-2015 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - * ======================================================================== */ - - -+function ($) { - 'use strict'; - - // AFFIX CLASS DEFINITION - // ====================== - - var Affix = function (element, options) { - this.options = $.extend({}, Affix.DEFAULTS, options) - - this.$target = $(this.options.target) - .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this)) - .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this)) - - this.$element = $(element) - this.affixed = null - this.unpin = null - this.pinnedOffset = null - - this.checkPosition() - } - - Affix.VERSION = '3.3.6' - - Affix.RESET = 'affix affix-top affix-bottom' - - Affix.DEFAULTS = { - offset: 0, - target: window - } - - Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) { - var scrollTop = this.$target.scrollTop() - var position = this.$element.offset() - var targetHeight = this.$target.height() - - if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false - - if (this.affixed == 'bottom') { - if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom' - return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom' - } - - var initializing = this.affixed == null - var colliderTop = initializing ? scrollTop : position.top - var colliderHeight = initializing ? targetHeight : height - - if (offsetTop != null && scrollTop <= offsetTop) return 'top' - if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom' - - return false - } - - Affix.prototype.getPinnedOffset = function () { - if (this.pinnedOffset) return this.pinnedOffset - this.$element.removeClass(Affix.RESET).addClass('affix') - var scrollTop = this.$target.scrollTop() - var position = this.$element.offset() - return (this.pinnedOffset = position.top - scrollTop) - } - - Affix.prototype.checkPositionWithEventLoop = function () { - setTimeout($.proxy(this.checkPosition, this), 1) - } - - Affix.prototype.checkPosition = function () { - if (!this.$element.is(':visible')) return - - var height = this.$element.height() - var offset = this.options.offset - var offsetTop = offset.top - var offsetBottom = offset.bottom - var scrollHeight = Math.max($(document).height(), $(document.body).height()) - - if (typeof offset != 'object') offsetBottom = offsetTop = offset - if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element) - if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element) - - var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom) - - if (this.affixed != affix) { - if (this.unpin != null) this.$element.css('top', '') - - var affixType = 'affix' + (affix ? '-' + affix : '') - var e = $.Event(affixType + '.bs.affix') - - this.$element.trigger(e) - - if (e.isDefaultPrevented()) return - - this.affixed = affix - this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null - - this.$element - .removeClass(Affix.RESET) - .addClass(affixType) - .trigger(affixType.replace('affix', 'affixed') + '.bs.affix') - } - - if (affix == 'bottom') { - this.$element.offset({ - top: scrollHeight - height - offsetBottom - }) - } - } - - - // AFFIX PLUGIN DEFINITION - // ======================= - - function Plugin(option) { - return this.each(function () { - var $this = $(this) - var data = $this.data('bs.affix') - var options = typeof option == 'object' && option - - if (!data) $this.data('bs.affix', (data = new Affix(this, options))) - if (typeof option == 'string') data[option]() - }) - } - - var old = $.fn.affix - - $.fn.affix = Plugin - $.fn.affix.Constructor = Affix - - - // AFFIX NO CONFLICT - // ================= - - $.fn.affix.noConflict = function () { - $.fn.affix = old - return this - } - - - // AFFIX DATA-API - // ============== - - $(window).on('load', function () { - $('[data-spy="affix"]').each(function () { - var $spy = $(this) - var data = $spy.data() - - data.offset = data.offset || {} - - if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom - if (data.offsetTop != null) data.offset.top = data.offsetTop - - Plugin.call($spy, data) - }) - }) - -}(jQuery); diff --git a/examples/blog/static/js/jquery-1.11.3.min.js b/examples/blog/static/js/jquery-1.11.3.min.js deleted file mode 100644 index 0f60b7bd0..000000000 --- a/examples/blog/static/js/jquery-1.11.3.min.js +++ /dev/null @@ -1,5 +0,0 @@ -/*! jQuery v1.11.3 | (c) 2005, 2015 jQuery Foundation, Inc. | jquery.org/license */ -!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l="1.11.3",m=function(a,b){return new m.fn.init(a,b)},n=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,o=/^-ms-/,p=/-([\da-z])/gi,q=function(a,b){return b.toUpperCase()};m.fn=m.prototype={jquery:l,constructor:m,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=m.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return m.each(this,a,b)},map:function(a){return this.pushStack(m.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},m.extend=m.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||m.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(m.isPlainObject(c)||(b=m.isArray(c)))?(b?(b=!1,f=a&&m.isArray(a)?a:[]):f=a&&m.isPlainObject(a)?a:{},g[d]=m.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},m.extend({expando:"jQuery"+(l+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===m.type(a)},isArray:Array.isArray||function(a){return"array"===m.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return!m.isArray(a)&&a-parseFloat(a)+1>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==m.type(a)||a.nodeType||m.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(k.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&m.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(o,"ms-").replace(p,q)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=r(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(n,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(r(Object(a))?m.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=r(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),m.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||m.guid++,e):void 0},now:function(){return+new Date},support:k}),m.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function r(a){var b="length"in a&&a.length,c=m.type(a);return"function"===c||m.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var s=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,aa=/[+~]/,ba=/'|\\/g,ca=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),da=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ea=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fa){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(ba,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+ra(o[l]);w=aa.test(a)&&pa(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",ea,!1):e.attachEvent&&e.attachEvent("onunload",ea)),p=!f(g),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?la(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ca,da),a[3]=(a[3]||a[4]||a[5]||"").replace(ca,da),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ca,da).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(ca,da),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return W.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(ca,da).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:oa(function(){return[0]}),last:oa(function(a,b){return[b-1]}),eq:oa(function(a,b,c){return[0>c?c+b:c]}),even:oa(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:oa(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:oa(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:oa(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function sa(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function ta(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ua(a,b,c){for(var d=0,e=b.length;e>d;d++)ga(a,b[d],c);return c}function va(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wa(a,b,c,d,e,f){return d&&!d[u]&&(d=wa(d)),e&&!e[u]&&(e=wa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ua(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:va(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=va(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=va(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sa(function(a){return a===b},h,!0),l=sa(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sa(ta(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wa(i>1&&ta(m),i>1&&ra(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xa(a.slice(i,e)),f>e&&xa(a=a.slice(e)),f>e&&ra(a))}m.push(c)}return ta(m)}function ya(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=va(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&ga.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,ya(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ca,da),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ca,da),aa.test(j[0].type)&&pa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&ra(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,aa.test(a)&&pa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);m.find=s,m.expr=s.selectors,m.expr[":"]=m.expr.pseudos,m.unique=s.uniqueSort,m.text=s.getText,m.isXMLDoc=s.isXML,m.contains=s.contains;var t=m.expr.match.needsContext,u=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,v=/^.[^:#\[\.,]*$/;function w(a,b,c){if(m.isFunction(b))return m.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return m.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(v.test(b))return m.filter(b,a,c);b=m.filter(b,a)}return m.grep(a,function(a){return m.inArray(a,b)>=0!==c})}m.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?m.find.matchesSelector(d,a)?[d]:[]:m.find.matches(a,m.grep(b,function(a){return 1===a.nodeType}))},m.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(m(a).filter(function(){for(b=0;e>b;b++)if(m.contains(d[b],this))return!0}));for(b=0;e>b;b++)m.find(a,d[b],c);return c=this.pushStack(e>1?m.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(w(this,a||[],!1))},not:function(a){return this.pushStack(w(this,a||[],!0))},is:function(a){return!!w(this,"string"==typeof a&&t.test(a)?m(a):a||[],!1).length}});var x,y=a.document,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=m.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||x).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof m?b[0]:b,m.merge(this,m.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:y,!0)),u.test(c[1])&&m.isPlainObject(b))for(c in b)m.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=y.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return x.find(a);this.length=1,this[0]=d}return this.context=y,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):m.isFunction(a)?"undefined"!=typeof x.ready?x.ready(a):a(m):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),m.makeArray(a,this))};A.prototype=m.fn,x=m(y);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};m.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!m(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),m.fn.extend({has:function(a){var b,c=m(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(m.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=t.test(a)||"string"!=typeof a?m(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&m.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?m.unique(f):f)},index:function(a){return a?"string"==typeof a?m.inArray(this[0],m(a)):m.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(m.unique(m.merge(this.get(),m(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}m.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return m.dir(a,"parentNode")},parentsUntil:function(a,b,c){return m.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return m.dir(a,"nextSibling")},prevAll:function(a){return m.dir(a,"previousSibling")},nextUntil:function(a,b,c){return m.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return m.dir(a,"previousSibling",c)},siblings:function(a){return m.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return m.sibling(a.firstChild)},contents:function(a){return m.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:m.merge([],a.childNodes)}},function(a,b){m.fn[a]=function(c,d){var e=m.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=m.filter(d,e)),this.length>1&&(C[a]||(e=m.unique(e)),B.test(a)&&(e=e.reverse())),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return m.each(a.match(E)||[],function(a,c){b[c]=!0}),b}m.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):m.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){m.each(b,function(b,c){var d=m.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&m.each(arguments,function(a,c){var d;while((d=m.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?m.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},m.extend({Deferred:function(a){var b=[["resolve","done",m.Callbacks("once memory"),"resolved"],["reject","fail",m.Callbacks("once memory"),"rejected"],["notify","progress",m.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return m.Deferred(function(c){m.each(b,function(b,f){var g=m.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&m.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?m.extend(a,d):d}},e={};return d.pipe=d.then,m.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&m.isFunction(a.promise)?e:0,g=1===f?a:m.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&m.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;m.fn.ready=function(a){return m.ready.promise().done(a),this},m.extend({isReady:!1,readyWait:1,holdReady:function(a){a?m.readyWait++:m.ready(!0)},ready:function(a){if(a===!0?!--m.readyWait:!m.isReady){if(!y.body)return setTimeout(m.ready);m.isReady=!0,a!==!0&&--m.readyWait>0||(H.resolveWith(y,[m]),m.fn.triggerHandler&&(m(y).triggerHandler("ready"),m(y).off("ready")))}}});function I(){y.addEventListener?(y.removeEventListener("DOMContentLoaded",J,!1),a.removeEventListener("load",J,!1)):(y.detachEvent("onreadystatechange",J),a.detachEvent("onload",J))}function J(){(y.addEventListener||"load"===event.type||"complete"===y.readyState)&&(I(),m.ready())}m.ready.promise=function(b){if(!H)if(H=m.Deferred(),"complete"===y.readyState)setTimeout(m.ready);else if(y.addEventListener)y.addEventListener("DOMContentLoaded",J,!1),a.addEventListener("load",J,!1);else{y.attachEvent("onreadystatechange",J),a.attachEvent("onload",J);var c=!1;try{c=null==a.frameElement&&y.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!m.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}I(),m.ready()}}()}return H.promise(b)};var K="undefined",L;for(L in m(k))break;k.ownLast="0"!==L,k.inlineBlockNeedsLayout=!1,m(function(){var a,b,c,d;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",k.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(d))}),function(){var a=y.createElement("div");if(null==k.deleteExpando){k.deleteExpando=!0;try{delete a.test}catch(b){k.deleteExpando=!1}}a=null}(),m.acceptData=function(a){var b=m.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var M=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,N=/([A-Z])/g;function O(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(N,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:M.test(c)?m.parseJSON(c):c}catch(e){}m.data(a,b,c)}else c=void 0}return c}function P(a){var b;for(b in a)if(("data"!==b||!m.isEmptyObject(a[b]))&&"toJSON"!==b)return!1; - -return!0}function Q(a,b,d,e){if(m.acceptData(a)){var f,g,h=m.expando,i=a.nodeType,j=i?m.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=m.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML="
    a",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML="",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function aa(){return!0}function ba(){return!1}function ca(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=m.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(E)||[""],h=b.length;while(h--)f=_.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=m.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=m.event.special[o]||{},l=m.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&m.expr.match.needsContext.test(e),namespace:p.join(".")},i),(n=g[o])||(n=g[o]=[],n.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?n.splice(n.delegateCount++,0,l):n.push(l),m.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m.hasData(a)&&m._data(a);if(r&&(k=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=_.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=m.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,n=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=n.length;while(f--)g=n[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(n.splice(f,1),g.selector&&n.delegateCount--,l.remove&&l.remove.call(a,g));i&&!n.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||m.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)m.event.remove(a,o+b[j],c,d,!0);m.isEmptyObject(k)&&(delete r.handle,m._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,o=[d||y],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||y,3!==d.nodeType&&8!==d.nodeType&&!$.test(p+m.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[m.expando]?b:new m.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:m.makeArray(c,[b]),k=m.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!m.isWindow(d)){for(i=k.delegateType||p,$.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||y)&&o.push(l.defaultView||l.parentWindow||a)}n=0;while((h=o[n++])&&!b.isPropagationStopped())b.type=n>1?i:k.bindType||p,f=(m._data(h,"events")||{})[b.type]&&m._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&m.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&m.acceptData(d)&&g&&d[p]&&!m.isWindow(d)){l=d[g],l&&(d[g]=null),m.event.triggered=p;try{d[p]()}catch(r){}m.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=m.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(m._data(this,"events")||{})[a.type]||[],k=m.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=m.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((m.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?m(c,this).index(i)>=0:m.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h]","i"),ha=/^\s+/,ia=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,ja=/<([\w:]+)/,ka=/\s*$/g,ra={option:[1,""],legend:[1,"
    ","
    "],area:[1,"",""],param:[1,"",""],thead:[1,"","
    "],tr:[2,"","
    "],col:[2,"","
    "],td:[3,"","
    "],_default:k.htmlSerialize?[0,"",""]:[1,"X
    ","
    "]},sa=da(y),ta=sa.appendChild(y.createElement("div"));ra.optgroup=ra.option,ra.tbody=ra.tfoot=ra.colgroup=ra.caption=ra.thead,ra.th=ra.td;function ua(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==K?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==K?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||m.nodeName(d,b)?f.push(d):m.merge(f,ua(d,b));return void 0===b||b&&m.nodeName(a,b)?m.merge([a],f):f}function va(a){W.test(a.type)&&(a.defaultChecked=a.checked)}function wa(a,b){return m.nodeName(a,"table")&&m.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function xa(a){return a.type=(null!==m.find.attr(a,"type"))+"/"+a.type,a}function ya(a){var b=pa.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function za(a,b){for(var c,d=0;null!=(c=a[d]);d++)m._data(c,"globalEval",!b||m._data(b[d],"globalEval"))}function Aa(a,b){if(1===b.nodeType&&m.hasData(a)){var c,d,e,f=m._data(a),g=m._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)m.event.add(b,c,h[c][d])}g.data&&(g.data=m.extend({},g.data))}}function Ba(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!k.noCloneEvent&&b[m.expando]){e=m._data(b);for(d in e.events)m.removeEvent(b,d,e.handle);b.removeAttribute(m.expando)}"script"===c&&b.text!==a.text?(xa(b).text=a.text,ya(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),k.html5Clone&&a.innerHTML&&!m.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&W.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}m.extend({clone:function(a,b,c){var d,e,f,g,h,i=m.contains(a.ownerDocument,a);if(k.html5Clone||m.isXMLDoc(a)||!ga.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(ta.innerHTML=a.outerHTML,ta.removeChild(f=ta.firstChild)),!(k.noCloneEvent&&k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||m.isXMLDoc(a)))for(d=ua(f),h=ua(a),g=0;null!=(e=h[g]);++g)d[g]&&Ba(e,d[g]);if(b)if(c)for(h=h||ua(a),d=d||ua(f),g=0;null!=(e=h[g]);g++)Aa(e,d[g]);else Aa(a,f);return d=ua(f,"script"),d.length>0&&za(d,!i&&ua(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,l,n=a.length,o=da(b),p=[],q=0;n>q;q++)if(f=a[q],f||0===f)if("object"===m.type(f))m.merge(p,f.nodeType?[f]:f);else if(la.test(f)){h=h||o.appendChild(b.createElement("div")),i=(ja.exec(f)||["",""])[1].toLowerCase(),l=ra[i]||ra._default,h.innerHTML=l[1]+f.replace(ia,"<$1>")+l[2],e=l[0];while(e--)h=h.lastChild;if(!k.leadingWhitespace&&ha.test(f)&&p.push(b.createTextNode(ha.exec(f)[0])),!k.tbody){f="table"!==i||ka.test(f)?""!==l[1]||ka.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)m.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}m.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),k.appendChecked||m.grep(ua(p,"input"),va),q=0;while(f=p[q++])if((!d||-1===m.inArray(f,d))&&(g=m.contains(f.ownerDocument,f),h=ua(o.appendChild(f),"script"),g&&za(h),c)){e=0;while(f=h[e++])oa.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=m.expando,j=m.cache,l=k.deleteExpando,n=m.event.special;null!=(d=a[h]);h++)if((b||m.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)n[e]?m.event.remove(d,e):m.removeEvent(d,e,g.handle);j[f]&&(delete j[f],l?delete d[i]:typeof d.removeAttribute!==K?d.removeAttribute(i):d[i]=null,c.push(f))}}}),m.fn.extend({text:function(a){return V(this,function(a){return void 0===a?m.text(this):this.empty().append((this[0]&&this[0].ownerDocument||y).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wa(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wa(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?m.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||m.cleanData(ua(c)),c.parentNode&&(b&&m.contains(c.ownerDocument,c)&&za(ua(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&m.cleanData(ua(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&m.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return m.clone(this,a,b)})},html:function(a){return V(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(fa,""):void 0;if(!("string"!=typeof a||ma.test(a)||!k.htmlSerialize&&ga.test(a)||!k.leadingWhitespace&&ha.test(a)||ra[(ja.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(ia,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(m.cleanData(ua(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,m.cleanData(ua(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,n=this,o=l-1,p=a[0],q=m.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&na.test(p))return this.each(function(c){var d=n.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(i=m.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=m.map(ua(i,"script"),xa),f=g.length;l>j;j++)d=i,j!==o&&(d=m.clone(d,!0,!0),f&&m.merge(g,ua(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,m.map(g,ya),j=0;f>j;j++)d=g[j],oa.test(d.type||"")&&!m._data(d,"globalEval")&&m.contains(h,d)&&(d.src?m._evalUrl&&m._evalUrl(d.src):m.globalEval((d.text||d.textContent||d.innerHTML||"").replace(qa,"")));i=c=null}return this}}),m.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){m.fn[a]=function(a){for(var c,d=0,e=[],g=m(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),m(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Ca,Da={};function Ea(b,c){var d,e=m(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:m.css(e[0],"display");return e.detach(),f}function Fa(a){var b=y,c=Da[a];return c||(c=Ea(a,b),"none"!==c&&c||(Ca=(Ca||m(".*?\n", - }, - // set class - { - `{{< youtube w7Ft2ymGmfc video>}}`, - "(?s)\n
    .*?.*?
    \n", - }, - // set class and autoplay (using named params) - { - `{{< youtube id="w7Ft2ymGmfc" class="video" autoplay="true" >}}`, - "(?s)\n
    .*?.*?
    ", - }, - } { - var ( - cfg, fs = newTestCfg() - th = testHelper{cfg, fs, t} - ) - - writeSource(t, fs, filepath.Join("content", "simple.md"), fmt.Sprintf(`--- -title: Shorty ---- -%s`, this.in)) - writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .Content }}`) - - buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{}) - - th.assertFileContentRegexp(filepath.Join("public", "simple", "index.html"), this.expected) - } - -} - -func TestShortcodeVimeo(t *testing.T) { - t.Parallel() - - for _, this := range []struct { - in, expected string - }{ - { - `{{< vimeo 146022717 >}}`, - "(?s)\n
    .*?.*?
    \n", - }, - // set class - { - `{{< vimeo 146022717 video >}}`, - "(?s)\n
    .*?.*?
    \n", - }, - // set class (using named params) - { - `{{< vimeo id="146022717" class="video" >}}`, - "(?s)^
    .*?.*?
    ", - }, - } { - var ( - cfg, fs = newTestCfg() - th = testHelper{cfg, fs, t} - ) - - writeSource(t, fs, filepath.Join("content", "simple.md"), fmt.Sprintf(`--- -title: Shorty ---- -%s`, this.in)) - writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .Content }}`) - - buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{}) - - th.assertFileContentRegexp(filepath.Join("public", "simple", "index.html"), this.expected) - - } -} - -func TestShortcodeGist(t *testing.T) { - t.Parallel() - - for _, this := range []struct { - in, expected string - }{ - { - `{{< gist spf13 7896402 >}}`, - "(?s)^", - }, - { - `{{< gist spf13 7896402 "img.html" >}}`, - "(?s)^", - }, - } { - var ( - cfg, fs = newTestCfg() - th = testHelper{cfg, fs, t} - ) - - writeSource(t, fs, filepath.Join("content", "simple.md"), fmt.Sprintf(`--- -title: Shorty ---- -%s`, this.in)) - writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .Content }}`) - - buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{}) - - th.assertFileContentRegexp(filepath.Join("public", "simple", "index.html"), this.expected) - - } -} - -func TestShortcodeTweet(t *testing.T) { - t.Parallel() - - for i, this := range []struct { - in, resp, expected string - }{ - { - `{{< tweet 666616452582129664 >}}`, - `{"url":"https:\/\/twitter.com\/spf13\/status\/666616452582129664","author_name":"Steve Francia","author_url":"https:\/\/twitter.com\/spf13","html":"\u003Cblockquote class=\"twitter-tweet\"\u003E\u003Cp lang=\"en\" dir=\"ltr\"\u003EHugo 0.15 will have 30%+ faster render times thanks to this commit \u003Ca href=\"https:\/\/t.co\/FfzhM8bNhT\"\u003Ehttps:\/\/t.co\/FfzhM8bNhT\u003C\/a\u003E \u003Ca href=\"https:\/\/twitter.com\/hashtag\/gohugo?src=hash\"\u003E#gohugo\u003C\/a\u003E \u003Ca href=\"https:\/\/twitter.com\/hashtag\/golang?src=hash\"\u003E#golang\u003C\/a\u003E \u003Ca href=\"https:\/\/t.co\/ITbMNU2BUf\"\u003Ehttps:\/\/t.co\/ITbMNU2BUf\u003C\/a\u003E\u003C\/p\u003E— Steve Francia (@spf13) \u003Ca href=\"https:\/\/twitter.com\/spf13\/status\/666616452582129664\"\u003ENovember 17, 2015\u003C\/a\u003E\u003C\/blockquote\u003E\n\u003Cscript async src=\"\/\/platform.twitter.com\/widgets.js\" charset=\"utf-8\"\u003E\u003C\/script\u003E","width":550,"height":null,"type":"rich","cache_age":"3153600000","provider_name":"Twitter","provider_url":"https:\/\/twitter.com","version":"1.0"}`, - `(?s)^.*?`, - }, - } { - // overload getJSON to return mock API response from Twitter - tweetFuncMap := template.FuncMap{ - "getJSON": func(urlParts ...string) interface{} { - var v interface{} - err := json.Unmarshal([]byte(this.resp), &v) - if err != nil { - t.Fatalf("[%d] unexpected error in json.Unmarshal: %s", i, err) - return err - } - return v - }, - } - - var ( - cfg, fs = newTestCfg() - th = testHelper{cfg, fs, t} - ) - - withTemplate := func(templ tpl.TemplateHandler) error { - templ.(tpl.TemplateTestMocker).SetFuncs(tweetFuncMap) - return nil - } - - writeSource(t, fs, filepath.Join("content", "simple.md"), fmt.Sprintf(`--- -title: Shorty ---- -%s`, this.in)) - writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .Content }}`) - - buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg, WithTemplate: withTemplate}, BuildCfg{}) - - th.assertFileContentRegexp(filepath.Join("public", "simple", "index.html"), this.expected) - - } -} - -func TestShortcodeInstagram(t *testing.T) { - t.Parallel() - - for i, this := range []struct { - in, hidecaption, resp, expected string - }{ - { - `{{< instagram BMokmydjG-M >}}`, - `0`, - `{"provider_url": "https://www.instagram.com", "media_id": "1380514280986406796_25025320", "author_name": "instagram", "height": null, "thumbnail_url": "https://scontent-amt2-1.cdninstagram.com/t51.2885-15/s640x640/sh0.08/e35/15048135_1880160212214218_7827880881132929024_n.jpg?ig_cache_key=MTM4MDUxNDI4MDk4NjQwNjc5Ng%3D%3D.2", "thumbnail_width": 640, "thumbnail_height": 640, "provider_name": "Instagram", "title": "Today, we\u2019re introducing a few new tools to help you make your story even more fun: Boomerang and mentions. We\u2019re also starting to test links inside some stories.\nBoomerang lets you turn everyday moments into something fun and unexpected. Now you can easily take a Boomerang right inside Instagram. Swipe right from your feed to open the stories camera. A new format picker under the record button lets you select \u201cBoomerang\u201d mode.\nYou can also now share who you\u2019re with or who you\u2019re thinking of by mentioning them in your story. When you add text to your story, type \u201c@\u201d followed by a username and select the person you\u2019d like to mention. Their username will appear underlined in your story. And when someone taps the mention, they'll see a pop-up that takes them to that profile.\nYou may begin to spot \u201cSee More\u201d links at the bottom of some stories. This is a test that lets verified accounts add links so it\u2019s easy to learn more. From your favorite chefs\u2019 recipes to articles from top journalists or concert dates from the musicians you love, tap \u201cSee More\u201d or swipe up to view the link right inside the app.\nTo learn more about today\u2019s updates, check out help.instagram.com.\nThese updates for Instagram Stories are available as part of Instagram version 9.7 available for iOS in the Apple App Store, for Android in Google Play and for Windows 10 in the Windows Store.", "html": "\u003cblockquote class=\"instagram-media\" data-instgrm-captioned data-instgrm-version=\"7\" style=\" background:#FFF; border:0; border-radius:3px; box-shadow:0 0 1px 0 rgba(0,0,0,0.5),0 1px 10px 0 rgba(0,0,0,0.15); margin: 1px; max-width:658px; padding:0; width:99.375%; width:-webkit-calc(100% - 2px); width:calc(100% - 2px);\"\u003e\u003cdiv style=\"padding:8px;\"\u003e \u003cdiv style=\" background:#F8F8F8; line-height:0; margin-top:40px; padding:50.0% 0; text-align:center; width:100%;\"\u003e \u003cdiv style=\" background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAMAAAApWqozAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAMUExURczMzPf399fX1+bm5mzY9AMAAADiSURBVDjLvZXbEsMgCES5/P8/t9FuRVCRmU73JWlzosgSIIZURCjo/ad+EQJJB4Hv8BFt+IDpQoCx1wjOSBFhh2XssxEIYn3ulI/6MNReE07UIWJEv8UEOWDS88LY97kqyTliJKKtuYBbruAyVh5wOHiXmpi5we58Ek028czwyuQdLKPG1Bkb4NnM+VeAnfHqn1k4+GPT6uGQcvu2h2OVuIf/gWUFyy8OWEpdyZSa3aVCqpVoVvzZZ2VTnn2wU8qzVjDDetO90GSy9mVLqtgYSy231MxrY6I2gGqjrTY0L8fxCxfCBbhWrsYYAAAAAElFTkSuQmCC); display:block; height:44px; margin:0 auto -44px; position:relative; top:-22px; width:44px;\"\u003e\u003c/div\u003e\u003c/div\u003e \u003cp style=\" margin:8px 0 0 0; padding:0 4px;\"\u003e \u003ca href=\"https://www.instagram.com/p/BMokmydjG-M/\" style=\" color:#000; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none; word-wrap:break-word;\" target=\"_blank\"\u003eToday, we\u2019re introducing a few new tools to help you make your story even more fun: Boomerang and mentions. We\u2019re also starting to test links inside some stories. Boomerang lets you turn everyday moments into something fun and unexpected. Now you can easily take a Boomerang right inside Instagram. Swipe right from your feed to open the stories camera. A new format picker under the record button lets you select \u201cBoomerang\u201d mode. You can also now share who you\u2019re with or who you\u2019re thinking of by mentioning them in your story. When you add text to your story, type \u201c@\u201d followed by a username and select the person you\u2019d like to mention. Their username will appear underlined in your story. And when someone taps the mention, they\u0026#39;ll see a pop-up that takes them to that profile. You may begin to spot \u201cSee More\u201d links at the bottom of some stories. This is a test that lets verified accounts add links so it\u2019s easy to learn more. From your favorite chefs\u2019 recipes to articles from top journalists or concert dates from the musicians you love, tap \u201cSee More\u201d or swipe up to view the link right inside the app. To learn more about today\u2019s updates, check out help.instagram.com. These updates for Instagram Stories are available as part of Instagram version 9.7 available for iOS in the Apple App Store, for Android in Google Play and for Windows 10 in the Windows Store.\u003c/a\u003e\u003c/p\u003e \u003cp style=\" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;\"\u003eA photo posted by Instagram (@instagram) on \u003ctime style=\" font-family:Arial,sans-serif; font-size:14px; line-height:17px;\" datetime=\"2016-11-10T15:02:28+00:00\"\u003eNov 10, 2016 at 7:02am PST\u003c/time\u003e\u003c/p\u003e\u003c/div\u003e\u003c/blockquote\u003e\n\u003cscript async defer src=\"//platform.instagram.com/en_US/embeds.js\"\u003e\u003c/script\u003e", "width": 658, "version": "1.0", "author_url": "https://www.instagram.com/instagram", "author_id": 25025320, "type": "rich"}`, - `(?s)
    `, - }, - { - `{{< instagram BMokmydjG-M hidecaption >}}`, - `1`, - `{"provider_url": "https://www.instagram.com", "media_id": "1380514280986406796_25025320", "author_name": "instagram", "height": null, "thumbnail_url": "https://scontent-amt2-1.cdninstagram.com/t51.2885-15/s640x640/sh0.08/e35/15048135_1880160212214218_7827880881132929024_n.jpg?ig_cache_key=MTM4MDUxNDI4MDk4NjQwNjc5Ng%3D%3D.2", "thumbnail_width": 640, "thumbnail_height": 640, "provider_name": "Instagram", "title": "Today, we\u2019re introducing a few new tools to help you make your story even more fun: Boomerang and mentions. We\u2019re also starting to test links inside some stories.\nBoomerang lets you turn everyday moments into something fun and unexpected. Now you can easily take a Boomerang right inside Instagram. Swipe right from your feed to open the stories camera. A new format picker under the record button lets you select \u201cBoomerang\u201d mode.\nYou can also now share who you\u2019re with or who you\u2019re thinking of by mentioning them in your story. When you add text to your story, type \u201c@\u201d followed by a username and select the person you\u2019d like to mention. Their username will appear underlined in your story. And when someone taps the mention, they'll see a pop-up that takes them to that profile.\nYou may begin to spot \u201cSee More\u201d links at the bottom of some stories. This is a test that lets verified accounts add links so it\u2019s easy to learn more. From your favorite chefs\u2019 recipes to articles from top journalists or concert dates from the musicians you love, tap \u201cSee More\u201d or swipe up to view the link right inside the app.\nTo learn more about today\u2019s updates, check out help.instagram.com.\nThese updates for Instagram Stories are available as part of Instagram version 9.7 available for iOS in the Apple App Store, for Android in Google Play and for Windows 10 in the Windows Store.", "html": "\u003cblockquote class=\"instagram-media\" data-instgrm-version=\"7\" style=\" background:#FFF; border:0; border-radius:3px; box-shadow:0 0 1px 0 rgba(0,0,0,0.5),0 1px 10px 0 rgba(0,0,0,0.15); margin: 1px; max-width:658px; padding:0; width:99.375%; width:-webkit-calc(100% - 2px); width:calc(100% - 2px);\"\u003e\u003cdiv style=\"padding:8px;\"\u003e \u003cdiv style=\" background:#F8F8F8; line-height:0; margin-top:40px; padding:50.0% 0; text-align:center; width:100%;\"\u003e \u003cdiv style=\" background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAsCAMAAAApWqozAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAMUExURczMzPf399fX1+bm5mzY9AMAAADiSURBVDjLvZXbEsMgCES5/P8/t9FuRVCRmU73JWlzosgSIIZURCjo/ad+EQJJB4Hv8BFt+IDpQoCx1wjOSBFhh2XssxEIYn3ulI/6MNReE07UIWJEv8UEOWDS88LY97kqyTliJKKtuYBbruAyVh5wOHiXmpi5we58Ek028czwyuQdLKPG1Bkb4NnM+VeAnfHqn1k4+GPT6uGQcvu2h2OVuIf/gWUFyy8OWEpdyZSa3aVCqpVoVvzZZ2VTnn2wU8qzVjDDetO90GSy9mVLqtgYSy231MxrY6I2gGqjrTY0L8fxCxfCBbhWrsYYAAAAAElFTkSuQmCC); display:block; height:44px; margin:0 auto -44px; position:relative; top:-22px; width:44px;\"\u003e\u003c/div\u003e\u003c/div\u003e\u003cp style=\" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; line-height:17px; margin-bottom:0; margin-top:8px; overflow:hidden; padding:8px 0 7px; text-align:center; text-overflow:ellipsis; white-space:nowrap;\"\u003e\u003ca href=\"https://www.instagram.com/p/BMokmydjG-M/\" style=\" color:#c9c8cd; font-family:Arial,sans-serif; font-size:14px; font-style:normal; font-weight:normal; line-height:17px; text-decoration:none;\" target=\"_blank\"\u003eA photo posted by Instagram (@instagram)\u003c/a\u003e on \u003ctime style=\" font-family:Arial,sans-serif; font-size:14px; line-height:17px;\" datetime=\"2016-11-10T15:02:28+00:00\"\u003eNov 10, 2016 at 7:02am PST\u003c/time\u003e\u003c/p\u003e\u003c/div\u003e\u003c/blockquote\u003e\n\u003cscript async defer src=\"//platform.instagram.com/en_US/embeds.js\"\u003e\u003c/script\u003e", "width": 658, "version": "1.0", "author_url": "https://www.instagram.com/instagram", "author_id": 25025320, "type": "rich"}`, - `(?s)
    `, - }, - } { - // overload getJSON to return mock API response from Instagram - instagramFuncMap := template.FuncMap{ - "getJSON": func(urlParts ...string) interface{} { - var v interface{} - err := json.Unmarshal([]byte(this.resp), &v) - if err != nil { - t.Fatalf("[%d] unexpected error in json.Unmarshal: %s", i, err) - return err - } - return v - }, - } - - var ( - cfg, fs = newTestCfg() - th = testHelper{cfg, fs, t} - ) - - withTemplate := func(templ tpl.TemplateHandler) error { - templ.(tpl.TemplateTestMocker).SetFuncs(instagramFuncMap) - return nil - } - - writeSource(t, fs, filepath.Join("content", "simple.md"), fmt.Sprintf(`--- -title: Shorty ---- -%s`, this.in)) - writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .Content | safeHTML }}`) - - buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg, WithTemplate: withTemplate}, BuildCfg{}) - - th.assertFileContentRegexp(filepath.Join("public", "simple", "index.html"), this.expected) - - } -} diff --git a/hugolib/gitinfo.go b/hugolib/gitinfo.go deleted file mode 100644 index 16d8c43a5..000000000 --- a/hugolib/gitinfo.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2016-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "path" - "path/filepath" - "strings" - - "github.com/bep/gitmap" - "github.com/gohugoio/hugo/helpers" -) - -func (h *HugoSites) assembleGitInfo() { - if !h.Cfg.GetBool("enableGitInfo") { - return - } - - var ( - workingDir = h.Cfg.GetString("workingDir") - contentDir = h.Cfg.GetString("contentDir") - ) - - gitRepo, err := gitmap.Map(workingDir, "") - if err != nil { - h.Log.ERROR.Printf("Got error reading Git log: %s", err) - return - } - - gitMap := gitRepo.Files - repoPath := filepath.FromSlash(gitRepo.TopLevelAbsPath) - - // The Hugo site may be placed in a sub folder in the Git repo, - // one example being the Hugo docs. - // We have to find the root folder to the Hugo site below the Git root. - contentRoot := strings.TrimPrefix(workingDir, repoPath) - contentRoot = strings.TrimPrefix(contentRoot, helpers.FilePathSeparator) - - s := h.Sites[0] - - for _, p := range s.AllPages { - if p.Path() == "" { - // Home page etc. with no content file. - continue - } - // Git normalizes file paths on this form: - filename := path.Join(filepath.ToSlash(contentRoot), contentDir, filepath.ToSlash(p.Path())) - g, ok := gitMap[filename] - if !ok { - h.Log.WARN.Printf("Failed to find GitInfo for %q", filename) - return - } - - p.GitInfo = g - p.Lastmod = p.GitInfo.AuthorDate - } - -} diff --git a/hugolib/handler_base.go b/hugolib/handler_base.go deleted file mode 100644 index d7e4a63a3..000000000 --- a/hugolib/handler_base.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "github.com/gohugoio/hugo/source" -) - -// Handler is used for processing files of a specific type. -type Handler interface { - FileConvert(*source.File, *Site) HandledResult - PageConvert(*Page) HandledResult - Read(*source.File, *Site) HandledResult - Extensions() []string -} - -// Handle identifies functionality associated with certain file extensions. -type Handle struct { - extensions []string -} - -// Extensions returns a list of extensions. -func (h Handle) Extensions() []string { - return h.extensions -} - -// HandledResult describes the results of a file handling operation. -type HandledResult struct { - page *Page - file *source.File - err error -} - -// HandledResult is an error -func (h HandledResult) Error() string { - if h.err != nil { - if h.page != nil { - return "Error: " + h.err.Error() + " for " + h.page.File.LogicalName() - } - if h.file != nil { - return "Error: " + h.err.Error() + " for " + h.file.LogicalName() - } - } - return h.err.Error() -} - -func (h HandledResult) String() string { - return h.Error() -} - -// Page returns the affected page. -func (h HandledResult) Page() *Page { - return h.page -} diff --git a/hugolib/handler_file.go b/hugolib/handler_file.go deleted file mode 100644 index 82ea85fb2..000000000 --- a/hugolib/handler_file.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "bytes" - - "github.com/dchest/cssmin" - "github.com/gohugoio/hugo/source" -) - -func init() { - RegisterHandler(new(cssHandler)) - RegisterHandler(new(defaultHandler)) -} - -type basicFileHandler Handle - -func (h basicFileHandler) Read(f *source.File, s *Site) HandledResult { - return HandledResult{file: f} -} - -func (h basicFileHandler) PageConvert(*Page) HandledResult { - return HandledResult{} -} - -type defaultHandler struct{ basicFileHandler } - -func (h defaultHandler) Extensions() []string { return []string{"*"} } -func (h defaultHandler) FileConvert(f *source.File, s *Site) HandledResult { - err := s.publish(f.Path(), f.Contents) - if err != nil { - return HandledResult{err: err} - } - return HandledResult{file: f} -} - -type cssHandler struct{ basicFileHandler } - -func (h cssHandler) Extensions() []string { return []string{"css"} } -func (h cssHandler) FileConvert(f *source.File, s *Site) HandledResult { - x := cssmin.Minify(f.Bytes()) - err := s.publish(f.Path(), bytes.NewReader(x)) - if err != nil { - return HandledResult{err: err} - } - return HandledResult{file: f} -} diff --git a/hugolib/handler_meta.go b/hugolib/handler_meta.go deleted file mode 100644 index ae68ed5c7..000000000 --- a/hugolib/handler_meta.go +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "errors" - - "fmt" - - "github.com/gohugoio/hugo/source" -) - -var handlers []Handler - -// MetaHandler abstracts reading and converting functionality of a Handler. -type MetaHandler interface { - // Read the Files in and register - Read(*source.File, *Site, HandleResults) - - // Generic Convert Function with coordination - Convert(interface{}, *Site, HandleResults) - - Handle() Handler -} - -// HandledResults is a channel for HandledResult. -type HandleResults chan<- HandledResult - -// NewMetaHandler creates a MetaHandle for a given extensions. -func NewMetaHandler(in string) *MetaHandle { - x := &MetaHandle{ext: in} - x.Handler() - return x -} - -// MetaHandle is a generic MetaHandler that internally uses -// the globally registered handlers for handling specific file types. -type MetaHandle struct { - handler Handler - ext string -} - -func (mh *MetaHandle) Read(f *source.File, s *Site, results HandleResults) { - if h := mh.Handler(); h != nil { - results <- h.Read(f, s) - return - } - - results <- HandledResult{err: errors.New("No handler found"), file: f} -} - -// Convert handles the conversion of files and pages. -func (mh *MetaHandle) Convert(i interface{}, s *Site, results HandleResults) { - h := mh.Handler() - - if f, ok := i.(*source.File); ok { - results <- h.FileConvert(f, s) - return - } - - if p, ok := i.(*Page); ok { - if p == nil { - results <- HandledResult{err: errors.New("file resulted in a nil page")} - return - } - - if h == nil { - results <- HandledResult{err: fmt.Errorf("No handler found for page '%s'. Verify the markup is supported by Hugo.", p.FullFilePath())} - return - } - - results <- h.PageConvert(p) - } -} - -// Handler finds the registered handler for the used extensions. -func (mh *MetaHandle) Handler() Handler { - if mh.handler == nil { - mh.handler = FindHandler(mh.ext) - - // if no handler found, use default handler - if mh.handler == nil { - mh.handler = FindHandler("*") - } - } - return mh.handler -} - -// FindHandler finds a Handler in the globally registered handlers. -func FindHandler(ext string) Handler { - for _, h := range Handlers() { - if HandlerMatch(h, ext) { - return h - } - } - return nil -} - -// HandlerMatch checks if the given extensions matches. -func HandlerMatch(h Handler, ext string) bool { - for _, x := range h.Extensions() { - if ext == x { - return true - } - } - return false -} - -// RegisterHandler adds a handler to the globally registered ones. -func RegisterHandler(h Handler) { - handlers = append(handlers, h) -} - -// Handlers returns the globally registered handlers. -func Handlers() []Handler { - return handlers -} diff --git a/hugolib/handler_page.go b/hugolib/handler_page.go deleted file mode 100644 index 6e230dad0..000000000 --- a/hugolib/handler_page.go +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright 2016 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "fmt" - - "github.com/gohugoio/hugo/helpers" - "github.com/gohugoio/hugo/source" -) - -func init() { - RegisterHandler(new(markdownHandler)) - RegisterHandler(new(htmlHandler)) - RegisterHandler(new(asciidocHandler)) - RegisterHandler(new(rstHandler)) - RegisterHandler(new(mmarkHandler)) - RegisterHandler(new(orgHandler)) -} - -type basicPageHandler Handle - -func (b basicPageHandler) Read(f *source.File, s *Site) HandledResult { - page, err := s.NewPage(f.Path()) - - if err != nil { - return HandledResult{file: f, err: err} - } - - if _, err := page.ReadFrom(f.Contents); err != nil { - return HandledResult{file: f, err: err} - } - - // In a multilanguage setup, we use the first site to - // do the initial processing. - // That site may be different than where the page will end up, - // so we do the assignment here. - // We should clean up this, but that will have to wait. - s.assignSiteByLanguage(page) - - return HandledResult{file: f, page: page, err: err} -} - -func (b basicPageHandler) FileConvert(*source.File, *Site) HandledResult { - return HandledResult{} -} - -type markdownHandler struct { - basicPageHandler -} - -func (h markdownHandler) Extensions() []string { return []string{"mdown", "markdown", "md"} } -func (h markdownHandler) PageConvert(p *Page) HandledResult { - return commonConvert(p) -} - -type htmlHandler struct { - basicPageHandler -} - -func (h htmlHandler) Extensions() []string { return []string{"html", "htm"} } - -func (h htmlHandler) PageConvert(p *Page) HandledResult { - if p.rendered { - panic(fmt.Sprintf("Page %q already rendered, does not need conversion", p.BaseFileName())) - } - - // Work on a copy of the raw content from now on. - p.createWorkContentCopy() - - if err := p.processShortcodes(); err != nil { - p.s.Log.ERROR.Println(err) - } - - return HandledResult{err: nil} -} - -type asciidocHandler struct { - basicPageHandler -} - -func (h asciidocHandler) Extensions() []string { return []string{"asciidoc", "adoc", "ad"} } -func (h asciidocHandler) PageConvert(p *Page) HandledResult { - return commonConvert(p) -} - -type rstHandler struct { - basicPageHandler -} - -func (h rstHandler) Extensions() []string { return []string{"rest", "rst"} } -func (h rstHandler) PageConvert(p *Page) HandledResult { - return commonConvert(p) -} - -type mmarkHandler struct { - basicPageHandler -} - -func (h mmarkHandler) Extensions() []string { return []string{"mmark"} } -func (h mmarkHandler) PageConvert(p *Page) HandledResult { - return commonConvert(p) -} - -type orgHandler struct { - basicPageHandler -} - -func (h orgHandler) Extensions() []string { return []string{"org"} } -func (h orgHandler) PageConvert(p *Page) HandledResult { - return commonConvert(p) -} - -func commonConvert(p *Page) HandledResult { - if p.rendered { - panic(fmt.Sprintf("Page %q already rendered, does not need conversion", p.BaseFileName())) - } - - // Work on a copy of the raw content from now on. - p.createWorkContentCopy() - - if err := p.processShortcodes(); err != nil { - p.s.Log.ERROR.Println(err) - } - - // TODO(bep) these page handlers need to be re-evaluated, as it is hard to - // process a page in isolation. See the new preRender func. - if p.s.Cfg.GetBool("enableEmoji") { - p.workContent = helpers.Emojify(p.workContent) - } - - p.workContent = p.replaceDivider(p.workContent) - p.workContent = p.renderContent(p.workContent) - - return HandledResult{err: nil} -} diff --git a/hugolib/handler_test.go b/hugolib/handler_test.go deleted file mode 100644 index aa58d1c43..000000000 --- a/hugolib/handler_test.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "path/filepath" - "testing" - - "github.com/gohugoio/hugo/deps" - "github.com/gohugoio/hugo/helpers" -) - -func TestDefaultHandler(t *testing.T) { - t.Parallel() - - var ( - cfg, fs = newTestCfg() - ) - - cfg.Set("verbose", true) - cfg.Set("uglyURLs", true) - - writeSource(t, fs, filepath.FromSlash("content/sect/doc1.html"), "---\nmarkup: markdown\n---\n# title\nsome *content*") - writeSource(t, fs, filepath.FromSlash("content/sect/doc2.html"), "more content") - writeSource(t, fs, filepath.FromSlash("content/sect/doc3.md"), "# doc3\n*some* content") - writeSource(t, fs, filepath.FromSlash("content/sect/doc4.md"), "---\ntitle: doc4\n---\n# doc4\n*some content*") - writeSource(t, fs, filepath.FromSlash("content/sect/doc3/img1.png"), "‰PNG  ��� IHDR����������:~›U��� IDATWcø��ZMoñ����IEND®B`‚") - writeSource(t, fs, filepath.FromSlash("content/sect/img2.gif"), "GIF89a��€��ÿÿÿ���,�������D�;") - writeSource(t, fs, filepath.FromSlash("content/sect/img2.spf"), "****FAKE-FILETYPE****") - writeSource(t, fs, filepath.FromSlash("content/doc7.html"), "doc7 content") - writeSource(t, fs, filepath.FromSlash("content/sect/doc8.html"), "---\nmarkup: md\n---\n# title\nsome *content*") - - writeSource(t, fs, filepath.FromSlash("layouts/_default/single.html"), "{{.Content}}") - writeSource(t, fs, filepath.FromSlash("head"), "") - writeSource(t, fs, filepath.FromSlash("head_abs"), "title\n\n

    some content

    \n"}, - {filepath.FromSlash("public/sect/doc2.html"), "more content"}, - {filepath.FromSlash("public/sect/doc3.html"), "\n\n

    doc3

    \n\n

    some content

    \n"}, - {filepath.FromSlash("public/sect/doc3/img1.png"), string([]byte("‰PNG  ��� IHDR����������:~›U��� IDATWcø��ZMoñ����IEND®B`‚"))}, - {filepath.FromSlash("public/sect/img2.gif"), string([]byte("GIF89a��€��ÿÿÿ���,�������D�;"))}, - {filepath.FromSlash("public/sect/img2.spf"), string([]byte("****FAKE-FILETYPE****"))}, - {filepath.FromSlash("public/doc7.html"), "doc7 content"}, - {filepath.FromSlash("public/sect/doc8.html"), "\n\n

    title

    \n\n

    some content

    \n"}, - } - - for _, test := range tests { - file, err := fs.Destination.Open(test.doc) - if err != nil { - t.Fatalf("Did not find %s in target.", test.doc) - } - - content := helpers.ReaderToString(file) - - if content != test.expected { - t.Errorf("%s content expected:\n%q\ngot:\n%q", test.doc, test.expected, content) - } - } - -} diff --git a/hugolib/hugo_info.go b/hugolib/hugo_info.go deleted file mode 100644 index 1e0c192e5..000000000 --- a/hugolib/hugo_info.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "fmt" - "html/template" - - "github.com/gohugoio/hugo/helpers" -) - -var ( - // CommitHash contains the current Git revision. Use make to build to make - // sure this gets set. - CommitHash string - - // BuildDate contains the date of the current build. - BuildDate string -) - -var hugoInfo *HugoInfo - -// HugoInfo contains information about the current Hugo environment -type HugoInfo struct { - Version string - Generator template.HTML - CommitHash string - BuildDate string -} - -func init() { - hugoInfo = &HugoInfo{ - Version: helpers.CurrentHugoVersion.String(), - CommitHash: CommitHash, - BuildDate: BuildDate, - Generator: template.HTML(fmt.Sprintf(``, helpers.CurrentHugoVersion.String())), - } -} diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go deleted file mode 100644 index e763588bd..000000000 --- a/hugolib/hugo_sites.go +++ /dev/null @@ -1,655 +0,0 @@ -// Copyright 2016-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "errors" - "fmt" - "strings" - "sync" - - "path/filepath" - - "github.com/gohugoio/hugo/deps" - "github.com/gohugoio/hugo/helpers" - - "github.com/gohugoio/hugo/i18n" - "github.com/gohugoio/hugo/tpl" - "github.com/gohugoio/hugo/tpl/tplimpl" -) - -// HugoSites represents the sites to build. Each site represents a language. -type HugoSites struct { - Sites []*Site - - runMode runmode - - multilingual *Multilingual - - *deps.Deps -} - -// GetContentPage finds a Page with content given the absolute filename. -// Returns nil if none found. -func (h *HugoSites) GetContentPage(filename string) *Page { - s := h.Sites[0] - contendDir := filepath.Join(s.PathSpec.AbsPathify(s.Cfg.GetString("contentDir"))) - if !strings.HasPrefix(filename, contendDir) { - return nil - } - - rel := strings.TrimPrefix(filename, contendDir) - rel = strings.TrimPrefix(rel, helpers.FilePathSeparator) - - pos := s.rawAllPages.findPagePosByFilePath(rel) - - if pos == -1 { - return nil - } - return s.rawAllPages[pos] - -} - -// NewHugoSites creates a new collection of sites given the input sites, building -// a language configuration based on those. -func newHugoSites(cfg deps.DepsCfg, sites ...*Site) (*HugoSites, error) { - - if cfg.Language != nil { - return nil, errors.New("Cannot provide Language in Cfg when sites are provided") - } - - langConfig, err := newMultiLingualFromSites(cfg.Cfg, sites...) - - if err != nil { - return nil, err - } - - h := &HugoSites{ - multilingual: langConfig, - Sites: sites} - - for _, s := range sites { - s.owner = h - } - - // TODO(bep) - cfg.Cfg.Set("multilingual", sites[0].multilingualEnabled()) - - if err := applyDepsIfNeeded(cfg, sites...); err != nil { - return nil, err - } - - h.Deps = sites[0].Deps - - return h, nil -} - -func applyDepsIfNeeded(cfg deps.DepsCfg, sites ...*Site) error { - if cfg.TemplateProvider == nil { - cfg.TemplateProvider = tplimpl.DefaultTemplateProvider - } - - if cfg.TranslationProvider == nil { - cfg.TranslationProvider = i18n.NewTranslationProvider() - } - - var ( - d *deps.Deps - err error - ) - - for _, s := range sites { - if s.Deps != nil { - continue - } - - if d == nil { - cfg.Language = s.Language - cfg.WithTemplate = s.withSiteTemplates(cfg.WithTemplate) - - var err error - d, err = deps.New(cfg) - if err != nil { - return err - } - - d.OutputFormatsConfig = s.outputFormatsConfig - s.Deps = d - - if err = d.LoadResources(); err != nil { - return err - } - - } else { - d, err = d.ForLanguage(s.Language) - if err != nil { - return err - } - d.OutputFormatsConfig = s.outputFormatsConfig - s.Deps = d - } - - } - - return nil -} - -// NewHugoSites creates HugoSites from the given config. -func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) { - sites, err := createSitesFromConfig(cfg) - if err != nil { - return nil, err - } - return newHugoSites(cfg, sites...) -} - -func (s *Site) withSiteTemplates(withTemplates ...func(templ tpl.TemplateHandler) error) func(templ tpl.TemplateHandler) error { - return func(templ tpl.TemplateHandler) error { - templ.LoadTemplates(s.PathSpec.GetLayoutDirPath(), "") - if s.PathSpec.ThemeSet() { - templ.LoadTemplates(s.PathSpec.GetThemeDir()+"/layouts", "theme") - } - - for _, wt := range withTemplates { - if wt == nil { - continue - } - if err := wt(templ); err != nil { - return err - } - } - - return nil - } -} - -func createSitesFromConfig(cfg deps.DepsCfg) ([]*Site, error) { - - var ( - sites []*Site - ) - - multilingual := cfg.Cfg.GetStringMap("languages") - - if len(multilingual) == 0 { - l := helpers.NewDefaultLanguage(cfg.Cfg) - cfg.Language = l - s, err := newSite(cfg) - if err != nil { - return nil, err - } - sites = append(sites, s) - } - - if len(multilingual) > 0 { - var err error - - languages, err := toSortedLanguages(cfg.Cfg, multilingual) - - if err != nil { - return nil, fmt.Errorf("Failed to parse multilingual config: %s", err) - } - - for _, lang := range languages { - var s *Site - var err error - cfg.Language = lang - s, err = newSite(cfg) - - if err != nil { - return nil, err - } - - sites = append(sites, s) - } - } - - return sites, nil -} - -// Reset resets the sites and template caches, making it ready for a full rebuild. -func (h *HugoSites) reset() { - for i, s := range h.Sites { - h.Sites[i] = s.reset() - } -} - -func (h *HugoSites) createSitesFromConfig() error { - - depsCfg := deps.DepsCfg{Fs: h.Fs, Cfg: h.Cfg} - sites, err := createSitesFromConfig(depsCfg) - - if err != nil { - return err - } - - langConfig, err := newMultiLingualFromSites(depsCfg.Cfg, sites...) - - if err != nil { - return err - } - - h.Sites = sites - - for _, s := range sites { - s.owner = h - } - - if err := applyDepsIfNeeded(depsCfg, sites...); err != nil { - return err - } - - h.Deps = sites[0].Deps - - h.multilingual = langConfig - - return nil -} - -func (h *HugoSites) toSiteInfos() []*SiteInfo { - infos := make([]*SiteInfo, len(h.Sites)) - for i, s := range h.Sites { - infos[i] = &s.Info - } - return infos -} - -// BuildCfg holds build options used to, as an example, skip the render step. -type BuildCfg struct { - // Whether we are in watch (server) mode - Watching bool - // Print build stats at the end of a build - PrintStats bool - // Reset site state before build. Use to force full rebuilds. - ResetState bool - // Re-creates the sites from configuration before a build. - // This is needed if new languages are added. - CreateSitesFromConfig bool - // Skip rendering. Useful for testing. - SkipRender bool - // Use this to indicate what changed (for rebuilds). - whatChanged *whatChanged -} - -func (h *HugoSites) renderCrossSitesArtifacts() error { - - if !h.multilingual.enabled() { - return nil - } - - if h.Cfg.GetBool("disableSitemap") { - return nil - } - - sitemapEnabled := false - for _, s := range h.Sites { - if s.isEnabled(kindSitemap) { - sitemapEnabled = true - break - } - } - - if !sitemapEnabled { - return nil - } - - // TODO(bep) DRY - sitemapDefault := parseSitemap(h.Cfg.GetStringMap("sitemap")) - - s := h.Sites[0] - - smLayouts := []string{"sitemapindex.xml", "_default/sitemapindex.xml", "_internal/_default/sitemapindex.xml"} - - return s.renderAndWriteXML("sitemapindex", - sitemapDefault.Filename, h.toSiteInfos(), s.appendThemeTemplates(smLayouts)...) -} - -func (h *HugoSites) assignMissingTranslations() error { - // This looks heavy, but it should be a small number of nodes by now. - allPages := h.findAllPagesByKindNotIn(KindPage) - for _, nodeType := range []string{KindHome, KindSection, KindTaxonomy, KindTaxonomyTerm} { - nodes := h.findPagesByKindIn(nodeType, allPages) - - // Assign translations - for _, t1 := range nodes { - for _, t2 := range nodes { - if t1.isNewTranslation(t2) { - t1.translations = append(t1.translations, t2) - } - } - } - } - - // Now we can sort the translations. - for _, p := range allPages { - if len(p.translations) > 0 { - pageBy(languagePageSort).Sort(p.translations) - } - } - return nil - -} - -// createMissingPages creates home page, taxonomies etc. that isnt't created as an -// effect of having a content file. -func (h *HugoSites) createMissingPages() error { - var newPages Pages - - for _, s := range h.Sites { - if s.isEnabled(KindHome) { - // home pages - home := s.findPagesByKind(KindHome) - if len(home) > 1 { - panic("Too many homes") - } - if len(home) == 0 { - n := s.newHomePage() - s.Pages = append(s.Pages, n) - newPages = append(newPages, n) - } - } - - // Will create content-less root sections. - newSections := s.assembleSections() - s.Pages = append(s.Pages, newSections...) - newPages = append(newPages, newSections...) - - // taxonomy list and terms pages - taxonomies := s.Language.GetStringMapString("taxonomies") - if len(taxonomies) > 0 { - taxonomyPages := s.findPagesByKind(KindTaxonomy) - taxonomyTermsPages := s.findPagesByKind(KindTaxonomyTerm) - for _, plural := range taxonomies { - if s.isEnabled(KindTaxonomyTerm) { - foundTaxonomyTermsPage := false - for _, p := range taxonomyTermsPages { - if p.sections[0] == plural { - foundTaxonomyTermsPage = true - break - } - } - - if !foundTaxonomyTermsPage { - n := s.newTaxonomyTermsPage(plural) - s.Pages = append(s.Pages, n) - newPages = append(newPages, n) - } - } - - if s.isEnabled(KindTaxonomy) { - for key := range s.Taxonomies[plural] { - foundTaxonomyPage := false - origKey := key - - if s.Info.preserveTaxonomyNames { - key = s.PathSpec.MakePathSanitized(key) - } - for _, p := range taxonomyPages { - if p.sections[0] == plural && p.sections[1] == key { - foundTaxonomyPage = true - break - } - } - - if !foundTaxonomyPage { - n := s.newTaxonomyPage(plural, origKey) - s.Pages = append(s.Pages, n) - newPages = append(newPages, n) - } - } - } - } - } - } - - if len(newPages) > 0 { - // This resorting is unfortunate, but it also needs to be sorted - // when sections are created. - first := h.Sites[0] - - first.AllPages = append(first.AllPages, newPages...) - - first.AllPages.Sort() - - for _, s := range h.Sites { - s.Pages.Sort() - } - - for i := 1; i < len(h.Sites); i++ { - h.Sites[i].AllPages = first.AllPages - } - } - - return nil -} - -func (s *Site) assignSiteByLanguage(p *Page) { - - pageLang := p.Lang() - - if pageLang == "" { - panic("Page language missing: " + p.Title) - } - - for _, site := range s.owner.Sites { - if strings.HasPrefix(site.Language.Lang, pageLang) { - p.s = site - p.Site = &site.Info - return - } - } - -} - -func (h *HugoSites) setupTranslations() { - - master := h.Sites[0] - - for _, p := range master.rawAllPages { - if p.Lang() == "" { - panic("Page language missing: " + p.Title) - } - - if p.Kind == kindUnknown { - p.Kind = p.s.kindFromSections(p.sections) - } - - if !p.s.isEnabled(p.Kind) { - continue - } - - shouldBuild := p.shouldBuild() - - for i, site := range h.Sites { - // The site is assigned by language when read. - if site == p.s { - site.updateBuildStats(p) - if shouldBuild { - site.Pages = append(site.Pages, p) - } - } - - if !shouldBuild { - continue - } - - if i == 0 { - site.AllPages = append(site.AllPages, p) - } - } - - } - - // Pull over the collections from the master site - for i := 1; i < len(h.Sites); i++ { - h.Sites[i].AllPages = h.Sites[0].AllPages - h.Sites[i].Data = h.Sites[0].Data - } - - if len(h.Sites) > 1 { - pages := h.Sites[0].AllPages - allTranslations := pagesToTranslationsMap(pages) - assignTranslationsToPages(allTranslations, pages) - } -} - -func (s *Site) preparePagesForRender(cfg *BuildCfg) { - - pageChan := make(chan *Page) - wg := &sync.WaitGroup{} - numWorkers := getGoMaxProcs() * 4 - - for i := 0; i < numWorkers; i++ { - wg.Add(1) - go func(pages <-chan *Page, wg *sync.WaitGroup) { - defer wg.Done() - for p := range pages { - if !p.shouldRenderTo(s.rc.Format) { - // No need to prepare - continue - } - var shortcodeUpdate bool - if p.shortcodeState != nil { - shortcodeUpdate = p.shortcodeState.updateDelta() - } - - if !shortcodeUpdate && !cfg.whatChanged.other && p.rendered { - // No need to process it again. - continue - } - - // If we got this far it means that this is either a new Page pointer - // or a template or similar has changed so wee need to do a rerendering - // of the shortcodes etc. - - // Mark it as rendered - p.rendered = true - - // If in watch mode or if we have multiple output formats, - // we need to keep the original so we can - // potentially repeat this process on rebuild. - needsACopy := cfg.Watching || len(p.outputFormats) > 1 - var workContentCopy []byte - if needsACopy { - workContentCopy = make([]byte, len(p.workContent)) - copy(workContentCopy, p.workContent) - } else { - // Just reuse the same slice. - workContentCopy = p.workContent - } - - if p.Markup == "markdown" { - tmpContent, tmpTableOfContents := helpers.ExtractTOC(workContentCopy) - p.TableOfContents = helpers.BytesToHTML(tmpTableOfContents) - workContentCopy = tmpContent - } - - var err error - if workContentCopy, err = handleShortcodes(p, workContentCopy); err != nil { - s.Log.ERROR.Printf("Failed to handle shortcodes for page %s: %s", p.BaseFileName(), err) - } - - if p.Markup != "html" { - - // Now we know enough to create a summary of the page and count some words - summaryContent, err := p.setUserDefinedSummaryIfProvided(workContentCopy) - - if err != nil { - s.Log.ERROR.Printf("Failed to set user defined summary for page %q: %s", p.Path(), err) - } else if summaryContent != nil { - workContentCopy = summaryContent.content - } - - p.Content = helpers.BytesToHTML(workContentCopy) - - if summaryContent == nil { - if err := p.setAutoSummary(); err != nil { - s.Log.ERROR.Printf("Failed to set user auto summary for page %q: %s", p.pathOrTitle(), err) - } - } - - } else { - p.Content = helpers.BytesToHTML(workContentCopy) - } - - //analyze for raw stats - p.analyzePage() - - } - }(pageChan, wg) - } - - for _, p := range s.Pages { - pageChan <- p - } - - close(pageChan) - - wg.Wait() - -} - -// Pages returns all pages for all sites. -func (h *HugoSites) Pages() Pages { - return h.Sites[0].AllPages -} - -func handleShortcodes(p *Page, rawContentCopy []byte) ([]byte, error) { - if p.shortcodeState != nil && len(p.shortcodeState.contentShortcodes) > 0 { - p.s.Log.DEBUG.Printf("Replace %d shortcodes in %q", len(p.shortcodeState.contentShortcodes), p.BaseFileName()) - err := p.shortcodeState.executeShortcodesForDelta(p) - - if err != nil { - return rawContentCopy, err - } - - rawContentCopy, err = replaceShortcodeTokens(rawContentCopy, shortcodePlaceholderPrefix, p.shortcodeState.renderedShortcodes) - - if err != nil { - p.s.Log.FATAL.Printf("Failed to replace shortcode tokens in %s:\n%s", p.BaseFileName(), err.Error()) - } - } - - return rawContentCopy, nil -} - -func (s *Site) updateBuildStats(page *Page) { - if page.IsDraft() { - s.draftCount++ - } - - if page.IsFuture() { - s.futureCount++ - } - - if page.IsExpired() { - s.expiredCount++ - } -} - -func (h *HugoSites) findPagesByKindNotIn(kind string, inPages Pages) Pages { - return h.Sites[0].findPagesByKindNotIn(kind, inPages) -} - -func (h *HugoSites) findPagesByKindIn(kind string, inPages Pages) Pages { - return h.Sites[0].findPagesByKindIn(kind, inPages) -} - -func (h *HugoSites) findAllPagesByKind(kind string) Pages { - return h.findPagesByKindIn(kind, h.Sites[0].AllPages) -} - -func (h *HugoSites) findAllPagesByKindNotIn(kind string) Pages { - return h.findPagesByKindNotIn(kind, h.Sites[0].AllPages) -} diff --git a/hugolib/hugo_sites_build.go b/hugolib/hugo_sites_build.go deleted file mode 100644 index fa0eac702..000000000 --- a/hugolib/hugo_sites_build.go +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright 2016-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "time" - - "errors" - - "github.com/fsnotify/fsnotify" - "github.com/gohugoio/hugo/helpers" -) - -// Build builds all sites. If filesystem events are provided, -// this is considered to be a potential partial rebuild. -func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error { - t0 := time.Now() - - // Need a pointer as this may be modified. - conf := &config - - if conf.whatChanged == nil { - // Assume everything has changed - conf.whatChanged = &whatChanged{source: true, other: true} - } - - if len(events) > 0 { - // Rebuild - if err := h.initRebuild(conf); err != nil { - return err - } - } else { - if err := h.init(conf); err != nil { - return err - } - } - - if err := h.process(conf, events...); err != nil { - return err - } - - if err := h.assemble(conf); err != nil { - return err - } - - if err := h.render(conf); err != nil { - return err - } - - if config.PrintStats { - h.Log.FEEDBACK.Printf("total in %v ms\n", int(1000*time.Since(t0).Seconds())) - } - - return nil - -} - -// Build lifecycle methods below. -// The order listed matches the order of execution. - -func (h *HugoSites) init(config *BuildCfg) error { - - for _, s := range h.Sites { - if s.PageCollections == nil { - s.PageCollections = newPageCollections() - } - } - - if config.ResetState { - h.reset() - } - - if config.CreateSitesFromConfig { - if err := h.createSitesFromConfig(); err != nil { - return err - } - } - - h.runMode.Watching = config.Watching - - return nil -} - -func (h *HugoSites) initRebuild(config *BuildCfg) error { - if config.CreateSitesFromConfig { - return errors.New("Rebuild does not support 'CreateSitesFromConfig'.") - } - - if config.ResetState { - return errors.New("Rebuild does not support 'ResetState'.") - } - - if !config.Watching { - return errors.New("Rebuild called when not in watch mode") - } - - h.runMode.Watching = config.Watching - - if config.whatChanged.source { - // This is for the non-renderable content pages (rarely used, I guess). - // We could maybe detect if this is really needed, but it should be - // pretty fast. - h.TemplateHandler().RebuildClone() - } - - for _, s := range h.Sites { - s.resetBuildState() - } - - helpers.InitLoggers() - - return nil -} - -func (h *HugoSites) process(config *BuildCfg, events ...fsnotify.Event) error { - // We should probably refactor the Site and pull up most of the logic from there to here, - // but that seems like a daunting task. - // So for now, if there are more than one site (language), - // we pre-process the first one, then configure all the sites based on that. - - firstSite := h.Sites[0] - - if len(events) > 0 { - // This is a rebuild - changed, err := firstSite.reProcess(events) - config.whatChanged = &changed - return err - } - - return firstSite.process(*config) - -} - -func (h *HugoSites) assemble(config *BuildCfg) error { - if config.whatChanged.source { - for _, s := range h.Sites { - s.createTaxonomiesEntries() - } - } - - // TODO(bep) we could probably wait and do this in one go later - h.setupTranslations() - - if len(h.Sites) > 1 { - // The first is initialized during process; initialize the rest - for _, site := range h.Sites[1:] { - site.initializeSiteInfo() - } - } - - if config.whatChanged.source { - h.assembleGitInfo() - - for _, s := range h.Sites { - if err := s.buildSiteMeta(); err != nil { - return err - } - } - } - - if err := h.createMissingPages(); err != nil { - return err - } - - for _, s := range h.Sites { - s.siteStats = &siteStats{} - for _, p := range s.Pages { - // May have been set in front matter - if len(p.outputFormats) == 0 { - p.outputFormats = s.outputFormats[p.Kind] - } - - cnt := len(p.outputFormats) - if p.Kind == KindPage { - s.siteStats.pageCountRegular += cnt - } - s.siteStats.pageCount += cnt - - if err := p.initTargetPathDescriptor(); err != nil { - return err - } - if err := p.initURLs(); err != nil { - return err - } - } - s.assembleMenus() - s.refreshPageCaches() - s.setupSitePages() - } - - if err := h.assignMissingTranslations(); err != nil { - return err - } - - return nil - -} - -func (h *HugoSites) render(config *BuildCfg) error { - - for _, s := range h.Sites { - s.initRenderFormats() - for i, rf := range s.renderFormats { - s.rc = &siteRenderingContext{Format: rf} - s.preparePagesForRender(config) - - if !config.SkipRender { - if err := s.render(i); err != nil { - return err - } - } - } - - if !config.SkipRender && config.PrintStats { - s.Stats() - } - } - - if !config.SkipRender { - if err := h.renderCrossSitesArtifacts(); err != nil { - return err - } - } - - return nil -} diff --git a/hugolib/hugo_sites_build_test.go b/hugolib/hugo_sites_build_test.go deleted file mode 100644 index 96e2c66b2..000000000 --- a/hugolib/hugo_sites_build_test.go +++ /dev/null @@ -1,1352 +0,0 @@ -package hugolib - -import ( - "bytes" - "fmt" - "strings" - "testing" - - "html/template" - "os" - "path/filepath" - "time" - - "github.com/fortytw2/leaktest" - "github.com/fsnotify/fsnotify" - "github.com/gohugoio/hugo/deps" - "github.com/gohugoio/hugo/helpers" - "github.com/gohugoio/hugo/hugofs" - "github.com/gohugoio/hugo/source" - "github.com/spf13/afero" - "github.com/spf13/viper" - "github.com/stretchr/testify/require" -) - -type testSiteConfig struct { - DefaultContentLanguage string - DefaultContentLanguageInSubdir bool - Fs afero.Fs -} - -func TestMultiSitesMainLangInRoot(t *testing.T) { - t.Parallel() - for _, b := range []bool{true, false} { - doTestMultiSitesMainLangInRoot(t, b) - } -} - -func doTestMultiSitesMainLangInRoot(t *testing.T, defaultInSubDir bool) { - - siteConfig := testSiteConfig{Fs: afero.NewMemMapFs(), DefaultContentLanguage: "fr", DefaultContentLanguageInSubdir: defaultInSubDir} - - sites := createMultiTestSites(t, siteConfig, multiSiteTOMLConfigTemplate) - fs := sites.Fs - th := testHelper{sites.Cfg, fs, t} - - err := sites.Build(BuildCfg{}) - - if err != nil { - t.Fatalf("Failed to build sites: %s", err) - } - - require.Len(t, sites.Sites, 4) - - enSite := sites.Sites[0] - frSite := sites.Sites[1] - - require.Equal(t, "/en", enSite.Info.LanguagePrefix) - - if defaultInSubDir { - require.Equal(t, "/fr", frSite.Info.LanguagePrefix) - } else { - require.Equal(t, "", frSite.Info.LanguagePrefix) - } - - require.Equal(t, "/blog/en/foo", enSite.PathSpec.RelURL("foo", true)) - - doc1en := enSite.RegularPages[0] - doc1fr := frSite.RegularPages[0] - - enPerm := doc1en.Permalink() - enRelPerm := doc1en.RelPermalink() - require.Equal(t, "http://example.com/blog/en/sect/doc1-slug/", enPerm) - require.Equal(t, "/blog/en/sect/doc1-slug/", enRelPerm) - - frPerm := doc1fr.Permalink() - frRelPerm := doc1fr.RelPermalink() - // Main language in root - require.Equal(t, th.replaceDefaultContentLanguageValue("http://example.com/blog/fr/sect/doc1/"), frPerm) - require.Equal(t, th.replaceDefaultContentLanguageValue("/blog/fr/sect/doc1/"), frRelPerm) - - th.assertFileContent("public/fr/sect/doc1/index.html", "Single", "Bonjour") - th.assertFileContent("public/en/sect/doc1-slug/index.html", "Single", "Hello") - - // Check home - if defaultInSubDir { - // should have a redirect on top level. - th.assertFileContentStraight("public/index.html", ``) - } else { - // should have redirect back to root - th.assertFileContentStraight("public/fr/index.html", ``) - } - th.assertFileContent("public/fr/index.html", "Home", "Bonjour") - th.assertFileContent("public/en/index.html", "Home", "Hello") - - // Check list pages - th.assertFileContent("public/fr/sect/index.html", "List", "Bonjour") - th.assertFileContent("public/en/sect/index.html", "List", "Hello") - th.assertFileContent("public/fr/plaques/frtag1/index.html", "List", "Bonjour") - th.assertFileContent("public/en/tags/tag1/index.html", "List", "Hello") - - // Check sitemaps - // Sitemaps behaves different: In a multilanguage setup there will always be a index file and - // one sitemap in each lang folder. - th.assertFileContentStraight("public/sitemap.xml", - "http://example.com/blog/en/sitemap.xml", - "http://example.com/blog/fr/sitemap.xml") - - if defaultInSubDir { - th.assertFileContentStraight("public/fr/sitemap.xml", "http://example.com/blog/fr/") - } else { - th.assertFileContentStraight("public/fr/sitemap.xml", "http://example.com/blog/") - } - th.assertFileContent("public/en/sitemap.xml", "http://example.com/blog/en/") - - // Check rss - th.assertFileContent("public/fr/index.xml", `http://example.com/blog/en/sitemap.xml"), sitemapIndex) - require.True(t, strings.Contains(sitemapIndex, "http://example.com/blog/fr/sitemap.xml"), sitemapIndex) - sitemapEn := readDestination(t, fs, "public/en/sitemap.xml") - sitemapFr := readDestination(t, fs, "public/fr/sitemap.xml") - require.True(t, strings.Contains(sitemapEn, "http://example.com/blog/en/sect/doc2/"), sitemapEn) - require.True(t, strings.Contains(sitemapFr, "http://example.com/blog/fr/sect/doc1/"), sitemapFr) - - // Check taxonomies - enTags := enSite.Taxonomies["tags"] - frTags := frSite.Taxonomies["plaques"] - require.Len(t, enTags, 2, fmt.Sprintf("Tags in en: %v", enTags)) - require.Len(t, frTags, 2, fmt.Sprintf("Tags in fr: %v", frTags)) - require.NotNil(t, enTags["tag1"]) - require.NotNil(t, frTags["frtag1"]) - readDestination(t, fs, "public/fr/plaques/frtag1/index.html") - readDestination(t, fs, "public/en/tags/tag1/index.html") - - // Check Blackfriday config - require.True(t, strings.Contains(string(doc1fr.Content), "«"), string(doc1fr.Content)) - require.False(t, strings.Contains(string(doc1en.Content), "«"), string(doc1en.Content)) - require.True(t, strings.Contains(string(doc1en.Content), "“"), string(doc1en.Content)) - - // Check that the drafts etc. are not built/processed/rendered. - assertShouldNotBuild(t, sites) - - // en and nn have custom site menus - require.Len(t, frSite.Menus, 0, "fr: "+configSuffix) - require.Len(t, enSite.Menus, 1, "en: "+configSuffix) - require.Len(t, nnSite.Menus, 1, "nn: "+configSuffix) - - require.Equal(t, "Home", enSite.Menus["main"].ByName()[0].Name) - require.Equal(t, "Heim", nnSite.Menus["main"].ByName()[0].Name) - - // Issue #1302 - require.Equal(t, template.URL(""), enSite.RegularPages[0].RSSLink()) - - // Issue #3108 - next := enSite.RegularPages[0].Next - require.NotNil(t, next) - require.Equal(t, KindPage, next.Kind) - - for { - if next == nil { - break - } - require.Equal(t, KindPage, next.Kind) - next = next.Next - } - -} - -func TestMultiSitesRebuild(t *testing.T) { - // t.Parallel() not supported, see https://github.com/fortytw2/leaktest/issues/4 - // This leaktest seems to be a little bit shaky on Travis. - if !isCI() { - defer leaktest.CheckTimeout(t, 30*time.Second)() - } - siteConfig := testSiteConfig{Fs: afero.NewMemMapFs(), DefaultContentLanguage: "fr", DefaultContentLanguageInSubdir: true} - sites := createMultiTestSites(t, siteConfig, multiSiteTOMLConfigTemplate) - fs := sites.Fs - cfg := BuildCfg{Watching: true} - th := testHelper{sites.Cfg, fs, t} - - err := sites.Build(cfg) - - if err != nil { - t.Fatalf("Failed to build sites: %s", err) - } - - _, err = fs.Destination.Open("public/en/sect/doc2/index.html") - - if err != nil { - t.Fatalf("Unable to locate file") - } - - enSite := sites.Sites[0] - frSite := sites.Sites[1] - - require.Len(t, enSite.RegularPages, 4) - require.Len(t, frSite.RegularPages, 3) - - // Verify translations - th.assertFileContent("public/en/sect/doc1-slug/index.html", "Hello") - th.assertFileContent("public/fr/sect/doc1/index.html", "Bonjour") - - // check single page content - th.assertFileContent("public/fr/sect/doc1/index.html", "Single", "Shortcode: Bonjour") - th.assertFileContent("public/en/sect/doc1-slug/index.html", "Single", "Shortcode: Hello") - - for i, this := range []struct { - preFunc func(t *testing.T) - events []fsnotify.Event - assertFunc func(t *testing.T) - }{ - // * Remove doc - // * Add docs existing languages - // (Add doc new language: TODO(bep) we should load config.toml as part of these so we can add languages). - // * Rename file - // * Change doc - // * Change a template - // * Change language file - { - nil, - []fsnotify.Event{{Name: "content/sect/doc2.en.md", Op: fsnotify.Remove}}, - func(t *testing.T) { - require.Len(t, enSite.RegularPages, 3, "1 en removed") - - // Check build stats - require.Equal(t, 1, enSite.draftCount, "Draft") - require.Equal(t, 1, enSite.futureCount, "Future") - require.Equal(t, 1, enSite.expiredCount, "Expired") - require.Equal(t, 0, frSite.draftCount, "Draft") - require.Equal(t, 1, frSite.futureCount, "Future") - require.Equal(t, 1, frSite.expiredCount, "Expired") - }, - }, - { - func(t *testing.T) { - writeNewContentFile(t, fs, "new_en_1", "2016-07-31", "content/new1.en.md", -5) - writeNewContentFile(t, fs, "new_en_2", "1989-07-30", "content/new2.en.md", -10) - writeNewContentFile(t, fs, "new_fr_1", "2016-07-30", "content/new1.fr.md", 10) - }, - []fsnotify.Event{ - {Name: "content/new1.en.md", Op: fsnotify.Create}, - {Name: "content/new2.en.md", Op: fsnotify.Create}, - {Name: "content/new1.fr.md", Op: fsnotify.Create}, - }, - func(t *testing.T) { - require.Len(t, enSite.RegularPages, 5) - require.Len(t, enSite.AllPages, 30) - require.Len(t, frSite.RegularPages, 4) - require.Equal(t, "new_fr_1", frSite.RegularPages[3].Title) - require.Equal(t, "new_en_2", enSite.RegularPages[0].Title) - require.Equal(t, "new_en_1", enSite.RegularPages[1].Title) - - rendered := readDestination(t, fs, "public/en/new1/index.html") - require.True(t, strings.Contains(rendered, "new_en_1"), rendered) - }, - }, - { - func(t *testing.T) { - p := "content/sect/doc1.en.md" - doc1 := readSource(t, fs, p) - doc1 += "CHANGED" - writeSource(t, fs, p, doc1) - }, - []fsnotify.Event{{Name: "content/sect/doc1.en.md", Op: fsnotify.Write}}, - func(t *testing.T) { - require.Len(t, enSite.RegularPages, 5) - doc1 := readDestination(t, fs, "public/en/sect/doc1-slug/index.html") - require.True(t, strings.Contains(doc1, "CHANGED"), doc1) - - }, - }, - // Rename a file - { - func(t *testing.T) { - if err := fs.Source.Rename("content/new1.en.md", "content/new1renamed.en.md"); err != nil { - t.Fatalf("Rename failed: %s", err) - } - }, - []fsnotify.Event{ - {Name: "content/new1renamed.en.md", Op: fsnotify.Rename}, - {Name: "content/new1.en.md", Op: fsnotify.Rename}, - }, - func(t *testing.T) { - require.Len(t, enSite.RegularPages, 5, "Rename") - require.Equal(t, "new_en_1", enSite.RegularPages[1].Title) - rendered := readDestination(t, fs, "public/en/new1renamed/index.html") - require.True(t, strings.Contains(rendered, "new_en_1"), rendered) - }}, - { - // Change a template - func(t *testing.T) { - template := "layouts/_default/single.html" - templateContent := readSource(t, fs, template) - templateContent += "{{ print \"Template Changed\"}}" - writeSource(t, fs, template, templateContent) - }, - []fsnotify.Event{{Name: "layouts/_default/single.html", Op: fsnotify.Write}}, - func(t *testing.T) { - require.Len(t, enSite.RegularPages, 5) - require.Len(t, enSite.AllPages, 30) - require.Len(t, frSite.RegularPages, 4) - doc1 := readDestination(t, fs, "public/en/sect/doc1-slug/index.html") - require.True(t, strings.Contains(doc1, "Template Changed"), doc1) - }, - }, - { - // Change a language file - func(t *testing.T) { - languageFile := "i18n/fr.yaml" - langContent := readSource(t, fs, languageFile) - langContent = strings.Replace(langContent, "Bonjour", "Salut", 1) - writeSource(t, fs, languageFile, langContent) - }, - []fsnotify.Event{{Name: "i18n/fr.yaml", Op: fsnotify.Write}}, - func(t *testing.T) { - require.Len(t, enSite.RegularPages, 5) - require.Len(t, enSite.AllPages, 30) - require.Len(t, frSite.RegularPages, 4) - docEn := readDestination(t, fs, "public/en/sect/doc1-slug/index.html") - require.True(t, strings.Contains(docEn, "Hello"), "No Hello") - docFr := readDestination(t, fs, "public/fr/sect/doc1/index.html") - require.True(t, strings.Contains(docFr, "Salut"), "No Salut") - - homeEn := enSite.getPage(KindHome) - require.NotNil(t, homeEn) - require.Len(t, homeEn.Translations(), 3) - require.Equal(t, "fr", homeEn.Translations()[0].Lang()) - - }, - }, - // Change a shortcode - { - func(t *testing.T) { - writeSource(t, fs, "layouts/shortcodes/shortcode.html", "Modified Shortcode: {{ i18n \"hello\" }}") - }, - []fsnotify.Event{ - {Name: "layouts/shortcodes/shortcode.html", Op: fsnotify.Write}, - }, - func(t *testing.T) { - require.Len(t, enSite.RegularPages, 5) - require.Len(t, enSite.AllPages, 30) - require.Len(t, frSite.RegularPages, 4) - th.assertFileContent("public/fr/sect/doc1/index.html", "Single", "Modified Shortcode: Salut") - th.assertFileContent("public/en/sect/doc1-slug/index.html", "Single", "Modified Shortcode: Hello") - }, - }, - } { - - if this.preFunc != nil { - this.preFunc(t) - } - - err = sites.Build(cfg, this.events...) - - if err != nil { - t.Fatalf("[%d] Failed to rebuild sites: %s", i, err) - } - - this.assertFunc(t) - } - - // Check that the drafts etc. are not built/processed/rendered. - assertShouldNotBuild(t, sites) - -} - -func assertShouldNotBuild(t *testing.T, sites *HugoSites) { - s := sites.Sites[0] - - for _, p := range s.rawAllPages { - // No HTML when not processed - require.Equal(t, p.shouldBuild(), bytes.Contains(p.workContent, []byte("}} - -# Heading 1 {#1} - -Some text. - -## Subheading 1.1 {#1-1} - -Some more text. - -# Heading 2 {#2} - -Even more text. - -## Subheading 2.1 {#2-1} - -Lorem ipsum... -` - -var tocPageSimpleExpected = `` - -var tocPageWithShortcodesInHeadings = `--- -title: tocTest -publishdate: "2000-01-01" ---- - -{{< toc >}} - -# Heading 1 {#1} - -Some text. - -## Subheading 1.1 {{< shortcode >}} {#1-1} - -Some more text. - -# Heading 2 {{% shortcode %}} {#2} - -Even more text. - -## Subheading 2.1 {#2-1} - -Lorem ipsum... -` - -var tocPageWithShortcodesInHeadingsExpected = `` - -var multiSiteTOMLConfigTemplate = ` -baseURL = "http://example.com/blog" -disableSitemap = false -disableRSS = false -rssURI = "index.xml" - -paginate = 1 -disablePathToLower = true -defaultContentLanguage = "{{ .DefaultContentLanguage }}" -defaultContentLanguageInSubdir = {{ .DefaultContentLanguageInSubdir }} - -[permalinks] -other = "/somewhere/else/:filename" - -[blackfriday] -angledQuotes = true - -[Taxonomies] -tag = "tags" - -[Languages] -[Languages.en] -weight = 10 -title = "In English" -languageName = "English" -[Languages.en.blackfriday] -angledQuotes = false -[[Languages.en.menu.main]] -url = "/" -name = "Home" -weight = 0 - -[Languages.fr] -weight = 20 -title = "Le Français" -languageName = "Français" -[Languages.fr.Taxonomies] -plaque = "plaques" - -[Languages.nn] -weight = 30 -title = "På nynorsk" -languageName = "Nynorsk" -paginatePath = "side" -[Languages.nn.Taxonomies] -lag = "lag" -[[Languages.nn.menu.main]] -url = "/" -name = "Heim" -weight = 1 - -[Languages.nb] -weight = 40 -title = "På bokmål" -languageName = "Bokmål" -paginatePath = "side" -[Languages.nb.Taxonomies] -lag = "lag" -` - -var multiSiteYAMLConfigTemplate = ` -baseURL: "http://example.com/blog" -disableSitemap: false -disableRSS: false -rssURI: "index.xml" - -disablePathToLower: true -paginate: 1 -defaultContentLanguage: "{{ .DefaultContentLanguage }}" -defaultContentLanguageInSubdir: {{ .DefaultContentLanguageInSubdir }} - -permalinks: - other: "/somewhere/else/:filename" - -blackfriday: - angledQuotes: true - -Taxonomies: - tag: "tags" - -Languages: - en: - weight: 10 - title: "In English" - languageName: "English" - blackfriday: - angledQuotes: false - menu: - main: - - url: "/" - name: "Home" - weight: 0 - fr: - weight: 20 - title: "Le Français" - languageName: "Français" - Taxonomies: - plaque: "plaques" - nn: - weight: 30 - title: "På nynorsk" - languageName: "Nynorsk" - paginatePath: "side" - Taxonomies: - lag: "lag" - menu: - main: - - url: "/" - name: "Heim" - weight: 1 - nb: - weight: 40 - title: "På bokmål" - languageName: "Bokmål" - paginatePath: "side" - Taxonomies: - lag: "lag" - -` - -var multiSiteJSONConfigTemplate = ` -{ - "baseURL": "http://example.com/blog", - "disableSitemap": false, - "disableRSS": false, - "rssURI": "index.xml", - "paginate": 1, - "disablePathToLower": true, - "defaultContentLanguage": "{{ .DefaultContentLanguage }}", - "defaultContentLanguageInSubdir": true, - "permalinks": { - "other": "/somewhere/else/:filename" - }, - "blackfriday": { - "angledQuotes": true - }, - "Taxonomies": { - "tag": "tags" - }, - "Languages": { - "en": { - "weight": 10, - "title": "In English", - "languageName": "English", - "blackfriday": { - "angledQuotes": false - }, - "menu": { - "main": [ - { - "url": "/", - "name": "Home", - "weight": 0 - } - ] - } - }, - "fr": { - "weight": 20, - "title": "Le Français", - "languageName": "Français", - "Taxonomies": { - "plaque": "plaques" - } - }, - "nn": { - "weight": 30, - "title": "På nynorsk", - "paginatePath": "side", - "languageName": "Nynorsk", - "Taxonomies": { - "lag": "lag" - }, - "menu": { - "main": [ - { - "url": "/", - "name": "Heim", - "weight": 1 - } - ] - } - }, - "nb": { - "weight": 40, - "title": "På bokmål", - "paginatePath": "side", - "languageName": "Bokmål", - "Taxonomies": { - "lag": "lag" - } - } - } -} -` - -func createMultiTestSites(t *testing.T, siteConfig testSiteConfig, tomlConfigTemplate string) *HugoSites { - return createMultiTestSitesForConfig(t, siteConfig, tomlConfigTemplate, "toml") -} - -func createMultiTestSitesForConfig(t *testing.T, siteConfig testSiteConfig, configTemplate, configSuffix string) *HugoSites { - - configContent := createConfig(t, siteConfig, configTemplate) - - mf := siteConfig.Fs - - // Add some layouts - if err := afero.WriteFile(mf, - filepath.Join("layouts", "_default/single.html"), - []byte("Single: {{ .Title }}|{{ i18n \"hello\" }}|{{.Lang}}|{{ .Content }}"), - 0755); err != nil { - t.Fatalf("Failed to write layout file: %s", err) - } - - if err := afero.WriteFile(mf, - filepath.Join("layouts", "_default/list.html"), - []byte("{{ $p := .Paginator }}List Page {{ $p.PageNumber }}: {{ .Title }}|{{ i18n \"hello\" }}|{{ .Permalink }}"), - 0755); err != nil { - t.Fatalf("Failed to write layout file: %s", err) - } - - if err := afero.WriteFile(mf, - filepath.Join("layouts", "index.html"), - []byte("{{ $p := .Paginator }}Default Home Page {{ $p.PageNumber }}: {{ .Title }}|{{ .IsHome }}|{{ i18n \"hello\" }}|{{ .Permalink }}|{{ .Site.Data.hugo.slogan }}"), - 0755); err != nil { - t.Fatalf("Failed to write layout file: %s", err) - } - - if err := afero.WriteFile(mf, - filepath.Join("layouts", "index.fr.html"), - []byte("{{ $p := .Paginator }}French Home Page {{ $p.PageNumber }}: {{ .Title }}|{{ .IsHome }}|{{ i18n \"hello\" }}|{{ .Permalink }}|{{ .Site.Data.hugo.slogan }}"), - 0755); err != nil { - t.Fatalf("Failed to write layout file: %s", err) - } - - // Add a shortcode - if err := afero.WriteFile(mf, - filepath.Join("layouts", "shortcodes", "shortcode.html"), - []byte("Shortcode: {{ i18n \"hello\" }}"), - 0755); err != nil { - t.Fatalf("Failed to write layout file: %s", err) - } - - // A shortcode in multiple languages - if err := afero.WriteFile(mf, - filepath.Join("layouts", "shortcodes", "lingo.html"), - []byte("LingoDefault"), - 0755); err != nil { - t.Fatalf("Failed to write layout file: %s", err) - } - - if err := afero.WriteFile(mf, - filepath.Join("layouts", "shortcodes", "lingo.fr.html"), - []byte("LingoFrench"), - 0755); err != nil { - t.Fatalf("Failed to write layout file: %s", err) - } - - // Add some language files - if err := afero.WriteFile(mf, - filepath.Join("i18n", "en.yaml"), - []byte(` -hello: - other: "Hello" -`), - 0755); err != nil { - t.Fatalf("Failed to write language file: %s", err) - } - if err := afero.WriteFile(mf, - filepath.Join("i18n", "fr.yaml"), - []byte(` -hello: - other: "Bonjour" -`), - 0755); err != nil { - t.Fatalf("Failed to write language file: %s", err) - } - - // Sources - sources := []source.ByteSource{ - {Name: filepath.FromSlash("root.en.md"), Content: []byte(`--- -title: root -weight: 10000 -slug: root -publishdate: "2000-01-01" ---- -# root -`)}, - {Name: filepath.FromSlash("sect/doc1.en.md"), Content: []byte(`--- -title: doc1 -weight: 1 -slug: doc1-slug -tags: - - tag1 -publishdate: "2000-01-01" ---- -# doc1 -*some "content"* - -{{< shortcode >}} - -{{< lingo >}} - -NOTE: slug should be used as URL -`)}, - {Name: filepath.FromSlash("sect/doc1.fr.md"), Content: []byte(`--- -title: doc1 -weight: 1 -plaques: - - frtag1 - - frtag2 -publishdate: "2000-01-04" ---- -# doc1 -*quelque "contenu"* - -{{< shortcode >}} - -{{< lingo >}} - -NOTE: should be in the 'en' Page's 'Translations' field. -NOTE: date is after "doc3" -`)}, - {Name: filepath.FromSlash("sect/doc2.en.md"), Content: []byte(`--- -title: doc2 -weight: 2 -publishdate: "2000-01-02" ---- -# doc2 -*some content* -NOTE: without slug, "doc2" should be used, without ".en" as URL -`)}, - {Name: filepath.FromSlash("sect/doc3.en.md"), Content: []byte(`--- -title: doc3 -weight: 3 -publishdate: "2000-01-03" -tags: - - tag2 - - tag1 -url: /superbob ---- -# doc3 -*some content* -NOTE: third 'en' doc, should trigger pagination on home page. -`)}, - {Name: filepath.FromSlash("sect/doc4.md"), Content: []byte(`--- -title: doc4 -weight: 4 -plaques: - - frtag1 -publishdate: "2000-01-05" ---- -# doc4 -*du contenu francophone* -NOTE: should use the defaultContentLanguage and mark this doc as 'fr'. -NOTE: doesn't have any corresponding translation in 'en' -`)}, - {Name: filepath.FromSlash("other/doc5.fr.md"), Content: []byte(`--- -title: doc5 -weight: 5 -publishdate: "2000-01-06" ---- -# doc5 -*autre contenu francophone* -NOTE: should use the "permalinks" configuration with :filename -`)}, - // Add some for the stats - {Name: filepath.FromSlash("stats/expired.fr.md"), Content: []byte(`--- -title: expired -publishdate: "2000-01-06" -expiryDate: "2001-01-06" ---- -# Expired -`)}, - {Name: filepath.FromSlash("stats/future.fr.md"), Content: []byte(`--- -title: future -weight: 6 -publishdate: "2100-01-06" ---- -# Future -`)}, - {Name: filepath.FromSlash("stats/expired.en.md"), Content: []byte(`--- -title: expired -weight: 7 -publishdate: "2000-01-06" -expiryDate: "2001-01-06" ---- -# Expired -`)}, - {Name: filepath.FromSlash("stats/future.en.md"), Content: []byte(`--- -title: future -weight: 6 -publishdate: "2100-01-06" ---- -# Future -`)}, - {Name: filepath.FromSlash("stats/draft.en.md"), Content: []byte(`--- -title: expired -publishdate: "2000-01-06" -draft: true ---- -# Draft -`)}, - {Name: filepath.FromSlash("stats/tax.nn.md"), Content: []byte(`--- -title: Tax NN -weight: 8 -publishdate: "2000-01-06" -weight: 1001 -lag: -- Sogndal ---- -# Tax NN -`)}, - {Name: filepath.FromSlash("stats/tax.nb.md"), Content: []byte(`--- -title: Tax NB -weight: 8 -publishdate: "2000-01-06" -weight: 1002 -lag: -- Sogndal ---- -# Tax NB -`)}, - } - - configFile := "multilangconfig." + configSuffix - writeToFs(t, mf, configFile, configContent) - - cfg, err := LoadConfig(mf, "", configFile) - require.NoError(t, err) - - fs := hugofs.NewFrom(mf, cfg) - - // Hugo support using ByteSource's directly (for testing), - // but to make it more real, we write them to the mem file system. - for _, s := range sources { - if err := afero.WriteFile(mf, filepath.Join("content", s.Name), s.Content, 0755); err != nil { - t.Fatalf("Failed to write file: %s", err) - } - } - - // Add some data - writeSource(t, fs, "data/hugo.toml", "slogan = \"Hugo Rocks!\"") - - sites, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg}) //, Logger: newDebugLogger()}) - - if err != nil { - t.Fatalf("Failed to create sites: %s", err) - } - - if len(sites.Sites) != 4 { - t.Fatalf("Got %d sites", len(sites.Sites)) - } - - if sites.Fs.Source != mf { - t.Fatal("FS mismatch") - } - - return sites -} - -func writeSource(t testing.TB, fs *hugofs.Fs, filename, content string) { - writeToFs(t, fs.Source, filename, content) -} - -func writeToFs(t testing.TB, fs afero.Fs, filename, content string) { - if err := afero.WriteFile(fs, filepath.FromSlash(filename), []byte(content), 0755); err != nil { - t.Fatalf("Failed to write file: %s", err) - } -} - -func readDestination(t testing.TB, fs *hugofs.Fs, filename string) string { - return readFileFromFs(t, fs.Destination, filename) -} - -func destinationExists(fs *hugofs.Fs, filename string) bool { - b, err := helpers.Exists(filename, fs.Destination) - if err != nil { - panic(err) - } - return b -} - -func readSource(t *testing.T, fs *hugofs.Fs, filename string) string { - return readFileFromFs(t, fs.Source, filename) -} - -func readFileFromFs(t testing.TB, fs afero.Fs, filename string) string { - filename = filepath.FromSlash(filename) - b, err := afero.ReadFile(fs, filename) - if err != nil { - // Print some debug info - root := strings.Split(filename, helpers.FilePathSeparator)[0] - afero.Walk(fs, root, func(path string, info os.FileInfo, err error) error { - if info != nil && !info.IsDir() { - fmt.Println(" ", path) - } - - return nil - }) - t.Fatalf("Failed to read file: %s", err) - } - return string(b) -} - -const testPageTemplate = `--- -title: "%s" -publishdate: "%s" -weight: %d ---- -# Doc %s -` - -func newTestPage(title, date string, weight int) string { - return fmt.Sprintf(testPageTemplate, title, date, weight, title) -} - -func writeNewContentFile(t *testing.T, fs *hugofs.Fs, title, date, filename string, weight int) { - content := newTestPage(title, date, weight) - writeSource(t, fs, filename, content) -} - -func createConfig(t *testing.T, config testSiteConfig, configTemplate string) string { - templ, err := template.New("test").Parse(configTemplate) - if err != nil { - t.Fatal("Template parse failed:", err) - } - var b bytes.Buffer - templ.Execute(&b, config) - return b.String() -} diff --git a/hugolib/media.go b/hugolib/media.go deleted file mode 100644 index aae9a7870..000000000 --- a/hugolib/media.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -// An Image contains metadata for images + image sitemaps -// https://support.google.com/webmasters/answer/178636?hl=en -type Image struct { - - // The URL of the image. In some cases, the image URL may not be on the - // same domain as your main site. This is fine, as long as both domains - // are verified in Webmaster Tools. If, for example, you use a - // content delivery network (CDN) to host your images, make sure that the - // hosting site is verified in Webmaster Tools OR that you submit your - // sitemap using robots.txt. In addition, make sure that your robots.txt - // file doesn’t disallow the crawling of any content you want indexed. - URL string - Title string - Caption string - AltText string - - // The geographic location of the image. For example, - // Limerick, Ireland. - GeoLocation string - - // A URL to the license of the image. - License string -} - -// A Video contains metadata for videos + video sitemaps -// https://support.google.com/webmasters/answer/80471?hl=en -type Video struct { - ThumbnailLoc string - Title string - Description string - ContentLoc string - PlayerLoc string - Duration string - ExpirationDate string - Rating string - ViewCount string - PublicationDate string - FamilyFriendly string - Restriction string - GalleryLoc string - Price string - RequiresSubscription string - Uploader string - Live string -} diff --git a/hugolib/menu.go b/hugolib/menu.go deleted file mode 100644 index 4f6bd2b4e..000000000 --- a/hugolib/menu.go +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "html/template" - "sort" - "strings" - - "github.com/spf13/cast" -) - -// MenuEntry represents a menu item defined in either Page front matter -// or in the site config. -type MenuEntry struct { - URL string - Name string - Menu string - Identifier string - Pre template.HTML - Post template.HTML - Weight int - Parent string - Children Menu -} - -// Menu is a collection of menu entries. -type Menu []*MenuEntry - -// Menus is a dictionary of menus. -type Menus map[string]*Menu - -// PageMenus is a dictionary of menus defined in the Pages. -type PageMenus map[string]*MenuEntry - -// addChild adds a new child to this menu entry. -// The default sort order will then be applied. -func (m *MenuEntry) addChild(child *MenuEntry) { - m.Children = append(m.Children, child) - m.Children.Sort() -} - -// HasChildren returns whether this menu item has any children. -func (m *MenuEntry) HasChildren() bool { - return m.Children != nil -} - -// KeyName returns the key used to identify this menu entry. -func (m *MenuEntry) KeyName() string { - if m.Identifier != "" { - return m.Identifier - } - return m.Name -} - -func (m *MenuEntry) hopefullyUniqueID() string { - if m.Identifier != "" { - return m.Identifier - } else if m.URL != "" { - return m.URL - } else { - return m.Name - } -} - -// IsEqual returns whether the two menu entries represents the same menu entry. -func (m *MenuEntry) IsEqual(inme *MenuEntry) bool { - return m.hopefullyUniqueID() == inme.hopefullyUniqueID() && m.Parent == inme.Parent -} - -// IsSameResource returns whether the two menu entries points to the same -// resource (URL). -func (m *MenuEntry) IsSameResource(inme *MenuEntry) bool { - return m.URL != "" && inme.URL != "" && m.URL == inme.URL -} - -func (m *MenuEntry) marshallMap(ime map[string]interface{}) { - for k, v := range ime { - loki := strings.ToLower(k) - switch loki { - case "url": - m.URL = cast.ToString(v) - case "weight": - m.Weight = cast.ToInt(v) - case "name": - m.Name = cast.ToString(v) - case "pre": - m.Pre = template.HTML(cast.ToString(v)) - case "post": - m.Post = template.HTML(cast.ToString(v)) - case "identifier": - m.Identifier = cast.ToString(v) - case "parent": - m.Parent = cast.ToString(v) - } - } -} - -func (m Menu) add(me *MenuEntry) Menu { - app := func(slice Menu, x ...*MenuEntry) Menu { - n := len(slice) + len(x) - if n > cap(slice) { - size := cap(slice) * 2 - if size < n { - size = n - } - new := make(Menu, size) - copy(new, slice) - slice = new - } - slice = slice[0:n] - copy(slice[n-len(x):], x) - return slice - } - - m = app(m, me) - m.Sort() - return m -} - -/* - * Implementation of a custom sorter for Menu - */ - -// A type to implement the sort interface for Menu -type menuSorter struct { - menu Menu - by menuEntryBy -} - -// Closure used in the Sort.Less method. -type menuEntryBy func(m1, m2 *MenuEntry) bool - -func (by menuEntryBy) Sort(menu Menu) { - ms := &menuSorter{ - menu: menu, - by: by, // The Sort method's receiver is the function (closure) that defines the sort order. - } - sort.Stable(ms) -} - -var defaultMenuEntrySort = func(m1, m2 *MenuEntry) bool { - if m1.Weight == m2.Weight { - if m1.Name == m2.Name { - return m1.Identifier < m2.Identifier - } - return m1.Name < m2.Name - } - - if m2.Weight == 0 { - return true - } - - if m1.Weight == 0 { - return false - } - - return m1.Weight < m2.Weight -} - -func (ms *menuSorter) Len() int { return len(ms.menu) } -func (ms *menuSorter) Swap(i, j int) { ms.menu[i], ms.menu[j] = ms.menu[j], ms.menu[i] } - -// Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter. -func (ms *menuSorter) Less(i, j int) bool { return ms.by(ms.menu[i], ms.menu[j]) } - -// Sort sorts the menu by weight, name and then by identifier. -func (m Menu) Sort() Menu { - menuEntryBy(defaultMenuEntrySort).Sort(m) - return m -} - -// Limit limits the returned menu to n entries. -func (m Menu) Limit(n int) Menu { - if len(m) > n { - return m[0:n] - } - return m -} - -// ByWeight sorts the menu by the weight defined in the menu configuration. -func (m Menu) ByWeight() Menu { - menuEntryBy(defaultMenuEntrySort).Sort(m) - return m -} - -// ByName sorts the menu by the name defined in the menu configuration. -func (m Menu) ByName() Menu { - title := func(m1, m2 *MenuEntry) bool { - return m1.Name < m2.Name - } - - menuEntryBy(title).Sort(m) - return m -} - -// Reverse reverses the order of the menu entries. -func (m Menu) Reverse() Menu { - for i, j := 0, len(m)-1; i < j; i, j = i+1, j-1 { - m[i], m[j] = m[j], m[i] - } - - return m -} diff --git a/hugolib/menu_old_test.go b/hugolib/menu_old_test.go deleted file mode 100644 index 7c49ed908..000000000 --- a/hugolib/menu_old_test.go +++ /dev/null @@ -1,642 +0,0 @@ -// Copyright 2016 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -// TODO(bep) remove this file when the reworked tests in menu_test.go is done. -// NOTE: Do not add more tests to this file! - -import ( - "fmt" - "strings" - "testing" - - "github.com/gohugoio/hugo/deps" - - "path/filepath" - - "github.com/BurntSushi/toml" - "github.com/gohugoio/hugo/source" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const ( - confMenu1 = ` -[[menu.main]] - name = "Go Home" - url = "/" - weight = 1 - pre = "
    " - post = "
    " -[[menu.main]] - name = "Blog" - url = "/posts" -[[menu.main]] - name = "ext" - url = "http://gohugo.io" - identifier = "ext" -[[menu.main]] - name = "ext2" - url = "http://foo.local/Zoo/foo" - identifier = "ext2" -[[menu.grandparent]] - name = "grandparent" - url = "/grandparent" - identifier = "grandparentId" -[[menu.grandparent]] - name = "parent" - url = "/parent" - identifier = "parentId" - parent = "grandparentId" -[[menu.grandparent]] - name = "Go Home3" - url = "/" - identifier = "grandchildId" - parent = "parentId" -[[menu.tax]] - name = "Tax1" - url = "/two/key/" - identifier="1" -[[menu.tax]] - name = "Tax2" - url = "/two/key/" - identifier="2" -[[menu.tax]] - name = "Tax RSS" - url = "/two/key.xml" - identifier="xml" -[[menu.hash]] - name = "Tax With #" - url = "/resource#anchor" - identifier="hash" -[[menu.unicode]] - name = "Unicode Russian" - identifier = "unicode-russian" - url = "/новости-проекта"` // Russian => "news-project" -) - -var menuPage1 = []byte(`+++ -title = "One" -weight = 1 -[menu] - [menu.p_one] -+++ -Front Matter with Menu Pages`) - -var menuPage2 = []byte(`+++ -title = "Two" -weight = 2 -[menu] - [menu.p_one] - [menu.p_two] - identifier = "Two" - -+++ -Front Matter with Menu Pages`) - -var menuPage3 = []byte(`+++ -title = "Three" -weight = 3 -[menu] - [menu.p_two] - Name = "Three" - Parent = "Two" -+++ -Front Matter with Menu Pages`) - -var menuPage4 = []byte(`+++ -title = "Four" -weight = 4 -[menu] - [menu.p_two] - Name = "Four" - Parent = "Three" -+++ -Front Matter with Menu Pages`) - -var menuPageSources = []source.ByteSource{ - {Name: filepath.FromSlash("sect/doc1.md"), Content: menuPage1}, - {Name: filepath.FromSlash("sect/doc2.md"), Content: menuPage2}, - {Name: filepath.FromSlash("sect/doc3.md"), Content: menuPage3}, -} - -var menuPageSectionsSources = []source.ByteSource{ - {Name: filepath.FromSlash("first/doc1.md"), Content: menuPage1}, - {Name: filepath.FromSlash("first/doc2.md"), Content: menuPage2}, - {Name: filepath.FromSlash("second-section/doc3.md"), Content: menuPage3}, - {Name: filepath.FromSlash("Fish and Chips/doc4.md"), Content: menuPage4}, -} - -func tstCreateMenuPageWithNameTOML(title, menu, name string) []byte { - return []byte(fmt.Sprintf(`+++ -title = "%s" -weight = 1 -[menu] - [menu.%s] - name = "%s" -+++ -Front Matter with Menu with Name`, title, menu, name)) -} - -func tstCreateMenuPageWithIdentifierTOML(title, menu, identifier string) []byte { - return []byte(fmt.Sprintf(`+++ -title = "%s" -weight = 1 -[menu] - [menu.%s] - identifier = "%s" - name = "somename" -+++ -Front Matter with Menu with Identifier`, title, menu, identifier)) -} - -func tstCreateMenuPageWithNameYAML(title, menu, name string) []byte { - return []byte(fmt.Sprintf(`--- -title: "%s" -weight: 1 -menu: - %s: - name: "%s" ---- -Front Matter with Menu with Name`, title, menu, name)) -} - -func tstCreateMenuPageWithIdentifierYAML(title, menu, identifier string) []byte { - return []byte(fmt.Sprintf(`--- -title: "%s" -weight: 1 -menu: - %s: - identifier: "%s" - name: "somename" ---- -Front Matter with Menu with Identifier`, title, menu, identifier)) -} - -// Issue 817 - identifier should trump everything -func TestPageMenuWithIdentifier(t *testing.T) { - t.Parallel() - toml := []source.ByteSource{ - {Name: "sect/doc1.md", Content: tstCreateMenuPageWithIdentifierTOML("t1", "m1", "i1")}, - {Name: "sect/doc2.md", Content: tstCreateMenuPageWithIdentifierTOML("t1", "m1", "i2")}, - {Name: "sect/doc3.md", Content: tstCreateMenuPageWithIdentifierTOML("t1", "m1", "i2")}, // duplicate - } - - yaml := []source.ByteSource{ - {Name: "sect/doc1.md", Content: tstCreateMenuPageWithIdentifierYAML("t1", "m1", "i1")}, - {Name: "sect/doc2.md", Content: tstCreateMenuPageWithIdentifierYAML("t1", "m1", "i2")}, - {Name: "sect/doc3.md", Content: tstCreateMenuPageWithIdentifierYAML("t1", "m1", "i2")}, // duplicate - } - - doTestPageMenuWithIdentifier(t, toml) - doTestPageMenuWithIdentifier(t, yaml) - -} - -func doTestPageMenuWithIdentifier(t *testing.T, menuPageSources []source.ByteSource) { - - s := setupMenuTests(t, menuPageSources) - - assert.Equal(t, 3, len(s.RegularPages), "Not enough pages") - - me1 := findTestMenuEntryByID(s, "m1", "i1") - me2 := findTestMenuEntryByID(s, "m1", "i2") - - require.NotNil(t, me1) - require.NotNil(t, me2) - - assert.True(t, strings.Contains(me1.URL, "doc1"), me1.URL) - assert.True(t, strings.Contains(me2.URL, "doc2") || strings.Contains(me2.URL, "doc3"), me2.URL) - -} - -// Issue 817 contd - name should be second identifier in -func TestPageMenuWithDuplicateName(t *testing.T) { - t.Parallel() - toml := []source.ByteSource{ - {Name: "sect/doc1.md", Content: tstCreateMenuPageWithNameTOML("t1", "m1", "n1")}, - {Name: "sect/doc2.md", Content: tstCreateMenuPageWithNameTOML("t1", "m1", "n2")}, - {Name: "sect/doc3.md", Content: tstCreateMenuPageWithNameTOML("t1", "m1", "n2")}, // duplicate - } - - yaml := []source.ByteSource{ - {Name: "sect/doc1.md", Content: tstCreateMenuPageWithNameYAML("t1", "m1", "n1")}, - {Name: "sect/doc2.md", Content: tstCreateMenuPageWithNameYAML("t1", "m1", "n2")}, - {Name: "sect/doc3.md", Content: tstCreateMenuPageWithNameYAML("t1", "m1", "n2")}, // duplicate - } - - doTestPageMenuWithDuplicateName(t, toml) - doTestPageMenuWithDuplicateName(t, yaml) - -} - -func doTestPageMenuWithDuplicateName(t *testing.T, menuPageSources []source.ByteSource) { - - s := setupMenuTests(t, menuPageSources) - - assert.Equal(t, 3, len(s.RegularPages), "Not enough pages") - - me1 := findTestMenuEntryByName(s, "m1", "n1") - me2 := findTestMenuEntryByName(s, "m1", "n2") - - require.NotNil(t, me1) - require.NotNil(t, me2) - - assert.True(t, strings.Contains(me1.URL, "doc1"), me1.URL) - assert.True(t, strings.Contains(me2.URL, "doc2") || strings.Contains(me2.URL, "doc3"), me2.URL) - -} - -func TestPageMenu(t *testing.T) { - t.Parallel() - s := setupMenuTests(t, menuPageSources) - - if len(s.RegularPages) != 3 { - t.Fatalf("Posts not created, expected 3 got %d", len(s.RegularPages)) - } - - first := s.RegularPages[0] - second := s.RegularPages[1] - third := s.RegularPages[2] - - pOne := findTestMenuEntryByName(s, "p_one", "One") - pTwo := findTestMenuEntryByID(s, "p_two", "Two") - - for i, this := range []struct { - menu string - page *Page - menuItem *MenuEntry - isMenuCurrent bool - hasMenuCurrent bool - }{ - {"p_one", first, pOne, true, false}, - {"p_one", first, pTwo, false, false}, - {"p_one", second, pTwo, false, false}, - {"p_two", second, pTwo, true, false}, - {"p_two", third, pTwo, false, true}, - {"p_one", third, pTwo, false, false}, - } { - - if i != 4 { - continue - } - - isMenuCurrent := this.page.IsMenuCurrent(this.menu, this.menuItem) - hasMenuCurrent := this.page.HasMenuCurrent(this.menu, this.menuItem) - - if isMenuCurrent != this.isMenuCurrent { - t.Errorf("[%d] Wrong result from IsMenuCurrent: %v", i, isMenuCurrent) - } - - if hasMenuCurrent != this.hasMenuCurrent { - t.Errorf("[%d] Wrong result for menuItem %v for HasMenuCurrent: %v", i, this.menuItem, hasMenuCurrent) - } - - } - -} - -func TestMenuURL(t *testing.T) { - t.Parallel() - s := setupMenuTests(t, menuPageSources) - - for i, this := range []struct { - me *MenuEntry - expectedURL string - }{ - // issue #888 - {findTestMenuEntryByID(s, "hash", "hash"), "/Zoo/resource#anchor"}, - // issue #1774 - {findTestMenuEntryByID(s, "main", "ext"), "http://gohugo.io"}, - {findTestMenuEntryByID(s, "main", "ext2"), "http://foo.local/Zoo/foo"}, - } { - - if this.me == nil { - t.Errorf("[%d] MenuEntry not found", i) - continue - } - - if this.me.URL != this.expectedURL { - t.Errorf("[%d] Got URL %s expected %s", i, this.me.URL, this.expectedURL) - } - - } - -} - -// Issue #1934 -func TestYAMLMenuWithMultipleEntries(t *testing.T) { - t.Parallel() - ps1 := []byte(`--- -title: "Yaml 1" -weight: 5 -menu: ["p_one", "p_two"] ---- -Yaml Front Matter with Menu Pages`) - - ps2 := []byte(`--- -title: "Yaml 2" -weight: 5 -menu: - p_three: - p_four: ---- -Yaml Front Matter with Menu Pages`) - - s := setupMenuTests(t, []source.ByteSource{ - {Name: filepath.FromSlash("sect/yaml1.md"), Content: ps1}, - {Name: filepath.FromSlash("sect/yaml2.md"), Content: ps2}}) - - p1 := s.RegularPages[0] - assert.Len(t, p1.Menus(), 2, "List YAML") - p2 := s.RegularPages[1] - assert.Len(t, p2.Menus(), 2, "Map YAML") - -} - -// issue #719 -func TestMenuWithUnicodeURLs(t *testing.T) { - t.Parallel() - for _, canonifyURLs := range []bool{true, false} { - doTestMenuWithUnicodeURLs(t, canonifyURLs) - } -} - -func doTestMenuWithUnicodeURLs(t *testing.T, canonifyURLs bool) { - - s := setupMenuTests(t, menuPageSources, "canonifyURLs", canonifyURLs) - - unicodeRussian := findTestMenuEntryByID(s, "unicode", "unicode-russian") - - expected := "/%D0%BD%D0%BE%D0%B2%D0%BE%D1%81%D1%82%D0%B8-%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B0" - - if !canonifyURLs { - expected = "/Zoo" + expected - } - - assert.Equal(t, expected, unicodeRussian.URL) -} - -// Issue #1114 -func TestSectionPagesMenu2(t *testing.T) { - t.Parallel() - doTestSectionPagesMenu(true, t) - doTestSectionPagesMenu(false, t) -} - -func doTestSectionPagesMenu(canonifyURLs bool, t *testing.T) { - - s := setupMenuTests(t, menuPageSectionsSources, - "sectionPagesMenu", "spm", - "canonifyURLs", canonifyURLs, - ) - - sects := s.getPage(KindHome).Sections() - - require.Equal(t, 3, len(sects)) - - firstSectionPages := s.getPage(KindSection, "first").Pages - require.Equal(t, 2, len(firstSectionPages)) - secondSectionPages := s.getPage(KindSection, "second-section").Pages - require.Equal(t, 1, len(secondSectionPages)) - fishySectionPages := s.getPage(KindSection, "Fish and Chips").Pages - require.Equal(t, 1, len(fishySectionPages)) - - nodeFirst := s.getPage(KindSection, "first") - require.NotNil(t, nodeFirst) - nodeSecond := s.getPage(KindSection, "second-section") - require.NotNil(t, nodeSecond) - nodeFishy := s.getPage(KindSection, "Fish and Chips") - require.Equal(t, "Fish and Chips", nodeFishy.sections[0]) - - firstSectionMenuEntry := findTestMenuEntryByID(s, "spm", "first") - secondSectionMenuEntry := findTestMenuEntryByID(s, "spm", "second-section") - fishySectionMenuEntry := findTestMenuEntryByID(s, "spm", "Fish and Chips") - - require.NotNil(t, firstSectionMenuEntry) - require.NotNil(t, secondSectionMenuEntry) - require.NotNil(t, nodeFirst) - require.NotNil(t, nodeSecond) - require.NotNil(t, fishySectionMenuEntry) - require.NotNil(t, nodeFishy) - - require.True(t, nodeFirst.IsMenuCurrent("spm", firstSectionMenuEntry)) - require.False(t, nodeFirst.IsMenuCurrent("spm", secondSectionMenuEntry)) - require.False(t, nodeFirst.IsMenuCurrent("spm", fishySectionMenuEntry)) - require.True(t, nodeFishy.IsMenuCurrent("spm", fishySectionMenuEntry)) - require.Equal(t, "Fish and Chips", fishySectionMenuEntry.Name) - - for _, p := range firstSectionPages { - require.True(t, p.HasMenuCurrent("spm", firstSectionMenuEntry)) - require.False(t, p.HasMenuCurrent("spm", secondSectionMenuEntry)) - } - - for _, p := range secondSectionPages { - require.False(t, p.HasMenuCurrent("spm", firstSectionMenuEntry)) - require.True(t, p.HasMenuCurrent("spm", secondSectionMenuEntry)) - } - - for _, p := range fishySectionPages { - require.False(t, p.HasMenuCurrent("spm", firstSectionMenuEntry)) - require.False(t, p.HasMenuCurrent("spm", secondSectionMenuEntry)) - require.True(t, p.HasMenuCurrent("spm", fishySectionMenuEntry)) - } -} - -func TestMenuLimit(t *testing.T) { - t.Parallel() - s := setupMenuTests(t, menuPageSources) - m := *s.Menus["main"] - - // main menu has 4 entries - firstTwo := m.Limit(2) - assert.Equal(t, 2, len(firstTwo)) - for i := 0; i < 2; i++ { - assert.Equal(t, m[i], firstTwo[i]) - } - assert.Equal(t, m, m.Limit(4)) - assert.Equal(t, m, m.Limit(5)) -} - -func TestMenuSortByN(t *testing.T) { - t.Parallel() - for i, this := range []struct { - sortFunc func(p Menu) Menu - assertFunc func(p Menu) bool - }{ - {(Menu).Sort, func(p Menu) bool { return p[0].Weight == 1 && p[1].Name == "nx" && p[2].Identifier == "ib" }}, - {(Menu).ByWeight, func(p Menu) bool { return p[0].Weight == 1 && p[1].Name == "nx" && p[2].Identifier == "ib" }}, - {(Menu).ByName, func(p Menu) bool { return p[0].Name == "na" }}, - {(Menu).Reverse, func(p Menu) bool { return p[0].Identifier == "ib" && p[len(p)-1].Identifier == "ia" }}, - } { - menu := Menu{&MenuEntry{Weight: 3, Name: "nb", Identifier: "ia"}, - &MenuEntry{Weight: 1, Name: "na", Identifier: "ic"}, - &MenuEntry{Weight: 1, Name: "nx", Identifier: "ic"}, - &MenuEntry{Weight: 2, Name: "nb", Identifier: "ix"}, - &MenuEntry{Weight: 2, Name: "nb", Identifier: "ib"}} - - sorted := this.sortFunc(menu) - - if !this.assertFunc(sorted) { - t.Errorf("[%d] sort error", i) - } - } - -} - -func TestHomeNodeMenu(t *testing.T) { - t.Parallel() - s := setupMenuTests(t, menuPageSources, - "canonifyURLs", true, - "uglyURLs", false, - ) - - home := s.getPage(KindHome) - homeMenuEntry := &MenuEntry{Name: home.Title, URL: home.URL()} - - for i, this := range []struct { - menu string - menuItem *MenuEntry - isMenuCurrent bool - hasMenuCurrent bool - }{ - {"main", homeMenuEntry, true, false}, - {"doesnotexist", homeMenuEntry, false, false}, - {"main", &MenuEntry{Name: "Somewhere else", URL: "/somewhereelse"}, false, false}, - {"grandparent", findTestMenuEntryByID(s, "grandparent", "grandparentId"), false, true}, - {"grandparent", findTestMenuEntryByID(s, "grandparent", "parentId"), false, true}, - {"grandparent", findTestMenuEntryByID(s, "grandparent", "grandchildId"), true, false}, - } { - - isMenuCurrent := home.IsMenuCurrent(this.menu, this.menuItem) - hasMenuCurrent := home.HasMenuCurrent(this.menu, this.menuItem) - - if isMenuCurrent != this.isMenuCurrent { - fmt.Println("isMenuCurrent", isMenuCurrent) - fmt.Printf("this: %#v\n", this) - t.Errorf("[%d] Wrong result from IsMenuCurrent: %v for %q", i, isMenuCurrent, this.menuItem) - } - - if hasMenuCurrent != this.hasMenuCurrent { - fmt.Println("hasMenuCurrent", hasMenuCurrent) - fmt.Printf("this: %#v\n", this) - t.Errorf("[%d] Wrong result for menu %q menuItem %v for HasMenuCurrent: %v", i, this.menu, this.menuItem, hasMenuCurrent) - } - } -} - -func TestHopefullyUniqueID(t *testing.T) { - t.Parallel() - assert.Equal(t, "i", (&MenuEntry{Identifier: "i", URL: "u", Name: "n"}).hopefullyUniqueID()) - assert.Equal(t, "u", (&MenuEntry{Identifier: "", URL: "u", Name: "n"}).hopefullyUniqueID()) - assert.Equal(t, "n", (&MenuEntry{Identifier: "", URL: "", Name: "n"}).hopefullyUniqueID()) -} - -func TestAddMenuEntryChild(t *testing.T) { - t.Parallel() - root := &MenuEntry{Weight: 1} - root.addChild(&MenuEntry{Weight: 2}) - root.addChild(&MenuEntry{Weight: 1}) - assert.Equal(t, 2, len(root.Children)) - assert.Equal(t, 1, root.Children[0].Weight) -} - -var testMenuIdentityMatcher = func(me *MenuEntry, id string) bool { return me.Identifier == id } -var testMenuNameMatcher = func(me *MenuEntry, id string) bool { return me.Name == id } - -func findTestMenuEntryByID(s *Site, mn string, id string) *MenuEntry { - return findTestMenuEntry(s, mn, id, testMenuIdentityMatcher) -} -func findTestMenuEntryByName(s *Site, mn string, id string) *MenuEntry { - return findTestMenuEntry(s, mn, id, testMenuNameMatcher) -} - -func findTestMenuEntry(s *Site, mn string, id string, matcher func(me *MenuEntry, id string) bool) *MenuEntry { - var found *MenuEntry - if menu, ok := s.Menus[mn]; ok { - for _, me := range *menu { - - if matcher(me, id) { - if found != nil { - panic(fmt.Sprintf("Duplicate menu entry in menu %s with id/name %s", mn, id)) - } - found = me - } - - descendant := findDescendantTestMenuEntry(me, id, matcher) - if descendant != nil { - if found != nil { - panic(fmt.Sprintf("Duplicate menu entry in menu %s with id/name %s", mn, id)) - } - found = descendant - } - } - } - return found -} - -func findDescendantTestMenuEntry(parent *MenuEntry, id string, matcher func(me *MenuEntry, id string) bool) *MenuEntry { - var found *MenuEntry - if parent.HasChildren() { - for _, child := range parent.Children { - - if matcher(child, id) { - if found != nil { - panic(fmt.Sprintf("Duplicate menu entry in menuitem %s with id/name %s", parent.KeyName(), id)) - } - found = child - } - - descendant := findDescendantTestMenuEntry(child, id, matcher) - if descendant != nil { - if found != nil { - panic(fmt.Sprintf("Duplicate menu entry in menuitem %s with id/name %s", parent.KeyName(), id)) - } - found = descendant - } - } - } - return found -} - -func setupMenuTests(t *testing.T, pageSources []source.ByteSource, configKeyValues ...interface{}) *Site { - - var ( - cfg, fs = newTestCfg() - ) - - menus, err := tomlToMap(confMenu1) - require.NoError(t, err) - - cfg.Set("menu", menus["menu"]) - cfg.Set("baseURL", "http://foo.local/Zoo/") - - for i := 0; i < len(configKeyValues); i += 2 { - cfg.Set(configKeyValues[i].(string), configKeyValues[i+1]) - } - - for _, src := range pageSources { - writeSource(t, fs, filepath.Join("content", src.Name), string(src.Content)) - - } - - return buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) - -} - -func tomlToMap(s string) (map[string]interface{}, error) { - var data = make(map[string]interface{}) - _, err := toml.Decode(s, &data) - return data, err -} diff --git a/hugolib/menu_test.go b/hugolib/menu_test.go deleted file mode 100644 index f044fb5e0..000000000 --- a/hugolib/menu_test.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2017 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "testing" - - "fmt" - - "github.com/spf13/afero" - - "github.com/stretchr/testify/require" -) - -const ( - menuPageTemplate = `--- -title: %q -weight: %d -menu: - %s: - weight: %d ---- -# Doc Menu -` -) - -func TestSectionPagesMenu(t *testing.T) { - t.Parallel() - - siteConfig := ` -baseurl = "http://example.com/" -title = "Section Menu" -sectionPagesMenu = "sect" -` - - th, h := newTestSitesFromConfig(t, afero.NewMemMapFs(), siteConfig, - "layouts/partials/menu.html", `{{- $p := .page -}} -{{- $m := .menu -}} -{{ range (index $p.Site.Menus $m) -}} -{{- .URL }}|{{ .Name }}|{{ .Weight -}}| -{{- if $p.IsMenuCurrent $m . }}IsMenuCurrent{{ else }}-{{ end -}}| -{{- if $p.HasMenuCurrent $m . }}HasMenuCurrent{{ else }}-{{ end -}}| -{{- end -}} -`, - "layouts/_default/single.html", - `Single|{{ .Title }} -Menu Sect: {{ partial "menu.html" (dict "page" . "menu" "sect") }} -Menu Main: {{ partial "menu.html" (dict "page" . "menu" "main") }}`, - "layouts/_default/list.html", "List|{{ .Title }}|{{ .Content }}", - ) - require.Len(t, h.Sites, 1) - - fs := th.Fs - - writeSource(t, fs, "content/sect1/p1.md", fmt.Sprintf(menuPageTemplate, "p1", 1, "main", 40)) - writeSource(t, fs, "content/sect1/p2.md", fmt.Sprintf(menuPageTemplate, "p2", 2, "main", 30)) - writeSource(t, fs, "content/sect2/p3.md", fmt.Sprintf(menuPageTemplate, "p3", 3, "main", 20)) - writeSource(t, fs, "content/sect2/p4.md", fmt.Sprintf(menuPageTemplate, "p4", 4, "main", 10)) - writeSource(t, fs, "content/sect3/p5.md", fmt.Sprintf(menuPageTemplate, "p5", 5, "main", 5)) - - writeNewContentFile(t, fs, "Section One", "2017-01-01", "content/sect1/_index.md", 100) - writeNewContentFile(t, fs, "Section Five", "2017-01-01", "content/sect5/_index.md", 10) - - err := h.Build(BuildCfg{}) - - require.NoError(t, err) - - s := h.Sites[0] - - require.Len(t, s.Menus, 2) - - p1 := s.RegularPages[0].Menus() - - // There is only one menu in the page, but it is "member of" 2 - require.Len(t, p1, 1) - - th.assertFileContent("public/sect1/p1/index.html", "Single", - "Menu Sect: /sect5/|Section Five|10|-|-|/sect1/|Section One|100|-|HasMenuCurrent|/sect2/|Sect2s|0|-|-|/sect3/|Sect3s|0|-|-|", - "Menu Main: /sect3/p5/|p5|5|-|-|/sect2/p4/|p4|10|-|-|/sect2/p3/|p3|20|-|-|/sect1/p2/|p2|30|-|-|/sect1/p1/|p1|40|IsMenuCurrent|-|", - ) - - th.assertFileContent("public/sect2/p3/index.html", "Single", - "Menu Sect: /sect5/|Section Five|10|-|-|/sect1/|Section One|100|-|-|/sect2/|Sect2s|0|-|HasMenuCurrent|/sect3/|Sect3s|0|-|-|") - -} diff --git a/hugolib/multilingual.go b/hugolib/multilingual.go deleted file mode 100644 index e0245fa2b..000000000 --- a/hugolib/multilingual.go +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright 2016-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "sync" - - "sort" - - "errors" - "fmt" - - "github.com/gohugoio/hugo/config" - "github.com/gohugoio/hugo/helpers" - "github.com/spf13/cast" -) - -// Multilingual manages the all languages used in a multilingual site. -type Multilingual struct { - Languages helpers.Languages - - DefaultLang *helpers.Language - - langMap map[string]*helpers.Language - langMapInit sync.Once -} - -// Language returns the Language associated with the given string. -func (ml *Multilingual) Language(lang string) *helpers.Language { - ml.langMapInit.Do(func() { - ml.langMap = make(map[string]*helpers.Language) - for _, l := range ml.Languages { - ml.langMap[l.Lang] = l - } - }) - return ml.langMap[lang] -} - -func newMultiLingualFromSites(cfg config.Provider, sites ...*Site) (*Multilingual, error) { - languages := make(helpers.Languages, len(sites)) - - for i, s := range sites { - if s.Language == nil { - return nil, errors.New("Missing language for site") - } - languages[i] = s.Language - } - - defaultLang := cfg.GetString("defaultContentLanguage") - - if defaultLang == "" { - defaultLang = "en" - } - - return &Multilingual{Languages: languages, DefaultLang: helpers.NewLanguage(defaultLang, cfg)}, nil - -} - -func newMultiLingualForLanguage(language *helpers.Language) *Multilingual { - languages := helpers.Languages{language} - return &Multilingual{Languages: languages, DefaultLang: language} -} -func (ml *Multilingual) enabled() bool { - return len(ml.Languages) > 1 -} - -func (s *Site) multilingualEnabled() bool { - if s.owner == nil { - return false - } - return s.owner.multilingual != nil && s.owner.multilingual.enabled() -} - -func toSortedLanguages(cfg config.Provider, l map[string]interface{}) (helpers.Languages, error) { - langs := make(helpers.Languages, len(l)) - i := 0 - - for lang, langConf := range l { - langsMap, err := cast.ToStringMapE(langConf) - - if err != nil { - return nil, fmt.Errorf("Language config is not a map: %T", langConf) - } - - language := helpers.NewLanguage(lang, cfg) - - for loki, v := range langsMap { - switch loki { - case "title": - language.Title = cast.ToString(v) - case "languagename": - language.LanguageName = cast.ToString(v) - case "weight": - language.Weight = cast.ToInt(v) - } - - // Put all into the Params map - language.SetParam(loki, v) - } - - langs[i] = language - i++ - } - - sort.Sort(langs) - - return langs, nil -} diff --git a/hugolib/node_as_page_test.go b/hugolib/node_as_page_test.go deleted file mode 100644 index 6cadafc0d..000000000 --- a/hugolib/node_as_page_test.go +++ /dev/null @@ -1,831 +0,0 @@ -// Copyright 2016 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "fmt" - "path/filepath" - "strings" - "testing" - - "time" - - "github.com/spf13/afero" - - "github.com/gohugoio/hugo/deps" - "github.com/gohugoio/hugo/hugofs" - "github.com/stretchr/testify/require" -) - -/* - This file will test the "making everything a page" transition. - - See https://github.com/gohugoio/hugo/issues/2297 - -*/ - -func TestNodesAsPage(t *testing.T) { - t.Parallel() - for _, preserveTaxonomyNames := range []bool{false, true} { - for _, ugly := range []bool{true, false} { - doTestNodeAsPage(t, ugly, preserveTaxonomyNames) - } - } -} - -func doTestNodeAsPage(t *testing.T, ugly, preserveTaxonomyNames bool) { - - /* Will have to decide what to name the node content files, but: - - Home page should have: - Content, shortcode support - Metadata (title, dates etc.) - Params - Taxonomies (categories, tags) - - */ - - var ( - cfg, fs = newTestCfg() - th = testHelper{cfg, fs, t} - ) - - cfg.Set("uglyURLs", ugly) - cfg.Set("preserveTaxonomyNames", preserveTaxonomyNames) - - cfg.Set("paginate", 1) - cfg.Set("title", "Hugo Rocks") - cfg.Set("rssURI", "customrss.xml") - - writeLayoutsForNodeAsPageTests(t, fs) - writeNodePagesForNodeAsPageTests(t, fs, "") - - writeRegularPagesForNodeAsPageTests(t, fs) - - sites, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg}) - - require.NoError(t, err) - - require.NoError(t, sites.Build(BuildCfg{})) - - // date order: home, sect1, sect2, cat/hugo, cat/web, categories - - th.assertFileContent(filepath.Join("public", "index.html"), - "Index Title: Home Sweet Home!", - "Home Content!", - "# Pages: 4", - "Date: 2009-01-02", - "Lastmod: 2009-01-03", - "GetPage: Section1 ", - ) - - th.assertFileContent(expectedFilePath(ugly, "public", "sect1", "regular1"), "Single Title: Page 01", "Content Page 01") - - nodes := sites.findAllPagesByKindNotIn(KindPage) - - require.Len(t, nodes, 8) - - home := nodes[7] // oldest - - require.True(t, home.IsHome()) - require.True(t, home.IsNode()) - require.False(t, home.IsPage()) - require.True(t, home.Path() != "") - - section2 := nodes[5] - require.Equal(t, "Section2", section2.Title) - - pages := sites.findAllPagesByKind(KindPage) - require.Len(t, pages, 4) - - first := pages[0] - - require.False(t, first.IsHome()) - require.False(t, first.IsNode()) - require.True(t, first.IsPage()) - - // Check Home paginator - th.assertFileContent(expectedFilePath(ugly, "public", "page", "2"), - "Pag: Page 02") - - // Check Sections - th.assertFileContent(expectedFilePath(ugly, "public", "sect1"), - "Section Title: Section", "Section1 Content!", - "Date: 2009-01-04", - "Lastmod: 2009-01-05", - ) - - th.assertFileContent(expectedFilePath(ugly, "public", "sect2"), - "Section Title: Section", "Section2 Content!", - "Date: 2009-01-06", - "Lastmod: 2009-01-07", - ) - - // Check Sections paginator - th.assertFileContent(expectedFilePath(ugly, "public", "sect1", "page", "2"), - "Pag: Page 02") - - sections := sites.findAllPagesByKind(KindSection) - - require.Len(t, sections, 2) - - // Check taxonomy lists - th.assertFileContent(expectedFilePath(ugly, "public", "categories", "hugo"), - "Taxonomy Title: Taxonomy Hugo", "Taxonomy Hugo Content!", - "Date: 2009-01-08", - "Lastmod: 2009-01-09", - ) - - th.assertFileContent(expectedFilePath(ugly, "public", "categories", "hugo-rocks"), - "Taxonomy Title: Taxonomy Hugo Rocks", - ) - - s := sites.Sites[0] - - web := s.getPage(KindTaxonomy, "categories", "web") - require.NotNil(t, web) - require.Len(t, web.Data["Pages"].(Pages), 4) - - th.assertFileContent(expectedFilePath(ugly, "public", "categories", "web"), - "Taxonomy Title: Taxonomy Web", - "Taxonomy Web Content!", - "Date: 2009-01-10", - "Lastmod: 2009-01-11", - ) - - // Check taxonomy list paginator - th.assertFileContent(expectedFilePath(ugly, "public", "categories", "hugo", "page", "2"), - "Taxonomy Title: Taxonomy Hugo", - "Pag: Page 02") - - // Check taxonomy terms - th.assertFileContent(expectedFilePath(ugly, "public", "categories"), - "Taxonomy Terms Title: Taxonomy Term Categories", "Taxonomy Term Categories Content!", "k/v: hugo", - "Date: 2009-01-14", - "Lastmod: 2009-01-15", - ) - - // Check taxonomy terms paginator - th.assertFileContent(expectedFilePath(ugly, "public", "categories", "page", "2"), - "Taxonomy Terms Title: Taxonomy Term Categories", - "Pag: Taxonomy Web") - - // RSS - th.assertFileContent(filepath.Join("public", "customrss.xml"), "Recent content in Home Sweet Home! on Hugo Rocks", "Content!") - th.assertFileContent(filepath.Join("public", "de", "index.html"), - "Index Title: Home Sweet Home!", "Content!") - - // Taxonomy list - th.assertFileContent(expectedFilePath(ugly, "public", "nn", "categories", "hugo"), - "Taxonomy Title: Hugo") - th.assertFileContent(expectedFilePath(ugly, "public", "en", "categories", "hugo"), - "Taxonomy Title: Taxonomy Hugo") - - // Taxonomy terms - th.assertFileContent(expectedFilePath(ugly, "public", "nn", "categories"), - "Taxonomy Terms Title: Categories") - th.assertFileContent(expectedFilePath(ugly, "public", "en", "categories"), - "Taxonomy Terms Title: Taxonomy Term Categories") - - // Sections - th.assertFileContent(expectedFilePath(ugly, "public", "nn", "sect1"), - "Section Title: Sect1s") - th.assertFileContent(expectedFilePath(ugly, "public", "nn", "sect2"), - "Section Title: Sect2s") - th.assertFileContent(expectedFilePath(ugly, "public", "en", "sect1"), - "Section Title: Section1") - th.assertFileContent(expectedFilePath(ugly, "public", "en", "sect2"), - "Section Title: Section2") - - // Regular pages - th.assertFileContent(expectedFilePath(ugly, "public", "en", "sect1", "regular1"), - "Single Title: Page 01") - th.assertFileContent(expectedFilePath(ugly, "public", "nn", "sect1", "regular2"), - "Single Title: Page 02") - - // RSS - th.assertFileContent(filepath.Join("public", "nn", "customrss.xml"), "Hugo på norsk", " 2 { - sect = "sect2" - - date, _ = time.Parse(format, "2008-07-15") // Nodes are placed in 2009 - - } - date = date.Add(-24 * time.Duration(i) * time.Hour) - writeSource(t, fs, filepath.Join("content", sect, fmt.Sprintf("regular%d.%smd", i, langStr)), fmt.Sprintf(`--- -title: Page %02d -lastMod : %q -date : %q -categories: [ - "Hugo", - "Web", - "Hugo Rocks!" -] ---- -Content Page %02d -`, i, date.Add(time.Duration(i)*-24*time.Hour).Format(time.RFC822), date.Add(time.Duration(i)*-2*24*time.Hour).Format(time.RFC822), i)) - } -} - -func writeNodePagesForNodeAsPageTests(t *testing.T, fs *hugofs.Fs, lang string) { - - filename := "_index.md" - - if lang != "" { - filename = fmt.Sprintf("_index.%s.md", lang) - } - - format := "2006-01-02" - - date, _ := time.Parse(format, "2009-01-01") - - writeSource(t, fs, filepath.Join("content", filename), fmt.Sprintf(`--- -title: Home Sweet Home! -date : %q -lastMod : %q ---- -l-%s Home **Content!** -`, date.Add(1*24*time.Hour).Format(time.RFC822), date.Add(2*24*time.Hour).Format(time.RFC822), lang)) - - writeSource(t, fs, filepath.Join("content", "sect1", filename), fmt.Sprintf(`--- -title: Section1 -date : %q -lastMod : %q ---- -Section1 **Content!** -`, date.Add(3*24*time.Hour).Format(time.RFC822), date.Add(4*24*time.Hour).Format(time.RFC822))) - writeSource(t, fs, filepath.Join("content", "sect2", filename), fmt.Sprintf(`--- -title: Section2 -date : %q -lastMod : %q ---- -Section2 **Content!** -`, date.Add(5*24*time.Hour).Format(time.RFC822), date.Add(6*24*time.Hour).Format(time.RFC822))) - - writeSource(t, fs, filepath.Join("content", "categories", "hugo", filename), fmt.Sprintf(`--- -title: Taxonomy Hugo -date : %q -lastMod : %q ---- -Taxonomy Hugo **Content!** -`, date.Add(7*24*time.Hour).Format(time.RFC822), date.Add(8*24*time.Hour).Format(time.RFC822))) - - writeSource(t, fs, filepath.Join("content", "categories", "web", filename), fmt.Sprintf(`--- -title: Taxonomy Web -date : %q -lastMod : %q ---- -Taxonomy Web **Content!** -`, date.Add(9*24*time.Hour).Format(time.RFC822), date.Add(10*24*time.Hour).Format(time.RFC822))) - - writeSource(t, fs, filepath.Join("content", "categories", "hugo-rocks", filename), fmt.Sprintf(`--- -title: Taxonomy Hugo Rocks -date : %q -lastMod : %q ---- -Taxonomy Hugo Rocks **Content!** -`, date.Add(11*24*time.Hour).Format(time.RFC822), date.Add(12*24*time.Hour).Format(time.RFC822))) - - writeSource(t, fs, filepath.Join("content", "categories", filename), fmt.Sprintf(`--- -title: Taxonomy Term Categories -date : %q -lastMod : %q ---- -Taxonomy Term Categories **Content!** -`, date.Add(13*24*time.Hour).Format(time.RFC822), date.Add(14*24*time.Hour).Format(time.RFC822))) - - writeSource(t, fs, filepath.Join("content", "tags", filename), fmt.Sprintf(`--- -title: Taxonomy Term Tags -date : %q -lastMod : %q ---- -Taxonomy Term Tags **Content!** -`, date.Add(15*24*time.Hour).Format(time.RFC822), date.Add(16*24*time.Hour).Format(time.RFC822))) - -} - -func writeLayoutsForNodeAsPageTests(t *testing.T, fs *hugofs.Fs) { - writeSource(t, fs, filepath.Join("layouts", "index.html"), ` -Index Title: {{ .Title }} -Index Content: {{ .Content }} -# Pages: {{ len .Data.Pages }} -{{ range .Paginator.Pages }} - Pag: {{ .Title }} -{{ end }} -{{ with .Site.Menus.mymenu }} -{{ range . }} -Home Menu Item: {{ .Name }}: {{ .URL }} -{{ end }} -{{ end }} -Date: {{ .Date.Format "2006-01-02" }} -Lastmod: {{ .Lastmod.Format "2006-01-02" }} -GetPage: {{ with .Site.GetPage "section" "sect1" }}{{ .Title }}{{ end }} -`) - - writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), ` -Single Title: {{ .Title }} -Single Content: {{ .Content }} -Date: {{ .Date.Format "2006-01-02" }} -Lastmod: {{ .Lastmod.Format "2006-01-02" }} -`) - - writeSource(t, fs, filepath.Join("layouts", "_default", "section.html"), ` -Section Title: {{ .Title }} -Section Content: {{ .Content }} -# Pages: {{ len .Data.Pages }} -{{ range .Paginator.Pages }} - Pag: {{ .Title }} -{{ end }} -{{ with .Site.Menus.mymenu }} -{{ range . }} -Section Menu Item: {{ .Name }}: {{ .URL }} -{{ end }} -{{ end }} -Date: {{ .Date.Format "2006-01-02" }} -Lastmod: {{ .Lastmod.Format "2006-01-02" }} -`) - - // Taxonomy lists - writeSource(t, fs, filepath.Join("layouts", "_default", "taxonomy.html"), ` -Taxonomy Title: {{ .Title }} -Taxonomy Content: {{ .Content }} -# Pages: {{ len .Data.Pages }} -{{ range .Paginator.Pages }} - Pag: {{ .Title }} -{{ end }} -{{ with .Site.Menus.mymenu }} -{{ range . }} -Taxonomy Menu Item: {{ .Name }}: {{ .URL }} -{{ end }} -{{ end }} -Date: {{ .Date.Format "2006-01-02" }} -Lastmod: {{ .Lastmod.Format "2006-01-02" }} -`) - - // Taxonomy terms - writeSource(t, fs, filepath.Join("layouts", "_default", "terms.html"), ` -Taxonomy Terms Title: {{ .Title }} -Taxonomy Terms Content: {{ .Content }} -# Pages: {{ len .Data.Pages }} -{{ range .Paginator.Pages }} - Pag: {{ .Title }} -{{ end }} -{{ range $key, $value := .Data.Terms }} - k/v: {{ $key | lower }} / {{ printf "%s" $value }} -{{ end }} -{{ with .Site.Menus.mymenu }} -{{ range . }} -Taxonomy Terms Menu Item: {{ .Name }}: {{ .URL }} -{{ end }} -{{ end }} -Date: {{ .Date.Format "2006-01-02" }} -Lastmod: {{ .Lastmod.Format "2006-01-02" }} -`) -} - -func expectedFilePath(ugly bool, path ...string) string { - if ugly { - return filepath.Join(append(path[0:len(path)-1], path[len(path)-1]+".html")...) - } - return filepath.Join(append(path, "index.html")...) -} - -func expetedPermalink(ugly bool, path string) string { - if ugly { - return strings.TrimSuffix(path, "/") + ".html" - } - return path -} diff --git a/hugolib/page.go b/hugolib/page.go deleted file mode 100644 index 0120d6ef0..000000000 --- a/hugolib/page.go +++ /dev/null @@ -1,1777 +0,0 @@ -// Copyright 2016 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "bytes" - "errors" - "fmt" - "reflect" - - "github.com/bep/gitmap" - - "github.com/gohugoio/hugo/helpers" - "github.com/gohugoio/hugo/output" - "github.com/gohugoio/hugo/parser" - "github.com/mitchellh/mapstructure" - - "html/template" - "io" - "path" - "path/filepath" - "regexp" - "strings" - "sync" - "time" - "unicode/utf8" - - bp "github.com/gohugoio/hugo/bufferpool" - "github.com/gohugoio/hugo/source" - "github.com/spf13/cast" -) - -var ( - cjk = regexp.MustCompile(`\p{Han}|\p{Hangul}|\p{Hiragana}|\p{Katakana}`) - - // This is all the kinds we can expect to find in .Site.Pages. - allKindsInPages = []string{KindPage, KindHome, KindSection, KindTaxonomy, KindTaxonomyTerm} - - allKinds = append(allKindsInPages, []string{kindRSS, kindSitemap, kindRobotsTXT, kind404}...) -) - -const ( - KindPage = "page" - - // The rest are node types; home page, sections etc. - KindHome = "home" - KindSection = "section" - KindTaxonomy = "taxonomy" - KindTaxonomyTerm = "taxonomyTerm" - - // Temporary state. - kindUnknown = "unknown" - - // The following are (currently) temporary nodes, - // i.e. nodes we create just to render in isolation. - kindRSS = "RSS" - kindSitemap = "sitemap" - kindRobotsTXT = "robotsTXT" - kind404 = "404" -) - -type Page struct { - *pageInit - - // Kind is the discriminator that identifies the different page types - // in the different page collections. This can, as an example, be used - // to to filter regular pages, find sections etc. - // Kind will, for the pages available to the templates, be one of: - // page, home, section, taxonomy and taxonomyTerm. - // It is of string type to make it easy to reason about in - // the templates. - Kind string - - // Since Hugo 0.18 we got rid of the Node type. So now all pages are ... - // pages (regular pages, home page, sections etc.). - // Sections etc. will have child pages. These were earlier placed in .Data.Pages, - // but can now be more intuitively also be fetched directly from .Pages. - // This collection will be nil for regular pages. - Pages Pages - - // translations will contain references to this page in other language - // if available. - translations Pages - - // Params contains configuration defined in the params section of page frontmatter. - Params map[string]interface{} - - // Content sections - Content template.HTML - Summary template.HTML - TableOfContents template.HTML - - Aliases []string - - Images []Image - Videos []Video - - Truncated bool - Draft bool - Status string - - PublishDate time.Time - ExpiryDate time.Time - - // PageMeta contains page stats such as word count etc. - PageMeta - - // Markup contains the markup type for the content. - Markup string - - extension string - contentType string - renderable bool - - Layout string - - // For npn-renderable pages (see IsRenderable), the content itself - // is used as template and the template name is stored here. - selfLayout string - - linkTitle string - - frontmatter []byte - - // rawContent is the raw content read from the content file. - rawContent []byte - - // workContent is a copy of rawContent that may be mutated during site build. - workContent []byte - - // state telling if this is a "new page" or if we have rendered it previously. - rendered bool - - // whether the content is in a CJK language. - isCJKLanguage bool - - shortcodeState *shortcodeHandler - - // the content stripped for HTML - plain string // TODO should be []byte - plainWords []string - - // rendering configuration - renderingConfig *helpers.Blackfriday - - // menus - pageMenus PageMenus - - Source - - Position `json:"-"` - - GitInfo *gitmap.GitInfo - - // This was added as part of getting the Nodes (taxonomies etc.) to work as - // Pages in Hugo 0.18. - // It is deliberately named similar to Section, but not exported (for now). - // We currently have only one level of section in Hugo, but the page can live - // any number of levels down the file path. - // To support taxonomies like /categories/hugo etc. we will need to keep track - // of that information in a general way. - // So, sections represents the path to the content, i.e. a content file or a - // virtual content file in the situations where a taxonomy or a section etc. - // isn't accomanied by one. - sections []string - - // Will only be set for sections and regular pages. - parent *Page - - // When we create paginator pages, we create a copy of the original, - // but keep track of it here. - origOnCopy *Page - - // Will only be set for section pages and the home page. - subSections Pages - - s *Site - - // Pulled over from old Node. TODO(bep) reorg and group (embed) - - Site *SiteInfo `json:"-"` - - Title string - Description string - Keywords []string - Data map[string]interface{} - - Date time.Time - Lastmod time.Time - - Sitemap Sitemap - - URLPath - permalink string - relPermalink string - - layoutDescriptor output.LayoutDescriptor - - scratch *Scratch - - // It would be tempting to use the language set on the Site, but in they way we do - // multi-site processing, these values may differ during the initial page processing. - language *helpers.Language - - lang string - - // The output formats this page will be rendered to. - outputFormats output.Formats - - // This is the PageOutput that represents the first item in outputFormats. - // Use with care, as there are potential for inifinite loops. - mainPageOutput *PageOutput - - targetPathDescriptorPrototype *targetPathDescriptor -} - -func (p *Page) RSSLink() template.URL { - f, found := p.outputFormats.GetByName(output.RSSFormat.Name) - if !found { - return "" - } - return template.URL(newOutputFormat(p, f).Permalink()) -} - -func (p *Page) createLayoutDescriptor() output.LayoutDescriptor { - var section string - - switch p.Kind { - case KindSection: - // In Hugo 0.22 we introduce nested sections, but we still only - // use the first level to pick the correct template. This may change in - // the future. - section = p.sections[0] - case KindTaxonomy, KindTaxonomyTerm: - section = p.s.taxonomiesPluralSingular[p.sections[0]] - default: - } - - return output.LayoutDescriptor{ - Kind: p.Kind, - Type: p.Type(), - Lang: p.Lang(), - Layout: p.Layout, - Section: section, - } -} - -// pageInit lazy initializes different parts of the page. It is extracted -// into its own type so we can easily create a copy of a given page. -type pageInit struct { - languageInit sync.Once - pageMenusInit sync.Once - pageMetaInit sync.Once - pageOutputInit sync.Once - plainInit sync.Once - plainWordsInit sync.Once - renderingConfigInit sync.Once -} - -// IsNode returns whether this is an item of one of the list types in Hugo, -// i.e. not a regular content page. -func (p *Page) IsNode() bool { - return p.Kind != KindPage -} - -// IsHome returns whether this is the home page. -func (p *Page) IsHome() bool { - return p.Kind == KindHome -} - -// IsSection returns whether this is a section page. -func (p *Page) IsSection() bool { - return p.Kind == KindSection -} - -// IsPage returns whether this is a regular content page. -func (p *Page) IsPage() bool { - return p.Kind == KindPage -} - -type Source struct { - Frontmatter []byte - Content []byte - source.File -} -type PageMeta struct { - wordCount int - fuzzyWordCount int - readingTime int - Weight int -} - -type Position struct { - Prev *Page - Next *Page - PrevInSection *Page - NextInSection *Page -} - -type Pages []*Page - -func (ps Pages) String() string { - return fmt.Sprintf("Pages(%d)", len(ps)) -} - -func (ps Pages) findPagePosByFilePath(inPath string) int { - for i, x := range ps { - if x.Source.Path() == inPath { - return i - } - } - return -1 -} - -func (ps Pages) findFirstPagePosByFilePathPrefix(prefix string) int { - if prefix == "" { - return -1 - } - for i, x := range ps { - if strings.HasPrefix(x.Source.Path(), prefix) { - return i - } - } - return -1 -} - -// findPagePos Given a page, it will find the position in Pages -// will return -1 if not found -func (ps Pages) findPagePos(page *Page) int { - for i, x := range ps { - if x.Source.Path() == page.Source.Path() { - return i - } - } - return -1 -} - -func (p *Page) createWorkContentCopy() { - p.workContent = make([]byte, len(p.rawContent)) - copy(p.workContent, p.rawContent) -} - -func (p *Page) Plain() string { - p.initPlain() - return p.plain -} - -func (p *Page) PlainWords() []string { - p.initPlainWords() - return p.plainWords -} - -func (p *Page) initPlain() { - p.plainInit.Do(func() { - p.plain = helpers.StripHTML(string(p.Content)) - return - }) -} - -func (p *Page) initPlainWords() { - p.plainWordsInit.Do(func() { - p.plainWords = strings.Fields(p.Plain()) - return - }) -} - -// Param is a convenience method to do lookups in Page's and Site's Params map, -// in that order. -// -// This method is also implemented on Node and SiteInfo. -func (p *Page) Param(key interface{}) (interface{}, error) { - keyStr, err := cast.ToStringE(key) - if err != nil { - return nil, err - } - - keyStr = strings.ToLower(keyStr) - result, _ := p.traverseDirect(keyStr) - if result != nil { - return result, nil - } - - keySegments := strings.Split(keyStr, ".") - if len(keySegments) == 1 { - return nil, nil - } - - return p.traverseNested(keySegments) -} - -func (p *Page) traverseDirect(key string) (interface{}, error) { - keyStr := strings.ToLower(key) - if val, ok := p.Params[keyStr]; ok { - return val, nil - } - - return p.Site.Params[keyStr], nil -} - -func (p *Page) traverseNested(keySegments []string) (interface{}, error) { - result := traverse(keySegments, p.Params) - if result != nil { - return result, nil - } - - result = traverse(keySegments, p.Site.Params) - if result != nil { - return result, nil - } - - // Didn't find anything, but also no problems. - return nil, nil -} - -func traverse(keys []string, m map[string]interface{}) interface{} { - // Shift first element off. - firstKey, rest := keys[0], keys[1:] - result := m[firstKey] - - // No point in continuing here. - if result == nil { - return result - } - - if len(rest) == 0 { - // That was the last key. - return result - } else { - // That was not the last key. - return traverse(rest, cast.ToStringMap(result)) - } -} - -func (p *Page) Author() Author { - authors := p.Authors() - - for _, author := range authors { - return author - } - return Author{} -} - -func (p *Page) Authors() AuthorList { - authorKeys, ok := p.Params["authors"] - if !ok { - return AuthorList{} - } - authors := authorKeys.([]string) - if len(authors) < 1 || len(p.Site.Authors) < 1 { - return AuthorList{} - } - - al := make(AuthorList) - for _, author := range authors { - a, ok := p.Site.Authors[author] - if ok { - al[author] = a - } - } - return al -} - -func (p *Page) UniqueID() string { - return p.Source.UniqueID() -} - -// for logging -func (p *Page) lineNumRawContentStart() int { - return bytes.Count(p.frontmatter, []byte("\n")) + 1 -} - -var ( - internalSummaryDivider = []byte("HUGOMORE42") -) - -// We have to replace the with something that survives all the -// rendering engines. -// TODO(bep) inline replace -func (p *Page) replaceDivider(content []byte) []byte { - summaryDivider := helpers.SummaryDivider - // TODO(bep) handle better. - if p.Ext() == "org" || p.Markup == "org" { - summaryDivider = []byte("# more") - } - sections := bytes.Split(content, summaryDivider) - - // If the raw content has nothing but whitespace after the summary - // marker then the page shouldn't be marked as truncated. This check - // is simplest against the raw content because different markup engines - // (rst and asciidoc in particular) add div and p elements after the - // summary marker. - p.Truncated = (len(sections) == 2 && - len(bytes.Trim(sections[1], " \n\r")) > 0) - - return bytes.Join(sections, internalSummaryDivider) -} - -// Returns the page as summary and main if a user defined split is provided. -func (p *Page) setUserDefinedSummaryIfProvided(rawContentCopy []byte) (*summaryContent, error) { - - sc, err := splitUserDefinedSummaryAndContent(p.Markup, rawContentCopy) - - if err != nil { - return nil, err - } - - if sc == nil { - // No divider found - return nil, nil - } - - p.Summary = helpers.BytesToHTML(sc.summary) - - return sc, nil -} - -// Make this explicit so there is no doubt about what is what. -type summaryContent struct { - summary []byte - content []byte -} - -func splitUserDefinedSummaryAndContent(markup string, c []byte) (sc *summaryContent, err error) { - defer func() { - if r := recover(); r != nil { - err = fmt.Errorf("summary split failed: %s", r) - } - }() - - c = bytes.TrimSpace(c) - startDivider := bytes.Index(c, internalSummaryDivider) - - if startDivider == -1 { - return - } - - endDivider := startDivider + len(internalSummaryDivider) - endSummary := startDivider - - var ( - startMarkup []byte - endMarkup []byte - addDiv bool - ) - - switch markup { - default: - startMarkup = []byte("

    ") - endMarkup = []byte("

    ") - case "asciidoc": - startMarkup = []byte("
    ") - endMarkup = []byte("
    ") - case "rst": - startMarkup = []byte("

    ") - endMarkup = []byte("

    ") - addDiv = true - } - - // Find the closest end/start markup string to the divider - fromStart := -1 - fromIdx := bytes.LastIndex(c[:startDivider], startMarkup) - if fromIdx != -1 { - fromStart = startDivider - fromIdx - len(startMarkup) - } - fromEnd := bytes.Index(c[endDivider:], endMarkup) - - if fromEnd != -1 && fromEnd <= fromStart { - endSummary = startDivider + fromEnd + len(endMarkup) - } else if fromStart != -1 && fromEnd != -1 { - endSummary = startDivider - fromStart - len(startMarkup) - } - - withoutDivider := bytes.TrimSpace(append(c[:startDivider], c[endDivider:]...)) - var ( - summary []byte - ) - - if len(withoutDivider) > 0 { - summary = bytes.TrimSpace(withoutDivider[:endSummary]) - } - - if addDiv { - // For the rst - summary = append(append([]byte(nil), summary...), []byte("")...) - } - - if err != nil { - return - } - - sc = &summaryContent{ - summary: summary, - content: withoutDivider, - } - - return -} - -func (p *Page) setAutoSummary() error { - var summary string - var truncated bool - if p.isCJKLanguage { - summary, truncated = helpers.TruncateWordsByRune(p.PlainWords(), helpers.SummaryLength) - } else { - summary, truncated = helpers.TruncateWordsToWholeSentence(p.Plain(), helpers.SummaryLength) - } - p.Summary = template.HTML(summary) - p.Truncated = truncated - - return nil -} - -func (p *Page) renderContent(content []byte) []byte { - return p.s.ContentSpec.RenderBytes(&helpers.RenderingContext{ - Content: content, RenderTOC: true, PageFmt: p.determineMarkupType(), - Cfg: p.Language(), - DocumentID: p.UniqueID(), DocumentName: p.Path(), - Config: p.getRenderingConfig()}) -} - -func (p *Page) getRenderingConfig() *helpers.Blackfriday { - p.renderingConfigInit.Do(func() { - p.renderingConfig = p.s.ContentSpec.NewBlackfriday() - - if p.Language() == nil { - panic(fmt.Sprintf("nil language for %s with source lang %s", p.BaseFileName(), p.lang)) - } - - bfParam := p.GetParam("blackfriday") - if bfParam == nil { - return - } - - pageParam := cast.ToStringMap(bfParam) - if err := mapstructure.Decode(pageParam, &p.renderingConfig); err != nil { - p.s.Log.FATAL.Printf("Failed to get rendering config for %s:\n%s", p.BaseFileName(), err.Error()) - } - - }) - - return p.renderingConfig -} - -func (s *Site) newPage(filename string) *Page { - sp := source.NewSourceSpec(s.Cfg, s.Fs) - p := &Page{ - pageInit: &pageInit{}, - Kind: kindFromFilename(filename), - contentType: "", - Source: Source{File: *sp.NewFile(filename)}, - Keywords: []string{}, Sitemap: Sitemap{Priority: -1}, - Params: make(map[string]interface{}), - translations: make(Pages, 0), - sections: sectionsFromFilename(filename), - Site: &s.Info, - s: s, - } - - s.Log.DEBUG.Println("Reading from", p.File.Path()) - return p -} - -func (p *Page) IsRenderable() bool { - return p.renderable -} - -func (p *Page) Type() string { - if p.contentType != "" { - return p.contentType - } - - if x := p.Section(); x != "" { - return x - } - - return "page" -} - -// Section returns the first path element below the content root. Note that -// since Hugo 0.22 we support nested sections, but this will always be the first -// element of any nested path. -func (p *Page) Section() string { - if p.Kind == KindSection { - return p.sections[0] - } - return p.Source.Section() -} - -func (s *Site) NewPageFrom(buf io.Reader, name string) (*Page, error) { - p, err := s.NewPage(name) - if err != nil { - return p, err - } - _, err = p.ReadFrom(buf) - - return p, err -} - -func (s *Site) NewPage(name string) (*Page, error) { - if len(name) == 0 { - return nil, errors.New("Zero length page name") - } - - // Create new page - p := s.newPage(name) - p.s = s - p.Site = &s.Info - - return p, nil -} - -func (p *Page) ReadFrom(buf io.Reader) (int64, error) { - // Parse for metadata & body - if err := p.parse(buf); err != nil { - p.s.Log.ERROR.Print(err) - return 0, err - } - - return int64(len(p.rawContent)), nil -} - -func (p *Page) WordCount() int { - p.analyzePage() - return p.wordCount -} - -func (p *Page) ReadingTime() int { - p.analyzePage() - return p.readingTime -} - -func (p *Page) FuzzyWordCount() int { - p.analyzePage() - return p.fuzzyWordCount -} - -func (p *Page) analyzePage() { - p.pageMetaInit.Do(func() { - if p.isCJKLanguage { - p.wordCount = 0 - for _, word := range p.PlainWords() { - runeCount := utf8.RuneCountInString(word) - if len(word) == runeCount { - p.wordCount++ - } else { - p.wordCount += runeCount - } - } - } else { - p.wordCount = helpers.TotalWords(p.Plain()) - } - - // TODO(bep) is set in a test. Fix that. - if p.fuzzyWordCount == 0 { - p.fuzzyWordCount = (p.wordCount + 100) / 100 * 100 - } - - if p.isCJKLanguage { - p.readingTime = (p.wordCount + 500) / 501 - } else { - p.readingTime = (p.wordCount + 212) / 213 - } - }) -} - -func (p *Page) Extension() string { - // Remove in Hugo 0.22. - helpers.Deprecated("Page", "Extension", "See OutputFormats with its MediaType", true) - return p.extension -} - -// HasShortcode return whether the page has a shortcode with the given name. -// This method is mainly motivated with the Hugo Docs site's need for a list -// of pages with the `todo` shortcode in it. -func (p *Page) HasShortcode(name string) bool { - if p.shortcodeState == nil { - return false - } - - return p.shortcodeState.nameSet[name] -} - -// AllTranslations returns all translations, including the current Page. -func (p *Page) AllTranslations() Pages { - return p.translations -} - -// IsTranslated returns whether this content file is translated to -// other language(s). -func (p *Page) IsTranslated() bool { - return len(p.translations) > 1 -} - -// Translations returns the translations excluding the current Page. -func (p *Page) Translations() Pages { - translations := make(Pages, 0) - for _, t := range p.translations { - if t.Lang() != p.Lang() { - translations = append(translations, t) - } - } - return translations -} - -func (p *Page) LinkTitle() string { - if len(p.linkTitle) > 0 { - return p.linkTitle - } - return p.Title -} - -func (p *Page) shouldBuild() bool { - return shouldBuild(p.s.Cfg.GetBool("buildFuture"), p.s.Cfg.GetBool("buildExpired"), - p.s.Cfg.GetBool("buildDrafts"), p.Draft, p.PublishDate, p.ExpiryDate) -} - -func shouldBuild(buildFuture bool, buildExpired bool, buildDrafts bool, Draft bool, - publishDate time.Time, expiryDate time.Time) bool { - if !(buildDrafts || !Draft) { - return false - } - if !buildFuture && !publishDate.IsZero() && publishDate.After(time.Now()) { - return false - } - if !buildExpired && !expiryDate.IsZero() && expiryDate.Before(time.Now()) { - return false - } - return true -} - -func (p *Page) IsDraft() bool { - return p.Draft -} - -func (p *Page) IsFuture() bool { - if p.PublishDate.IsZero() { - return false - } - return p.PublishDate.After(time.Now()) -} - -func (p *Page) IsExpired() bool { - if p.ExpiryDate.IsZero() { - return false - } - return p.ExpiryDate.Before(time.Now()) -} - -func (p *Page) URL() string { - - if p.IsPage() && p.URLPath.URL != "" { - // This is the url set in front matter - return p.URLPath.URL - } - // Fall back to the relative permalink. - u := p.RelPermalink() - return u -} - -// Permalink returns the absolute URL to this Page. -func (p *Page) Permalink() string { - return p.permalink -} - -// RelPermalink gets a URL to the resource relative to the host. -func (p *Page) RelPermalink() string { - return p.relPermalink -} - -func (p *Page) initURLs() error { - if len(p.outputFormats) == 0 { - p.outputFormats = p.s.outputFormats[p.Kind] - } - rel := p.createRelativePermalink() - - var err error - p.permalink, err = p.s.permalinkForOutputFormat(rel, p.outputFormats[0]) - if err != nil { - return err - } - rel = p.s.PathSpec.PrependBasePath(rel) - p.relPermalink = rel - p.layoutDescriptor = p.createLayoutDescriptor() - return nil -} - -var ErrHasDraftAndPublished = errors.New("both draft and published parameters were found in page's frontmatter") - -func (p *Page) update(f interface{}) error { - if f == nil { - return errors.New("no metadata found") - } - m := f.(map[string]interface{}) - // Needed for case insensitive fetching of params values - helpers.ToLowerMap(m) - - var err error - var draft, published, isCJKLanguage *bool - for k, v := range m { - loki := strings.ToLower(k) - switch loki { - case "title": - p.Title = cast.ToString(v) - p.Params[loki] = p.Title - case "linktitle": - p.linkTitle = cast.ToString(v) - p.Params[loki] = p.linkTitle - case "description": - p.Description = cast.ToString(v) - p.Params[loki] = p.Description - case "slug": - p.Slug = cast.ToString(v) - p.Params[loki] = p.Slug - case "url": - if url := cast.ToString(v); strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") { - return fmt.Errorf("Only relative URLs are supported, %v provided", url) - } - p.URLPath.URL = cast.ToString(v) - p.Params[loki] = p.URLPath.URL - case "type": - p.contentType = cast.ToString(v) - p.Params[loki] = p.contentType - case "extension", "ext": - p.extension = cast.ToString(v) - p.Params[loki] = p.extension - case "keywords": - p.Keywords = cast.ToStringSlice(v) - p.Params[loki] = p.Keywords - case "date": - p.Date, err = cast.ToTimeE(v) - if err != nil { - p.s.Log.ERROR.Printf("Failed to parse date '%v' in page %s", v, p.File.Path()) - } - p.Params[loki] = p.Date - case "lastmod": - p.Lastmod, err = cast.ToTimeE(v) - if err != nil { - p.s.Log.ERROR.Printf("Failed to parse lastmod '%v' in page %s", v, p.File.Path()) - } - case "outputs": - o := cast.ToStringSlice(v) - if len(o) > 0 { - // Output formats are exlicitly set in front matter, use those. - outFormats, err := p.s.outputFormatsConfig.GetByNames(o...) - - if err != nil { - p.s.Log.ERROR.Printf("Failed to resolve output formats: %s", err) - } else { - p.outputFormats = outFormats - p.Params[loki] = outFormats - } - - } - //p.Params[loki] = p.Keywords - case "publishdate", "pubdate": - p.PublishDate, err = cast.ToTimeE(v) - if err != nil { - p.s.Log.ERROR.Printf("Failed to parse publishdate '%v' in page %s", v, p.File.Path()) - } - case "expirydate", "unpublishdate": - p.ExpiryDate, err = cast.ToTimeE(v) - if err != nil { - p.s.Log.ERROR.Printf("Failed to parse expirydate '%v' in page %s", v, p.File.Path()) - } - case "draft": - draft = new(bool) - *draft = cast.ToBool(v) - case "published": // Intentionally undocumented - published = new(bool) - *published = cast.ToBool(v) - case "layout": - p.Layout = cast.ToString(v) - p.Params[loki] = p.Layout - case "markup": - p.Markup = cast.ToString(v) - p.Params[loki] = p.Markup - case "weight": - p.Weight = cast.ToInt(v) - p.Params[loki] = p.Weight - case "aliases": - p.Aliases = cast.ToStringSlice(v) - for _, alias := range p.Aliases { - if strings.HasPrefix(alias, "http://") || strings.HasPrefix(alias, "https://") { - return fmt.Errorf("Only relative aliases are supported, %v provided", alias) - } - } - p.Params[loki] = p.Aliases - case "status": - p.Status = cast.ToString(v) - p.Params[loki] = p.Status - case "sitemap": - p.Sitemap = parseSitemap(cast.ToStringMap(v)) - p.Params[loki] = p.Sitemap - case "iscjklanguage": - isCJKLanguage = new(bool) - *isCJKLanguage = cast.ToBool(v) - default: - // If not one of the explicit values, store in Params - switch vv := v.(type) { - case bool: - p.Params[loki] = vv - case string: - p.Params[loki] = vv - case int64, int32, int16, int8, int: - p.Params[loki] = vv - case float64, float32: - p.Params[loki] = vv - case time.Time: - p.Params[loki] = vv - default: // handle array of strings as well - switch vvv := vv.(type) { - case []interface{}: - if len(vvv) > 0 { - switch vvv[0].(type) { - case map[interface{}]interface{}: // Proper parsing structured array from YAML based FrontMatter - p.Params[loki] = vvv - case map[string]interface{}: // Proper parsing structured array from JSON based FrontMatter - p.Params[loki] = vvv - case []interface{}: - p.Params[loki] = vvv - default: - a := make([]string, len(vvv)) - for i, u := range vvv { - a[i] = cast.ToString(u) - } - - p.Params[loki] = a - } - } else { - p.Params[loki] = []string{} - } - default: - p.Params[loki] = vv - } - } - } - } - - if draft != nil && published != nil { - p.Draft = *draft - p.s.Log.ERROR.Printf("page %s has both draft and published settings in its frontmatter. Using draft.", p.File.Path()) - return ErrHasDraftAndPublished - } else if draft != nil { - p.Draft = *draft - } else if published != nil { - p.Draft = !*published - } - p.Params["draft"] = p.Draft - - if p.Date.IsZero() && p.s.Cfg.GetBool("useModTimeAsFallback") { - fi, err := p.s.Fs.Source.Stat(filepath.Join(p.s.PathSpec.AbsPathify(p.s.Cfg.GetString("contentDir")), p.File.Path())) - if err == nil { - p.Date = fi.ModTime() - p.Params["date"] = p.Date - } - } - - if p.Lastmod.IsZero() { - p.Lastmod = p.Date - } - p.Params["lastmod"] = p.Lastmod - - if isCJKLanguage != nil { - p.isCJKLanguage = *isCJKLanguage - } else if p.s.Cfg.GetBool("hasCJKLanguage") { - if cjk.Match(p.rawContent) { - p.isCJKLanguage = true - } else { - p.isCJKLanguage = false - } - } - p.Params["iscjklanguage"] = p.isCJKLanguage - - return nil - -} - -func (p *Page) GetParam(key string) interface{} { - return p.getParam(key, true) -} - -func (p *Page) getParam(key string, stringToLower bool) interface{} { - v := p.Params[strings.ToLower(key)] - - if v == nil { - return nil - } - - switch val := v.(type) { - case bool: - return val - case string: - if stringToLower { - return strings.ToLower(val) - } - return val - case int64, int32, int16, int8, int: - return cast.ToInt(v) - case float64, float32: - return cast.ToFloat64(v) - case time.Time: - return val - case []string: - if stringToLower { - return helpers.SliceToLower(val) - } - return v - case map[string]interface{}: // JSON and TOML - return v - case map[interface{}]interface{}: // YAML - return v - } - - p.s.Log.ERROR.Printf("GetParam(\"%s\"): Unknown type %s\n", key, reflect.TypeOf(v)) - return nil -} - -func (p *Page) HasMenuCurrent(menuID string, me *MenuEntry) bool { - - sectionPagesMenu := p.Site.sectionPagesMenu - - // page is labeled as "shadow-member" of the menu with the same identifier as the section - if sectionPagesMenu != "" { - section := p.Section() - - if section != "" && sectionPagesMenu == menuID && section == me.Identifier { - return true - } - } - - if !me.HasChildren() { - return false - } - - menus := p.Menus() - - if m, ok := menus[menuID]; ok { - - for _, child := range me.Children { - if child.IsEqual(m) { - return true - } - if p.HasMenuCurrent(menuID, child) { - return true - } - } - - } - - if p.IsPage() { - return false - } - - // The following logic is kept from back when Hugo had both Page and Node types. - // TODO(bep) consolidate / clean - nme := MenuEntry{Name: p.Title, URL: p.URL()} - - for _, child := range me.Children { - if nme.IsSameResource(child) { - return true - } - if p.HasMenuCurrent(menuID, child) { - return true - } - } - - return false - -} - -func (p *Page) IsMenuCurrent(menuID string, inme *MenuEntry) bool { - - menus := p.Menus() - - if me, ok := menus[menuID]; ok { - if me.IsEqual(inme) { - return true - } - } - - if p.IsPage() { - return false - } - - // The following logic is kept from back when Hugo had both Page and Node types. - // TODO(bep) consolidate / clean - me := MenuEntry{Name: p.Title, URL: p.URL()} - - if !me.IsSameResource(inme) { - return false - } - - // this resource may be included in several menus - // search for it to make sure that it is in the menu with the given menuId - if menu, ok := (*p.Site.Menus)[menuID]; ok { - for _, menuEntry := range *menu { - if menuEntry.IsSameResource(inme) { - return true - } - - descendantFound := p.isSameAsDescendantMenu(inme, menuEntry) - if descendantFound { - return descendantFound - } - - } - } - - return false -} - -func (p *Page) isSameAsDescendantMenu(inme *MenuEntry, parent *MenuEntry) bool { - if parent.HasChildren() { - for _, child := range parent.Children { - if child.IsSameResource(inme) { - return true - } - descendantFound := p.isSameAsDescendantMenu(inme, child) - if descendantFound { - return descendantFound - } - } - } - return false -} - -func (p *Page) Menus() PageMenus { - p.pageMenusInit.Do(func() { - p.pageMenus = PageMenus{} - - if ms, ok := p.Params["menu"]; ok { - link := p.RelPermalink() - - me := MenuEntry{Name: p.LinkTitle(), Weight: p.Weight, URL: link} - - // Could be the name of the menu to attach it to - mname, err := cast.ToStringE(ms) - - if err == nil { - me.Menu = mname - p.pageMenus[mname] = &me - return - } - - // Could be a slice of strings - mnames, err := cast.ToStringSliceE(ms) - - if err == nil { - for _, mname := range mnames { - me.Menu = mname - p.pageMenus[mname] = &me - } - return - } - - // Could be a structured menu entry - menus, err := cast.ToStringMapE(ms) - - if err != nil { - p.s.Log.ERROR.Printf("unable to process menus for %q\n", p.Title) - } - - for name, menu := range menus { - menuEntry := MenuEntry{Name: p.LinkTitle(), URL: link, Weight: p.Weight, Menu: name} - if menu != nil { - p.s.Log.DEBUG.Printf("found menu: %q, in %q\n", name, p.Title) - ime, err := cast.ToStringMapE(menu) - if err != nil { - p.s.Log.ERROR.Printf("unable to process menus for %q: %s", p.Title, err) - } - - menuEntry.marshallMap(ime) - } - p.pageMenus[name] = &menuEntry - - } - } - }) - - return p.pageMenus -} - -func (p *Page) shouldRenderTo(f output.Format) bool { - _, found := p.outputFormats.GetByName(f.Name) - return found -} - -func (p *Page) determineMarkupType() string { - // Try markup explicitly set in the frontmatter - p.Markup = helpers.GuessType(p.Markup) - if p.Markup == "unknown" { - // Fall back to file extension (might also return "unknown") - p.Markup = helpers.GuessType(p.Source.Ext()) - } - - return p.Markup -} - -func (p *Page) parse(reader io.Reader) error { - psr, err := parser.ReadFrom(reader) - if err != nil { - return err - } - - p.renderable = psr.IsRenderable() - p.frontmatter = psr.FrontMatter() - p.rawContent = psr.Content() - p.lang = p.Source.File.Lang() - - meta, err := psr.Metadata() - if err != nil { - return fmt.Errorf("failed to parse page metadata for %q: %s", p.File.Path(), err) - } - - if meta != nil { - if err = p.update(meta); err != nil { - return err - } - } - - return nil -} - -func (p *Page) RawContent() string { - return string(p.rawContent) -} - -func (p *Page) SetSourceContent(content []byte) { - p.Source.Content = content -} - -func (p *Page) SetSourceMetaData(in interface{}, mark rune) (err error) { - // See https://github.com/gohugoio/hugo/issues/2458 - defer func() { - if r := recover(); r != nil { - var ok bool - err, ok = r.(error) - if !ok { - err = fmt.Errorf("error from marshal: %v", r) - } - } - }() - - buf := bp.GetBuffer() - defer bp.PutBuffer(buf) - - err = parser.InterfaceToFrontMatter(in, mark, buf) - if err != nil { - return - } - - _, err = buf.WriteRune('\n') - if err != nil { - return - } - - p.Source.Frontmatter = buf.Bytes() - - return -} - -func (p *Page) SafeSaveSourceAs(path string) error { - return p.saveSourceAs(path, true) -} - -func (p *Page) SaveSourceAs(path string) error { - return p.saveSourceAs(path, false) -} - -func (p *Page) saveSourceAs(path string, safe bool) error { - b := bp.GetBuffer() - defer bp.PutBuffer(b) - - b.Write(p.Source.Frontmatter) - b.Write(p.Source.Content) - - bc := make([]byte, b.Len(), b.Len()) - copy(bc, b.Bytes()) - - return p.saveSource(bc, path, safe) -} - -func (p *Page) saveSource(by []byte, inpath string, safe bool) (err error) { - if !filepath.IsAbs(inpath) { - inpath = p.s.PathSpec.AbsPathify(inpath) - } - p.s.Log.INFO.Println("creating", inpath) - if safe { - err = helpers.SafeWriteToDisk(inpath, bytes.NewReader(by), p.s.Fs.Source) - } else { - err = helpers.WriteToDisk(inpath, bytes.NewReader(by), p.s.Fs.Source) - } - if err != nil { - return - } - return nil -} - -func (p *Page) SaveSource() error { - return p.SaveSourceAs(p.FullFilePath()) -} - -func (p *Page) processShortcodes() error { - p.shortcodeState = newShortcodeHandler(p) - tmpContent, err := p.shortcodeState.extractShortcodes(string(p.workContent), p) - if err != nil { - return err - } - p.workContent = []byte(tmpContent) - - return nil - -} - -func (p *Page) FullFilePath() string { - return filepath.Join(p.Dir(), p.LogicalName()) -} - -// Pre render prepare steps - -func (p *Page) prepareLayouts() error { - // TODO(bep): Check the IsRenderable logic. - if p.Kind == KindPage { - if !p.IsRenderable() { - self := "__" + p.UniqueID() - err := p.s.TemplateHandler().AddLateTemplate(self, string(p.Content)) - if err != nil { - return err - } - p.selfLayout = self - } - } - - return nil -} - -func (p *Page) prepareData(s *Site) error { - if p.Kind != KindSection { - var pages Pages - p.Data = make(map[string]interface{}) - - switch p.Kind { - case KindPage: - case KindHome: - pages = s.RegularPages - case KindTaxonomy: - plural := p.sections[0] - term := p.sections[1] - - if s.Info.preserveTaxonomyNames { - if v, ok := s.taxonomiesOrigKey[fmt.Sprintf("%s-%s", plural, term)]; ok { - term = v - } - } - - singular := s.taxonomiesPluralSingular[plural] - taxonomy := s.Taxonomies[plural].Get(term) - - p.Data[singular] = taxonomy - p.Data["Singular"] = singular - p.Data["Plural"] = plural - p.Data["Term"] = term - pages = taxonomy.Pages() - case KindTaxonomyTerm: - plural := p.sections[0] - singular := s.taxonomiesPluralSingular[plural] - - p.Data["Singular"] = singular - p.Data["Plural"] = plural - p.Data["Terms"] = s.Taxonomies[plural] - // keep the following just for legacy reasons - p.Data["OrderedIndex"] = p.Data["Terms"] - p.Data["Index"] = p.Data["Terms"] - - // A list of all KindTaxonomy pages with matching plural - for _, p := range s.findPagesByKind(KindTaxonomy) { - if p.sections[0] == plural { - pages = append(pages, p) - } - } - } - - p.Data["Pages"] = pages - p.Pages = pages - } - - // Now we know enough to set missing dates on home page etc. - p.updatePageDates() - - return nil -} - -func (p *Page) updatePageDates() { - // TODO(bep) there is a potential issue with page sorting for home pages - // etc. without front matter dates set, but let us wrap the head around - // that in another time. - if !p.IsNode() { - return - } - - if !p.Date.IsZero() { - if p.Lastmod.IsZero() { - p.Lastmod = p.Date - } - return - } else if !p.Lastmod.IsZero() { - if p.Date.IsZero() { - p.Date = p.Lastmod - } - return - } - - // Set it to the first non Zero date in children - var foundDate, foundLastMod bool - - for _, child := range p.Pages { - if !child.Date.IsZero() { - p.Date = child.Date - foundDate = true - } - if !child.Lastmod.IsZero() { - p.Lastmod = child.Lastmod - foundLastMod = true - } - - if foundDate && foundLastMod { - break - } - } -} - -// copy creates a copy of this page with the lazy sync.Once vars reset -// so they will be evaluated again, for word count calculations etc. -func (p *Page) copy() *Page { - c := *p - c.pageInit = &pageInit{} - return &c -} - -func (p *Page) Now() time.Time { - // Delete in Hugo 0.22 - helpers.Deprecated("Page", "Now", "Use now (the template func)", true) - return time.Now() -} - -func (p *Page) Hugo() *HugoInfo { - return hugoInfo -} - -func (p *Page) Ref(refs ...string) (string, error) { - if len(refs) == 0 { - return "", nil - } - if len(refs) > 1 { - return p.Site.Ref(refs[0], nil, refs[1]) - } - return p.Site.Ref(refs[0], nil) -} - -func (p *Page) RelRef(refs ...string) (string, error) { - if len(refs) == 0 { - return "", nil - } - if len(refs) > 1 { - return p.Site.RelRef(refs[0], nil, refs[1]) - } - return p.Site.RelRef(refs[0], nil) -} - -func (p *Page) String() string { - return fmt.Sprintf("Page(%q)", p.Title) -} - -type URLPath struct { - URL string - Permalink string - Slug string - Section string -} - -// Scratch returns the writable context associated with this Page. -func (p *Page) Scratch() *Scratch { - if p.scratch == nil { - p.scratch = newScratch() - } - return p.scratch -} - -func (p *Page) Language() *helpers.Language { - p.initLanguage() - return p.language -} - -func (p *Page) Lang() string { - // When set, Language can be different from lang in the case where there is a - // content file (doc.sv.md) with language indicator, but there is no language - // config for that language. Then the language will fall back on the site default. - if p.Language() != nil { - return p.Language().Lang - } - return p.lang -} - -func (p *Page) isNewTranslation(candidate *Page) bool { - - if p.Kind != candidate.Kind { - return false - } - - if p.Kind == KindPage || p.Kind == kindUnknown { - panic("Node type not currently supported for this op") - } - - // At this point, we know that this is a traditional Node (home page, section, taxonomy) - // It represents the same node, but different language, if the sections is the same. - if len(p.sections) != len(candidate.sections) { - return false - } - - for i := 0; i < len(p.sections); i++ { - if p.sections[i] != candidate.sections[i] { - return false - } - } - - // Finally check that it is not already added. - for _, translation := range p.translations { - if candidate == translation { - return false - } - } - - return true - -} - -func (p *Page) shouldAddLanguagePrefix() bool { - if !p.Site.IsMultiLingual() { - return false - } - - if p.Lang() == "" { - return false - } - - if !p.Site.defaultContentLanguageInSubdir && p.Lang() == p.Site.multilingual.DefaultLang.Lang { - return false - } - - return true -} - -func (p *Page) initLanguage() { - p.languageInit.Do(func() { - if p.language != nil { - return - } - - ml := p.Site.multilingual - if ml == nil { - panic("Multilanguage not set") - } - if p.lang == "" { - p.lang = ml.DefaultLang.Lang - p.language = ml.DefaultLang - return - } - - language := ml.Language(p.lang) - - if language == nil { - // It can be a file named stefano.chiodino.md. - p.s.Log.WARN.Printf("Page language (if it is that) not found in multilang setup: %s.", p.lang) - language = ml.DefaultLang - } - - p.language = language - - }) -} - -func (p *Page) LanguagePrefix() string { - return p.Site.LanguagePrefix -} - -func (p *Page) addLangPathPrefix(outfile string) string { - return p.addLangPathPrefixIfFlagSet(outfile, p.shouldAddLanguagePrefix()) -} - -func (p *Page) addLangPathPrefixIfFlagSet(outfile string, should bool) string { - if helpers.IsAbsURL(outfile) { - return outfile - } - - if !should { - return outfile - } - - hadSlashSuffix := strings.HasSuffix(outfile, "/") - - outfile = "/" + path.Join(p.Lang(), outfile) - if hadSlashSuffix { - outfile += "/" - } - return outfile -} - -func sectionsFromFilename(filename string) []string { - var sections []string - dir, _ := filepath.Split(filename) - dir = strings.TrimSuffix(dir, helpers.FilePathSeparator) - if dir == "" { - return sections - } - sections = strings.Split(dir, helpers.FilePathSeparator) - return sections -} - -const ( - regularPageFileNameDoesNotStartWith = "_index" - - // There can be "my_regular_index_page.md but not /_index_file.md - regularPageFileNameDoesNotContain = helpers.FilePathSeparator + regularPageFileNameDoesNotStartWith -) - -func kindFromFilename(filename string) string { - if !strings.HasPrefix(filename, regularPageFileNameDoesNotStartWith) && !strings.Contains(filename, regularPageFileNameDoesNotContain) { - return KindPage - } - - if strings.HasPrefix(filename, "_index") { - return KindHome - } - - // We don't know enough yet to determine the type. - return kindUnknown -} - -func (p *Page) setValuesForKind(s *Site) { - if p.Kind == kindUnknown { - // This is either a taxonomy list, taxonomy term or a section - nodeType := s.kindFromSections(p.sections) - - if nodeType == kindUnknown { - panic(fmt.Sprintf("Unable to determine page kind from %q", p.sections)) - } - - p.Kind = nodeType - } - - switch p.Kind { - case KindHome: - p.URLPath.URL = "/" - case KindPage: - default: - p.URLPath.URL = "/" + path.Join(p.sections...) + "/" - } -} - -// Used in error logs. -func (p *Page) pathOrTitle() string { - if p.Path() != "" { - return p.Path() - } - return p.Title -} diff --git a/hugolib/pageCache.go b/hugolib/pageCache.go deleted file mode 100644 index e0a3a160b..000000000 --- a/hugolib/pageCache.go +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "sync" -) - -type pageCache struct { - sync.RWMutex - m map[string][][2]Pages -} - -func newPageCache() *pageCache { - return &pageCache{m: make(map[string][][2]Pages)} -} - -// get gets a Pages slice from the cache matching the given key and Pages slice. -// If none found in cache, a copy of the supplied slice is created. -// -// If an apply func is provided, that func is applied to the newly created copy. -// -// The cache and the execution of the apply func is protected by a RWMutex. -func (c *pageCache) get(key string, p Pages, apply func(p Pages)) (Pages, bool) { - c.RLock() - if cached, ok := c.m[key]; ok { - for _, ps := range cached { - if probablyEqualPages(p, ps[0]) { - c.RUnlock() - return ps[1], true - } - } - - } - c.RUnlock() - - c.Lock() - defer c.Unlock() - - // double-check - if cached, ok := c.m[key]; ok { - for _, ps := range cached { - if probablyEqualPages(p, ps[0]) { - return ps[1], true - } - } - } - - pagesCopy := append(Pages(nil), p...) - - if apply != nil { - apply(pagesCopy) - } - - if v, ok := c.m[key]; ok { - c.m[key] = append(v, [2]Pages{p, pagesCopy}) - } else { - c.m[key] = [][2]Pages{{p, pagesCopy}} - } - - return pagesCopy, false - -} - -// "probably" as in: we do not compare every element for big slices, but that is -// good enough for our use case. -// TODO(bep) there is a similar method in pagination.go. DRY. -func probablyEqualPages(p1, p2 Pages) bool { - if p1 == nil && p2 == nil { - return true - } - - if p1 == nil || p2 == nil { - return false - } - - if p1.Len() != p2.Len() { - return false - } - - if p1.Len() == 0 { - return true - } - - step := 1 - - if len(p1) >= 50 { - step = len(p1) / 10 - } - - for i := 0; i < len(p1); i += step { - if p1[i] != p2[i] { - return false - } - } - return true -} diff --git a/hugolib/pageCache_test.go b/hugolib/pageCache_test.go deleted file mode 100644 index 62837394f..000000000 --- a/hugolib/pageCache_test.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "sync" - "sync/atomic" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestPageCache(t *testing.T) { - t.Parallel() - c1 := newPageCache() - - changeFirst := func(p Pages) { - p[0].Description = "changed" - } - - var o1 uint64 - var o2 uint64 - - var wg sync.WaitGroup - - var l1 sync.Mutex - var l2 sync.Mutex - - var testPageSets []Pages - - s := newTestSite(t) - - for i := 0; i < 50; i++ { - testPageSets = append(testPageSets, createSortTestPages(s, i+1)) - } - - for j := 0; j < 100; j++ { - wg.Add(1) - go func() { - defer wg.Done() - for k, pages := range testPageSets { - l1.Lock() - p, c := c1.get("k1", pages, nil) - assert.Equal(t, !atomic.CompareAndSwapUint64(&o1, uint64(k), uint64(k+1)), c) - l1.Unlock() - p2, c2 := c1.get("k1", p, nil) - assert.True(t, c2) - assert.True(t, probablyEqualPages(p, p2)) - assert.True(t, probablyEqualPages(p, pages)) - assert.NotNil(t, p) - - l2.Lock() - p3, c3 := c1.get("k2", pages, changeFirst) - assert.Equal(t, !atomic.CompareAndSwapUint64(&o2, uint64(k), uint64(k+1)), c3) - l2.Unlock() - assert.NotNil(t, p3) - assert.Equal(t, p3[0].Description, "changed") - } - }() - } - wg.Wait() -} diff --git a/hugolib/pageGroup.go b/hugolib/pageGroup.go deleted file mode 100644 index 343ecf52e..000000000 --- a/hugolib/pageGroup.go +++ /dev/null @@ -1,297 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "errors" - "reflect" - "sort" - "strings" - "time" -) - -// PageGroup represents a group of pages, grouped by the key. -// The key is typically a year or similar. -type PageGroup struct { - Key interface{} - Pages Pages -} - -type mapKeyValues []reflect.Value - -func (v mapKeyValues) Len() int { return len(v) } -func (v mapKeyValues) Swap(i, j int) { v[i], v[j] = v[j], v[i] } - -type mapKeyByInt struct{ mapKeyValues } - -func (s mapKeyByInt) Less(i, j int) bool { return s.mapKeyValues[i].Int() < s.mapKeyValues[j].Int() } - -type mapKeyByStr struct{ mapKeyValues } - -func (s mapKeyByStr) Less(i, j int) bool { - return s.mapKeyValues[i].String() < s.mapKeyValues[j].String() -} - -func sortKeys(v []reflect.Value, order string) []reflect.Value { - if len(v) <= 1 { - return v - } - - switch v[0].Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - if order == "desc" { - sort.Sort(sort.Reverse(mapKeyByInt{v})) - } else { - sort.Sort(mapKeyByInt{v}) - } - case reflect.String: - if order == "desc" { - sort.Sort(sort.Reverse(mapKeyByStr{v})) - } else { - sort.Sort(mapKeyByStr{v}) - } - } - return v -} - -// PagesGroup represents a list of page groups. -// This is what you get when doing page grouping in the templates. -type PagesGroup []PageGroup - -// Reverse reverses the order of this list of page groups. -func (p PagesGroup) Reverse() PagesGroup { - for i, j := 0, len(p)-1; i < j; i, j = i+1, j-1 { - p[i], p[j] = p[j], p[i] - } - - return p -} - -var ( - errorType = reflect.TypeOf((*error)(nil)).Elem() - pagePtrType = reflect.TypeOf((*Page)(nil)) -) - -// GroupBy groups by the value in the given field or method name and with the given order. -// Valid values for order is asc, desc, rev and reverse. -func (p Pages) GroupBy(key string, order ...string) (PagesGroup, error) { - if len(p) < 1 { - return nil, nil - } - - direction := "asc" - - if len(order) > 0 && (strings.ToLower(order[0]) == "desc" || strings.ToLower(order[0]) == "rev" || strings.ToLower(order[0]) == "reverse") { - direction = "desc" - } - - var ft interface{} - m, ok := pagePtrType.MethodByName(key) - if ok { - if m.Type.NumIn() != 1 || m.Type.NumOut() == 0 || m.Type.NumOut() > 2 { - return nil, errors.New(key + " is a Page method but you can't use it with GroupBy") - } - if m.Type.NumOut() == 1 && m.Type.Out(0).Implements(errorType) { - return nil, errors.New(key + " is a Page method but you can't use it with GroupBy") - } - if m.Type.NumOut() == 2 && !m.Type.Out(1).Implements(errorType) { - return nil, errors.New(key + " is a Page method but you can't use it with GroupBy") - } - ft = m - } else { - ft, ok = pagePtrType.Elem().FieldByName(key) - if !ok { - return nil, errors.New(key + " is neither a field nor a method of Page") - } - } - - var tmp reflect.Value - switch e := ft.(type) { - case reflect.StructField: - tmp = reflect.MakeMap(reflect.MapOf(e.Type, reflect.SliceOf(pagePtrType))) - case reflect.Method: - tmp = reflect.MakeMap(reflect.MapOf(e.Type.Out(0), reflect.SliceOf(pagePtrType))) - } - - for _, e := range p { - ppv := reflect.ValueOf(e) - var fv reflect.Value - switch ft.(type) { - case reflect.StructField: - fv = ppv.Elem().FieldByName(key) - case reflect.Method: - fv = ppv.MethodByName(key).Call([]reflect.Value{})[0] - } - if !fv.IsValid() { - continue - } - if !tmp.MapIndex(fv).IsValid() { - tmp.SetMapIndex(fv, reflect.MakeSlice(reflect.SliceOf(pagePtrType), 0, 0)) - } - tmp.SetMapIndex(fv, reflect.Append(tmp.MapIndex(fv), ppv)) - } - - var r []PageGroup - for _, k := range sortKeys(tmp.MapKeys(), direction) { - r = append(r, PageGroup{Key: k.Interface(), Pages: tmp.MapIndex(k).Interface().([]*Page)}) - } - - return r, nil -} - -// GroupByParam groups by the given page parameter key's value and with the given order. -// Valid values for order is asc, desc, rev and reverse. -func (p Pages) GroupByParam(key string, order ...string) (PagesGroup, error) { - if len(p) < 1 { - return nil, nil - } - - direction := "asc" - - if len(order) > 0 && (strings.ToLower(order[0]) == "desc" || strings.ToLower(order[0]) == "rev" || strings.ToLower(order[0]) == "reverse") { - direction = "desc" - } - - var tmp reflect.Value - var keyt reflect.Type - for _, e := range p { - param := e.GetParam(key) - if param != nil { - if _, ok := param.([]string); !ok { - keyt = reflect.TypeOf(param) - tmp = reflect.MakeMap(reflect.MapOf(keyt, reflect.SliceOf(pagePtrType))) - break - } - } - } - if !tmp.IsValid() { - return nil, errors.New("There is no such a param") - } - - for _, e := range p { - param := e.getParam(key, false) - if param == nil || reflect.TypeOf(param) != keyt { - continue - } - v := reflect.ValueOf(param) - if !tmp.MapIndex(v).IsValid() { - tmp.SetMapIndex(v, reflect.MakeSlice(reflect.SliceOf(pagePtrType), 0, 0)) - } - tmp.SetMapIndex(v, reflect.Append(tmp.MapIndex(v), reflect.ValueOf(e))) - } - - var r []PageGroup - for _, k := range sortKeys(tmp.MapKeys(), direction) { - r = append(r, PageGroup{Key: k.Interface(), Pages: tmp.MapIndex(k).Interface().([]*Page)}) - } - - return r, nil -} - -func (p Pages) groupByDateField(sorter func(p Pages) Pages, formatter func(p *Page) string, order ...string) (PagesGroup, error) { - if len(p) < 1 { - return nil, nil - } - - sp := sorter(p) - - if !(len(order) > 0 && (strings.ToLower(order[0]) == "asc" || strings.ToLower(order[0]) == "rev" || strings.ToLower(order[0]) == "reverse")) { - sp = sp.Reverse() - } - - date := formatter(sp[0]) - var r []PageGroup - r = append(r, PageGroup{Key: date, Pages: make(Pages, 0)}) - r[0].Pages = append(r[0].Pages, sp[0]) - - i := 0 - for _, e := range sp[1:] { - date = formatter(e) - if r[i].Key.(string) != date { - r = append(r, PageGroup{Key: date}) - i++ - } - r[i].Pages = append(r[i].Pages, e) - } - return r, nil -} - -// GroupByDate groups by the given page's Date value in -// the given format and with the given order. -// Valid values for order is asc, desc, rev and reverse. -// For valid format strings, see https://golang.org/pkg/time/#Time.Format -func (p Pages) GroupByDate(format string, order ...string) (PagesGroup, error) { - sorter := func(p Pages) Pages { - return p.ByDate() - } - formatter := func(p *Page) string { - return p.Date.Format(format) - } - return p.groupByDateField(sorter, formatter, order...) -} - -// GroupByPublishDate groups by the given page's PublishDate value in -// the given format and with the given order. -// Valid values for order is asc, desc, rev and reverse. -// For valid format strings, see https://golang.org/pkg/time/#Time.Format -func (p Pages) GroupByPublishDate(format string, order ...string) (PagesGroup, error) { - sorter := func(p Pages) Pages { - return p.ByPublishDate() - } - formatter := func(p *Page) string { - return p.PublishDate.Format(format) - } - return p.groupByDateField(sorter, formatter, order...) -} - -// GroupByExpiryDate groups by the given page's ExpireDate value in -// the given format and with the given order. -// Valid values for order is asc, desc, rev and reverse. -// For valid format strings, see https://golang.org/pkg/time/#Time.Format -func (p Pages) GroupByExpiryDate(format string, order ...string) (PagesGroup, error) { - sorter := func(p Pages) Pages { - return p.ByExpiryDate() - } - formatter := func(p *Page) string { - return p.ExpiryDate.Format(format) - } - return p.groupByDateField(sorter, formatter, order...) -} - -// GroupByParamDate groups by a date set as a param on the page in -// the given format and with the given order. -// Valid values for order is asc, desc, rev and reverse. -// For valid format strings, see https://golang.org/pkg/time/#Time.Format -func (p Pages) GroupByParamDate(key string, format string, order ...string) (PagesGroup, error) { - sorter := func(p Pages) Pages { - var r Pages - for _, e := range p { - param := e.GetParam(key) - if param != nil { - if _, ok := param.(time.Time); ok { - r = append(r, e) - } - } - } - pdate := func(p1, p2 *Page) bool { - return p1.GetParam(key).(time.Time).Unix() < p2.GetParam(key).(time.Time).Unix() - } - pageBy(pdate).Sort(r) - return r - } - formatter := func(p *Page) string { - return p.GetParam(key).(time.Time).Format(format) - } - return p.groupByDateField(sorter, formatter, order...) -} diff --git a/hugolib/pageGroup_test.go b/hugolib/pageGroup_test.go deleted file mode 100644 index 8cc381b61..000000000 --- a/hugolib/pageGroup_test.go +++ /dev/null @@ -1,457 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "errors" - "path/filepath" - "reflect" - "testing" - - "github.com/spf13/cast" -) - -type pageGroupTestObject struct { - path string - weight int - date string - param string -} - -var pageGroupTestSources = []pageGroupTestObject{ - {"/section1/testpage1.md", 3, "2012-04-06", "foo"}, - {"/section1/testpage2.md", 3, "2012-01-01", "bar"}, - {"/section1/testpage3.md", 2, "2012-04-06", "foo"}, - {"/section2/testpage4.md", 1, "2012-03-02", "bar"}, - {"/section2/testpage5.md", 1, "2012-04-06", "baz"}, -} - -func preparePageGroupTestPages(t *testing.T) Pages { - s := newTestSite(t) - var pages Pages - for _, src := range pageGroupTestSources { - p, err := s.NewPage(filepath.FromSlash(src.path)) - if err != nil { - t.Fatalf("failed to prepare test page %s", src.path) - } - p.Weight = src.weight - p.Date = cast.ToTime(src.date) - p.PublishDate = cast.ToTime(src.date) - p.ExpiryDate = cast.ToTime(src.date) - p.Params["custom_param"] = src.param - p.Params["custom_date"] = cast.ToTime(src.date) - pages = append(pages, p) - } - return pages -} - -func TestGroupByWithFieldNameArg(t *testing.T) { - t.Parallel() - pages := preparePageGroupTestPages(t) - expect := PagesGroup{ - {Key: 1, Pages: Pages{pages[3], pages[4]}}, - {Key: 2, Pages: Pages{pages[2]}}, - {Key: 3, Pages: Pages{pages[0], pages[1]}}, - } - - groups, err := pages.GroupBy("Weight") - if err != nil { - t.Fatalf("Unable to make PagesGroup array: %s", err) - } - if !reflect.DeepEqual(groups, expect) { - t.Errorf("PagesGroup has unexpected groups. It should be %#v, got %#v", expect, groups) - } -} - -func TestGroupByWithMethodNameArg(t *testing.T) { - t.Parallel() - pages := preparePageGroupTestPages(t) - expect := PagesGroup{ - {Key: "section1", Pages: Pages{pages[0], pages[1], pages[2]}}, - {Key: "section2", Pages: Pages{pages[3], pages[4]}}, - } - - groups, err := pages.GroupBy("Type") - if err != nil { - t.Fatalf("Unable to make PagesGroup array: %s", err) - } - if !reflect.DeepEqual(groups, expect) { - t.Errorf("PagesGroup has unexpected groups. It should be %#v, got %#v", expect, groups) - } -} - -func TestGroupByWithSectionArg(t *testing.T) { - t.Parallel() - pages := preparePageGroupTestPages(t) - expect := PagesGroup{ - {Key: "section1", Pages: Pages{pages[0], pages[1], pages[2]}}, - {Key: "section2", Pages: Pages{pages[3], pages[4]}}, - } - - groups, err := pages.GroupBy("Section") - if err != nil { - t.Fatalf("Unable to make PagesGroup array: %s", err) - } - if !reflect.DeepEqual(groups, expect) { - t.Errorf("PagesGroup has unexpected groups. It should be %#v, got %#v", expect, groups) - } -} - -func TestGroupByInReverseOrder(t *testing.T) { - t.Parallel() - pages := preparePageGroupTestPages(t) - expect := PagesGroup{ - {Key: 3, Pages: Pages{pages[0], pages[1]}}, - {Key: 2, Pages: Pages{pages[2]}}, - {Key: 1, Pages: Pages{pages[3], pages[4]}}, - } - - groups, err := pages.GroupBy("Weight", "desc") - if err != nil { - t.Fatalf("Unable to make PagesGroup array: %s", err) - } - if !reflect.DeepEqual(groups, expect) { - t.Errorf("PagesGroup has unexpected groups. It should be %#v, got %#v", expect, groups) - } -} - -func TestGroupByCalledWithEmptyPages(t *testing.T) { - t.Parallel() - var pages Pages - groups, err := pages.GroupBy("Weight") - if err != nil { - t.Fatalf("Unable to make PagesGroup array: %s", err) - } - if groups != nil { - t.Errorf("PagesGroup isn't empty. It should be %#v, got %#v", nil, groups) - } -} - -func TestGroupByCalledWithUnavailableKey(t *testing.T) { - t.Parallel() - pages := preparePageGroupTestPages(t) - _, err := pages.GroupBy("UnavailableKey") - if err == nil { - t.Errorf("GroupByParam should return an error but didn't") - } -} - -func (page *Page) DummyPageMethodWithArgForTest(s string) string { - return s -} - -func (page *Page) DummyPageMethodReturnThreeValueForTest() (string, string, string) { - return "foo", "bar", "baz" -} - -func (page *Page) DummyPageMethodReturnErrorOnlyForTest() error { - return errors.New("some error occurred") -} - -func (page *Page) dummyPageMethodReturnTwoValueForTest() (string, string) { - return "foo", "bar" -} - -func TestGroupByCalledWithInvalidMethod(t *testing.T) { - t.Parallel() - var err error - pages := preparePageGroupTestPages(t) - - _, err = pages.GroupBy("DummyPageMethodWithArgForTest") - if err == nil { - t.Errorf("GroupByParam should return an error but didn't") - } - - _, err = pages.GroupBy("DummyPageMethodReturnThreeValueForTest") - if err == nil { - t.Errorf("GroupByParam should return an error but didn't") - } - - _, err = pages.GroupBy("DummyPageMethodReturnErrorOnlyForTest") - if err == nil { - t.Errorf("GroupByParam should return an error but didn't") - } - - _, err = pages.GroupBy("DummyPageMethodReturnTwoValueForTest") - if err == nil { - t.Errorf("GroupByParam should return an error but didn't") - } -} - -func TestReverse(t *testing.T) { - t.Parallel() - pages := preparePageGroupTestPages(t) - - groups1, err := pages.GroupBy("Weight", "desc") - if err != nil { - t.Fatalf("Unable to make PagesGroup array: %s", err) - } - - groups2, err := pages.GroupBy("Weight") - if err != nil { - t.Fatalf("Unable to make PagesGroup array: %s", err) - } - groups2 = groups2.Reverse() - - if !reflect.DeepEqual(groups2, groups1) { - t.Errorf("PagesGroup is sorted in unexpected order. It should be %#v, got %#v", groups2, groups1) - } -} - -func TestGroupByParam(t *testing.T) { - t.Parallel() - pages := preparePageGroupTestPages(t) - expect := PagesGroup{ - {Key: "bar", Pages: Pages{pages[1], pages[3]}}, - {Key: "baz", Pages: Pages{pages[4]}}, - {Key: "foo", Pages: Pages{pages[0], pages[2]}}, - } - - groups, err := pages.GroupByParam("custom_param") - if err != nil { - t.Fatalf("Unable to make PagesGroup array: %s", err) - } - if !reflect.DeepEqual(groups, expect) { - t.Errorf("PagesGroup has unexpected groups. It should be %#v, got %#v", expect, groups) - } -} - -func TestGroupByParamInReverseOrder(t *testing.T) { - t.Parallel() - pages := preparePageGroupTestPages(t) - expect := PagesGroup{ - {Key: "foo", Pages: Pages{pages[0], pages[2]}}, - {Key: "baz", Pages: Pages{pages[4]}}, - {Key: "bar", Pages: Pages{pages[1], pages[3]}}, - } - - groups, err := pages.GroupByParam("custom_param", "desc") - if err != nil { - t.Fatalf("Unable to make PagesGroup array: %s", err) - } - if !reflect.DeepEqual(groups, expect) { - t.Errorf("PagesGroup has unexpected groups. It should be %#v, got %#v", expect, groups) - } -} - -func TestGroupByParamCalledWithCapitalLetterString(t *testing.T) { - testStr := "TestString" - f := "/section1/test_capital.md" - s := newTestSite(t) - p, err := s.NewPage(filepath.FromSlash(f)) - if err != nil { - t.Fatalf("failed to prepare test page %s", f) - } - p.Params["custom_param"] = testStr - pages := Pages{p} - - groups, err := pages.GroupByParam("custom_param") - if err != nil { - t.Fatalf("Unable to make PagesGroup array: %s", err) - } - if groups[0].Key != testStr { - t.Errorf("PagesGroup key is converted to a lower character string. It should be %#v, got %#v", testStr, groups[0].Key) - } -} - -func TestGroupByParamCalledWithSomeUnavailableParams(t *testing.T) { - t.Parallel() - pages := preparePageGroupTestPages(t) - delete(pages[1].Params, "custom_param") - delete(pages[3].Params, "custom_param") - delete(pages[4].Params, "custom_param") - - expect := PagesGroup{ - {Key: "foo", Pages: Pages{pages[0], pages[2]}}, - } - - groups, err := pages.GroupByParam("custom_param") - if err != nil { - t.Fatalf("Unable to make PagesGroup array: %s", err) - } - if !reflect.DeepEqual(groups, expect) { - t.Errorf("PagesGroup has unexpected groups. It should be %#v, got %#v", expect, groups) - } -} - -func TestGroupByParamCalledWithEmptyPages(t *testing.T) { - t.Parallel() - var pages Pages - groups, err := pages.GroupByParam("custom_param") - if err != nil { - t.Fatalf("Unable to make PagesGroup array: %s", err) - } - if groups != nil { - t.Errorf("PagesGroup isn't empty. It should be %#v, got %#v", nil, groups) - } -} - -func TestGroupByParamCalledWithUnavailableParam(t *testing.T) { - t.Parallel() - pages := preparePageGroupTestPages(t) - _, err := pages.GroupByParam("unavailable_param") - if err == nil { - t.Errorf("GroupByParam should return an error but didn't") - } -} - -func TestGroupByDate(t *testing.T) { - t.Parallel() - pages := preparePageGroupTestPages(t) - expect := PagesGroup{ - {Key: "2012-04", Pages: Pages{pages[4], pages[2], pages[0]}}, - {Key: "2012-03", Pages: Pages{pages[3]}}, - {Key: "2012-01", Pages: Pages{pages[1]}}, - } - - groups, err := pages.GroupByDate("2006-01") - if err != nil { - t.Fatalf("Unable to make PagesGroup array: %s", err) - } - if !reflect.DeepEqual(groups, expect) { - t.Errorf("PagesGroup has unexpected groups. It should be %#v, got %#v", expect, groups) - } -} - -func TestGroupByDateInReverseOrder(t *testing.T) { - t.Parallel() - pages := preparePageGroupTestPages(t) - expect := PagesGroup{ - {Key: "2012-01", Pages: Pages{pages[1]}}, - {Key: "2012-03", Pages: Pages{pages[3]}}, - {Key: "2012-04", Pages: Pages{pages[0], pages[2], pages[4]}}, - } - - groups, err := pages.GroupByDate("2006-01", "asc") - if err != nil { - t.Fatalf("Unable to make PagesGroup array: %s", err) - } - if !reflect.DeepEqual(groups, expect) { - t.Errorf("PagesGroup has unexpected groups. It should be %#v, got %#v", expect, groups) - } -} - -func TestGroupByPublishDate(t *testing.T) { - t.Parallel() - pages := preparePageGroupTestPages(t) - expect := PagesGroup{ - {Key: "2012-04", Pages: Pages{pages[4], pages[2], pages[0]}}, - {Key: "2012-03", Pages: Pages{pages[3]}}, - {Key: "2012-01", Pages: Pages{pages[1]}}, - } - - groups, err := pages.GroupByPublishDate("2006-01") - if err != nil { - t.Fatalf("Unable to make PagesGroup array: %s", err) - } - if !reflect.DeepEqual(groups, expect) { - t.Errorf("PagesGroup has unexpected groups. It should be %#v, got %#v", expect, groups) - } -} - -func TestGroupByPublishDateInReverseOrder(t *testing.T) { - t.Parallel() - pages := preparePageGroupTestPages(t) - expect := PagesGroup{ - {Key: "2012-01", Pages: Pages{pages[1]}}, - {Key: "2012-03", Pages: Pages{pages[3]}}, - {Key: "2012-04", Pages: Pages{pages[0], pages[2], pages[4]}}, - } - - groups, err := pages.GroupByDate("2006-01", "asc") - if err != nil { - t.Fatalf("Unable to make PagesGroup array: %s", err) - } - if !reflect.DeepEqual(groups, expect) { - t.Errorf("PagesGroup has unexpected groups. It should be %#v, got %#v", expect, groups) - } -} - -func TestGroupByPublishDateWithEmptyPages(t *testing.T) { - t.Parallel() - var pages Pages - groups, err := pages.GroupByPublishDate("2006-01") - if err != nil { - t.Fatalf("Unable to make PagesGroup array: %s", err) - } - if groups != nil { - t.Errorf("PagesGroup isn't empty. It should be %#v, got %#v", nil, groups) - } -} - -func TestGroupByExpiryDate(t *testing.T) { - t.Parallel() - pages := preparePageGroupTestPages(t) - expect := PagesGroup{ - {Key: "2012-04", Pages: Pages{pages[4], pages[2], pages[0]}}, - {Key: "2012-03", Pages: Pages{pages[3]}}, - {Key: "2012-01", Pages: Pages{pages[1]}}, - } - - groups, err := pages.GroupByExpiryDate("2006-01") - if err != nil { - t.Fatalf("Unable to make PagesGroup array: %s", err) - } - if !reflect.DeepEqual(groups, expect) { - t.Errorf("PagesGroup has unexpected groups. It should be %#v, got %#v", expect, groups) - } -} - -func TestGroupByParamDate(t *testing.T) { - t.Parallel() - pages := preparePageGroupTestPages(t) - expect := PagesGroup{ - {Key: "2012-04", Pages: Pages{pages[4], pages[2], pages[0]}}, - {Key: "2012-03", Pages: Pages{pages[3]}}, - {Key: "2012-01", Pages: Pages{pages[1]}}, - } - - groups, err := pages.GroupByParamDate("custom_date", "2006-01") - if err != nil { - t.Fatalf("Unable to make PagesGroup array: %s", err) - } - if !reflect.DeepEqual(groups, expect) { - t.Errorf("PagesGroup has unexpected groups. It should be %#v, got %#v", expect, groups) - } -} - -func TestGroupByParamDateInReverseOrder(t *testing.T) { - t.Parallel() - pages := preparePageGroupTestPages(t) - expect := PagesGroup{ - {Key: "2012-01", Pages: Pages{pages[1]}}, - {Key: "2012-03", Pages: Pages{pages[3]}}, - {Key: "2012-04", Pages: Pages{pages[0], pages[2], pages[4]}}, - } - - groups, err := pages.GroupByParamDate("custom_date", "2006-01", "asc") - if err != nil { - t.Fatalf("Unable to make PagesGroup array: %s", err) - } - if !reflect.DeepEqual(groups, expect) { - t.Errorf("PagesGroup has unexpected groups. It should be %#v, got %#v", expect, groups) - } -} - -func TestGroupByParamDateWithEmptyPages(t *testing.T) { - t.Parallel() - var pages Pages - groups, err := pages.GroupByParamDate("custom_date", "2006-01") - if err != nil { - t.Fatalf("Unable to make PagesGroup array: %s", err) - } - if groups != nil { - t.Errorf("PagesGroup isn't empty. It should be %#v, got %#v", nil, groups) - } -} diff --git a/hugolib/pageSort.go b/hugolib/pageSort.go deleted file mode 100644 index 6d2431cec..000000000 --- a/hugolib/pageSort.go +++ /dev/null @@ -1,303 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "github.com/spf13/cast" - "sort" -) - -var spc = newPageCache() - -/* - * Implementation of a custom sorter for Pages - */ - -// A pageSorter implements the sort interface for Pages -type pageSorter struct { - pages Pages - by pageBy -} - -// pageBy is a closure used in the Sort.Less method. -type pageBy func(p1, p2 *Page) bool - -// Sort stable sorts the pages given the receiver's sort order. -func (by pageBy) Sort(pages Pages) { - ps := &pageSorter{ - pages: pages, - by: by, // The Sort method's receiver is the function (closure) that defines the sort order. - } - sort.Stable(ps) -} - -// defaultPageSort is the default sort for pages in Hugo: -// Order by Weight, Date, LinkTitle and then full file path. -var defaultPageSort = func(p1, p2 *Page) bool { - if p1.Weight == p2.Weight { - if p1.Date.Unix() == p2.Date.Unix() { - if p1.LinkTitle() == p2.LinkTitle() { - return (p1.FullFilePath() < p2.FullFilePath()) - } - return (p1.LinkTitle() < p2.LinkTitle()) - } - return p1.Date.Unix() > p2.Date.Unix() - } - - if p2.Weight == 0 { - return true - } - - if p1.Weight == 0 { - return false - } - - return p1.Weight < p2.Weight -} - -var languagePageSort = func(p1, p2 *Page) bool { - if p1.Language().Weight == p2.Language().Weight { - if p1.Date.Unix() == p2.Date.Unix() { - if p1.LinkTitle() == p2.LinkTitle() { - return (p1.FullFilePath() < p2.FullFilePath()) - } - return (p1.LinkTitle() < p2.LinkTitle()) - } - return p1.Date.Unix() > p2.Date.Unix() - } - - if p2.Language().Weight == 0 { - return true - } - - if p1.Language().Weight == 0 { - return false - } - - return p1.Language().Weight < p2.Language().Weight -} - -func (ps *pageSorter) Len() int { return len(ps.pages) } -func (ps *pageSorter) Swap(i, j int) { ps.pages[i], ps.pages[j] = ps.pages[j], ps.pages[i] } - -// Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter. -func (ps *pageSorter) Less(i, j int) bool { return ps.by(ps.pages[i], ps.pages[j]) } - -// Sort sorts the pages by the default sort order defined: -// Order by Weight, Date, LinkTitle and then full file path. -func (p Pages) Sort() { - pageBy(defaultPageSort).Sort(p) -} - -// Limit limits the number of pages returned to n. -func (p Pages) Limit(n int) Pages { - if len(p) > n { - return p[0:n] - } - return p -} - -// ByWeight sorts the Pages by weight and returns a copy. -// -// Adjacent invocations on the same receiver will return a cached result. -// -// This may safely be executed in parallel. -func (p Pages) ByWeight() Pages { - key := "pageSort.ByWeight" - pages, _ := spc.get(key, p, pageBy(defaultPageSort).Sort) - return pages -} - -// ByTitle sorts the Pages by title and returns a copy. -// -// Adjacent invocations on the same receiver will return a cached result. -// -// This may safely be executed in parallel. -func (p Pages) ByTitle() Pages { - - key := "pageSort.ByTitle" - - title := func(p1, p2 *Page) bool { - return p1.Title < p2.Title - } - - pages, _ := spc.get(key, p, pageBy(title).Sort) - return pages -} - -// ByLinkTitle sorts the Pages by link title and returns a copy. -// -// Adjacent invocations on the same receiver will return a cached result. -// -// This may safely be executed in parallel. -func (p Pages) ByLinkTitle() Pages { - - key := "pageSort.ByLinkTitle" - - linkTitle := func(p1, p2 *Page) bool { - return p1.linkTitle < p2.linkTitle - } - - pages, _ := spc.get(key, p, pageBy(linkTitle).Sort) - - return pages -} - -// ByDate sorts the Pages by date and returns a copy. -// -// Adjacent invocations on the same receiver will return a cached result. -// -// This may safely be executed in parallel. -func (p Pages) ByDate() Pages { - - key := "pageSort.ByDate" - - date := func(p1, p2 *Page) bool { - return p1.Date.Unix() < p2.Date.Unix() - } - - pages, _ := spc.get(key, p, pageBy(date).Sort) - - return pages -} - -// ByPublishDate sorts the Pages by publish date and returns a copy. -// -// Adjacent invocations on the same receiver will return a cached result. -// -// This may safely be executed in parallel. -func (p Pages) ByPublishDate() Pages { - - key := "pageSort.ByPublishDate" - - pubDate := func(p1, p2 *Page) bool { - return p1.PublishDate.Unix() < p2.PublishDate.Unix() - } - - pages, _ := spc.get(key, p, pageBy(pubDate).Sort) - - return pages -} - -// ByExpiryDate sorts the Pages by publish date and returns a copy. -// -// Adjacent invocations on the same receiver will return a cached result. -// -// This may safely be executed in parallel. -func (p Pages) ByExpiryDate() Pages { - - key := "pageSort.ByExpiryDate" - - expDate := func(p1, p2 *Page) bool { - return p1.ExpiryDate.Unix() < p2.ExpiryDate.Unix() - } - - pages, _ := spc.get(key, p, pageBy(expDate).Sort) - - return pages -} - -// ByLastmod sorts the Pages by the last modification date and returns a copy. -// -// Adjacent invocations on the same receiver will return a cached result. -// -// This may safely be executed in parallel. -func (p Pages) ByLastmod() Pages { - - key := "pageSort.ByLastmod" - - date := func(p1, p2 *Page) bool { - return p1.Lastmod.Unix() < p2.Lastmod.Unix() - } - - pages, _ := spc.get(key, p, pageBy(date).Sort) - - return pages -} - -// ByLength sorts the Pages by length and returns a copy. -// -// Adjacent invocations on the same receiver will return a cached result. -// -// This may safely be executed in parallel. -func (p Pages) ByLength() Pages { - - key := "pageSort.ByLength" - - length := func(p1, p2 *Page) bool { - return len(p1.Content) < len(p2.Content) - } - - pages, _ := spc.get(key, p, pageBy(length).Sort) - - return pages -} - -// ByLanguage sorts the Pages by the language's Weight. -// -// Adjacent invocations on the same receiver will return a cached result. -// -// This may safely be executed in parallel. -func (p Pages) ByLanguage() Pages { - - key := "pageSort.ByLanguage" - - pages, _ := spc.get(key, p, pageBy(languagePageSort).Sort) - - return pages -} - -// Reverse reverses the order in Pages and returns a copy. -// -// Adjacent invocations on the same receiver will return a cached result. -// -// This may safely be executed in parallel. -func (p Pages) Reverse() Pages { - key := "pageSort.Reverse" - - reverseFunc := func(pages Pages) { - for i, j := 0, len(pages)-1; i < j; i, j = i+1, j-1 { - pages[i], pages[j] = pages[j], pages[i] - } - } - - pages, _ := spc.get(key, p, reverseFunc) - - return pages -} - -func (p Pages) ByParam(paramsKey interface{}) Pages { - paramsKeyStr := cast.ToString(paramsKey) - key := "pageSort.ByParam." + paramsKeyStr - - paramsKeyComparator := func(p1, p2 *Page) bool { - v1, _ := p1.Param(paramsKeyStr) - v2, _ := p2.Param(paramsKeyStr) - s1 := cast.ToString(v1) - s2 := cast.ToString(v2) - - // Sort nils last. - if s1 == "" { - return false - } else if s2 == "" { - return true - } - - return s1 < s2 - } - - pages, _ := spc.get(key, p, pageBy(paramsKeyComparator).Sort) - - return pages -} diff --git a/hugolib/pageSort_test.go b/hugolib/pageSort_test.go deleted file mode 100644 index a17f53dc6..000000000 --- a/hugolib/pageSort_test.go +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "fmt" - "html/template" - "path/filepath" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestDefaultSort(t *testing.T) { - t.Parallel() - d1 := time.Now() - d2 := d1.Add(-1 * time.Hour) - d3 := d1.Add(-2 * time.Hour) - d4 := d1.Add(-3 * time.Hour) - - s := newTestSite(t) - - p := createSortTestPages(s, 4) - - // first by weight - setSortVals([4]time.Time{d1, d2, d3, d4}, [4]string{"b", "a", "c", "d"}, [4]int{4, 3, 2, 1}, p) - p.Sort() - - assert.Equal(t, 1, p[0].Weight) - - // Consider zero weight, issue #2673 - setSortVals([4]time.Time{d1, d2, d3, d4}, [4]string{"b", "a", "d", "c"}, [4]int{0, 0, 0, 1}, p) - p.Sort() - - assert.Equal(t, 1, p[0].Weight) - - // next by date - setSortVals([4]time.Time{d3, d4, d1, d2}, [4]string{"a", "b", "c", "d"}, [4]int{1, 1, 1, 1}, p) - p.Sort() - assert.Equal(t, d1, p[0].Date) - - // finally by link title - setSortVals([4]time.Time{d3, d3, d3, d3}, [4]string{"b", "c", "a", "d"}, [4]int{1, 1, 1, 1}, p) - p.Sort() - assert.Equal(t, "al", p[0].LinkTitle()) - assert.Equal(t, "bl", p[1].LinkTitle()) - assert.Equal(t, "cl", p[2].LinkTitle()) -} - -func TestSortByN(t *testing.T) { - t.Parallel() - s := newTestSite(t) - d1 := time.Now() - d2 := d1.Add(-2 * time.Hour) - d3 := d1.Add(-10 * time.Hour) - d4 := d1.Add(-20 * time.Hour) - - p := createSortTestPages(s, 4) - - for i, this := range []struct { - sortFunc func(p Pages) Pages - assertFunc func(p Pages) bool - }{ - {(Pages).ByWeight, func(p Pages) bool { return p[0].Weight == 1 }}, - {(Pages).ByTitle, func(p Pages) bool { return p[0].Title == "ab" }}, - {(Pages).ByLinkTitle, func(p Pages) bool { return p[0].LinkTitle() == "abl" }}, - {(Pages).ByDate, func(p Pages) bool { return p[0].Date == d4 }}, - {(Pages).ByPublishDate, func(p Pages) bool { return p[0].PublishDate == d4 }}, - {(Pages).ByExpiryDate, func(p Pages) bool { return p[0].ExpiryDate == d4 }}, - {(Pages).ByLastmod, func(p Pages) bool { return p[1].Lastmod == d3 }}, - {(Pages).ByLength, func(p Pages) bool { return p[0].Content == "b_content" }}, - } { - setSortVals([4]time.Time{d1, d2, d3, d4}, [4]string{"b", "ab", "cde", "fg"}, [4]int{0, 3, 2, 1}, p) - - sorted := this.sortFunc(p) - if !this.assertFunc(sorted) { - t.Errorf("[%d] sort error", i) - } - } - -} - -func TestLimit(t *testing.T) { - t.Parallel() - s := newTestSite(t) - p := createSortTestPages(s, 10) - firstFive := p.Limit(5) - assert.Equal(t, 5, len(firstFive)) - for i := 0; i < 5; i++ { - assert.Equal(t, p[i], firstFive[i]) - } - assert.Equal(t, p, p.Limit(10)) - assert.Equal(t, p, p.Limit(11)) -} - -func TestPageSortReverse(t *testing.T) { - t.Parallel() - s := newTestSite(t) - p1 := createSortTestPages(s, 10) - assert.Equal(t, 0, p1[0].fuzzyWordCount) - assert.Equal(t, 9, p1[9].fuzzyWordCount) - p2 := p1.Reverse() - assert.Equal(t, 9, p2[0].fuzzyWordCount) - assert.Equal(t, 0, p2[9].fuzzyWordCount) - // cached - assert.True(t, probablyEqualPages(p2, p1.Reverse())) -} - -func TestPageSortByParam(t *testing.T) { - t.Parallel() - var k interface{} = "arbitrarily.nested" - s := newTestSite(t) - - unsorted := createSortTestPages(s, 10) - delete(unsorted[9].Params, "arbitrarily") - - firstSetValue, _ := unsorted[0].Param(k) - secondSetValue, _ := unsorted[1].Param(k) - lastSetValue, _ := unsorted[8].Param(k) - unsetValue, _ := unsorted[9].Param(k) - - assert.Equal(t, "xyz100", firstSetValue) - assert.Equal(t, "xyz99", secondSetValue) - assert.Equal(t, "xyz92", lastSetValue) - assert.Equal(t, nil, unsetValue) - - sorted := unsorted.ByParam("arbitrarily.nested") - firstSetSortedValue, _ := sorted[0].Param(k) - secondSetSortedValue, _ := sorted[1].Param(k) - lastSetSortedValue, _ := sorted[8].Param(k) - unsetSortedValue, _ := sorted[9].Param(k) - - assert.Equal(t, firstSetValue, firstSetSortedValue) - assert.Equal(t, secondSetValue, lastSetSortedValue) - assert.Equal(t, lastSetValue, secondSetSortedValue) - assert.Equal(t, unsetValue, unsetSortedValue) -} - -func BenchmarkSortByWeightAndReverse(b *testing.B) { - s := newTestSite(b) - p := createSortTestPages(s, 300) - - b.ResetTimer() - for i := 0; i < b.N; i++ { - p = p.ByWeight().Reverse() - } -} - -func setSortVals(dates [4]time.Time, titles [4]string, weights [4]int, pages Pages) { - for i := range dates { - pages[i].Date = dates[i] - pages[i].Lastmod = dates[i] - pages[i].Weight = weights[i] - pages[i].Title = titles[i] - // make sure we compare apples and ... apples ... - pages[len(dates)-1-i].linkTitle = pages[i].Title + "l" - pages[len(dates)-1-i].PublishDate = dates[i] - pages[len(dates)-1-i].ExpiryDate = dates[i] - pages[len(dates)-1-i].Content = template.HTML(titles[i] + "_content") - } - lastLastMod := pages[2].Lastmod - pages[2].Lastmod = pages[1].Lastmod - pages[1].Lastmod = lastLastMod -} - -func createSortTestPages(s *Site, num int) Pages { - pages := make(Pages, num) - - for i := 0; i < num; i++ { - p := s.newPage(filepath.FromSlash(fmt.Sprintf("/x/y/p%d.md", i))) - p.Params = map[string]interface{}{ - "arbitrarily": map[string]interface{}{ - "nested": ("xyz" + fmt.Sprintf("%v", 100-i)), - }, - } - - w := 5 - - if i%2 == 0 { - w = 10 - } - p.fuzzyWordCount = i - p.Weight = w - p.Description = "initial" - - pages[i] = p - } - - return pages -} diff --git a/hugolib/page_collections.go b/hugolib/page_collections.go deleted file mode 100644 index e72f9a731..000000000 --- a/hugolib/page_collections.go +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright 2016 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "path" - "path/filepath" - - "github.com/gohugoio/hugo/cache" -) - -// PageCollections contains the page collections for a site. -type PageCollections struct { - // Includes only pages of all types, and only pages in the current language. - Pages Pages - - // Includes all pages in all languages, including the current one. - // Includes pages of all types. - AllPages Pages - - // A convenience cache for the traditional index types, taxonomies, home page etc. - // This is for the current language only. - indexPages Pages - - // A convenience cache for the regular pages. - // This is for the current language only. - RegularPages Pages - - // A convenience cache for the all the regular pages. - AllRegularPages Pages - - // Includes absolute all pages (of all types), including drafts etc. - rawAllPages Pages - - pageCache *cache.PartitionedLazyCache -} - -func (c *PageCollections) refreshPageCaches() { - c.indexPages = c.findPagesByKindNotIn(KindPage, c.Pages) - c.RegularPages = c.findPagesByKindIn(KindPage, c.Pages) - c.AllRegularPages = c.findPagesByKindIn(KindPage, c.AllPages) - - cacheLoader := func(kind string) func() (map[string]interface{}, error) { - return func() (map[string]interface{}, error) { - cache := make(map[string]interface{}) - switch kind { - case KindPage: - // Note that we deliberately use the pages from all sites - // in this cache, as we intend to use this in the ref and relref - // shortcodes. If the user says "sect/doc1.en.md", he/she knows - // what he/she is looking for. - for _, p := range c.AllRegularPages { - cache[filepath.ToSlash(p.Source.Path())] = p - // Ref/Relref supports this potentially ambiguous lookup. - cache[p.Source.LogicalName()] = p - } - default: - for _, p := range c.indexPages { - key := path.Join(p.sections...) - cache[key] = p - } - } - - return cache, nil - } - } - - var partitions []cache.Partition - - for _, kind := range allKindsInPages { - partitions = append(partitions, cache.Partition{Key: kind, Load: cacheLoader(kind)}) - } - - c.pageCache = cache.NewPartitionedLazyCache(partitions...) -} - -func newPageCollections() *PageCollections { - return &PageCollections{} -} - -func newPageCollectionsFromPages(pages Pages) *PageCollections { - return &PageCollections{rawAllPages: pages} -} - -func (c *PageCollections) getPage(typ string, sections ...string) *Page { - var key string - if len(sections) == 1 { - key = filepath.ToSlash(sections[0]) - } else { - key = path.Join(sections...) - } - - p, _ := c.pageCache.Get(typ, key) - if p == nil { - return nil - } - return p.(*Page) - -} - -func (*PageCollections) findPagesByKindIn(kind string, inPages Pages) Pages { - var pages Pages - for _, p := range inPages { - if p.Kind == kind { - pages = append(pages, p) - } - } - return pages -} - -func (*PageCollections) findPagesByKindNotIn(kind string, inPages Pages) Pages { - var pages Pages - for _, p := range inPages { - if p.Kind != kind { - pages = append(pages, p) - } - } - return pages -} - -func (c *PageCollections) findPagesByKind(kind string) Pages { - return c.findPagesByKindIn(kind, c.Pages) -} - -func (c *PageCollections) addPage(page *Page) { - c.rawAllPages = append(c.rawAllPages, page) -} - -// When we get a REMOVE event we're not always getting all the individual files, -// so we need to remove all below a given path. -func (c *PageCollections) removePageByPathPrefix(path string) { - for { - i := c.rawAllPages.findFirstPagePosByFilePathPrefix(path) - if i == -1 { - break - } - c.rawAllPages = append(c.rawAllPages[:i], c.rawAllPages[i+1:]...) - } -} - -func (c *PageCollections) removePageByPath(path string) { - if i := c.rawAllPages.findPagePosByFilePath(path); i >= 0 { - c.rawAllPages = append(c.rawAllPages[:i], c.rawAllPages[i+1:]...) - } -} - -func (c *PageCollections) removePage(page *Page) { - if i := c.rawAllPages.findPagePos(page); i >= 0 { - c.rawAllPages = append(c.rawAllPages[:i], c.rawAllPages[i+1:]...) - } -} - -func (c *PageCollections) findPagesByShortcode(shortcode string) Pages { - var pages Pages - - for _, p := range c.rawAllPages { - if p.shortcodeState != nil { - if _, ok := p.shortcodeState.nameSet[shortcode]; ok { - pages = append(pages, p) - } - } - } - return pages -} - -func (c *PageCollections) replacePage(page *Page) { - // will find existing page that matches filepath and remove it - c.removePage(page) - c.addPage(page) -} diff --git a/hugolib/page_collections_test.go b/hugolib/page_collections_test.go deleted file mode 100644 index aee99040c..000000000 --- a/hugolib/page_collections_test.go +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2017 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "fmt" - "math/rand" - "path" - "path/filepath" - "testing" - "time" - - "github.com/gohugoio/hugo/deps" - "github.com/stretchr/testify/require" -) - -const pageCollectionsPageTemplate = `--- -title: "%s" -categories: -- Hugo ---- -# Doc -` - -func BenchmarkGetPage(b *testing.B) { - var ( - cfg, fs = newTestCfg() - r = rand.New(rand.NewSource(time.Now().UnixNano())) - ) - - for i := 0; i < 10; i++ { - for j := 0; j < 100; j++ { - writeSource(b, fs, filepath.Join("content", fmt.Sprintf("sect%d", i), fmt.Sprintf("page%d.md", j)), "CONTENT") - } - } - - s := buildSingleSite(b, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) - - pagePaths := make([]string, b.N) - - for i := 0; i < b.N; i++ { - pagePaths[i] = fmt.Sprintf("sect%d", r.Intn(10)) - } - - b.ResetTimer() - for i := 0; i < b.N; i++ { - home := s.getPage(KindHome) - if home == nil { - b.Fatal("Home is nil") - } - - p := s.getPage(KindSection, pagePaths[i]) - if p == nil { - b.Fatal("Section is nil") - } - - } -} - -func BenchmarkGetPageRegular(b *testing.B) { - var ( - cfg, fs = newTestCfg() - r = rand.New(rand.NewSource(time.Now().UnixNano())) - ) - - for i := 0; i < 10; i++ { - for j := 0; j < 100; j++ { - content := fmt.Sprintf(pageCollectionsPageTemplate, fmt.Sprintf("Title%d_%d", i, j)) - writeSource(b, fs, filepath.Join("content", fmt.Sprintf("sect%d", i), fmt.Sprintf("page%d.md", j)), content) - } - } - - s := buildSingleSite(b, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) - - pagePaths := make([]string, b.N) - - for i := 0; i < b.N; i++ { - pagePaths[i] = path.Join(fmt.Sprintf("sect%d", r.Intn(10)), fmt.Sprintf("page%d.md", r.Intn(100))) - } - - b.ResetTimer() - for i := 0; i < b.N; i++ { - page := s.getPage(KindPage, pagePaths[i]) - require.NotNil(b, page) - } -} - -func TestGetPage(t *testing.T) { - - var ( - assert = require.New(t) - cfg, fs = newTestCfg() - ) - - for i := 0; i < 10; i++ { - for j := 0; j < 10; j++ { - content := fmt.Sprintf(pageCollectionsPageTemplate, fmt.Sprintf("Title%d_%d", i, j)) - writeSource(t, fs, filepath.Join("content", fmt.Sprintf("sect%d", i), fmt.Sprintf("page%d.md", j)), content) - } - } - - content := fmt.Sprintf(pageCollectionsPageTemplate, "UniqueBase") - writeSource(t, fs, filepath.Join("content", "sect3", "unique.md"), content) - - s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) - - tests := []struct { - kind string - path []string - expectedTitle string - }{ - {KindHome, []string{}, ""}, - {KindSection, []string{"sect3"}, "Sect3s"}, - {KindPage, []string{"sect3", "page1.md"}, "Title3_1"}, - {KindPage, []string{"sect4/page2.md"}, "Title4_2"}, - {KindPage, []string{filepath.FromSlash("sect5/page3.md")}, "Title5_3"}, - // Ref/Relref supports this potentially ambiguous lookup. - {KindPage, []string{"unique.md"}, "UniqueBase"}, - } - - for i, test := range tests { - errorMsg := fmt.Sprintf("Test %d", i) - page := s.getPage(test.kind, test.path...) - assert.NotNil(page, errorMsg) - assert.Equal(test.kind, page.Kind) - assert.Equal(test.expectedTitle, page.Title) - } - -} diff --git a/hugolib/page_output.go b/hugolib/page_output.go deleted file mode 100644 index 3b1e07907..000000000 --- a/hugolib/page_output.go +++ /dev/null @@ -1,273 +0,0 @@ -// Copyright 2017 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "fmt" - "html/template" - "strings" - "sync" - - "github.com/gohugoio/hugo/media" - - "github.com/gohugoio/hugo/helpers" - "github.com/gohugoio/hugo/output" -) - -// PageOutput represents one of potentially many output formats of a given -// Page. -type PageOutput struct { - *Page - - // Pagination - paginator *Pager - paginatorInit sync.Once - - // Keep this to create URL/path variations, i.e. paginators. - targetPathDescriptor targetPathDescriptor - - outputFormat output.Format -} - -func (p *PageOutput) targetPath(addends ...string) (string, error) { - tp, err := p.createTargetPath(p.outputFormat, addends...) - if err != nil { - return "", err - } - return tp, nil -} - -func newPageOutput(p *Page, createCopy bool, f output.Format) (*PageOutput, error) { - // TODO(bep) This is only needed for tests and we should get rid of it. - if p.targetPathDescriptorPrototype == nil { - if err := p.initTargetPathDescriptor(); err != nil { - return nil, err - } - if err := p.initURLs(); err != nil { - return nil, err - } - } - - if createCopy { - p = p.copy() - } - - td, err := p.createTargetPathDescriptor(f) - - if err != nil { - return nil, err - } - - return &PageOutput{ - Page: p, - outputFormat: f, - targetPathDescriptor: td, - }, nil -} - -// copy creates a copy of this PageOutput with the lazy sync.Once vars reset -// so they will be evaluated again, for word count calculations etc. -func (p *PageOutput) copyWithFormat(f output.Format) (*PageOutput, error) { - c, err := newPageOutput(p.Page, true, f) - if err != nil { - return nil, err - } - c.paginator = p.paginator - return c, nil -} - -func (p *PageOutput) copy() (*PageOutput, error) { - return p.copyWithFormat(p.outputFormat) -} - -func (p *PageOutput) layouts(layouts ...string) ([]string, error) { - if len(layouts) == 0 && p.selfLayout != "" { - return []string{p.selfLayout}, nil - } - - layoutOverride := "" - if len(layouts) > 0 { - layoutOverride = layouts[0] - } - - return p.s.layoutHandler.For( - p.layoutDescriptor, - layoutOverride, - p.outputFormat) -} - -func (p *PageOutput) Render(layout ...string) template.HTML { - if !p.checkRender() { - return "" - } - - l, err := p.layouts(layout...) - if err != nil { - helpers.DistinctErrorLog.Printf("in .Render: Failed to resolve layout %q for page %q", layout, p.pathOrTitle()) - return "" - } - - for _, layout := range l { - templ := p.s.Tmpl.Lookup(layout) - if templ == nil { - // This is legacy from when we had only one output format and - // HTML templates only. Some have references to layouts without suffix. - // We default to good old HTML. - templ = p.s.Tmpl.Lookup(layout + ".html") - } - if templ != nil { - res, err := templ.ExecuteToString(p) - if err != nil { - helpers.DistinctErrorLog.Printf("in .Render: Failed to execute template %q for page %q", layout, p.pathOrTitle()) - return template.HTML("") - } - return template.HTML(res) - } - } - - return "" - -} - -func (p *Page) Render(layout ...string) template.HTML { - if !p.checkRender() { - return "" - } - - p.pageOutputInit.Do(func() { - if p.mainPageOutput != nil { - return - } - // If Render is called in a range loop, the page output isn't available. - // So, create one. - outFormat := p.outputFormats[0] - pageOutput, err := newPageOutput(p, true, outFormat) - - if err != nil { - p.s.Log.ERROR.Printf("Failed to create output page for type %q for page %q: %s", outFormat.Name, p.pathOrTitle(), err) - return - } - - p.mainPageOutput = pageOutput - - }) - - return p.mainPageOutput.Render(layout...) -} - -// We may fix this in the future, but the layout handling in Render isn't built -// for list pages. -func (p *Page) checkRender() bool { - if p.Kind != KindPage { - helpers.DistinctWarnLog.Printf(".Render only available for regular pages, not for of kind %q. You probably meant .Site.RegularPages and not.Site.Pages.", p.Kind) - return false - } - return true -} - -// OutputFormats holds a list of the relevant output formats for a given resource. -type OutputFormats []*OutputFormat - -// OutputFormat links to a representation of a resource. -type OutputFormat struct { - // Rel constains a value that can be used to construct a rel link. - // This is value is fetched from the output format definition. - // Note that for pages with only one output format, - // this method will always return "canonical". - // As an example, the AMP output format will, by default, return "amphtml". - // - // See: - // https://www.ampproject.org/docs/guides/deploy/discovery - // - // Most other output formats will have "alternate" as value for this. - Rel string - - // It may be tempting to export this, but let us hold on to that horse for a while. - f output.Format - - p *Page -} - -// Name returns this OutputFormat's name, i.e. HTML, AMP, JSON etc. -func (o OutputFormat) Name() string { - return o.f.Name -} - -// MediaType returns this OutputFormat's MediaType (MIME type). -func (o OutputFormat) MediaType() media.Type { - return o.f.MediaType -} - -// OutputFormats gives the output formats for this Page. -func (p *Page) OutputFormats() OutputFormats { - var o OutputFormats - for _, f := range p.outputFormats { - o = append(o, newOutputFormat(p, f)) - } - return o -} - -func newOutputFormat(p *Page, f output.Format) *OutputFormat { - rel := f.Rel - isCanonical := len(p.outputFormats) == 1 - if isCanonical { - rel = "canonical" - } - return &OutputFormat{Rel: rel, f: f, p: p} -} - -// AlternativeOutputFormats gives the alternative output formats for this PageOutput. -// Note that we use the term "alternative" and not "alternate" here, as it -// does not necessarily replace the other format, it is an alternative representation. -func (p *PageOutput) AlternativeOutputFormats() (OutputFormats, error) { - var o OutputFormats - for _, of := range p.OutputFormats() { - if of.f.NotAlternative || of.f == p.outputFormat { - continue - } - o = append(o, of) - } - return o, nil -} - -// AlternativeOutputFormats is only available on the top level rendering -// entry point, and not inside range loops on the Page collections. -// This method is just here to inform users of that restriction. -func (p *Page) AlternativeOutputFormats() (OutputFormats, error) { - return nil, fmt.Errorf("AlternativeOutputFormats only available from the top level template context for page %q", p.Path()) -} - -// Get gets a OutputFormat given its name, i.e. json, html etc. -// It returns nil if not found. -func (o OutputFormats) Get(name string) *OutputFormat { - for _, f := range o { - if strings.EqualFold(f.f.Name, name) { - return f - } - } - return nil -} - -// Permalink returns the absolute permalink to this output format. -func (o *OutputFormat) Permalink() string { - rel := o.p.createRelativePermalinkForOutputFormat(o.f) - perm, _ := o.p.s.permalinkForOutputFormat(rel, o.f) - return perm -} - -// RelPermalink returns the relative permalink to this output format. -func (o *OutputFormat) RelPermalink() string { - rel := o.p.createRelativePermalinkForOutputFormat(o.f) - return o.p.s.PathSpec.PrependBasePath(rel) -} diff --git a/hugolib/page_paths.go b/hugolib/page_paths.go deleted file mode 100644 index 73fd62278..000000000 --- a/hugolib/page_paths.go +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright 2017 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "fmt" - "path/filepath" - - "net/url" - "strings" - - "github.com/gohugoio/hugo/helpers" - "github.com/gohugoio/hugo/output" -) - -// targetPathDescriptor describes how a file path for a given resource -// should look like on the file system. The same descriptor is then later used to -// create both the permalinks and the relative links, paginator URLs etc. -// -// The big motivating behind this is to have only one source of truth for URLs, -// and by that also get rid of most of the fragile string parsing/encoding etc. -// -// Page.createTargetPathDescriptor is the Page adapter. -// -type targetPathDescriptor struct { - PathSpec *helpers.PathSpec - - Type output.Format - Kind string - - Sections []string - - // For regular content pages this is either - // 1) the Slug, if set, - // 2) the file base name (TranslationBaseName). - BaseName string - - // Source directory. - Dir string - - // Language prefix, set if multilingual and if page should be placed in its - // language subdir. - LangPrefix string - - // Page.URLPath.URL. Will override any Slug etc. for regular pages. - URL string - - // Used to create paginator links. - Addends string - - // The expanded permalink if defined for the section, ready to use. - ExpandedPermalink string - - // Some types cannot have uglyURLs, even if globally enabled, RSS being one example. - UglyURLs bool -} - -// createTargetPathDescriptor adapts a Page and the given output.Format into -// a targetPathDescriptor. This descriptor can then be used to create paths -// and URLs for this Page. -func (p *Page) createTargetPathDescriptor(t output.Format) (targetPathDescriptor, error) { - if p.targetPathDescriptorPrototype == nil { - panic(fmt.Sprintf("Must run initTargetPathDescriptor() for page %q, kind %q", p.Title, p.Kind)) - } - d := *p.targetPathDescriptorPrototype - d.Type = t - return d, nil -} - -func (p *Page) initTargetPathDescriptor() error { - - d := &targetPathDescriptor{ - PathSpec: p.s.PathSpec, - Kind: p.Kind, - Sections: p.sections, - UglyURLs: p.s.Info.uglyURLs, - Dir: filepath.ToSlash(p.Source.Dir()), - URL: p.URLPath.URL, - } - - if p.Slug != "" { - d.BaseName = p.Slug - } else { - d.BaseName = p.TranslationBaseName() - } - - if p.shouldAddLanguagePrefix() { - d.LangPrefix = p.Lang() - } - - if override, ok := p.Site.Permalinks[p.Section()]; ok { - opath, err := override.Expand(p) - if err != nil { - return err - } - - opath, _ = url.QueryUnescape(opath) - opath = filepath.FromSlash(opath) - d.ExpandedPermalink = opath - } - - p.targetPathDescriptorPrototype = d - return nil - -} - -// createTargetPath creates the target filename for this Page for the given -// output.Format. Some additional URL parts can also be provided, the typical -// use case being pagination. -func (p *Page) createTargetPath(t output.Format, addends ...string) (string, error) { - d, err := p.createTargetPathDescriptor(t) - if err != nil { - return "", nil - } - - if len(addends) > 0 { - d.Addends = filepath.Join(addends...) - } - - return createTargetPath(d), nil -} - -func createTargetPath(d targetPathDescriptor) string { - - pagePath := helpers.FilePathSeparator - - // The top level index files, i.e. the home page etc., needs - // the index base even when uglyURLs is enabled. - needsBase := true - - isUgly := d.UglyURLs && !d.Type.NoUgly - - // If the page output format's base name is the same as the page base name, - // we treat it as an ugly path, i.e. - // my-blog-post-1/index.md => my-blog-post-1/index.html - // (given the default values for that content file, i.e. no slug set etc.). - // This introduces the behaviour from < Hugo 0.20, see issue #3396. - if d.BaseName != "" && d.BaseName == d.Type.BaseName { - isUgly = true - } - - if d.Kind != KindPage && len(d.Sections) > 0 { - pagePath = filepath.Join(d.Sections...) - needsBase = false - } - - if d.Type.Path != "" { - pagePath = filepath.Join(pagePath, d.Type.Path) - } - - if d.Kind == KindPage { - // Always use URL if it's specified - if d.URL != "" { - pagePath = filepath.Join(pagePath, d.URL) - if strings.HasSuffix(d.URL, "/") || !strings.Contains(d.URL, ".") { - pagePath = filepath.Join(pagePath, d.Type.BaseName+d.Type.MediaType.FullSuffix()) - } - } else { - if d.ExpandedPermalink != "" { - pagePath = filepath.Join(pagePath, d.ExpandedPermalink) - - } else { - if d.Dir != "" { - pagePath = filepath.Join(pagePath, d.Dir) - } - if d.BaseName != "" { - pagePath = filepath.Join(pagePath, d.BaseName) - } - } - - if d.Addends != "" { - pagePath = filepath.Join(pagePath, d.Addends) - } - - if isUgly { - pagePath += d.Type.MediaType.Delimiter + d.Type.MediaType.Suffix - } else { - pagePath = filepath.Join(pagePath, d.Type.BaseName+d.Type.MediaType.FullSuffix()) - } - - if d.LangPrefix != "" { - pagePath = filepath.Join(d.LangPrefix, pagePath) - } - } - } else { - if d.Addends != "" { - pagePath = filepath.Join(pagePath, d.Addends) - } - - needsBase = needsBase && d.Addends == "" - - // No permalink expansion etc. for node type pages (for now) - base := "" - - if needsBase || !isUgly { - base = helpers.FilePathSeparator + d.Type.BaseName - } - - pagePath += base + d.Type.MediaType.FullSuffix() - - if d.LangPrefix != "" { - pagePath = filepath.Join(d.LangPrefix, pagePath) - } - } - - pagePath = filepath.Join(helpers.FilePathSeparator, pagePath) - - // Note: MakePathSanitized will lower case the path if - // disablePathToLower isn't set. - return d.PathSpec.MakePathSanitized(pagePath) -} - -func (p *Page) createRelativePermalink() string { - - if len(p.outputFormats) == 0 { - panic(fmt.Sprintf("Page %q missing output format(s)", p.Title)) - } - - // Choose the main output format. In most cases, this will be HTML. - f := p.outputFormats[0] - - return p.createRelativePermalinkForOutputFormat(f) - -} - -func (p *Page) createRelativePermalinkForOutputFormat(f output.Format) string { - tp, err := p.createTargetPath(f) - - if err != nil { - p.s.Log.ERROR.Printf("Failed to create permalink for page %q: %s", p.FullFilePath(), err) - return "" - } - // For /index.json etc. we must use the full path. - if strings.HasSuffix(f.BaseFilename(), "html") { - tp = strings.TrimSuffix(tp, f.BaseFilename()) - } - - return p.s.PathSpec.URLizeFilename(tp) -} - -func (p *Page) TargetPath() (outfile string) { - // Delete in Hugo 0.22 - helpers.Deprecated("Page", "TargetPath", "This method does not make sanse any more.", true) - return "" -} diff --git a/hugolib/page_paths_test.go b/hugolib/page_paths_test.go deleted file mode 100644 index 80dc390cc..000000000 --- a/hugolib/page_paths_test.go +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright 2017 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "path/filepath" - "strings" - "testing" - - "github.com/gohugoio/hugo/media" - - "fmt" - - "github.com/gohugoio/hugo/output" -) - -func TestPageTargetPath(t *testing.T) { - - pathSpec := newTestDefaultPathSpec() - - noExtNoDelimMediaType := media.TextType - noExtNoDelimMediaType.Suffix = "" - noExtNoDelimMediaType.Delimiter = "" - - // Netlify style _redirects - noExtDelimFormat := output.Format{ - Name: "NER", - MediaType: noExtNoDelimMediaType, - BaseName: "_redirects", - } - - for _, langPrefix := range []string{"", "no"} { - for _, uglyURLs := range []bool{false, true} { - t.Run(fmt.Sprintf("langPrefix=%q,uglyURLs=%t", langPrefix, uglyURLs), - func(t *testing.T) { - - tests := []struct { - name string - d targetPathDescriptor - expected string - }{ - {"JSON home", targetPathDescriptor{Kind: KindHome, Type: output.JSONFormat}, "/index.json"}, - {"AMP home", targetPathDescriptor{Kind: KindHome, Type: output.AMPFormat}, "/amp/index.html"}, - {"HTML home", targetPathDescriptor{Kind: KindHome, BaseName: "_index", Type: output.HTMLFormat}, "/index.html"}, - {"Netlify redirects", targetPathDescriptor{Kind: KindHome, BaseName: "_index", Type: noExtDelimFormat}, "/_redirects"}, - {"HTML section list", targetPathDescriptor{ - Kind: KindSection, - Sections: []string{"sect1"}, - BaseName: "_index", - Type: output.HTMLFormat}, "/sect1/index.html"}, - {"HTML taxonomy list", targetPathDescriptor{ - Kind: KindTaxonomy, - Sections: []string{"tags", "hugo"}, - BaseName: "_index", - Type: output.HTMLFormat}, "/tags/hugo/index.html"}, - {"HTML taxonomy term", targetPathDescriptor{ - Kind: KindTaxonomy, - Sections: []string{"tags"}, - BaseName: "_index", - Type: output.HTMLFormat}, "/tags/index.html"}, - { - "HTML page", targetPathDescriptor{ - Kind: KindPage, - Dir: "/a/b", - BaseName: "mypage", - Sections: []string{"a"}, - Type: output.HTMLFormat}, "/a/b/mypage/index.html"}, - - { - // Issue #3396 - "HTML page with index as base", targetPathDescriptor{ - Kind: KindPage, - Dir: "/a/b", - BaseName: "index", - Sections: []string{"a"}, - Type: output.HTMLFormat}, "/a/b/index.html"}, - - { - "HTML page with special chars", targetPathDescriptor{ - Kind: KindPage, - Dir: "/a/b", - BaseName: "My Page!", - Type: output.HTMLFormat}, "/a/b/My-Page/index.html"}, - {"RSS home", targetPathDescriptor{Kind: kindRSS, Type: output.RSSFormat}, "/index.xml"}, - {"RSS section list", targetPathDescriptor{ - Kind: kindRSS, - Sections: []string{"sect1"}, - Type: output.RSSFormat}, "/sect1/index.xml"}, - { - "AMP page", targetPathDescriptor{ - Kind: KindPage, - Dir: "/a/b/c", - BaseName: "myamp", - Type: output.AMPFormat}, "/amp/a/b/c/myamp/index.html"}, - { - "AMP page with URL with suffix", targetPathDescriptor{ - Kind: KindPage, - Dir: "/sect/", - BaseName: "mypage", - URL: "/some/other/url.xhtml", - Type: output.HTMLFormat}, "/some/other/url.xhtml"}, - { - "JSON page with URL without suffix", targetPathDescriptor{ - Kind: KindPage, - Dir: "/sect/", - BaseName: "mypage", - URL: "/some/other/path/", - Type: output.JSONFormat}, "/some/other/path/index.json"}, - { - "JSON page with URL without suffix and no trailing slash", targetPathDescriptor{ - Kind: KindPage, - Dir: "/sect/", - BaseName: "mypage", - URL: "/some/other/path", - Type: output.JSONFormat}, "/some/other/path/index.json"}, - { - "HTML page with expanded permalink", targetPathDescriptor{ - Kind: KindPage, - Dir: "/a/b", - BaseName: "mypage", - ExpandedPermalink: "/2017/10/my-title", - Type: output.HTMLFormat}, "/2017/10/my-title/index.html"}, - { - "Paginated HTML home", targetPathDescriptor{ - Kind: KindHome, - BaseName: "_index", - Type: output.HTMLFormat, - Addends: "page/3"}, "/page/3/index.html"}, - { - "Paginated Taxonomy list", targetPathDescriptor{ - Kind: KindTaxonomy, - BaseName: "_index", - Sections: []string{"tags", "hugo"}, - Type: output.HTMLFormat, - Addends: "page/3"}, "/tags/hugo/page/3/index.html"}, - { - "Regular page with addend", targetPathDescriptor{ - Kind: KindPage, - Dir: "/a/b", - BaseName: "mypage", - Addends: "c/d/e", - Type: output.HTMLFormat}, "/a/b/mypage/c/d/e/index.html"}, - } - - for i, test := range tests { - test.d.PathSpec = pathSpec - test.d.UglyURLs = uglyURLs - test.d.LangPrefix = langPrefix - test.d.Dir = filepath.FromSlash(test.d.Dir) - isUgly := uglyURLs && !test.d.Type.NoUgly - - expected := test.expected - - // TODO(bep) simplify - if test.d.Kind == KindPage && test.d.BaseName == test.d.Type.BaseName { - - } else if test.d.Kind == KindHome && test.d.Type.Path != "" { - } else if (!strings.HasPrefix(expected, "/index") || test.d.Addends != "") && test.d.URL == "" && isUgly { - expected = strings.Replace(expected, - "/"+test.d.Type.BaseName+"."+test.d.Type.MediaType.Suffix, - "."+test.d.Type.MediaType.Suffix, -1) - } - - if test.d.LangPrefix != "" && !(test.d.Kind == KindPage && test.d.URL != "") { - expected = "/" + test.d.LangPrefix + expected - } - - expected = filepath.FromSlash(expected) - - pagePath := createTargetPath(test.d) - - if pagePath != expected { - t.Fatalf("[%d] [%s] targetPath expected %q, got: %q", i, test.name, expected, pagePath) - } - } - }) - } - } -} diff --git a/hugolib/page_permalink_test.go b/hugolib/page_permalink_test.go deleted file mode 100644 index 6f899efae..000000000 --- a/hugolib/page_permalink_test.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "fmt" - "html/template" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/gohugoio/hugo/deps" -) - -func TestPermalink(t *testing.T) { - t.Parallel() - - tests := []struct { - file string - base template.URL - slug string - url string - uglyURLs bool - canonifyURLs bool - expectedAbs string - expectedRel string - }{ - {"x/y/z/boofar.md", "", "", "", false, false, "/x/y/z/boofar/", "/x/y/z/boofar/"}, - {"x/y/z/boofar.md", "", "", "", false, false, "/x/y/z/boofar/", "/x/y/z/boofar/"}, - // Issue #1174 - {"x/y/z/boofar.md", "http://gopher.com/", "", "", false, true, "http://gopher.com/x/y/z/boofar/", "/x/y/z/boofar/"}, - {"x/y/z/boofar.md", "http://gopher.com/", "", "", true, true, "http://gopher.com/x/y/z/boofar.html", "/x/y/z/boofar.html"}, - {"x/y/z/boofar.md", "", "boofar", "", false, false, "/x/y/z/boofar/", "/x/y/z/boofar/"}, - {"x/y/z/boofar.md", "http://barnew/", "", "", false, false, "http://barnew/x/y/z/boofar/", "/x/y/z/boofar/"}, - {"x/y/z/boofar.md", "http://barnew/", "boofar", "", false, false, "http://barnew/x/y/z/boofar/", "/x/y/z/boofar/"}, - {"x/y/z/boofar.md", "", "", "", true, false, "/x/y/z/boofar.html", "/x/y/z/boofar.html"}, - {"x/y/z/boofar.md", "", "", "", true, false, "/x/y/z/boofar.html", "/x/y/z/boofar.html"}, - {"x/y/z/boofar.md", "", "boofar", "", true, false, "/x/y/z/boofar.html", "/x/y/z/boofar.html"}, - {"x/y/z/boofar.md", "http://barnew/", "", "", true, false, "http://barnew/x/y/z/boofar.html", "/x/y/z/boofar.html"}, - {"x/y/z/boofar.md", "http://barnew/", "boofar", "", true, false, "http://barnew/x/y/z/boofar.html", "/x/y/z/boofar.html"}, - {"x/y/z/boofar.md", "http://barnew/boo/", "booslug", "", true, false, "http://barnew/boo/x/y/z/booslug.html", "/boo/x/y/z/booslug.html"}, - {"x/y/z/boofar.md", "http://barnew/boo/", "booslug", "", false, true, "http://barnew/boo/x/y/z/booslug/", "/x/y/z/booslug/"}, - {"x/y/z/boofar.md", "http://barnew/boo/", "booslug", "", false, false, "http://barnew/boo/x/y/z/booslug/", "/boo/x/y/z/booslug/"}, - {"x/y/z/boofar.md", "http://barnew/boo/", "booslug", "", true, true, "http://barnew/boo/x/y/z/booslug.html", "/x/y/z/booslug.html"}, - {"x/y/z/boofar.md", "http://barnew/boo", "booslug", "", true, true, "http://barnew/boo/x/y/z/booslug.html", "/x/y/z/booslug.html"}, - - // test URL overrides - {"x/y/z/boofar.md", "", "", "/z/y/q/", false, false, "/z/y/q/", "/z/y/q/"}, - } - - for i, test := range tests { - - cfg, fs := newTestCfg() - - cfg.Set("uglyURLs", test.uglyURLs) - cfg.Set("canonifyURLs", test.canonifyURLs) - cfg.Set("baseURL", test.base) - - pageContent := fmt.Sprintf(`--- -title: Page -slug: %q -url: %q ---- -Content -`, test.slug, test.url) - - writeSource(t, fs, filepath.Join("content", filepath.FromSlash(test.file)), pageContent) - - s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) - require.Len(t, s.RegularPages, 1) - - p := s.RegularPages[0] - - u := p.Permalink() - - expected := test.expectedAbs - if u != expected { - t.Fatalf("[%d] Expected abs url: %s, got: %s", i, expected, u) - } - - u = p.RelPermalink() - - expected = test.expectedRel - if u != expected { - t.Errorf("[%d] Expected rel url: %s, got: %s", i, expected, u) - } - } -} diff --git a/hugolib/page_taxonomy_test.go b/hugolib/page_taxonomy_test.go deleted file mode 100644 index e0dc1ffbc..000000000 --- a/hugolib/page_taxonomy_test.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "reflect" - "strings" - "testing" -) - -var pageYamlWithTaxonomiesA = `--- -tags: ['a', 'B', 'c'] -categories: 'd' ---- -YAML frontmatter with tags and categories taxonomy.` - -var pageYamlWithTaxonomiesB = `--- -tags: - - "a" - - "B" - - "c" -categories: 'd' ---- -YAML frontmatter with tags and categories taxonomy.` - -var pageYamlWithTaxonomiesC = `--- -tags: 'E' -categories: 'd' ---- -YAML frontmatter with tags and categories taxonomy.` - -var pageJSONWithTaxonomies = `{ - "categories": "D", - "tags": [ - "a", - "b", - "c" - ] -} -JSON Front Matter with tags and categories` - -var pageTomlWithTaxonomies = `+++ -tags = [ "a", "B", "c" ] -categories = "d" -+++ -TOML Front Matter with tags and categories` - -func TestParseTaxonomies(t *testing.T) { - t.Parallel() - for _, test := range []string{pageTomlWithTaxonomies, - pageJSONWithTaxonomies, - pageYamlWithTaxonomiesA, - pageYamlWithTaxonomiesB, - pageYamlWithTaxonomiesC, - } { - - s := newTestSite(t) - p, _ := s.NewPage("page/with/taxonomy") - _, err := p.ReadFrom(strings.NewReader(test)) - if err != nil { - t.Fatalf("Failed parsing %q: %s", test, err) - } - - param := p.GetParam("tags") - - if params, ok := param.([]string); ok { - expected := []string{"a", "b", "c"} - if !reflect.DeepEqual(params, expected) { - t.Errorf("Expected %s: got: %s", expected, params) - } - } else if params, ok := param.(string); ok { - expected := "e" - if params != expected { - t.Errorf("Expected %s: got: %s", expected, params) - } - } - - param = p.GetParam("categories") - singleparam := param.(string) - - if singleparam != "d" { - t.Fatalf("Expected: d, got: %s", singleparam) - } - } -} diff --git a/hugolib/page_test.go b/hugolib/page_test.go deleted file mode 100644 index 5c5c06b07..000000000 --- a/hugolib/page_test.go +++ /dev/null @@ -1,1525 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "bytes" - "fmt" - "html/template" - "os" - "path/filepath" - "reflect" - "sort" - "strings" - "testing" - "time" - - "github.com/gohugoio/hugo/deps" - "github.com/gohugoio/hugo/helpers" - "github.com/spf13/cast" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -var emptyPage = "" - -const ( - simplePage = "---\ntitle: Simple\n---\nSimple Page\n" - invalidFrontMatterMissing = "This is a test" - renderNoFrontmatter = "This is a test" - contentWithCommentedFrontmatter = "\n\n# Network configuration\n\n##\nSummary" - contentWithCommentedTextFrontmatter = "\n\n# Network configuration\n\n##\nSummary" - contentWithCommentedLongFrontmatter = "\n\n# Network configuration\n\n##\nSummary" - contentWithCommentedLong2Frontmatter = "\n\n# Network configuration\n\n##\nSummary" - invalidFrontmatterShortDelim = ` --- -title: Short delim start ---- -Short Delim -` - - invalidFrontmatterShortDelimEnding = ` ---- -title: Short delim ending --- -Short Delim -` - - invalidFrontmatterLadingWs = ` - - --- -title: Leading WS ---- -Leading -` - - simplePageJSON = ` -{ -"title": "spf13-vim 3.0 release and new website", -"description": "spf13-vim is a cross platform distribution of vim plugins and resources for Vim.", -"tags": [ ".vimrc", "plugins", "spf13-vim", "VIm" ], -"date": "2012-04-06", -"categories": [ - "Development", - "VIM" -], -"slug": "-spf13-vim-3-0-release-and-new-website-" -} - -Content of the file goes Here -` - - simplePageRFC3339Date = "---\ntitle: RFC3339 Date\ndate: \"2013-05-17T16:59:30Z\"\n---\nrfc3339 content" - simplePageJSONMultiple = ` -{ - "title": "foobar", - "customData": { "foo": "bar" }, - "date": "2012-08-06" -} -Some text -` - - simplePageWithSummaryDelimiter = `--- -title: Simple ---- -Summary Next Line - - -Some more text -` - - simplePageWithSummaryDelimiterAndMarkdownThatCrossesBorder = `--- -title: Simple ---- -The [best static site generator][hugo].[^1] - -[hugo]: http://gohugo.io/ -[^1]: Many people say so. -` - simplePageWithShortcodeInSummary = `--- -title: Simple ---- -Summary Next Line. {{
    }}. -More text here. - -Some more text -` - - simplePageWithEmbeddedScript = `--- -title: Simple ---- - -` - - simplePageWithSummaryDelimiterSameLine = `--- -title: Simple ---- -Summary Same Line - -Some more text -` - - simplePageWithSummaryDelimiterOnlySummary = `--- -title: Simple ---- -Summary text - - -` - - simplePageWithAllCJKRunes = `--- -title: Simple ---- - - -€ € € € € -你好 -도형이 -カテゴリー - - -` - - simplePageWithMainEnglishWithCJKRunes = `--- -title: Simple ---- - - -In Chinese, 好 means good. In Chinese, 好 means good. -In Chinese, 好 means good. In Chinese, 好 means good. -In Chinese, 好 means good. In Chinese, 好 means good. -In Chinese, 好 means good. In Chinese, 好 means good. -In Chinese, 好 means good. In Chinese, 好 means good. -In Chinese, 好 means good. In Chinese, 好 means good. -In Chinese, 好 means good. In Chinese, 好 means good. -More then 70 words. - - -` - simplePageWithMainEnglishWithCJKRunesSummary = "In Chinese, 好 means good. In Chinese, 好 means good. " + - "In Chinese, 好 means good. In Chinese, 好 means good. " + - "In Chinese, 好 means good. In Chinese, 好 means good. " + - "In Chinese, 好 means good. In Chinese, 好 means good. " + - "In Chinese, 好 means good. In Chinese, 好 means good. " + - "In Chinese, 好 means good. In Chinese, 好 means good. " + - "In Chinese, 好 means good. In Chinese, 好 means good." - - simplePageWithIsCJKLanguageFalse = `--- -title: Simple -isCJKLanguage: false ---- - -In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. -In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. -In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. -In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. -In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. -In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. -In Chinese, 好的啊 means good. In Chinese, 好的呀呀 means good enough. -More then 70 words. - - -` - simplePageWithIsCJKLanguageFalseSummary = "In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. " + - "In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. " + - "In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. " + - "In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. " + - "In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. " + - "In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. " + - "In Chinese, 好的啊 means good. In Chinese, 好的呀呀 means good enough." - - simplePageWithLongContent = `--- -title: Simple ---- - -Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor -incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis -nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. -Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu -fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in -culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit -amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore -et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation -ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor -in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla -pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui -officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, -consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et -dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco -laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in -reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. -Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia -deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur -adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna -aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi -ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in -voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint -occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim -id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed -do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim -veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo -consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse -cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non -proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem -ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor -incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis -nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. -Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu -fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in -culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit -amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore -et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation -ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor -in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla -pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui -officia deserunt mollit anim id est laborum.` - - pageWithToC = `--- -title: TOC ---- -For some moments the old man did not reply. He stood with bowed head, buried in deep thought. But at last he spoke. - -## AA - -I have no idea, of course, how long it took me to reach the limit of the plain, -but at last I entered the foothills, following a pretty little canyon upward -toward the mountains. Beside me frolicked a laughing brooklet, hurrying upon -its noisy way down to the silent sea. In its quieter pools I discovered many -small fish, of four-or five-pound weight I should imagine. In appearance, -except as to size and color, they were not unlike the whale of our own seas. As -I watched them playing about I discovered, not only that they suckled their -young, but that at intervals they rose to the surface to breathe as well as to -feed upon certain grasses and a strange, scarlet lichen which grew upon the -rocks just above the water line. - -### AAA - -I remember I felt an extraordinary persuasion that I was being played with, -that presently, when I was upon the very verge of safety, this mysterious -death--as swift as the passage of light--would leap after me from the pit about -the cylinder and strike me down. ## BB - -### BBB - -"You're a great Granser," he cried delightedly, "always making believe them little marks mean something." -` - - simplePageWithAdditionalExtension = `+++ -[blackfriday] - extensions = ["hardLineBreak"] -+++ -first line. -second line. - -fourth line. -` - - simplePageWithURL = `--- -title: Simple -url: simple/url/ ---- -Simple Page With URL` - - simplePageWithSlug = `--- -title: Simple -slug: simple-slug ---- -Simple Page With Slug` - - simplePageWithDate = `--- -title: Simple -date: '2013-10-15T06:16:13' ---- -Simple Page With Date` - - UTF8Page = `--- -title: ラーメン ---- -UTF8 Page` - - UTF8PageWithURL = `--- -title: ラーメン -url: ラーメン/url/ ---- -UTF8 Page With URL` - - UTF8PageWithSlug = `--- -title: ラーメン -slug: ラーメン-slug ---- -UTF8 Page With Slug` - - UTF8PageWithDate = `--- -title: ラーメン -date: '2013-10-15T06:16:13' ---- -UTF8 Page With Date` -) - -var pageWithVariousFrontmatterTypes = `+++ -a_string = "bar" -an_integer = 1 -a_float = 1.3 -a_bool = false -a_date = 1979-05-27T07:32:00Z - -[a_table] -a_key = "a_value" -+++ -Front Matter with various frontmatter types` - -var pageWithCalendarYAMLFrontmatter = `--- -type: calendar -weeks: - - - start: "Jan 5" - days: - - activity: class - room: EN1000 - - activity: lab - - activity: class - - activity: lab - - activity: class - - - start: "Jan 12" - days: - - activity: class - - activity: lab - - activity: class - - activity: lab - - activity: exam ---- - -Hi. -` - -var pageWithCalendarJSONFrontmatter = `{ - "type": "calendar", - "weeks": [ - { - "start": "Jan 5", - "days": [ - { "activity": "class", "room": "EN1000" }, - { "activity": "lab" }, - { "activity": "class" }, - { "activity": "lab" }, - { "activity": "class" } - ] - }, - { - "start": "Jan 12", - "days": [ - { "activity": "class" }, - { "activity": "lab" }, - { "activity": "class" }, - { "activity": "lab" }, - { "activity": "exam" } - ] - } - ] -} - -Hi. -` - -var pageWithCalendarTOMLFrontmatter = `+++ -type = "calendar" - -[[weeks]] -start = "Jan 5" - -[[weeks.days]] -activity = "class" -room = "EN1000" - -[[weeks.days]] -activity = "lab" - -[[weeks.days]] -activity = "class" - -[[weeks.days]] -activity = "lab" - -[[weeks.days]] -activity = "class" - -[[weeks]] -start = "Jan 12" - -[[weeks.days]] -activity = "class" - -[[weeks.days]] -activity = "lab" - -[[weeks.days]] -activity = "class" - -[[weeks.days]] -activity = "lab" - -[[weeks.days]] -activity = "exam" -+++ - -Hi. -` - -func checkError(t *testing.T, err error, expected string) { - if err == nil { - t.Fatalf("err is nil. Expected: %s", expected) - } - if err.Error() != expected { - t.Errorf("err.Error() returned: '%s'. Expected: '%s'", err.Error(), expected) - } -} - -func TestDegenerateEmptyPageZeroLengthName(t *testing.T) { - t.Parallel() - s := newTestSite(t) - _, err := s.NewPage("") - if err == nil { - t.Fatalf("A zero length page name must return an error") - } - - checkError(t, err, "Zero length page name") -} - -func TestDegenerateEmptyPage(t *testing.T) { - t.Parallel() - s := newTestSite(t) - _, err := s.NewPageFrom(strings.NewReader(emptyPage), "test") - if err != nil { - t.Fatalf("Empty files should not trigger an error. Should be able to touch a file while watching without erroring out.") - } -} - -func checkPageTitle(t *testing.T, page *Page, title string) { - if page.Title != title { - t.Fatalf("Page title is: %s. Expected %s", page.Title, title) - } -} - -func checkPageContent(t *testing.T, page *Page, content string, msg ...interface{}) { - a := normalizeContent(content) - b := normalizeContent(string(page.Content)) - if a != b { - t.Fatalf("Page content is:\n%q\nExpected:\n%q (%q)", b, a, msg) - } -} - -func normalizeContent(c string) string { - norm := c - norm = strings.Replace(norm, "\n", " ", -1) - norm = strings.Replace(norm, " ", " ", -1) - norm = strings.Replace(norm, " ", " ", -1) - norm = strings.Replace(norm, " ", " ", -1) - norm = strings.Replace(norm, "p> ", "p>", -1) - norm = strings.Replace(norm, "> <", "> <", -1) - return strings.TrimSpace(norm) -} - -func checkPageTOC(t *testing.T, page *Page, toc string) { - if page.TableOfContents != template.HTML(toc) { - t.Fatalf("Page TableOfContents is: %q.\nExpected %q", page.TableOfContents, toc) - } -} - -func checkPageSummary(t *testing.T, page *Page, summary string, msg ...interface{}) { - a := normalizeContent(string(page.Summary)) - b := normalizeContent(summary) - if a != b { - t.Fatalf("Page summary is:\n%q.\nExpected\n%q (%q)", a, b, msg) - } -} - -func checkPageType(t *testing.T, page *Page, pageType string) { - if page.Type() != pageType { - t.Fatalf("Page type is: %s. Expected: %s", page.Type(), pageType) - } -} - -func checkPageDate(t *testing.T, page *Page, time time.Time) { - if page.Date != time { - t.Fatalf("Page date is: %s. Expected: %s", page.Date, time) - } -} - -func checkTruncation(t *testing.T, page *Page, shouldBe bool, msg string) { - if page.Summary == "" { - t.Fatal("page has no summary, can not check truncation") - } - if page.Truncated != shouldBe { - if shouldBe { - t.Fatalf("page wasn't truncated: %s", msg) - } else { - t.Fatalf("page was truncated: %s", msg) - } - } -} - -func normalizeExpected(ext, str string) string { - str = normalizeContent(str) - switch ext { - default: - return str - case "html": - return strings.Trim(helpers.StripHTML(str), " ") - case "ad": - paragraphs := strings.Split(str, "

    ") - expected := "" - for _, para := range paragraphs { - if para == "" { - continue - } - expected += fmt.Sprintf("
    \n%s

    \n", para) - } - return expected - case "rst": - return fmt.Sprintf("
    \n\n\n%s
    ", str) - } -} - -func testAllMarkdownEnginesForPages(t *testing.T, - assertFunc func(t *testing.T, ext string, pages Pages), settings map[string]interface{}, pageSources ...string) { - - engines := []struct { - ext string - shouldExecute func() bool - }{ - {"md", func() bool { return true }}, - {"mmark", func() bool { return true }}, - {"ad", func() bool { return helpers.HasAsciidoctor() || helpers.HasAsciidoc() }}, - // TODO(bep) figure a way to include this without too much work.{"html", func() bool { return true }}, - {"rst", func() bool { return helpers.HasRst() }}, - } - - for _, e := range engines { - if !e.shouldExecute() { - continue - } - - cfg, fs := newTestCfg() - - if settings != nil { - for k, v := range settings { - cfg.Set(k, v) - } - } - - contentDir := "content" - - if s := cfg.GetString("contentDir"); s != "" { - contentDir = s - } - - var fileSourcePairs []string - - for i, source := range pageSources { - fileSourcePairs = append(fileSourcePairs, fmt.Sprintf("p%d.%s", i, e.ext), source) - } - - for i := 0; i < len(fileSourcePairs); i += 2 { - writeSource(t, fs, filepath.Join(contentDir, fileSourcePairs[i]), fileSourcePairs[i+1]) - } - - s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) - - require.Len(t, s.RegularPages, len(pageSources)) - - assertFunc(t, e.ext, s.RegularPages) - - } - -} - -func TestCreateNewPage(t *testing.T) { - t.Parallel() - assertFunc := func(t *testing.T, ext string, pages Pages) { - p := pages[0] - - // issue #2290: Path is relative to the content dir and will continue to be so. - require.Equal(t, filepath.FromSlash(fmt.Sprintf("p0.%s", ext)), p.Path()) - assert.False(t, p.IsHome()) - checkPageTitle(t, p, "Simple") - checkPageContent(t, p, normalizeExpected(ext, "

    Simple Page

    \n")) - checkPageSummary(t, p, "Simple Page") - checkPageType(t, p, "page") - checkTruncation(t, p, false, "simple short page") - } - - settings := map[string]interface{}{ - "contentDir": "mycontent", - } - - testAllMarkdownEnginesForPages(t, assertFunc, settings, simplePage) -} - -func TestSplitSummaryAndContent(t *testing.T) { - t.Parallel() - for i, this := range []struct { - markup string - content string - expectedSummary string - expectedContent string - }{ - {"markdown", `

    Summary Same LineHUGOMORE42

    - -

    Some more text

    `, "

    Summary Same Line

    ", "

    Summary Same Line

    \n\n

    Some more text

    "}, - {"asciidoc", `

    sn

    HUGOMORE42Some more text

    `, - "

    sn

    ", - "

    sn

    Some more text

    "}, - {"rst", - "

    Summary Next Line

    HUGOMORE42Some more text

    ", - "

    Summary Next Line

    ", - "

    Summary Next Line

    Some more text

    "}, - {"markdown", "

    a

    b

    HUGOMORE42c

    ", "

    a

    b

    ", "

    a

    b

    c

    "}, - {"markdown", "

    a

    b

    cHUGOMORE42

    ", "

    a

    b

    c

    ", "

    a

    b

    c

    "}, - {"markdown", "

    a

    bHUGOMORE42

    c

    ", "

    a

    b

    ", "

    a

    b

    c

    "}, - {"markdown", "

    aHUGOMORE42

    b

    c

    ", "

    a

    ", "

    a

    b

    c

    "}, - {"markdown", " HUGOMORE42 ", "", ""}, - {"markdown", "HUGOMORE42", "", ""}, - {"markdown", "

    HUGOMORE42", "

    ", "

    "}, - {"markdown", "HUGOMORE42

    ", "", "

    "}, - {"markdown", "\n\n

    HUGOMORE42

    \n", "

    ", "

    "}, - // Issue #2586 - // Note: Hugo will not split mid-sentence but will look for the closest - // paragraph end marker. This may be a change from Hugo 0.16, but it makes sense. - {"markdown", `

    this is an example HUGOMORE42of the issue.

    `, - "

    this is an example of the issue.

    ", - "

    this is an example of the issue.

    "}, - // Issue: #2538 - {"markdown", fmt.Sprintf(`

    %s

    HUGOMORE42

    %s

    -`, - strings.Repeat("A", 10), strings.Repeat("B", 31)), - fmt.Sprintf(`

    %s

    `, strings.Repeat("A", 10)), - fmt.Sprintf(`

    %s

    %s

    `, strings.Repeat("A", 10), strings.Repeat("B", 31)), - }, - } { - - sc, err := splitUserDefinedSummaryAndContent(this.markup, []byte(this.content)) - - require.NoError(t, err) - require.NotNil(t, sc, fmt.Sprintf("[%d] Nil %s", i, this.markup)) - require.Equal(t, this.expectedSummary, string(sc.summary), fmt.Sprintf("[%d] Summary markup %s", i, this.markup)) - require.Equal(t, this.expectedContent, string(sc.content), fmt.Sprintf("[%d] Content markup %s", i, this.markup)) - } -} - -func TestPageWithDelimiter(t *testing.T) { - t.Parallel() - assertFunc := func(t *testing.T, ext string, pages Pages) { - p := pages[0] - checkPageTitle(t, p, "Simple") - checkPageContent(t, p, normalizeExpected(ext, "

    Summary Next Line

    \n\n

    Some more text

    \n"), ext) - checkPageSummary(t, p, normalizeExpected(ext, "

    Summary Next Line

    "), ext) - checkPageType(t, p, "page") - checkTruncation(t, p, true, "page with summary delimiter") - } - - testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithSummaryDelimiter) -} - -// Issue #1076 -func TestPageWithDelimiterForMarkdownThatCrossesBorder(t *testing.T) { - t.Parallel() - cfg, fs := newTestCfg() - - writeSource(t, fs, filepath.Join("content", "simple.md"), simplePageWithSummaryDelimiterAndMarkdownThatCrossesBorder) - - s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) - - require.Len(t, s.RegularPages, 1) - - p := s.RegularPages[0] - - if p.Summary != template.HTML("

    The best static site generator.1\n

    ") { - t.Fatalf("Got summary:\n%q", p.Summary) - } - - if p.Content != template.HTML("

    The best static site generator.1\n

    \n
    \n\n
    \n\n
      \n
    1. Many people say so.\n [return]
    2. \n
    \n
    ") { - t.Fatalf("Got content:\n%q", p.Content) - } -} - -// Issue #2601 -func TestPageRawContent(t *testing.T) { - t.Parallel() - cfg, fs := newTestCfg() - - writeSource(t, fs, filepath.Join("content", "raw.md"), `--- -title: Raw ---- -**Raw**`) - - writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .RawContent }}`) - - s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) - - require.Len(t, s.RegularPages, 1) - p := s.RegularPages[0] - - require.Contains(t, p.RawContent(), "**Raw**") - -} - -func TestPageWithShortCodeInSummary(t *testing.T) { - t.Parallel() - assertFunc := func(t *testing.T, ext string, pages Pages) { - p := pages[0] - checkPageTitle(t, p, "Simple") - checkPageContent(t, p, normalizeExpected(ext, "

    Summary Next Line. \n

    \n \n \n \n \n
    \n.\nMore text here.

    \n\n

    Some more text

    \n")) - checkPageSummary(t, p, "Summary Next Line. . More text here. Some more text") - checkPageType(t, p, "page") - } - - testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithShortcodeInSummary) -} - -func TestPageWithEmbeddedScriptTag(t *testing.T) { - t.Parallel() - assertFunc := func(t *testing.T, ext string, pages Pages) { - p := pages[0] - if ext == "ad" || ext == "rst" { - // TOD(bep) - return - } - checkPageContent(t, p, "\n", ext) - } - - testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithEmbeddedScript) -} - -func TestPageWithAdditionalExtension(t *testing.T) { - t.Parallel() - cfg, fs := newTestCfg() - - writeSource(t, fs, filepath.Join("content", "simple.md"), simplePageWithAdditionalExtension) - - s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) - - require.Len(t, s.RegularPages, 1) - - p := s.RegularPages[0] - - checkPageContent(t, p, "

    first line.
    \nsecond line.

    \n\n

    fourth line.

    \n") -} - -func TestTableOfContents(t *testing.T) { - - cfg, fs := newTestCfg() - - writeSource(t, fs, filepath.Join("content", "tocpage.md"), pageWithToC) - - s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) - - require.Len(t, s.RegularPages, 1) - - p := s.RegularPages[0] - - checkPageContent(t, p, "\n\n

    For some moments the old man did not reply. He stood with bowed head, buried in deep thought. But at last he spoke.

    \n\n

    AA

    \n\n

    I have no idea, of course, how long it took me to reach the limit of the plain,\nbut at last I entered the foothills, following a pretty little canyon upward\ntoward the mountains. Beside me frolicked a laughing brooklet, hurrying upon\nits noisy way down to the silent sea. In its quieter pools I discovered many\nsmall fish, of four-or five-pound weight I should imagine. In appearance,\nexcept as to size and color, they were not unlike the whale of our own seas. As\nI watched them playing about I discovered, not only that they suckled their\nyoung, but that at intervals they rose to the surface to breathe as well as to\nfeed upon certain grasses and a strange, scarlet lichen which grew upon the\nrocks just above the water line.

    \n\n

    AAA

    \n\n

    I remember I felt an extraordinary persuasion that I was being played with,\nthat presently, when I was upon the very verge of safety, this mysterious\ndeath–as swift as the passage of light–would leap after me from the pit about\nthe cylinder and strike me down. ## BB

    \n\n

    BBB

    \n\n

    “You’re a great Granser,” he cried delightedly, “always making believe them little marks mean something.”

    \n") - checkPageTOC(t, p, "") -} - -func TestPageWithMoreTag(t *testing.T) { - t.Parallel() - assertFunc := func(t *testing.T, ext string, pages Pages) { - p := pages[0] - checkPageTitle(t, p, "Simple") - checkPageContent(t, p, normalizeExpected(ext, "

    Summary Same Line

    \n\n

    Some more text

    \n")) - checkPageSummary(t, p, normalizeExpected(ext, "

    Summary Same Line

    ")) - checkPageType(t, p, "page") - - } - - testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithSummaryDelimiterSameLine) -} - -func TestPageWithMoreTagOnlySummary(t *testing.T) { - - assertFunc := func(t *testing.T, ext string, pages Pages) { - p := pages[0] - checkTruncation(t, p, false, "page with summary delimiter at end") - } - - testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithSummaryDelimiterOnlySummary) -} - -// #2973 -func TestSummaryWithHTMLTagsOnNextLine(t *testing.T) { - - assertFunc := func(t *testing.T, ext string, pages Pages) { - p := pages[0] - require.Contains(t, p.Summary, "Happy new year everyone!") - require.NotContains(t, p.Summary, "User interface") - } - - testAllMarkdownEnginesForPages(t, assertFunc, nil, `--- -title: Simple ---- -Happy new year everyone! - -Here is the last report for commits in the year 2016. It covers hrev50718-hrev50829. - - - -

    User interface

    - -`) -} - -func TestPageWithDate(t *testing.T) { - t.Parallel() - cfg, fs := newTestCfg() - - writeSource(t, fs, filepath.Join("content", "simple.md"), simplePageRFC3339Date) - - s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) - - require.Len(t, s.RegularPages, 1) - - p := s.RegularPages[0] - d, _ := time.Parse(time.RFC3339, "2013-05-17T16:59:30Z") - - checkPageDate(t, p, d) -} - -func TestWordCountWithAllCJKRunesWithoutHasCJKLanguage(t *testing.T) { - t.Parallel() - assertFunc := func(t *testing.T, ext string, pages Pages) { - p := pages[0] - if p.WordCount() != 8 { - t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.plain, 8, p.WordCount()) - } - } - - testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithAllCJKRunes) -} - -func TestWordCountWithAllCJKRunesHasCJKLanguage(t *testing.T) { - t.Parallel() - settings := map[string]interface{}{"hasCJKLanguage": true} - - assertFunc := func(t *testing.T, ext string, pages Pages) { - p := pages[0] - if p.WordCount() != 15 { - t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.plain, 15, p.WordCount()) - } - } - testAllMarkdownEnginesForPages(t, assertFunc, settings, simplePageWithAllCJKRunes) -} - -func TestWordCountWithMainEnglishWithCJKRunes(t *testing.T) { - t.Parallel() - settings := map[string]interface{}{"hasCJKLanguage": true} - - assertFunc := func(t *testing.T, ext string, pages Pages) { - p := pages[0] - if p.WordCount() != 74 { - t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.plain, 74, p.WordCount()) - } - - if p.Summary != simplePageWithMainEnglishWithCJKRunesSummary { - t.Fatalf("[%s] incorrect Summary for content '%s'. expected %v, got %v", ext, p.plain, - simplePageWithMainEnglishWithCJKRunesSummary, p.Summary) - } - } - - testAllMarkdownEnginesForPages(t, assertFunc, settings, simplePageWithMainEnglishWithCJKRunes) -} - -func TestWordCountWithIsCJKLanguageFalse(t *testing.T) { - t.Parallel() - settings := map[string]interface{}{ - "hasCJKLanguage": true, - } - - assertFunc := func(t *testing.T, ext string, pages Pages) { - p := pages[0] - if p.WordCount() != 75 { - t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.plain, 74, p.WordCount()) - } - - if p.Summary != simplePageWithIsCJKLanguageFalseSummary { - t.Fatalf("[%s] incorrect Summary for content '%s'. expected %v, got %v", ext, p.plain, - simplePageWithIsCJKLanguageFalseSummary, p.Summary) - } - } - - testAllMarkdownEnginesForPages(t, assertFunc, settings, simplePageWithIsCJKLanguageFalse) - -} - -func TestWordCount(t *testing.T) { - t.Parallel() - assertFunc := func(t *testing.T, ext string, pages Pages) { - p := pages[0] - if p.WordCount() != 483 { - t.Fatalf("[%s] incorrect word count. expected %v, got %v", ext, 483, p.WordCount()) - } - - if p.FuzzyWordCount() != 500 { - t.Fatalf("[%s] incorrect word count. expected %v, got %v", ext, 500, p.WordCount()) - } - - if p.ReadingTime() != 3 { - t.Fatalf("[%s] incorrect min read. expected %v, got %v", ext, 3, p.ReadingTime()) - } - - checkTruncation(t, p, true, "long page") - } - - testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithLongContent) -} - -func TestCreatePage(t *testing.T) { - t.Parallel() - var tests = []struct { - r string - }{ - {simplePageJSON}, - {simplePageJSONMultiple}, - //{strings.NewReader(SIMPLE_PAGE_JSON_COMPACT)}, - } - - for i, test := range tests { - s := newTestSite(t) - p, _ := s.NewPage("page") - if _, err := p.ReadFrom(strings.NewReader(test.r)); err != nil { - t.Fatalf("[%d] Unable to parse page: %s", i, err) - } - } -} - -func TestDegenerateInvalidFrontMatterShortDelim(t *testing.T) { - t.Parallel() - var tests = []struct { - r string - err string - }{ - {invalidFrontmatterShortDelimEnding, "unable to read frontmatter at filepos 45: EOF"}, - } - for _, test := range tests { - s := newTestSite(t) - p, _ := s.NewPage("invalid/front/matter/short/delim") - _, err := p.ReadFrom(strings.NewReader(test.r)) - checkError(t, err, test.err) - } -} - -func TestShouldRenderContent(t *testing.T) { - t.Parallel() - var tests = []struct { - text string - render bool - }{ - {invalidFrontMatterMissing, true}, - // TODO how to deal with malformed frontmatter. In this case it'll be rendered as markdown. - {invalidFrontmatterShortDelim, true}, - {renderNoFrontmatter, false}, - {contentWithCommentedFrontmatter, true}, - {contentWithCommentedTextFrontmatter, true}, - {contentWithCommentedLongFrontmatter, false}, - {contentWithCommentedLong2Frontmatter, true}, - } - - for _, test := range tests { - s := newTestSite(t) - p, _ := s.NewPage("render/front/matter") - _, err := p.ReadFrom(strings.NewReader(test.text)) - p = pageMust(p, err) - if p.IsRenderable() != test.render { - t.Errorf("expected p.IsRenderable() == %t, got %t", test.render, p.IsRenderable()) - } - } -} - -// Issue #768 -func TestCalendarParamsVariants(t *testing.T) { - t.Parallel() - s := newTestSite(t) - pageJSON, _ := s.NewPage("test/fileJSON.md") - _, _ = pageJSON.ReadFrom(strings.NewReader(pageWithCalendarJSONFrontmatter)) - - pageYAML, _ := s.NewPage("test/fileYAML.md") - _, _ = pageYAML.ReadFrom(strings.NewReader(pageWithCalendarYAMLFrontmatter)) - - pageTOML, _ := s.NewPage("test/fileTOML.md") - _, _ = pageTOML.ReadFrom(strings.NewReader(pageWithCalendarTOMLFrontmatter)) - - assert.True(t, compareObjects(pageJSON.Params, pageYAML.Params)) - assert.True(t, compareObjects(pageJSON.Params, pageTOML.Params)) - -} - -func TestDifferentFrontMatterVarTypes(t *testing.T) { - t.Parallel() - s := newTestSite(t) - page, _ := s.NewPage("test/file1.md") - _, _ = page.ReadFrom(strings.NewReader(pageWithVariousFrontmatterTypes)) - - dateval, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z") - if page.GetParam("a_string") != "bar" { - t.Errorf("frontmatter not handling strings correctly should be %s, got: %s", "bar", page.GetParam("a_string")) - } - if page.GetParam("an_integer") != 1 { - t.Errorf("frontmatter not handling ints correctly should be %s, got: %s", "1", page.GetParam("an_integer")) - } - if page.GetParam("a_float") != 1.3 { - t.Errorf("frontmatter not handling floats correctly should be %f, got: %s", 1.3, page.GetParam("a_float")) - } - if page.GetParam("a_bool") != false { - t.Errorf("frontmatter not handling bools correctly should be %t, got: %s", false, page.GetParam("a_bool")) - } - if page.GetParam("a_date") != dateval { - t.Errorf("frontmatter not handling dates correctly should be %s, got: %s", dateval, page.GetParam("a_date")) - } - param := page.GetParam("a_table") - if param == nil { - t.Errorf("frontmatter not handling tables correctly should be type of %v, got: type of %v", reflect.TypeOf(page.Params["a_table"]), reflect.TypeOf(param)) - } - if cast.ToStringMap(param)["a_key"] != "a_value" { - t.Errorf("frontmatter not handling values inside a table correctly should be %s, got: %s", "a_value", cast.ToStringMap(page.Params["a_table"])["a_key"]) - } -} - -func TestDegenerateInvalidFrontMatterLeadingWhitespace(t *testing.T) { - t.Parallel() - s := newTestSite(t) - p, _ := s.NewPage("invalid/front/matter/leading/ws") - _, err := p.ReadFrom(strings.NewReader(invalidFrontmatterLadingWs)) - if err != nil { - t.Fatalf("Unable to parse front matter given leading whitespace: %s", err) - } -} - -func TestSectionEvaluation(t *testing.T) { - t.Parallel() - s := newTestSite(t) - page, _ := s.NewPage(filepath.FromSlash("blue/file1.md")) - page.ReadFrom(strings.NewReader(simplePage)) - if page.Section() != "blue" { - t.Errorf("Section should be %s, got: %s", "blue", page.Section()) - } -} - -func TestSliceToLower(t *testing.T) { - t.Parallel() - tests := []struct { - value []string - expected []string - }{ - {[]string{"a", "b", "c"}, []string{"a", "b", "c"}}, - {[]string{"a", "B", "c"}, []string{"a", "b", "c"}}, - {[]string{"A", "B", "C"}, []string{"a", "b", "c"}}, - } - - for _, test := range tests { - res := helpers.SliceToLower(test.value) - for i, val := range res { - if val != test.expected[i] { - t.Errorf("Case mismatch. Expected %s, got %s", test.expected[i], res[i]) - } - } - } -} - -func TestPagePaths(t *testing.T) { - t.Parallel() - - siteParmalinksSetting := map[string]string{ - "post": ":year/:month/:day/:title/", - } - - tests := []struct { - content string - path string - hasPermalink bool - expected string - }{ - {simplePage, "post/x.md", false, "post/x.html"}, - {simplePageWithURL, "post/x.md", false, "simple/url/index.html"}, - {simplePageWithSlug, "post/x.md", false, "post/simple-slug.html"}, - {simplePageWithDate, "post/x.md", true, "2013/10/15/simple/index.html"}, - {UTF8Page, "post/x.md", false, "post/x.html"}, - {UTF8PageWithURL, "post/x.md", false, "ラーメン/url/index.html"}, - {UTF8PageWithSlug, "post/x.md", false, "post/ラーメン-slug.html"}, - {UTF8PageWithDate, "post/x.md", true, "2013/10/15/ラーメン/index.html"}, - } - - for _, test := range tests { - cfg, fs := newTestCfg() - - if test.hasPermalink { - cfg.Set("permalinks", siteParmalinksSetting) - } - - writeSource(t, fs, filepath.Join("content", filepath.FromSlash(test.path)), test.content) - - s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) - require.Len(t, s.RegularPages, 1) - - } -} - -var pageWithDraftAndPublished = `--- -title: broken -published: false -draft: true ---- -some content -` - -func TestDraftAndPublishedFrontMatterError(t *testing.T) { - t.Parallel() - s := newTestSite(t) - _, err := s.NewPageFrom(strings.NewReader(pageWithDraftAndPublished), "content/post/broken.md") - if err != ErrHasDraftAndPublished { - t.Errorf("expected ErrHasDraftAndPublished, was %#v", err) - } -} - -var pagesWithPublishedFalse = `--- -title: okay -published: false ---- -some content -` -var pageWithPublishedTrue = `--- -title: okay -published: true ---- -some content -` - -func TestPublishedFrontMatter(t *testing.T) { - t.Parallel() - s := newTestSite(t) - p, err := s.NewPageFrom(strings.NewReader(pagesWithPublishedFalse), "content/post/broken.md") - if err != nil { - t.Fatalf("err during parse: %s", err) - } - if !p.Draft { - t.Errorf("expected true, got %t", p.Draft) - } - p, err = s.NewPageFrom(strings.NewReader(pageWithPublishedTrue), "content/post/broken.md") - if err != nil { - t.Fatalf("err during parse: %s", err) - } - if p.Draft { - t.Errorf("expected false, got %t", p.Draft) - } -} - -var pagesDraftTemplate = []string{`--- -title: "okay" -draft: %t ---- -some content -`, - `+++ -title = "okay" -draft = %t -+++ - -some content -`, -} - -func TestDraft(t *testing.T) { - t.Parallel() - s := newTestSite(t) - for _, draft := range []bool{true, false} { - for i, templ := range pagesDraftTemplate { - pageContent := fmt.Sprintf(templ, draft) - p, err := s.NewPageFrom(strings.NewReader(pageContent), "content/post/broken.md") - if err != nil { - t.Fatalf("err during parse: %s", err) - } - if p.Draft != draft { - t.Errorf("[%d] expected %t, got %t", i, draft, p.Draft) - } - } - } -} - -var pagesParamsTemplate = []string{`+++ -title = "okay" -draft = false -tags = [ "hugo", "web" ] -social= [ - [ "a", "#" ], - [ "b", "#" ], -] -+++ -some content -`, - `--- -title: "okay" -draft: false -tags: - - hugo - - web -social: - - - a - - "#" - - - b - - "#" ---- -some content -`, - `{ - "title": "okay", - "draft": false, - "tags": [ "hugo", "web" ], - "social": [ - [ "a", "#" ], - [ "b", "#" ] - ] -} -some content -`, -} - -func TestPageParams(t *testing.T) { - t.Parallel() - s := newTestSite(t) - wantedMap := map[string]interface{}{ - "tags": []string{"hugo", "web"}, - // Issue #2752 - "social": []interface{}{ - []interface{}{"a", "#"}, - []interface{}{"b", "#"}, - }, - } - - for i, c := range pagesParamsTemplate { - p, err := s.NewPageFrom(strings.NewReader(c), "content/post/params.md") - require.NoError(t, err, "err during parse", "#%d", i) - for key := range wantedMap { - assert.Equal(t, wantedMap[key], p.Params[key], "#%d", key) - } - } -} - -func TestTraverse(t *testing.T) { - exampleParams := `--- -rating: "5 stars" -tags: - - hugo - - web -social: - twitter: "@jxxf" - facebook: "https://example.com" ----` - t.Parallel() - s := newTestSite(t) - p, _ := s.NewPageFrom(strings.NewReader(exampleParams), "content/post/params.md") - - topLevelKeyValue, _ := p.Param("rating") - assert.Equal(t, "5 stars", topLevelKeyValue) - - nestedStringKeyValue, _ := p.Param("social.twitter") - assert.Equal(t, "@jxxf", nestedStringKeyValue) - - nonexistentKeyValue, _ := p.Param("doesn't.exist") - assert.Nil(t, nonexistentKeyValue) -} - -func TestPageSimpleMethods(t *testing.T) { - t.Parallel() - s := newTestSite(t) - for i, this := range []struct { - assertFunc func(p *Page) bool - }{ - {func(p *Page) bool { return !p.IsNode() }}, - {func(p *Page) bool { return p.IsPage() }}, - {func(p *Page) bool { return p.Plain() == "Do Be Do Be Do" }}, - {func(p *Page) bool { return strings.Join(p.PlainWords(), " ") == "Do Be Do Be Do" }}, - } { - - p, _ := s.NewPage("Test") - p.Content = "

    Do Be Do Be Do

    " - if !this.assertFunc(p) { - t.Errorf("[%d] Page method error", i) - } - } -} - -func TestIndexPageSimpleMethods(t *testing.T) { - s := newTestSite(t) - t.Parallel() - for i, this := range []struct { - assertFunc func(n *Page) bool - }{ - {func(n *Page) bool { return n.IsNode() }}, - {func(n *Page) bool { return !n.IsPage() }}, - {func(n *Page) bool { return n.Scratch() != nil }}, - {func(n *Page) bool { return n.Hugo() != nil }}, - {func(n *Page) bool { return n.Now().Unix() == time.Now().Unix() }}, - } { - - n := s.newHomePage() - - if !this.assertFunc(n) { - t.Errorf("[%d] Node method error", i) - } - } -} - -func TestKind(t *testing.T) { - t.Parallel() - // Add tests for these constants to make sure they don't change - require.Equal(t, "page", KindPage) - require.Equal(t, "home", KindHome) - require.Equal(t, "section", KindSection) - require.Equal(t, "taxonomy", KindTaxonomy) - require.Equal(t, "taxonomyTerm", KindTaxonomyTerm) - -} - -func TestChompBOM(t *testing.T) { - t.Parallel() - const utf8BOM = "\xef\xbb\xbf" - - cfg, fs := newTestCfg() - - writeSource(t, fs, filepath.Join("content", "simple.md"), utf8BOM+simplePage) - - s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) - - require.Len(t, s.RegularPages, 1) - - p := s.RegularPages[0] - - checkPageTitle(t, p, "Simple") -} - -// TODO(bep) this may be useful for other tests. -func compareObjects(a interface{}, b interface{}) bool { - aStr := strings.Split(fmt.Sprintf("%v", a), "") - sort.Strings(aStr) - - bStr := strings.Split(fmt.Sprintf("%v", b), "") - sort.Strings(bStr) - - return strings.Join(aStr, "") == strings.Join(bStr, "") -} - -func TestShouldBuild(t *testing.T) { - t.Parallel() - var past = time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC) - var future = time.Date(2037, 11, 17, 20, 34, 58, 651387237, time.UTC) - var zero = time.Time{} - - var publishSettings = []struct { - buildFuture bool - buildExpired bool - buildDrafts bool - draft bool - publishDate time.Time - expiryDate time.Time - out bool - }{ - // publishDate and expiryDate - {false, false, false, false, zero, zero, true}, - {false, false, false, false, zero, future, true}, - {false, false, false, false, past, zero, true}, - {false, false, false, false, past, future, true}, - {false, false, false, false, past, past, false}, - {false, false, false, false, future, future, false}, - {false, false, false, false, future, past, false}, - - // buildFuture and buildExpired - {false, true, false, false, past, past, true}, - {true, true, false, false, past, past, true}, - {true, false, false, false, past, past, false}, - {true, false, false, false, future, future, true}, - {true, true, false, false, future, future, true}, - {false, true, false, false, future, past, false}, - - // buildDrafts and draft - {true, true, false, true, past, future, false}, - {true, true, true, true, past, future, true}, - {true, true, true, true, past, future, true}, - } - - for _, ps := range publishSettings { - s := shouldBuild(ps.buildFuture, ps.buildExpired, ps.buildDrafts, ps.draft, - ps.publishDate, ps.expiryDate) - if s != ps.out { - t.Errorf("AssertShouldBuild unexpected output with params: %+v", ps) - } - } -} - -// "dot" in path: #1885 and #2110 -// disablePathToLower regression: #3374 -func TestPathIssues(t *testing.T) { - t.Parallel() - for _, disablePathToLower := range []bool{false, true} { - for _, uglyURLs := range []bool{false, true} { - t.Run(fmt.Sprintf("disablePathToLower=%t,uglyURLs=%t", disablePathToLower, uglyURLs), func(t *testing.T) { - - cfg, fs := newTestCfg() - th := testHelper{cfg, fs, t} - - cfg.Set("permalinks", map[string]string{ - "post": ":section/:title", - }) - - cfg.Set("uglyURLs", uglyURLs) - cfg.Set("disablePathToLower", disablePathToLower) - cfg.Set("paginate", 1) - - writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), "{{.Content}}") - writeSource(t, fs, filepath.Join("layouts", "_default", "list.html"), - "P{{.Paginator.PageNumber}}|URL: {{.Paginator.URL}}|{{ if .Paginator.HasNext }}Next: {{.Paginator.Next.URL }}{{ end }}") - - for i := 0; i < 3; i++ { - writeSource(t, fs, filepath.Join("content", "post", fmt.Sprintf("doc%d.md", i)), - fmt.Sprintf(`--- -title: "test%d.dot" -tags: -- ".net" ---- -# doc1 -*some content*`, i)) - } - - writeSource(t, fs, filepath.Join("content", "Blog", "Blog1.md"), - fmt.Sprintf(`--- -title: "testBlog" -tags: -- "Blog" ---- -# doc1 -*some blog content*`)) - - s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{}) - require.Len(t, s.RegularPages, 4) - - pathFunc := func(s string) string { - if uglyURLs { - return strings.Replace(s, "/index.html", ".html", 1) - } - return s - } - - blog := "blog" - - if disablePathToLower { - blog = "Blog" - } - - th.assertFileContent(pathFunc("public/"+blog+"/"+blog+"1/index.html"), "some blog content") - - th.assertFileContent(pathFunc("public/post/test0.dot/index.html"), "some content") - - if uglyURLs { - th.assertFileContent("public/post/page/1.html", `canonical" href="/post.html"/`) - th.assertFileContent("public/post.html", `P1|URL: /post.html|Next: /post/page/2.html`) - th.assertFileContent("public/post/page/2.html", `P2|URL: /post/page/2.html|Next: /post/page/3.html`) - } else { - th.assertFileContent("public/post/page/1/index.html", `canonical" href="/post/"/`) - th.assertFileContent("public/post/index.html", `P1|URL: /post/|Next: /post/page/2/`) - th.assertFileContent("public/post/page/2/index.html", `P2|URL: /post/page/2/|Next: /post/page/3/`) - th.assertFileContent("public/tags/.net/index.html", `P1|URL: /tags/.net/|Next: /tags/.net/page/2/`) - - } - - p := s.RegularPages[0] - if uglyURLs { - require.Equal(t, "/post/test0.dot.html", p.RelPermalink()) - } else { - require.Equal(t, "/post/test0.dot/", p.RelPermalink()) - } - - }) - } - } -} - -func BenchmarkParsePage(b *testing.B) { - s := newTestSite(b) - f, _ := os.Open("testdata/redis.cn.md") - var buf bytes.Buffer - buf.ReadFrom(f) - b.ResetTimer() - for i := 0; i < b.N; i++ { - page, _ := s.NewPage("bench") - page.ReadFrom(bytes.NewReader(buf.Bytes())) - } -} diff --git a/hugolib/page_time_integration_test.go b/hugolib/page_time_integration_test.go deleted file mode 100644 index 1bf83bdca..000000000 --- a/hugolib/page_time_integration_test.go +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "fmt" - "os" - "strings" - "sync" - "testing" - "time" - - "github.com/spf13/cast" -) - -const ( - pageWithInvalidDate = `--- -date: 2010-05-02_15:29:31+08:00 ---- -Page With Invalid Date (replace T with _ for RFC 3339)` - - pageWithDateRFC3339 = `--- -date: 2010-05-02T15:29:31+08:00 ---- -Page With Date RFC3339` - - pageWithDateRFC3339NoT = `--- -date: 2010-05-02 15:29:31+08:00 ---- -Page With Date RFC3339_NO_T` - - pageWithRFC1123 = `--- -date: Sun, 02 May 2010 15:29:31 PST ---- -Page With Date RFC1123` - - pageWithDateRFC1123Z = `--- -date: Sun, 02 May 2010 15:29:31 +0800 ---- -Page With Date RFC1123Z` - - pageWithDateRFC822 = `--- -date: 02 May 10 15:29 PST ---- -Page With Date RFC822` - - pageWithDateRFC822Z = `--- -date: 02 May 10 15:29 +0800 ---- -Page With Date RFC822Z` - - pageWithDateANSIC = `--- -date: Sun May 2 15:29:31 2010 ---- -Page With Date ANSIC` - - pageWithDateUnixDate = `--- -date: Sun May 2 15:29:31 PST 2010 ---- -Page With Date UnixDate` - - pageWithDateRubyDate = `--- -date: Sun May 02 15:29:31 +0800 2010 ---- -Page With Date RubyDate` - - pageWithDateHugoYearNumeric = `--- -date: 2010-05-02 ---- -Page With Date HugoYearNumeric` - - pageWithDateHugoYear = `--- -date: 02 May 2010 ---- -Page With Date HugoYear` - - pageWithDateHugoLong = `--- -date: 02 May 2010 15:29 PST ---- -Page With Date HugoLong` -) - -func TestDegenerateDateFrontMatter(t *testing.T) { - t.Parallel() - s := newTestSite(t) - p, _ := s.NewPageFrom(strings.NewReader(pageWithInvalidDate), "page/with/invalid/date") - if p.Date != *new(time.Time) { - t.Fatalf("Date should be set to time.Time zero value. Got: %s", p.Date) - } -} - -func TestParsingDateInFrontMatter(t *testing.T) { - t.Parallel() - s := newTestSite(t) - tests := []struct { - buf string - dt string - }{ - {pageWithDateRFC3339, "2010-05-02T15:29:31+08:00"}, - {pageWithDateRFC3339NoT, "2010-05-02T15:29:31+08:00"}, - {pageWithDateRFC1123Z, "2010-05-02T15:29:31+08:00"}, - {pageWithDateRFC822Z, "2010-05-02T15:29:00+08:00"}, - {pageWithDateANSIC, "2010-05-02T15:29:31Z"}, - {pageWithDateRubyDate, "2010-05-02T15:29:31+08:00"}, - {pageWithDateHugoYearNumeric, "2010-05-02T00:00:00Z"}, - {pageWithDateHugoYear, "2010-05-02T00:00:00Z"}, - } - - tzShortCodeTests := []struct { - buf string - dt string - }{ - {pageWithRFC1123, "2010-05-02T15:29:31-08:00"}, - {pageWithDateRFC822, "2010-05-02T15:29:00-08:00Z"}, - {pageWithDateUnixDate, "2010-05-02T15:29:31-08:00"}, - {pageWithDateHugoLong, "2010-05-02T15:21:00+08:00"}, - } - - if _, err := time.LoadLocation("PST"); err == nil { - tests = append(tests, tzShortCodeTests...) - } else { - fmt.Fprintf(os.Stderr, "Skipping shortname timezone tests.\n") - } - - for _, test := range tests { - dt, e := time.Parse(time.RFC3339, test.dt) - if e != nil { - t.Fatalf("Unable to parse date time (RFC3339) for running the test: %s", e) - } - p, err := s.NewPageFrom(strings.NewReader(test.buf), "page/with/date") - if err != nil { - t.Fatalf("Expected to be able to parse page.") - } - if !dt.Equal(p.Date) { - t.Errorf("Date does not equal frontmatter:\n%s\nExpecting: %s\n Got: %s. Diff: %s\n internal: %#v\n %#v", test.buf, dt, p.Date, dt.Sub(p.Date), dt, p.Date) - } - } -} - -// Temp test https://github.com/gohugoio/hugo/issues/3059 -func TestParsingDateParallel(t *testing.T) { - t.Parallel() - - var wg sync.WaitGroup - - for j := 0; j < 100; j++ { - wg.Add(1) - go func() { - defer wg.Done() - for j := 0; j < 100; j++ { - dateStr := "2010-05-02 15:29:31 +08:00" - - dt, err := time.Parse("2006-01-02 15:04:05 -07:00", dateStr) - if err != nil { - t.Fatal(err) - } - - if dt.Year() != 2010 { - t.Fatal("time.Parse: Invalid date:", dt) - } - - dt2 := cast.ToTime(dateStr) - - if dt2.Year() != 2010 { - t.Fatal("cast.ToTime: Invalid date:", dt2.Year()) - } - } - }() - } - wg.Wait() - -} diff --git a/hugolib/pagesPrevNext.go b/hugolib/pagesPrevNext.go deleted file mode 100644 index bb474c499..000000000 --- a/hugolib/pagesPrevNext.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -// Prev returns the previous page reletive to the given page. -func (p Pages) Prev(cur *Page) *Page { - for x, c := range p { - if c.UniqueID() == cur.UniqueID() { - if x == 0 { - return p[len(p)-1] - } - return p[x-1] - } - } - return nil -} - -// Next returns the next page reletive to the given page. -func (p Pages) Next(cur *Page) *Page { - for x, c := range p { - if c.UniqueID() == cur.UniqueID() { - if x < len(p)-1 { - return p[x+1] - } - return p[0] - } - } - return nil -} diff --git a/hugolib/pagesPrevNext_test.go b/hugolib/pagesPrevNext_test.go deleted file mode 100644 index 5945d8fe5..000000000 --- a/hugolib/pagesPrevNext_test.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "testing" - - "github.com/spf13/cast" - "github.com/stretchr/testify/assert" -) - -type pagePNTestObject struct { - path string - weight int - date string -} - -var pagePNTestSources = []pagePNTestObject{ - {"/section1/testpage1.md", 5, "2012-04-06"}, - {"/section1/testpage2.md", 4, "2012-01-01"}, - {"/section1/testpage3.md", 3, "2012-04-06"}, - {"/section2/testpage4.md", 2, "2012-03-02"}, - {"/section2/testpage5.md", 1, "2012-04-06"}, -} - -func TestPrev(t *testing.T) { - t.Parallel() - pages := preparePageGroupTestPages(t) - assert.Equal(t, pages.Prev(pages[0]), pages[4]) - assert.Equal(t, pages.Prev(pages[1]), pages[0]) - assert.Equal(t, pages.Prev(pages[4]), pages[3]) -} - -func TestNext(t *testing.T) { - t.Parallel() - pages := preparePageGroupTestPages(t) - assert.Equal(t, pages.Next(pages[0]), pages[1]) - assert.Equal(t, pages.Next(pages[1]), pages[2]) - assert.Equal(t, pages.Next(pages[4]), pages[0]) -} - -func prepareWeightedPagesPrevNext(t *testing.T) WeightedPages { - s := newTestSite(t) - w := WeightedPages{} - - for _, src := range pagePNTestSources { - p, err := s.NewPage(src.path) - if err != nil { - t.Fatalf("failed to prepare test page %s", src.path) - } - p.Weight = src.weight - p.Date = cast.ToTime(src.date) - p.PublishDate = cast.ToTime(src.date) - w = append(w, WeightedPage{p.Weight, p}) - } - - w.Sort() - return w -} - -func TestWeightedPagesPrev(t *testing.T) { - t.Parallel() - w := prepareWeightedPagesPrevNext(t) - assert.Equal(t, w.Prev(w[0].Page), w[4].Page) - assert.Equal(t, w.Prev(w[1].Page), w[0].Page) - assert.Equal(t, w.Prev(w[4].Page), w[3].Page) -} - -func TestWeightedPagesNext(t *testing.T) { - t.Parallel() - w := prepareWeightedPagesPrevNext(t) - assert.Equal(t, w.Next(w[0].Page), w[1].Page) - assert.Equal(t, w.Next(w[1].Page), w[2].Page) - assert.Equal(t, w.Next(w[4].Page), w[0].Page) -} diff --git a/hugolib/pagination.go b/hugolib/pagination.go deleted file mode 100644 index 4733cf7c8..000000000 --- a/hugolib/pagination.go +++ /dev/null @@ -1,535 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "errors" - "fmt" - "html/template" - "math" - "reflect" - "strings" - - "github.com/gohugoio/hugo/config" - - "github.com/spf13/cast" -) - -// Pager represents one of the elements in a paginator. -// The number, starting on 1, represents its place. -type Pager struct { - number int - *paginator -} - -func (p Pager) String() string { - return fmt.Sprintf("Pager %d", p.number) -} - -type paginatedElement interface { - Len() int -} - -// Len returns the number of pages in the list. -func (p Pages) Len() int { - return len(p) -} - -// Len returns the number of pages in the page group. -func (psg PagesGroup) Len() int { - l := 0 - for _, pg := range psg { - l += len(pg.Pages) - } - return l -} - -type pagers []*Pager - -var ( - paginatorEmptyPages Pages - paginatorEmptyPageGroups PagesGroup -) - -type paginator struct { - paginatedElements []paginatedElement - pagers - paginationURLFactory - total int - size int - source interface{} - options []interface{} -} - -type paginationURLFactory func(int) string - -// PageNumber returns the current page's number in the pager sequence. -func (p *Pager) PageNumber() int { - return p.number -} - -// URL returns the URL to the current page. -func (p *Pager) URL() template.HTML { - return template.HTML(p.paginationURLFactory(p.PageNumber())) -} - -// Pages returns the Pages on this page. -// Note: If this return a non-empty result, then PageGroups() will return empty. -func (p *Pager) Pages() Pages { - if len(p.paginatedElements) == 0 { - return paginatorEmptyPages - } - - if pages, ok := p.element().(Pages); ok { - return pages - } - - return paginatorEmptyPages -} - -// PageGroups return Page groups for this page. -// Note: If this return non-empty result, then Pages() will return empty. -func (p *Pager) PageGroups() PagesGroup { - if len(p.paginatedElements) == 0 { - return paginatorEmptyPageGroups - } - - if groups, ok := p.element().(PagesGroup); ok { - return groups - } - - return paginatorEmptyPageGroups -} - -func (p *Pager) element() paginatedElement { - if len(p.paginatedElements) == 0 { - return paginatorEmptyPages - } - return p.paginatedElements[p.PageNumber()-1] -} - -// page returns the Page with the given index -func (p *Pager) page(index int) (*Page, error) { - - if pages, ok := p.element().(Pages); ok { - if pages != nil && len(pages) > index { - return pages[index], nil - } - return nil, nil - } - - // must be PagesGroup - // this construction looks clumsy, but ... - // ... it is the difference between 99.5% and 100% test coverage :-) - groups := p.element().(PagesGroup) - - i := 0 - for _, v := range groups { - for _, page := range v.Pages { - if i == index { - return page, nil - } - i++ - } - } - return nil, nil -} - -// NumberOfElements gets the number of elements on this page. -func (p *Pager) NumberOfElements() int { - return p.element().Len() -} - -// HasPrev tests whether there are page(s) before the current. -func (p *Pager) HasPrev() bool { - return p.PageNumber() > 1 -} - -// Prev returns the pager for the previous page. -func (p *Pager) Prev() *Pager { - if !p.HasPrev() { - return nil - } - return p.pagers[p.PageNumber()-2] -} - -// HasNext tests whether there are page(s) after the current. -func (p *Pager) HasNext() bool { - return p.PageNumber() < len(p.paginatedElements) -} - -// Next returns the pager for the next page. -func (p *Pager) Next() *Pager { - if !p.HasNext() { - return nil - } - return p.pagers[p.PageNumber()] -} - -// First returns the pager for the first page. -func (p *Pager) First() *Pager { - return p.pagers[0] -} - -// Last returns the pager for the last page. -func (p *Pager) Last() *Pager { - return p.pagers[len(p.pagers)-1] -} - -// Pagers returns a list of pagers that can be used to build a pagination menu. -func (p *paginator) Pagers() pagers { - return p.pagers -} - -// PageSize returns the size of each paginator page. -func (p *paginator) PageSize() int { - return p.size -} - -// TotalPages returns the number of pages in the paginator. -func (p *paginator) TotalPages() int { - return len(p.paginatedElements) -} - -// TotalNumberOfElements returns the number of elements on all pages in this paginator. -func (p *paginator) TotalNumberOfElements() int { - return p.total -} - -func splitPages(pages Pages, size int) []paginatedElement { - var split []paginatedElement - for low, j := 0, len(pages); low < j; low += size { - high := int(math.Min(float64(low+size), float64(len(pages)))) - split = append(split, pages[low:high]) - } - - return split -} - -func splitPageGroups(pageGroups PagesGroup, size int) []paginatedElement { - - type keyPage struct { - key interface{} - page *Page - } - - var ( - split []paginatedElement - flattened []keyPage - ) - - for _, g := range pageGroups { - for _, p := range g.Pages { - flattened = append(flattened, keyPage{g.Key, p}) - } - } - - numPages := len(flattened) - - for low, j := 0, numPages; low < j; low += size { - high := int(math.Min(float64(low+size), float64(numPages))) - - var ( - pg PagesGroup - key interface{} - groupIndex = -1 - ) - - for k := low; k < high; k++ { - kp := flattened[k] - if key == nil || key != kp.key { - key = kp.key - pg = append(pg, PageGroup{Key: key}) - groupIndex++ - } - pg[groupIndex].Pages = append(pg[groupIndex].Pages, kp.page) - } - split = append(split, pg) - } - - return split -} - -// Paginator get this Page's main output's paginator. -func (p *Page) Paginator(options ...interface{}) (*Pager, error) { - return p.mainPageOutput.Paginator(options...) -} - -// Paginator gets this PageOutput's paginator if it's already created. -// If it's not, one will be created with all pages in Data["Pages"]. -func (p *PageOutput) Paginator(options ...interface{}) (*Pager, error) { - if !p.IsNode() { - return nil, fmt.Errorf("Paginators not supported for pages of type %q (%q)", p.Kind, p.Title) - } - pagerSize, err := resolvePagerSize(p.s.Cfg, options...) - - if err != nil { - return nil, err - } - - var initError error - - p.paginatorInit.Do(func() { - if p.paginator != nil { - return - } - - pagers, err := paginatePages(p.targetPathDescriptor, p.Data["Pages"], pagerSize) - - if err != nil { - initError = err - } - - if len(pagers) > 0 { - // the rest of the nodes will be created later - p.paginator = pagers[0] - p.paginator.source = "paginator" - p.paginator.options = options - p.Site.addToPaginationPageCount(uint64(p.paginator.TotalPages())) - } - - }) - - if initError != nil { - return nil, initError - } - - return p.paginator, nil -} - -// Paginate invokes this Page's main output's Paginate method. -func (p *Page) Paginate(seq interface{}, options ...interface{}) (*Pager, error) { - return p.mainPageOutput.Paginate(seq, options...) -} - -// Paginate gets this PageOutput's paginator if it's already created. -// If it's not, one will be created with the qiven sequence. -// Note that repeated calls will return the same result, even if the sequence is different. -func (p *PageOutput) Paginate(seq interface{}, options ...interface{}) (*Pager, error) { - if !p.IsNode() { - return nil, fmt.Errorf("Paginators not supported for pages of type %q (%q)", p.Kind, p.Title) - } - - pagerSize, err := resolvePagerSize(p.s.Cfg, options...) - - if err != nil { - return nil, err - } - - var initError error - - p.paginatorInit.Do(func() { - if p.paginator != nil { - return - } - pagers, err := paginatePages(p.targetPathDescriptor, seq, pagerSize) - - if err != nil { - initError = err - } - - if len(pagers) > 0 { - // the rest of the nodes will be created later - p.paginator = pagers[0] - p.paginator.source = seq - p.paginator.options = options - p.Site.addToPaginationPageCount(uint64(p.paginator.TotalPages())) - } - - }) - - if initError != nil { - return nil, initError - } - - if p.paginator.source == "paginator" { - return nil, errors.New("a Paginator was previously built for this Node without filters; look for earlier .Paginator usage") - } - - if !reflect.DeepEqual(options, p.paginator.options) || !probablyEqualPageLists(p.paginator.source, seq) { - return nil, errors.New("invoked multiple times with different arguments") - } - - return p.paginator, nil -} - -func resolvePagerSize(cfg config.Provider, options ...interface{}) (int, error) { - if len(options) == 0 { - return cfg.GetInt("paginate"), nil - } - - if len(options) > 1 { - return -1, errors.New("too many arguments, 'pager size' is currently the only option") - } - - pas, err := cast.ToIntE(options[0]) - - if err != nil || pas <= 0 { - return -1, errors.New(("'pager size' must be a positive integer")) - } - - return pas, nil -} - -func paginatePages(td targetPathDescriptor, seq interface{}, pagerSize int) (pagers, error) { - - if pagerSize <= 0 { - return nil, errors.New("'paginate' configuration setting must be positive to paginate") - } - - urlFactory := newPaginationURLFactory(td) - - var paginator *paginator - - if groups, ok := seq.(PagesGroup); ok { - paginator, _ = newPaginatorFromPageGroups(groups, pagerSize, urlFactory) - } else { - pages, err := toPages(seq) - if err != nil { - return nil, err - } - paginator, _ = newPaginatorFromPages(pages, pagerSize, urlFactory) - } - - pagers := paginator.Pagers() - - return pagers, nil -} - -func toPages(seq interface{}) (Pages, error) { - switch seq.(type) { - case Pages: - return seq.(Pages), nil - case *Pages: - return *(seq.(*Pages)), nil - case WeightedPages: - return (seq.(WeightedPages)).Pages(), nil - case PageGroup: - return (seq.(PageGroup)).Pages, nil - default: - return nil, fmt.Errorf("unsupported type in paginate, got %T", seq) - } -} - -// probablyEqual checks page lists for probable equality. -// It may return false positives. -// The motivation behind this is to avoid potential costly reflect.DeepEqual -// when "probably" is good enough. -func probablyEqualPageLists(a1 interface{}, a2 interface{}) bool { - - if a1 == nil || a2 == nil { - return a1 == a2 - } - - t1 := reflect.TypeOf(a1) - t2 := reflect.TypeOf(a2) - - if t1 != t2 { - return false - } - - if g1, ok := a1.(PagesGroup); ok { - g2 := a2.(PagesGroup) - if len(g1) != len(g2) { - return false - } - if len(g1) == 0 { - return true - } - if g1.Len() != g2.Len() { - return false - } - - return g1[0].Pages[0] == g2[0].Pages[0] - } - - p1, err1 := toPages(a1) - p2, err2 := toPages(a2) - - // probably the same wrong type - if err1 != nil && err2 != nil { - return true - } - - if len(p1) != len(p2) { - return false - } - - if len(p1) == 0 { - return true - } - - return p1[0] == p2[0] -} - -func newPaginatorFromPages(pages Pages, size int, urlFactory paginationURLFactory) (*paginator, error) { - - if size <= 0 { - return nil, errors.New("Paginator size must be positive") - } - - split := splitPages(pages, size) - - return newPaginator(split, len(pages), size, urlFactory) -} - -func newPaginatorFromPageGroups(pageGroups PagesGroup, size int, urlFactory paginationURLFactory) (*paginator, error) { - - if size <= 0 { - return nil, errors.New("Paginator size must be positive") - } - - split := splitPageGroups(pageGroups, size) - - return newPaginator(split, pageGroups.Len(), size, urlFactory) -} - -func newPaginator(elements []paginatedElement, total, size int, urlFactory paginationURLFactory) (*paginator, error) { - p := &paginator{total: total, paginatedElements: elements, size: size, paginationURLFactory: urlFactory} - - var ps pagers - - if len(elements) > 0 { - ps = make(pagers, len(elements)) - for i := range p.paginatedElements { - ps[i] = &Pager{number: (i + 1), paginator: p} - } - } else { - ps = make(pagers, 1) - ps[0] = &Pager{number: 1, paginator: p} - } - - p.pagers = ps - - return p, nil -} - -func newPaginationURLFactory(d targetPathDescriptor) paginationURLFactory { - - return func(page int) string { - pathDescriptor := d - var rel string - if page > 1 { - rel = fmt.Sprintf("/%s/%d/", d.PathSpec.PaginatePath(), page) - pathDescriptor.Addends = rel - } - - targetPath := createTargetPath(pathDescriptor) - targetPath = strings.TrimSuffix(targetPath, d.Type.BaseFilename()) - link := d.PathSpec.PrependBasePath(targetPath) - - // Note: The targetPath is massaged with MakePathSanitized - return d.PathSpec.URLizeFilename(link) - } -} diff --git a/hugolib/pagination_test.go b/hugolib/pagination_test.go deleted file mode 100644 index edfac3f3e..000000000 --- a/hugolib/pagination_test.go +++ /dev/null @@ -1,579 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "fmt" - "html/template" - "path/filepath" - "strings" - "testing" - - "github.com/gohugoio/hugo/deps" - "github.com/gohugoio/hugo/output" - "github.com/stretchr/testify/require" -) - -func TestSplitPages(t *testing.T) { - t.Parallel() - s := newTestSite(t) - - pages := createTestPages(s, 21) - chunks := splitPages(pages, 5) - require.Equal(t, 5, len(chunks)) - - for i := 0; i < 4; i++ { - require.Equal(t, 5, chunks[i].Len()) - } - - lastChunk := chunks[4] - require.Equal(t, 1, lastChunk.Len()) - -} - -func TestSplitPageGroups(t *testing.T) { - t.Parallel() - s := newTestSite(t) - pages := createTestPages(s, 21) - groups, _ := pages.GroupBy("Weight", "desc") - chunks := splitPageGroups(groups, 5) - require.Equal(t, 5, len(chunks)) - - firstChunk := chunks[0] - - // alternate weight 5 and 10 - if groups, ok := firstChunk.(PagesGroup); ok { - require.Equal(t, 5, groups.Len()) - for _, pg := range groups { - // first group 10 in weight - require.Equal(t, 10, pg.Key) - for _, p := range pg.Pages { - require.True(t, p.fuzzyWordCount%2 == 0) // magic test - } - } - } else { - t.Fatal("Excepted PageGroup") - } - - lastChunk := chunks[4] - - if groups, ok := lastChunk.(PagesGroup); ok { - require.Equal(t, 1, groups.Len()) - for _, pg := range groups { - // last should have 5 in weight - require.Equal(t, 5, pg.Key) - for _, p := range pg.Pages { - require.True(t, p.fuzzyWordCount%2 != 0) // magic test - } - } - } else { - t.Fatal("Excepted PageGroup") - } - -} - -func TestPager(t *testing.T) { - t.Parallel() - s := newTestSite(t) - pages := createTestPages(s, 21) - groups, _ := pages.GroupBy("Weight", "desc") - - urlFactory := func(page int) string { - return fmt.Sprintf("page/%d/", page) - } - - _, err := newPaginatorFromPages(pages, -1, urlFactory) - require.NotNil(t, err) - - _, err = newPaginatorFromPageGroups(groups, -1, urlFactory) - require.NotNil(t, err) - - pag, err := newPaginatorFromPages(pages, 5, urlFactory) - require.Nil(t, err) - doTestPages(t, pag) - first := pag.Pagers()[0].First() - require.Equal(t, "Pager 1", first.String()) - require.NotEmpty(t, first.Pages()) - require.Empty(t, first.PageGroups()) - - pag, err = newPaginatorFromPageGroups(groups, 5, urlFactory) - require.Nil(t, err) - doTestPages(t, pag) - first = pag.Pagers()[0].First() - require.NotEmpty(t, first.PageGroups()) - require.Empty(t, first.Pages()) - -} - -func doTestPages(t *testing.T, paginator *paginator) { - - paginatorPages := paginator.Pagers() - - require.Equal(t, 5, len(paginatorPages)) - require.Equal(t, 21, paginator.TotalNumberOfElements()) - require.Equal(t, 5, paginator.PageSize()) - require.Equal(t, 5, paginator.TotalPages()) - - first := paginatorPages[0] - require.Equal(t, template.HTML("page/1/"), first.URL()) - require.Equal(t, first, first.First()) - require.True(t, first.HasNext()) - require.Equal(t, paginatorPages[1], first.Next()) - require.False(t, first.HasPrev()) - require.Nil(t, first.Prev()) - require.Equal(t, 5, first.NumberOfElements()) - require.Equal(t, 1, first.PageNumber()) - - third := paginatorPages[2] - require.True(t, third.HasNext()) - require.True(t, third.HasPrev()) - require.Equal(t, paginatorPages[1], third.Prev()) - - last := paginatorPages[4] - require.Equal(t, template.HTML("page/5/"), last.URL()) - require.Equal(t, last, last.Last()) - require.False(t, last.HasNext()) - require.Nil(t, last.Next()) - require.True(t, last.HasPrev()) - require.Equal(t, 1, last.NumberOfElements()) - require.Equal(t, 5, last.PageNumber()) -} - -func TestPagerNoPages(t *testing.T) { - t.Parallel() - s := newTestSite(t) - pages := createTestPages(s, 0) - groups, _ := pages.GroupBy("Weight", "desc") - - urlFactory := func(page int) string { - return fmt.Sprintf("page/%d/", page) - } - - paginator, _ := newPaginatorFromPages(pages, 5, urlFactory) - doTestPagerNoPages(t, paginator) - - first := paginator.Pagers()[0].First() - require.Empty(t, first.PageGroups()) - require.Empty(t, first.Pages()) - - paginator, _ = newPaginatorFromPageGroups(groups, 5, urlFactory) - doTestPagerNoPages(t, paginator) - - first = paginator.Pagers()[0].First() - require.Empty(t, first.PageGroups()) - require.Empty(t, first.Pages()) - -} - -func doTestPagerNoPages(t *testing.T, paginator *paginator) { - paginatorPages := paginator.Pagers() - - require.Equal(t, 1, len(paginatorPages)) - require.Equal(t, 0, paginator.TotalNumberOfElements()) - require.Equal(t, 5, paginator.PageSize()) - require.Equal(t, 0, paginator.TotalPages()) - - // pageOne should be nothing but the first - pageOne := paginatorPages[0] - require.NotNil(t, pageOne.First()) - require.False(t, pageOne.HasNext()) - require.False(t, pageOne.HasPrev()) - require.Nil(t, pageOne.Next()) - require.Equal(t, 1, len(pageOne.Pagers())) - require.Equal(t, 0, pageOne.Pages().Len()) - require.Equal(t, 0, pageOne.NumberOfElements()) - require.Equal(t, 0, pageOne.TotalNumberOfElements()) - require.Equal(t, 0, pageOne.TotalPages()) - require.Equal(t, 1, pageOne.PageNumber()) - require.Equal(t, 5, pageOne.PageSize()) - -} - -func TestPaginationURLFactory(t *testing.T) { - t.Parallel() - cfg, fs := newTestCfg() - cfg.Set("paginatePath", "zoo") - - for _, uglyURLs := range []bool{false, true} { - for _, canonifyURLs := range []bool{false, true} { - t.Run(fmt.Sprintf("uglyURLs=%t,canonifyURLs=%t", uglyURLs, canonifyURLs), func(t *testing.T) { - - tests := []struct { - name string - d targetPathDescriptor - baseURL string - page int - expected string - }{ - {"HTML home page 32", - targetPathDescriptor{Kind: KindHome, Type: output.HTMLFormat}, "http://example.com/", 32, "/zoo/32/"}, - {"JSON home page 42", - targetPathDescriptor{Kind: KindHome, Type: output.JSONFormat}, "http://example.com/", 42, "/zoo/42/"}, - // Issue #1252 - {"BaseURL with sub path", - targetPathDescriptor{Kind: KindHome, Type: output.HTMLFormat}, "http://example.com/sub/", 999, "/sub/zoo/999/"}, - } - - for _, test := range tests { - d := test.d - cfg.Set("baseURL", test.baseURL) - cfg.Set("canonifyURLs", canonifyURLs) - cfg.Set("uglyURLs", uglyURLs) - d.UglyURLs = uglyURLs - - expected := test.expected - - if canonifyURLs { - expected = strings.Replace(expected, "/sub", "", 1) - } - - if uglyURLs { - expected = expected[:len(expected)-1] + "." + test.d.Type.MediaType.Suffix - } - - pathSpec := newTestPathSpec(fs, cfg) - d.PathSpec = pathSpec - - factory := newPaginationURLFactory(d) - - got := factory(test.page) - - require.Equal(t, expected, got) - - } - }) - } - } -} - -func TestPaginator(t *testing.T) { - t.Parallel() - for _, useViper := range []bool{false, true} { - doTestPaginator(t, useViper) - } -} - -func doTestPaginator(t *testing.T, useViper bool) { - - cfg, fs := newTestCfg() - - pagerSize := 5 - if useViper { - cfg.Set("paginate", pagerSize) - } else { - cfg.Set("paginate", -1) - } - - s, err := NewSiteForCfg(deps.DepsCfg{Cfg: cfg, Fs: fs}) - require.NoError(t, err) - - pages := createTestPages(s, 12) - n1, _ := newPageOutput(s.newHomePage(), false, output.HTMLFormat) - n2, _ := newPageOutput(s.newHomePage(), false, output.HTMLFormat) - n1.Data["Pages"] = pages - - var paginator1 *Pager - - if useViper { - paginator1, err = n1.Paginator() - } else { - paginator1, err = n1.Paginator(pagerSize) - } - - require.Nil(t, err) - require.NotNil(t, paginator1) - require.Equal(t, 3, paginator1.TotalPages()) - require.Equal(t, 12, paginator1.TotalNumberOfElements()) - - n2.paginator = paginator1.Next() - paginator2, err := n2.Paginator() - require.Nil(t, err) - require.Equal(t, paginator2, paginator1.Next()) - - n1.Data["Pages"] = createTestPages(s, 1) - samePaginator, _ := n1.Paginator() - require.Equal(t, paginator1, samePaginator) - - pp, _ := s.NewPage("test") - p, _ := newPageOutput(pp, false, output.HTMLFormat) - - _, err = p.Paginator() - require.NotNil(t, err) -} - -func TestPaginatorWithNegativePaginate(t *testing.T) { - t.Parallel() - s := newTestSite(t, "paginate", -1) - n1, _ := newPageOutput(s.newHomePage(), false, output.HTMLFormat) - _, err := n1.Paginator() - require.Error(t, err) -} - -func TestPaginate(t *testing.T) { - t.Parallel() - for _, useViper := range []bool{false, true} { - doTestPaginate(t, useViper) - } -} - -func TestPaginatorURL(t *testing.T) { - t.Parallel() - cfg, fs := newTestCfg() - - cfg.Set("paginate", 2) - cfg.Set("paginatePath", "testing") - - for i := 0; i < 10; i++ { - // Issue #2177, do not double encode URLs - writeSource(t, fs, filepath.Join("content", "阅读", fmt.Sprintf("page%d.md", (i+1))), - fmt.Sprintf(`--- -title: Page%d ---- -Conten%d -`, (i+1), i+1)) - - } - writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), "{{.Content}}") - writeSource(t, fs, filepath.Join("layouts", "_default", "list.html"), - ` - -Count: {{ .Paginator.TotalNumberOfElements }} -Pages: {{ .Paginator.TotalPages }} -{{ range .Paginator.Pagers -}} - {{ .PageNumber }}: {{ .URL }} -{{ end }} -`) - - s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{}) - - th := testHelper{s.Cfg, s.Fs, t} - - th.assertFileContent(filepath.Join("public", "阅读", "testing", "2", "index.html"), "2: /%E9%98%85%E8%AF%BB/testing/2/") - -} - -func doTestPaginate(t *testing.T, useViper bool) { - pagerSize := 5 - - var ( - s *Site - err error - ) - - if useViper { - s = newTestSite(t, "paginate", pagerSize) - } else { - s = newTestSite(t, "paginate", -1) - } - - pages := createTestPages(s, 6) - n1, _ := newPageOutput(s.newHomePage(), false, output.HTMLFormat) - n2, _ := newPageOutput(s.newHomePage(), false, output.HTMLFormat) - - var paginator1, paginator2 *Pager - - if useViper { - paginator1, err = n1.Paginate(pages) - } else { - paginator1, err = n1.Paginate(pages, pagerSize) - } - - require.Nil(t, err) - require.NotNil(t, paginator1) - require.Equal(t, 2, paginator1.TotalPages()) - require.Equal(t, 6, paginator1.TotalNumberOfElements()) - - n2.paginator = paginator1.Next() - if useViper { - paginator2, err = n2.Paginate(pages) - } else { - paginator2, err = n2.Paginate(pages, pagerSize) - } - require.Nil(t, err) - require.Equal(t, paginator2, paginator1.Next()) - - pp, err := s.NewPage("test") - p, _ := newPageOutput(pp, false, output.HTMLFormat) - - _, err = p.Paginate(pages) - require.NotNil(t, err) -} - -func TestInvalidOptions(t *testing.T) { - t.Parallel() - s := newTestSite(t) - n1, _ := newPageOutput(s.newHomePage(), false, output.HTMLFormat) - - _, err := n1.Paginate(createTestPages(s, 1), 1, 2) - require.NotNil(t, err) - _, err = n1.Paginator(1, 2) - require.NotNil(t, err) - _, err = n1.Paginator(-1) - require.NotNil(t, err) -} - -func TestPaginateWithNegativePaginate(t *testing.T) { - t.Parallel() - cfg, fs := newTestCfg() - cfg.Set("paginate", -1) - - s, err := NewSiteForCfg(deps.DepsCfg{Cfg: cfg, Fs: fs}) - require.NoError(t, err) - - n, _ := newPageOutput(s.newHomePage(), false, output.HTMLFormat) - - _, err = n.Paginate(createTestPages(s, 2)) - require.NotNil(t, err) -} - -func TestPaginatePages(t *testing.T) { - t.Parallel() - s := newTestSite(t) - - groups, _ := createTestPages(s, 31).GroupBy("Weight", "desc") - pd := targetPathDescriptor{Kind: KindHome, Type: output.HTMLFormat, PathSpec: s.PathSpec, Addends: "t"} - - for i, seq := range []interface{}{createTestPages(s, 11), groups, WeightedPages{}, PageGroup{}, &Pages{}} { - v, err := paginatePages(pd, seq, 11) - require.NotNil(t, v, "Val %d", i) - require.Nil(t, err, "Err %d", i) - } - _, err := paginatePages(pd, Site{}, 11) - require.NotNil(t, err) - -} - -// Issue #993 -func TestPaginatorFollowedByPaginateShouldFail(t *testing.T) { - t.Parallel() - s := newTestSite(t, "paginate", 10) - n1, _ := newPageOutput(s.newHomePage(), false, output.HTMLFormat) - n2, _ := newPageOutput(s.newHomePage(), false, output.HTMLFormat) - - _, err := n1.Paginator() - require.Nil(t, err) - _, err = n1.Paginate(createTestPages(s, 2)) - require.NotNil(t, err) - - _, err = n2.Paginate(createTestPages(s, 2)) - require.Nil(t, err) - -} - -func TestPaginateFollowedByDifferentPaginateShouldFail(t *testing.T) { - t.Parallel() - s := newTestSite(t, "paginate", 10) - - n1, _ := newPageOutput(s.newHomePage(), false, output.HTMLFormat) - n2, _ := newPageOutput(s.newHomePage(), false, output.HTMLFormat) - - p1 := createTestPages(s, 2) - p2 := createTestPages(s, 10) - - _, err := n1.Paginate(p1) - require.Nil(t, err) - - _, err = n1.Paginate(p1) - require.Nil(t, err) - - _, err = n1.Paginate(p2) - require.NotNil(t, err) - - _, err = n2.Paginate(p2) - require.Nil(t, err) -} - -func TestProbablyEqualPageLists(t *testing.T) { - t.Parallel() - s := newTestSite(t) - fivePages := createTestPages(s, 5) - zeroPages := createTestPages(s, 0) - zeroPagesByWeight, _ := createTestPages(s, 0).GroupBy("Weight", "asc") - fivePagesByWeight, _ := createTestPages(s, 5).GroupBy("Weight", "asc") - ninePagesByWeight, _ := createTestPages(s, 9).GroupBy("Weight", "asc") - - for i, this := range []struct { - v1 interface{} - v2 interface{} - expect bool - }{ - {nil, nil, true}, - {"a", "b", true}, - {"a", fivePages, false}, - {fivePages, "a", false}, - {fivePages, createTestPages(s, 2), false}, - {fivePages, fivePages, true}, - {zeroPages, zeroPages, true}, - {fivePagesByWeight, fivePagesByWeight, true}, - {zeroPagesByWeight, fivePagesByWeight, false}, - {zeroPagesByWeight, zeroPagesByWeight, true}, - {fivePagesByWeight, fivePages, false}, - {fivePagesByWeight, ninePagesByWeight, false}, - } { - result := probablyEqualPageLists(this.v1, this.v2) - - if result != this.expect { - t.Errorf("[%d] got %t but expected %t", i, result, this.expect) - - } - } -} - -func TestPage(t *testing.T) { - t.Parallel() - urlFactory := func(page int) string { - return fmt.Sprintf("page/%d/", page) - } - - s := newTestSite(t) - - fivePages := createTestPages(s, 7) - fivePagesFuzzyWordCount, _ := createTestPages(s, 7).GroupBy("FuzzyWordCount", "asc") - - p1, _ := newPaginatorFromPages(fivePages, 2, urlFactory) - p2, _ := newPaginatorFromPageGroups(fivePagesFuzzyWordCount, 2, urlFactory) - - f1 := p1.pagers[0].First() - f2 := p2.pagers[0].First() - - page11, _ := f1.page(1) - page1Nil, _ := f1.page(3) - - page21, _ := f2.page(1) - page2Nil, _ := f2.page(3) - - require.Equal(t, 3, page11.fuzzyWordCount) - require.Nil(t, page1Nil) - - require.Equal(t, 3, page21.fuzzyWordCount) - require.Nil(t, page2Nil) -} - -func createTestPages(s *Site, num int) Pages { - pages := make(Pages, num) - - for i := 0; i < num; i++ { - p := s.newPage(filepath.FromSlash(fmt.Sprintf("/x/y/z/p%d.md", i))) - w := 5 - if i%2 == 0 { - w = 10 - } - p.fuzzyWordCount = i + 2 - p.Weight = w - pages[i] = p - - } - - return pages -} diff --git a/hugolib/path_separators_test.go b/hugolib/path_separators_test.go deleted file mode 100644 index 3a73869ad..000000000 --- a/hugolib/path_separators_test.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "path/filepath" - "strings" - "testing" -) - -var simplePageYAML = `--- -contenttype: "" ---- -Sample Text -` - -func TestDegenerateMissingFolderInPageFilename(t *testing.T) { - t.Parallel() - s := newTestSite(t) - p, err := s.NewPageFrom(strings.NewReader(simplePageYAML), filepath.Join("foobar")) - if err != nil { - t.Fatalf("Error in NewPageFrom") - } - if p.Section() != "" { - t.Fatalf("No section should be set for a file path: foobar") - } -} diff --git a/hugolib/permalinker.go b/hugolib/permalinker.go deleted file mode 100644 index 5e7a13a02..000000000 --- a/hugolib/permalinker.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2017-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -var ( - _ Permalinker = (*Page)(nil) - _ Permalinker = (*OutputFormat)(nil) -) - -// Permalinker provides permalinks of both the relative and absolute kind. -type Permalinker interface { - Permalink() string - RelPermalink() string -} diff --git a/hugolib/permalinks.go b/hugolib/permalinks.go deleted file mode 100644 index 14d0cba5e..000000000 --- a/hugolib/permalinks.go +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "errors" - "fmt" - "path" - "regexp" - "strconv" - "strings" -) - -// pathPattern represents a string which builds up a URL from attributes -type pathPattern string - -// pageToPermaAttribute is the type of a function which, given a page and a tag -// can return a string to go in that position in the page (or an error) -type pageToPermaAttribute func(*Page, string) (string, error) - -// PermalinkOverrides maps a section name to a PathPattern -type PermalinkOverrides map[string]pathPattern - -// knownPermalinkAttributes maps :tags in a permalink specification to a -// function which, given a page and the tag, returns the resulting string -// to be used to replace that tag. -var knownPermalinkAttributes map[string]pageToPermaAttribute - -var attributeRegexp *regexp.Regexp - -// validate determines if a PathPattern is well-formed -func (pp pathPattern) validate() bool { - fragments := strings.Split(string(pp[1:]), "/") - var bail = false - for i := range fragments { - if bail { - return false - } - if len(fragments[i]) == 0 { - bail = true - continue - } - - matches := attributeRegexp.FindAllStringSubmatch(fragments[i], -1) - if matches == nil { - continue - } - - for _, match := range matches { - k := strings.ToLower(match[0][1:]) - if _, ok := knownPermalinkAttributes[k]; !ok { - return false - } - } - } - return true -} - -type permalinkExpandError struct { - pattern pathPattern - section string - err error -} - -func (pee *permalinkExpandError) Error() string { - return fmt.Sprintf("error expanding %q section %q: %s", string(pee.pattern), pee.section, pee.err) -} - -var ( - errPermalinkIllFormed = errors.New("permalink ill-formed") - errPermalinkAttributeUnknown = errors.New("permalink attribute not recognised") -) - -// Expand on a PathPattern takes a Page and returns the fully expanded Permalink -// or an error explaining the failure. -func (pp pathPattern) Expand(p *Page) (string, error) { - if !pp.validate() { - return "", &permalinkExpandError{pattern: pp, section: "", err: errPermalinkIllFormed} - } - sections := strings.Split(string(pp), "/") - for i, field := range sections { - if len(field) == 0 { - continue - } - - matches := attributeRegexp.FindAllStringSubmatch(field, -1) - - if matches == nil { - continue - } - - newField := field - - for _, match := range matches { - attr := match[0][1:] - callback, ok := knownPermalinkAttributes[attr] - - if !ok { - return "", &permalinkExpandError{pattern: pp, section: strconv.Itoa(i), err: errPermalinkAttributeUnknown} - } - - newAttr, err := callback(p, attr) - - if err != nil { - return "", &permalinkExpandError{pattern: pp, section: strconv.Itoa(i), err: err} - } - - newField = strings.Replace(newField, match[0], newAttr, 1) - } - - sections[i] = newField - } - return strings.Join(sections, "/"), nil -} - -func pageToPermalinkDate(p *Page, dateField string) (string, error) { - // a Page contains a Node which provides a field Date, time.Time - switch dateField { - case "year": - return strconv.Itoa(p.Date.Year()), nil - case "month": - return fmt.Sprintf("%02d", int(p.Date.Month())), nil - case "monthname": - return p.Date.Month().String(), nil - case "day": - return fmt.Sprintf("%02d", p.Date.Day()), nil - case "weekday": - return strconv.Itoa(int(p.Date.Weekday())), nil - case "weekdayname": - return p.Date.Weekday().String(), nil - case "yearday": - return strconv.Itoa(p.Date.YearDay()), nil - } - //TODO: support classic strftime escapes too - // (and pass those through despite not being in the map) - panic("coding error: should not be here") -} - -// pageToPermalinkTitle returns the URL-safe form of the title -func pageToPermalinkTitle(p *Page, _ string) (string, error) { - // Page contains Node which has Title - // (also contains URLPath which has Slug, sometimes) - return p.s.PathSpec.URLize(p.Title), nil -} - -// pageToPermalinkFilename returns the URL-safe form of the filename -func pageToPermalinkFilename(p *Page, _ string) (string, error) { - //var extension = p.Source.Ext - //var name = p.Source.Path()[0 : len(p.Source.Path())-len(extension)] - return p.s.PathSpec.URLize(p.Source.TranslationBaseName()), nil -} - -// if the page has a slug, return the slug, else return the title -func pageToPermalinkSlugElseTitle(p *Page, a string) (string, error) { - if p.Slug != "" { - // Don't start or end with a - - // TODO(bep) this doesn't look good... Set the Slug once. - if strings.HasPrefix(p.Slug, "-") { - p.Slug = p.Slug[1:len(p.Slug)] - } - - if strings.HasSuffix(p.Slug, "-") { - p.Slug = p.Slug[0 : len(p.Slug)-1] - } - return p.s.PathSpec.URLize(p.Slug), nil - } - return pageToPermalinkTitle(p, a) -} - -func pageToPermalinkSection(p *Page, _ string) (string, error) { - // Page contains Node contains URLPath which has Section - return p.Section(), nil -} - -func pageToPermalinkSections(p *Page, _ string) (string, error) { - // TODO(bep) we have some superflous URLize in this file, but let's - // deal with that later. - return path.Join(p.CurrentSection().sections...), nil -} - -func init() { - knownPermalinkAttributes = map[string]pageToPermaAttribute{ - "year": pageToPermalinkDate, - "month": pageToPermalinkDate, - "monthname": pageToPermalinkDate, - "day": pageToPermalinkDate, - "weekday": pageToPermalinkDate, - "weekdayname": pageToPermalinkDate, - "yearday": pageToPermalinkDate, - "section": pageToPermalinkSection, - "sections": pageToPermalinkSections, - "title": pageToPermalinkTitle, - "slug": pageToPermalinkSlugElseTitle, - "filename": pageToPermalinkFilename, - } - - attributeRegexp = regexp.MustCompile(`:\w+`) -} diff --git a/hugolib/permalinks_test.go b/hugolib/permalinks_test.go deleted file mode 100644 index 7a4bf78c2..000000000 --- a/hugolib/permalinks_test.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "strings" - "testing" -) - -// testdataPermalinks is used by a couple of tests; the expandsTo content is -// subject to the data in SIMPLE_PAGE_JSON. -var testdataPermalinks = []struct { - spec string - valid bool - expandsTo string -}{ - //{"/:year/:month/:title/", true, "/2012/04/spf13-vim-3.0-release-and-new-website/"}, - //{"/:title", true, "/spf13-vim-3.0-release-and-new-website"}, - //{":title", true, "spf13-vim-3.0-release-and-new-website"}, - //{"/blog/:year/:yearday/:title", true, "/blog/2012/97/spf13-vim-3.0-release-and-new-website"}, - {"/:year-:month-:title", true, "/2012-04-spf13-vim-3.0-release-and-new-website"}, - {"/blog/:year-:month-:title", true, "/blog/2012-04-spf13-vim-3.0-release-and-new-website"}, - {"/blog-:year-:month-:title", true, "/blog-2012-04-spf13-vim-3.0-release-and-new-website"}, - //{"/blog/:fred", false, ""}, - //{"/:year//:title", false, ""}, - //{ - //"/:section/:year/:month/:day/:weekdayname/:yearday/:title", - //true, - //"/blue/2012/04/06/Friday/97/spf13-vim-3.0-release-and-new-website", - //}, - //{ - //"/:weekday/:weekdayname/:month/:monthname", - //true, - //"/5/Friday/04/April", - //}, - //{ - //"/:slug/:title", - //true, - //"/spf13-vim-3-0-release-and-new-website/spf13-vim-3.0-release-and-new-website", - //}, -} - -func TestPermalinkValidation(t *testing.T) { - t.Parallel() - for _, item := range testdataPermalinks { - pp := pathPattern(item.spec) - have := pp.validate() - if have == item.valid { - continue - } - var howBad string - if have { - howBad = "validates but should not have" - } else { - howBad = "should have validated but did not" - } - t.Errorf("permlink spec %q %s", item.spec, howBad) - } -} - -func TestPermalinkExpansion(t *testing.T) { - t.Parallel() - s := newTestSite(t) - page, err := s.NewPageFrom(strings.NewReader(simplePageJSON), "blue/test-page.md") - - if err != nil { - t.Fatalf("failed before we began, could not parse SIMPLE_PAGE_JSON: %s", err) - } - for _, item := range testdataPermalinks { - if !item.valid { - continue - } - pp := pathPattern(item.spec) - result, err := pp.Expand(page) - if err != nil { - t.Errorf("failed to expand page: %s", err) - continue - } - if result != item.expandsTo { - t.Errorf("expansion mismatch!\n\tExpected: %q\n\tReceived: %q", item.expandsTo, result) - } - } -} diff --git a/hugolib/robotstxt_test.go b/hugolib/robotstxt_test.go deleted file mode 100644 index 03332cbce..000000000 --- a/hugolib/robotstxt_test.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2016 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "path/filepath" - "testing" - - "github.com/gohugoio/hugo/deps" -) - -const robotTxtTemplate = `User-agent: Googlebot - {{ range .Data.Pages }} - Disallow: {{.RelPermalink}} - {{ end }} -` - -func TestRobotsTXTOutput(t *testing.T) { - t.Parallel() - var ( - cfg, fs = newTestCfg() - th = testHelper{cfg, fs, t} - ) - - cfg.Set("baseURL", "http://auth/bub/") - cfg.Set("enableRobotsTXT", true) - - writeSource(t, fs, filepath.Join("layouts", "robots.txt"), robotTxtTemplate) - writeSourcesToSource(t, "content", fs, weightedSources...) - - buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{}) - - th.assertFileContent("public/robots.txt", "User-agent: Googlebot") - -} diff --git a/hugolib/rss_test.go b/hugolib/rss_test.go deleted file mode 100644 index 268b13073..000000000 --- a/hugolib/rss_test.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2016 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "path/filepath" - "strings" - "testing" - - "github.com/gohugoio/hugo/deps" -) - -func TestRSSOutput(t *testing.T) { - t.Parallel() - var ( - cfg, fs = newTestCfg() - th = testHelper{cfg, fs, t} - ) - - rssLimit := len(weightedSources) - 1 - - rssURI := "customrss.xml" - - cfg.Set("baseURL", "http://auth/bub/") - cfg.Set("rssURI", rssURI) - cfg.Set("title", "RSSTest") - cfg.Set("rssLimit", rssLimit) - - for _, src := range weightedSources { - writeSource(t, fs, filepath.Join("content", "sect", src.Name), string(src.Content)) - } - - buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{}) - - // Home RSS - th.assertFileContent(filepath.Join("public", rssURI), "") - if c != rssLimit { - t.Errorf("incorrect RSS item count: expected %d, got %d", rssLimit, c) - } -} diff --git a/hugolib/scratch.go b/hugolib/scratch.go deleted file mode 100644 index ca2c9d6a8..000000000 --- a/hugolib/scratch.go +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "reflect" - "sort" - "sync" - - "github.com/gohugoio/hugo/tpl/math" -) - -// Scratch is a writable context used for stateful operations in Page/Node rendering. -type Scratch struct { - values map[string]interface{} - mu sync.RWMutex -} - -// Add will, for single values, add (using the + operator) the addend to the existing addend (if found). -// Supports numeric values and strings. -// -// If the first add for a key is an array or slice, then the next value(s) will be appended. -func (c *Scratch) Add(key string, newAddend interface{}) (string, error) { - - var newVal interface{} - c.mu.RLock() - existingAddend, found := c.values[key] - c.mu.RUnlock() - if found { - var err error - - addendV := reflect.ValueOf(existingAddend) - - if addendV.Kind() == reflect.Slice || addendV.Kind() == reflect.Array { - nav := reflect.ValueOf(newAddend) - if nav.Kind() == reflect.Slice || nav.Kind() == reflect.Array { - newVal = reflect.AppendSlice(addendV, nav).Interface() - } else { - newVal = reflect.Append(addendV, nav).Interface() - } - } else { - newVal, err = math.DoArithmetic(existingAddend, newAddend, '+') - if err != nil { - return "", err - } - } - } else { - newVal = newAddend - } - c.mu.Lock() - c.values[key] = newVal - c.mu.Unlock() - return "", nil // have to return something to make it work with the Go templates -} - -// Set stores a value with the given key in the Node context. -// This value can later be retrieved with Get. -func (c *Scratch) Set(key string, value interface{}) string { - c.mu.Lock() - c.values[key] = value - c.mu.Unlock() - return "" -} - -// Get returns a value previously set by Add or Set -func (c *Scratch) Get(key string) interface{} { - c.mu.RLock() - val := c.values[key] - c.mu.RUnlock() - - return val -} - -// SetInMap stores a value to a map with the given key in the Node context. -// This map can later be retrieved with GetSortedMapValues. -func (c *Scratch) SetInMap(key string, mapKey string, value interface{}) string { - c.mu.Lock() - _, found := c.values[key] - if !found { - c.values[key] = make(map[string]interface{}) - } - - c.values[key].(map[string]interface{})[mapKey] = value - c.mu.Unlock() - return "" -} - -// GetSortedMapValues returns a sorted map previously filled with SetInMap -func (c *Scratch) GetSortedMapValues(key string) interface{} { - c.mu.RLock() - - if c.values[key] == nil { - c.mu.RUnlock() - return nil - } - - unsortedMap := c.values[key].(map[string]interface{}) - c.mu.RUnlock() - var keys []string - for mapKey := range unsortedMap { - keys = append(keys, mapKey) - } - - sort.Strings(keys) - - sortedArray := make([]interface{}, len(unsortedMap)) - for i, mapKey := range keys { - sortedArray[i] = unsortedMap[mapKey] - } - - return sortedArray -} - -func newScratch() *Scratch { - return &Scratch{values: make(map[string]interface{})} -} diff --git a/hugolib/scratch_test.go b/hugolib/scratch_test.go deleted file mode 100644 index f65c2ddfe..000000000 --- a/hugolib/scratch_test.go +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "reflect" - "sync" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestScratchAdd(t *testing.T) { - t.Parallel() - scratch := newScratch() - scratch.Add("int1", 10) - scratch.Add("int1", 20) - scratch.Add("int2", 20) - - assert.Equal(t, int64(30), scratch.Get("int1")) - assert.Equal(t, 20, scratch.Get("int2")) - - scratch.Add("float1", float64(10.5)) - scratch.Add("float1", float64(20.1)) - - assert.Equal(t, float64(30.6), scratch.Get("float1")) - - scratch.Add("string1", "Hello ") - scratch.Add("string1", "big ") - scratch.Add("string1", "World!") - - assert.Equal(t, "Hello big World!", scratch.Get("string1")) - - scratch.Add("scratch", scratch) - _, err := scratch.Add("scratch", scratch) - - if err == nil { - t.Errorf("Expected error from invalid arithmetic") - } - -} - -func TestScratchAddSlice(t *testing.T) { - t.Parallel() - scratch := newScratch() - - _, err := scratch.Add("intSlice", []int{1, 2}) - assert.Nil(t, err) - _, err = scratch.Add("intSlice", 3) - assert.Nil(t, err) - - sl := scratch.Get("intSlice") - expected := []int{1, 2, 3} - - if !reflect.DeepEqual(expected, sl) { - t.Errorf("Slice difference, go %q expected %q", sl, expected) - } - - _, err = scratch.Add("intSlice", []int{4, 5}) - - assert.Nil(t, err) - - sl = scratch.Get("intSlice") - expected = []int{1, 2, 3, 4, 5} - - if !reflect.DeepEqual(expected, sl) { - t.Errorf("Slice difference, go %q expected %q", sl, expected) - } - -} - -func TestScratchSet(t *testing.T) { - t.Parallel() - scratch := newScratch() - scratch.Set("key", "val") - assert.Equal(t, "val", scratch.Get("key")) -} - -// Issue #2005 -func TestScratchInParallel(t *testing.T) { - var wg sync.WaitGroup - scratch := newScratch() - key := "counter" - scratch.Set(key, int64(1)) - for i := 1; i <= 10; i++ { - wg.Add(1) - go func(j int) { - for k := 0; k < 10; k++ { - newVal := int64(k + j) - - _, err := scratch.Add(key, newVal) - if err != nil { - t.Errorf("Got err %s", err) - } - - scratch.Set(key, newVal) - - val := scratch.Get(key) - - if counter, ok := val.(int64); ok { - if counter < 1 { - t.Errorf("Got %d", counter) - } - } else { - t.Errorf("Got %T", val) - } - } - wg.Done() - }(i) - } - wg.Wait() -} - -func TestScratchGet(t *testing.T) { - t.Parallel() - scratch := newScratch() - nothing := scratch.Get("nothing") - if nothing != nil { - t.Errorf("Should not return anything, but got %v", nothing) - } -} - -func TestScratchSetInMap(t *testing.T) { - t.Parallel() - scratch := newScratch() - scratch.SetInMap("key", "lux", "Lux") - scratch.SetInMap("key", "abc", "Abc") - scratch.SetInMap("key", "zyx", "Zyx") - scratch.SetInMap("key", "abc", "Abc (updated)") - scratch.SetInMap("key", "def", "Def") - assert.Equal(t, []interface{}{0: "Abc (updated)", 1: "Def", 2: "Lux", 3: "Zyx"}, scratch.GetSortedMapValues("key")) -} - -func TestScratchGetSortedMapValues(t *testing.T) { - t.Parallel() - scratch := newScratch() - nothing := scratch.GetSortedMapValues("nothing") - if nothing != nil { - t.Errorf("Should not return anything, but got %v", nothing) - } -} - -func BenchmarkScratchGet(b *testing.B) { - scratch := newScratch() - scratch.Add("A", 1) - b.ResetTimer() - for i := 0; i < b.N; i++ { - scratch.Get("A") - } -} diff --git a/hugolib/shortcode.go b/hugolib/shortcode.go deleted file mode 100644 index 3cf472f82..000000000 --- a/hugolib/shortcode.go +++ /dev/null @@ -1,727 +0,0 @@ -// Copyright 2017 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "bytes" - "errors" - "fmt" - "html/template" - "reflect" - "regexp" - "sort" - "strings" - "sync" - - "github.com/gohugoio/hugo/output" - - "github.com/gohugoio/hugo/media" - - bp "github.com/gohugoio/hugo/bufferpool" - "github.com/gohugoio/hugo/helpers" - "github.com/gohugoio/hugo/tpl" -) - -// ShortcodeWithPage is the "." context in a shortcode template. -type ShortcodeWithPage struct { - Params interface{} - Inner template.HTML - Page *Page - Parent *ShortcodeWithPage - IsNamedParams bool - scratch *Scratch -} - -// Site returns information about the current site. -func (scp *ShortcodeWithPage) Site() *SiteInfo { - return scp.Page.Site -} - -// Ref is a shortcut to the Ref method on Page. -func (scp *ShortcodeWithPage) Ref(ref string) (string, error) { - return scp.Page.Ref(ref) -} - -// RelRef is a shortcut to the RelRef method on Page. -func (scp *ShortcodeWithPage) RelRef(ref string) (string, error) { - return scp.Page.RelRef(ref) -} - -// Scratch returns a scratch-pad scoped for this shortcode. This can be used -// as a temporary storage for variables, counters etc. -func (scp *ShortcodeWithPage) Scratch() *Scratch { - if scp.scratch == nil { - scp.scratch = newScratch() - } - return scp.scratch -} - -// Get is a convenience method to look up shortcode parameters by its key. -func (scp *ShortcodeWithPage) Get(key interface{}) interface{} { - if scp.Params == nil { - return nil - } - if reflect.ValueOf(scp.Params).Len() == 0 { - return nil - } - - var x reflect.Value - - switch key.(type) { - case int64, int32, int16, int8, int: - if reflect.TypeOf(scp.Params).Kind() == reflect.Map { - return "error: cannot access named params by position" - } else if reflect.TypeOf(scp.Params).Kind() == reflect.Slice { - idx := int(reflect.ValueOf(key).Int()) - ln := reflect.ValueOf(scp.Params).Len() - if idx > ln-1 { - helpers.DistinctErrorLog.Printf("No shortcode param at .Get %d in page %s, have params: %v", idx, scp.Page.FullFilePath(), scp.Params) - return fmt.Sprintf("error: index out of range for positional param at position %d", idx) - } - x = reflect.ValueOf(scp.Params).Index(idx) - } - case string: - if reflect.TypeOf(scp.Params).Kind() == reflect.Map { - x = reflect.ValueOf(scp.Params).MapIndex(reflect.ValueOf(key)) - if !x.IsValid() { - return "" - } - } else if reflect.TypeOf(scp.Params).Kind() == reflect.Slice { - if reflect.ValueOf(scp.Params).Len() == 1 && reflect.ValueOf(scp.Params).Index(0).String() == "" { - return nil - } - return "error: cannot access positional params by string name" - } - } - - switch x.Kind() { - case reflect.String: - return x.String() - case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int: - return x.Int() - default: - return x - } - -} - -// Note - this value must not contain any markup syntax -const shortcodePlaceholderPrefix = "HUGOSHORTCODE" - -type shortcode struct { - name string - inner []interface{} // string or nested shortcode - params interface{} // map or array - err error - doMarkup bool -} - -func (sc shortcode) String() string { - // for testing (mostly), so any change here will break tests! - var params interface{} - switch v := sc.params.(type) { - case map[string]string: - // sort the keys so test assertions won't fail - var keys []string - for k := range v { - keys = append(keys, k) - } - sort.Strings(keys) - var tmp = make([]string, len(keys)) - - for i, k := range keys { - tmp[i] = k + ":" + v[k] - } - params = tmp - - default: - // use it as is - params = sc.params - } - - return fmt.Sprintf("%s(%q, %t){%s}", sc.name, params, sc.doMarkup, sc.inner) -} - -// We may have special shortcode templates for AMP etc. -// Note that in the below, OutputFormat may be empty. -// We will try to look for the most specific shortcode template available. -type scKey struct { - Lang string - OutputFormat string - Suffix string - ShortcodePlaceholder string -} - -func newScKey(m media.Type, shortcodeplaceholder string) scKey { - return scKey{Suffix: m.Suffix, ShortcodePlaceholder: shortcodeplaceholder} -} - -func newScKeyFromLangAndOutputFormat(lang string, o output.Format, shortcodeplaceholder string) scKey { - return scKey{Lang: lang, Suffix: o.MediaType.Suffix, OutputFormat: o.Name, ShortcodePlaceholder: shortcodeplaceholder} -} - -func newDefaultScKey(shortcodeplaceholder string) scKey { - return newScKey(media.HTMLType, shortcodeplaceholder) -} - -type shortcodeHandler struct { - init sync.Once - - p *Page - - // This is all shortcode rendering funcs for all potential output formats. - contentShortcodes map[scKey]func() (string, error) - - // This map contains the new or changed set of shortcodes that need - // to be rendered for the current output format. - contentShortcodesDelta map[scKey]func() (string, error) - - // This maps the shorcode placeholders with the rendered content. - // We will do (potential) partial re-rendering per output format, - // so keep this for the unchanged. - renderedShortcodes map[string]string - - // Maps the shortcodeplaceholder with the actual shortcode. - shortcodes map[string]shortcode - - // All the shortcode names in this set. - nameSet map[string]bool -} - -func newShortcodeHandler(p *Page) *shortcodeHandler { - return &shortcodeHandler{ - p: p, - contentShortcodes: make(map[scKey]func() (string, error)), - shortcodes: make(map[string]shortcode), - nameSet: make(map[string]bool), - renderedShortcodes: make(map[string]string), - } -} - -// TODO(bep) make it non-global -var isInnerShortcodeCache = struct { - sync.RWMutex - m map[string]bool -}{m: make(map[string]bool)} - -// to avoid potential costly look-aheads for closing tags we look inside the template itself -// we could change the syntax to self-closing tags, but that would make users cry -// the value found is cached -func isInnerShortcode(t tpl.TemplateExecutor) (bool, error) { - isInnerShortcodeCache.RLock() - m, ok := isInnerShortcodeCache.m[t.Name()] - isInnerShortcodeCache.RUnlock() - - if ok { - return m, nil - } - - isInnerShortcodeCache.Lock() - defer isInnerShortcodeCache.Unlock() - match, _ := regexp.MatchString("{{.*?\\.Inner.*?}}", t.Tree()) - isInnerShortcodeCache.m[t.Name()] = match - - return match, nil -} - -func clearIsInnerShortcodeCache() { - isInnerShortcodeCache.Lock() - defer isInnerShortcodeCache.Unlock() - isInnerShortcodeCache.m = make(map[string]bool) -} - -func createShortcodePlaceholder(id int) string { - return fmt.Sprintf("HAHA%s-%dHBHB", shortcodePlaceholderPrefix, id) -} - -const innerNewlineRegexp = "\n" -const innerCleanupRegexp = `\A

    (.*)

    \n\z` -const innerCleanupExpand = "$1" - -func prepareShortcodeForPage(placeholder string, sc shortcode, parent *ShortcodeWithPage, p *Page) map[scKey]func() (string, error) { - - m := make(map[scKey]func() (string, error)) - lang := p.Lang() - - for _, f := range p.outputFormats { - // The most specific template will win. - key := newScKeyFromLangAndOutputFormat(lang, f, placeholder) - m[key] = func() (string, error) { - return renderShortcode(key, sc, nil, p), nil - } - } - - return m -} - -func renderShortcode( - tmplKey scKey, - sc shortcode, - parent *ShortcodeWithPage, - p *Page) string { - - tmpl := getShortcodeTemplateForTemplateKey(tmplKey, sc.name, p.s.Tmpl) - if tmpl == nil { - p.s.Log.ERROR.Printf("Unable to locate template for shortcode %q in page %q", sc.name, p.Path()) - return "" - } - - data := &ShortcodeWithPage{Params: sc.params, Page: p, Parent: parent} - if sc.params != nil { - data.IsNamedParams = reflect.TypeOf(sc.params).Kind() == reflect.Map - } - - if len(sc.inner) > 0 { - var inner string - for _, innerData := range sc.inner { - switch innerData.(type) { - case string: - inner += innerData.(string) - case shortcode: - inner += renderShortcode(tmplKey, innerData.(shortcode), data, p) - default: - p.s.Log.ERROR.Printf("Illegal state on shortcode rendering of %q in page %q. Illegal type in inner data: %s ", - sc.name, p.Path(), reflect.TypeOf(innerData)) - return "" - } - } - - if sc.doMarkup { - newInner := p.s.ContentSpec.RenderBytes(&helpers.RenderingContext{ - Content: []byte(inner), PageFmt: p.determineMarkupType(), - Cfg: p.Language(), - DocumentID: p.UniqueID(), - DocumentName: p.Path(), - Config: p.getRenderingConfig()}) - - // If the type is “unknown” or “markdown”, we assume the markdown - // generation has been performed. Given the input: `a line`, markdown - // specifies the HTML `

    a line

    \n`. When dealing with documents as a - // whole, this is OK. When dealing with an `{{ .Inner }}` block in Hugo, - // this is not so good. This code does two things: - // - // 1. Check to see if inner has a newline in it. If so, the Inner data is - // unchanged. - // 2 If inner does not have a newline, strip the wrapping

    block and - // the newline. This was previously tricked out by wrapping shortcode - // substitutions in

    HUGOSHORTCODE-1
    which prevents the - // generation, but means that you can’t use shortcodes inside of - // markdown structures itself (e.g., `[foo]({{% ref foo.md %}})`). - switch p.determineMarkupType() { - case "unknown", "markdown": - if match, _ := regexp.MatchString(innerNewlineRegexp, inner); !match { - cleaner, err := regexp.Compile(innerCleanupRegexp) - - if err == nil { - newInner = cleaner.ReplaceAll(newInner, []byte(innerCleanupExpand)) - } - } - } - - // TODO(bep) we may have plain text inner templates. - data.Inner = template.HTML(newInner) - } else { - data.Inner = template.HTML(inner) - } - - } - - return renderShortcodeWithPage(tmpl, data) -} - -// The delta represents new output format-versions of the shortcodes, -// which, combined with the ones that do not have alternative representations, -// builds a complete set ready for a full rebuild of the Page content. -// This method returns false if there are no new shortcode variants in the -// current rendering context's output format. This mean we can safely reuse -// the content from the previous output format, if any. -func (s *shortcodeHandler) updateDelta() bool { - s.init.Do(func() { - s.contentShortcodes = createShortcodeRenderers(s.shortcodes, s.p) - }) - - contentShortcodes := s.contentShortcodesForOutputFormat(s.p.s.rc.Format) - - if s.contentShortcodesDelta == nil || len(s.contentShortcodesDelta) == 0 { - s.contentShortcodesDelta = contentShortcodes - return true - } - - delta := make(map[scKey]func() (string, error)) - - for k, v := range contentShortcodes { - if _, found := s.contentShortcodesDelta[k]; !found { - delta[k] = v - } - } - - s.contentShortcodesDelta = delta - - return len(delta) > 0 -} - -func (s *shortcodeHandler) contentShortcodesForOutputFormat(f output.Format) map[scKey]func() (string, error) { - contentShortcodesForOuputFormat := make(map[scKey]func() (string, error)) - lang := s.p.Lang() - - for shortcodePlaceholder := range s.shortcodes { - - key := newScKeyFromLangAndOutputFormat(lang, f, shortcodePlaceholder) - renderFn, found := s.contentShortcodes[key] - - if !found { - key.OutputFormat = "" - renderFn, found = s.contentShortcodes[key] - } - - // Fall back to HTML - if !found && key.Suffix != "html" { - key.Suffix = "html" - renderFn, found = s.contentShortcodes[key] - } - - if !found { - panic(fmt.Sprintf("Shortcode %q could not be found", shortcodePlaceholder)) - } - contentShortcodesForOuputFormat[newScKeyFromLangAndOutputFormat(lang, f, shortcodePlaceholder)] = renderFn - } - - return contentShortcodesForOuputFormat -} - -func (s *shortcodeHandler) executeShortcodesForDelta(p *Page) error { - - for k, render := range s.contentShortcodesDelta { - renderedShortcode, err := render() - if err != nil { - return fmt.Errorf("Failed to execute shortcode in page %q: %s", p.Path(), err) - } - - s.renderedShortcodes[k.ShortcodePlaceholder] = renderedShortcode - } - - return nil - -} - -func createShortcodeRenderers(shortcodes map[string]shortcode, p *Page) map[scKey]func() (string, error) { - - shortcodeRenderers := make(map[scKey]func() (string, error)) - - for k, v := range shortcodes { - prepared := prepareShortcodeForPage(k, v, nil, p) - for kk, vv := range prepared { - shortcodeRenderers[kk] = vv - } - } - - return shortcodeRenderers -} - -var errShortCodeIllegalState = errors.New("Illegal shortcode state") - -// pageTokens state: -// - before: positioned just before the shortcode start -// - after: shortcode(s) consumed (plural when they are nested) -func (s *shortcodeHandler) extractShortcode(pt *pageTokens, p *Page) (shortcode, error) { - sc := shortcode{} - var isInner = false - - var currItem item - var cnt = 0 - -Loop: - for { - currItem = pt.next() - - switch currItem.typ { - case tLeftDelimScWithMarkup, tLeftDelimScNoMarkup: - next := pt.peek() - if next.typ == tScClose { - continue - } - - if cnt > 0 { - // nested shortcode; append it to inner content - pt.backup3(currItem, next) - nested, err := s.extractShortcode(pt, p) - if nested.name != "" { - s.nameSet[nested.name] = true - } - if err == nil { - sc.inner = append(sc.inner, nested) - } else { - return sc, err - } - - } else { - sc.doMarkup = currItem.typ == tLeftDelimScWithMarkup - } - - cnt++ - - case tRightDelimScWithMarkup, tRightDelimScNoMarkup: - // we trust the template on this: - // if there's no inner, we're done - if !isInner { - return sc, nil - } - - case tScClose: - next := pt.peek() - if !isInner { - if next.typ == tError { - // return that error, more specific - continue - } - return sc, fmt.Errorf("Shortcode '%s' in page '%s' has no .Inner, yet a closing tag was provided", next.val, p.FullFilePath()) - } - if next.typ == tRightDelimScWithMarkup || next.typ == tRightDelimScNoMarkup { - // self-closing - pt.consume(1) - } else { - pt.consume(2) - } - - return sc, nil - case tText: - sc.inner = append(sc.inner, currItem.val) - case tScName: - sc.name = currItem.val - // We pick the first template for an arbitrary output format - // if more than one. It is "all inner or no inner". - tmpl := getShortcodeTemplateForTemplateKey(scKey{}, sc.name, p.s.Tmpl) - if tmpl == nil { - return sc, fmt.Errorf("Unable to locate template for shortcode %q in page %q", sc.name, p.Path()) - } - - var err error - isInner, err = isInnerShortcode(tmpl) - if err != nil { - return sc, fmt.Errorf("Failed to handle template for shortcode %q for page %q: %s", sc.name, p.Path(), err) - } - - case tScParam: - if !pt.isValueNext() { - continue - } else if pt.peek().typ == tScParamVal { - // named params - if sc.params == nil { - params := make(map[string]string) - params[currItem.val] = pt.next().val - sc.params = params - } else { - if params, ok := sc.params.(map[string]string); ok { - params[currItem.val] = pt.next().val - } else { - return sc, errShortCodeIllegalState - } - - } - } else { - // positional params - if sc.params == nil { - var params []string - params = append(params, currItem.val) - sc.params = params - } else { - if params, ok := sc.params.([]string); ok { - params = append(params, currItem.val) - sc.params = params - } else { - return sc, errShortCodeIllegalState - } - - } - } - - case tError, tEOF: - // handled by caller - pt.backup() - break Loop - - } - } - return sc, nil -} - -func (s *shortcodeHandler) extractShortcodes(stringToParse string, p *Page) (string, error) { - - startIdx := strings.Index(stringToParse, "{{") - - // short cut for docs with no shortcodes - if startIdx < 0 { - return stringToParse, nil - } - - // the parser takes a string; - // since this is an internal API, it could make sense to use the mutable []byte all the way, but - // it seems that the time isn't really spent in the byte copy operations, and the impl. gets a lot cleaner - pt := &pageTokens{lexer: newShortcodeLexer("parse-page", stringToParse, pos(startIdx))} - - id := 1 // incremented id, will be appended onto temp. shortcode placeholders - - result := bp.GetBuffer() - defer bp.PutBuffer(result) - //var result bytes.Buffer - - // the parser is guaranteed to return items in proper order or fail, so … - // … it's safe to keep some "global" state - var currItem item - var currShortcode shortcode - -Loop: - for { - currItem = pt.next() - - switch currItem.typ { - case tText: - result.WriteString(currItem.val) - case tLeftDelimScWithMarkup, tLeftDelimScNoMarkup: - // let extractShortcode handle left delim (will do so recursively) - pt.backup() - - currShortcode, err := s.extractShortcode(pt, p) - - if currShortcode.name != "" { - s.nameSet[currShortcode.name] = true - } - - if err != nil { - return result.String(), err - } - - if currShortcode.params == nil { - currShortcode.params = make([]string, 0) - } - - placeHolder := createShortcodePlaceholder(id) - result.WriteString(placeHolder) - s.shortcodes[placeHolder] = currShortcode - id++ - case tEOF: - break Loop - case tError: - err := fmt.Errorf("%s:%d: %s", - p.FullFilePath(), (p.lineNumRawContentStart() + pt.lexer.lineNum() - 1), currItem) - currShortcode.err = err - return result.String(), err - } - } - - return result.String(), nil - -} - -// Replace prefixed shortcode tokens (HUGOSHORTCODE-1, HUGOSHORTCODE-2) with the real content. -// Note: This function will rewrite the input slice. -func replaceShortcodeTokens(source []byte, prefix string, replacements map[string]string) ([]byte, error) { - - if len(replacements) == 0 { - return source, nil - } - - sourceLen := len(source) - start := 0 - - pre := []byte("HAHA" + prefix) - post := []byte("HBHB") - pStart := []byte("

    ") - pEnd := []byte("

    ") - - k := bytes.Index(source[start:], pre) - - for k != -1 { - j := start + k - postIdx := bytes.Index(source[j:], post) - if postIdx < 0 { - // this should never happen, but let the caller decide to panic or not - return nil, errors.New("illegal state in content; shortcode token missing end delim") - } - - end := j + postIdx + 4 - - newVal := []byte(replacements[string(source[j:end])]) - - // Issue #1148: Check for wrapping p-tags

    - if j >= 3 && bytes.Equal(source[j-3:j], pStart) { - if (k+4) < sourceLen && bytes.Equal(source[end:end+4], pEnd) { - j -= 3 - end += 4 - } - } - - // This and other cool slice tricks: https://github.com/golang/go/wiki/SliceTricks - source = append(source[:j], append(newVal, source[end:]...)...) - start = j - k = bytes.Index(source[start:], pre) - - } - - return source, nil -} - -func getShortcodeTemplateForTemplateKey(key scKey, shortcodeName string, t tpl.TemplateFinder) *tpl.TemplateAdapter { - isInnerShortcodeCache.RLock() - defer isInnerShortcodeCache.RUnlock() - - var names []string - - suffix := strings.ToLower(key.Suffix) - outFormat := strings.ToLower(key.OutputFormat) - lang := strings.ToLower(key.Lang) - - if outFormat != "" && suffix != "" { - if lang != "" { - names = append(names, fmt.Sprintf("%s.%s.%s.%s", shortcodeName, lang, outFormat, suffix)) - } - names = append(names, fmt.Sprintf("%s.%s.%s", shortcodeName, outFormat, suffix)) - } - - if suffix != "" { - if lang != "" { - names = append(names, fmt.Sprintf("%s.%s.%s", shortcodeName, lang, suffix)) - } - names = append(names, fmt.Sprintf("%s.%s", shortcodeName, suffix)) - } - - names = append(names, shortcodeName) - - for _, name := range names { - - if x := t.Lookup("shortcodes/" + name); x != nil { - return x - } - if x := t.Lookup("theme/shortcodes/" + name); x != nil { - return x - } - if x := t.Lookup("_internal/shortcodes/" + name); x != nil { - return x - } - } - return nil -} - -func renderShortcodeWithPage(tmpl tpl.Template, data *ShortcodeWithPage) string { - buffer := bp.GetBuffer() - defer bp.PutBuffer(buffer) - - isInnerShortcodeCache.RLock() - err := tmpl.Execute(buffer, data) - isInnerShortcodeCache.RUnlock() - if err != nil { - data.Page.s.Log.ERROR.Printf("error processing shortcode %q for page %q: %s", tmpl.Name(), data.Page.Path(), err) - } - return buffer.String() -} diff --git a/hugolib/shortcode_test.go b/hugolib/shortcode_test.go deleted file mode 100644 index 485ae4b69..000000000 --- a/hugolib/shortcode_test.go +++ /dev/null @@ -1,845 +0,0 @@ -// Copyright 2016 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package hugolib - -import ( - "fmt" - "path/filepath" - "reflect" - "regexp" - "sort" - "strings" - "testing" - - jww "github.com/spf13/jwalterweatherman" - - "github.com/spf13/afero" - - "github.com/gohugoio/hugo/output" - - "github.com/gohugoio/hugo/media" - - "github.com/gohugoio/hugo/deps" - "github.com/gohugoio/hugo/helpers" - "github.com/gohugoio/hugo/source" - "github.com/gohugoio/hugo/tpl" - "github.com/stretchr/testify/require" -) - -// TODO(bep) remove -func pageFromString(in, filename string, withTemplate ...func(templ tpl.TemplateHandler) error) (*Page, error) { - s := newTestSite(nil) - if len(withTemplate) > 0 { - // Have to create a new site - var err error - cfg, fs := newTestCfg() - - d := deps.DepsCfg{Language: helpers.NewLanguage("en", cfg), Cfg: cfg, Fs: fs, WithTemplate: withTemplate[0]} - - s, err = NewSiteForCfg(d) - if err != nil { - return nil, err - } - } - return s.NewPageFrom(strings.NewReader(in), filename) -} - -func CheckShortCodeMatch(t *testing.T, input, expected string, withTemplate func(templ tpl.TemplateHandler) error) { - CheckShortCodeMatchAndError(t, input, expected, withTemplate, false) -} - -func CheckShortCodeMatchAndError(t *testing.T, input, expected string, withTemplate func(templ tpl.TemplateHandler) error, expectError bool) { - - cfg, fs := newTestCfg() - - // Need some front matter, see https://github.com/gohugoio/hugo/issues/2337 - contentFile := `--- -title: "Title" ---- -` + input - - writeSource(t, fs, "content/simple.md", contentFile) - - h, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg, WithTemplate: withTemplate}) - - require.NoError(t, err) - require.Len(t, h.Sites, 1) - - err = h.Build(BuildCfg{}) - - if err != nil && !expectError { - t.Fatalf("Shortcode rendered error %s.", err) - } - - if err == nil && expectError { - t.Fatalf("No error from shortcode") - } - - require.Len(t, h.Sites[0].RegularPages, 1) - - output := strings.TrimSpace(string(h.Sites[0].RegularPages[0].Content)) - output = strings.TrimPrefix(output, "

    ") - output = strings.TrimSuffix(output, "

    ") - - expected = strings.TrimSpace(expected) - - if output != expected { - t.Fatalf("Shortcode render didn't match. got \n%q but expected \n%q", output, expected) - } -} - -func TestNonSC(t *testing.T) { - t.Parallel() - // notice the syntax diff from 0.12, now comment delims must be added - CheckShortCodeMatch(t, "{{%/* movie 47238zzb */%}}", "{{% movie 47238zzb %}}", nil) -} - -// Issue #929 -func TestHyphenatedSC(t *testing.T) { - t.Parallel() - wt := func(tem tpl.TemplateHandler) error { - - tem.AddTemplate("_internal/shortcodes/hyphenated-video.html", `Playing Video {{ .Get 0 }}`) - return nil - } - - CheckShortCodeMatch(t, "{{< hyphenated-video 47238zzb >}}", "Playing Video 47238zzb", wt) -} - -// Issue #1753 -func TestNoTrailingNewline(t *testing.T) { - t.Parallel() - wt := func(tem tpl.TemplateHandler) error { - tem.AddTemplate("_internal/shortcodes/a.html", `{{ .Get 0 }}`) - return nil - } - - CheckShortCodeMatch(t, "ab{{< a c >}}d", "abcd", wt) -} - -func TestPositionalParamSC(t *testing.T) { - t.Parallel() - wt := func(tem tpl.TemplateHandler) error { - tem.AddTemplate("_internal/shortcodes/video.html", `Playing Video {{ .Get 0 }}`) - return nil - } - - CheckShortCodeMatch(t, "{{< video 47238zzb >}}", "Playing Video 47238zzb", wt) - CheckShortCodeMatch(t, "{{< video 47238zzb 132 >}}", "Playing Video 47238zzb", wt) - CheckShortCodeMatch(t, "{{
    + + {{ range $fields }} + + {{ end }} + + {{ range $list }} + + {{ range $k, $v := . }} + {{ $.Scratch.Set $k $v }} + {{ end }} + {{ range $fields }} + + {{ end }} + + {{ end }} +
    {{ . }}
    {{ $.Scratch.Get . }}
    diff --git a/layouts/shortcodes/directoryindex.html b/layouts/shortcodes/directoryindex.html new file mode 100644 index 000000000..37e7d3ad1 --- /dev/null +++ b/layouts/shortcodes/directoryindex.html @@ -0,0 +1,13 @@ +{{- $pathURL := .Get "pathURL" -}} +{{- $path := .Get "path" -}} +{{- $files := readDir $path -}} + + + +{{- range $files }} + + + + +{{- end }} +
    Size in bytesName
    {{ .Size }} {{ .Name }}
    diff --git a/layouts/shortcodes/docfile.html b/layouts/shortcodes/docfile.html new file mode 100644 index 000000000..2f982aae8 --- /dev/null +++ b/layouts/shortcodes/docfile.html @@ -0,0 +1,11 @@ +{{ $file := .Get 0}} +{{ $filepath := $file }} +{{ $syntax := index (split $file ".") 1 }} +{{ $syntaxoverride := eq (len .Params) 2 }} +
    +
    {{$filepath}}
    + +
    {{- readFile $file -}}
    +
    diff --git a/layouts/shortcodes/exfile.html b/layouts/shortcodes/exfile.html new file mode 100644 index 000000000..226782957 --- /dev/null +++ b/layouts/shortcodes/exfile.html @@ -0,0 +1,12 @@ +{{ $file := .Get 0}} +{{ $filepath := replace $file "static/" ""}} +{{ $syntax := index (split $file ".") 1 }} +{{ $syntaxoverride := eq (len .Params) 2 }} +
    +
    {{$filepath}}
    + +
    {{- readFile $file -}}
    + Source +
    diff --git a/layouts/shortcodes/exfm.html b/layouts/shortcodes/exfm.html new file mode 100644 index 000000000..c0429bbe1 --- /dev/null +++ b/layouts/shortcodes/exfm.html @@ -0,0 +1,13 @@ + +{{ $file := .Get 0}} +{{ $filepath := replace $file "static/" ""}} +{{ $syntax := index (split $file ".") 1 }} +{{ $syntaxoverride := eq (len .Params) 2 }} +
    +
    {{$filepath}}
    + +
    {{- readFile $file -}}
    + Source +
    \ No newline at end of file diff --git a/layouts/shortcodes/gh.html b/layouts/shortcodes/gh.html new file mode 100644 index 000000000..0d1a9498e --- /dev/null +++ b/layouts/shortcodes/gh.html @@ -0,0 +1,9 @@ +{{ range .Params }} + {{ if eq (substr . 0 1) "@" }} + {{ . }} + {{ else if eq (substr . 0 2) "0x" }} + {{ substr . 2 6 }} + {{ else }} + #{{ . }} + {{ end }} +{{ end }} \ No newline at end of file diff --git a/layouts/shortcodes/ghrepo.html b/layouts/shortcodes/ghrepo.html new file mode 100644 index 000000000..e9df40d6a --- /dev/null +++ b/layouts/shortcodes/ghrepo.html @@ -0,0 +1 @@ +GitHub repository \ No newline at end of file diff --git a/layouts/shortcodes/nohighlight.html b/layouts/shortcodes/nohighlight.html new file mode 100644 index 000000000..238234f17 --- /dev/null +++ b/layouts/shortcodes/nohighlight.html @@ -0,0 +1 @@ +
    {{ .Inner }}
    \ No newline at end of file diff --git a/layouts/shortcodes/note.html b/layouts/shortcodes/note.html new file mode 100644 index 000000000..fcf081bd5 --- /dev/null +++ b/layouts/shortcodes/note.html @@ -0,0 +1,8 @@ + diff --git a/layouts/shortcodes/output.html b/layouts/shortcodes/output.html new file mode 100644 index 000000000..e51d284bb --- /dev/null +++ b/layouts/shortcodes/output.html @@ -0,0 +1,8 @@ +{{$file := .Get "file"}} +{{$icon := index (split $file ".") 1 }} +
    +
    {{$file}}
    +
    +
    {{- .Inner | string -}}
    +
    +
    \ No newline at end of file diff --git a/layouts/shortcodes/readfile.html b/layouts/shortcodes/readfile.html new file mode 100644 index 000000000..f777abe26 --- /dev/null +++ b/layouts/shortcodes/readfile.html @@ -0,0 +1,6 @@ +{{$file := .Get "file"}} +{{- if eq (.Get "markdown") "true" -}} +{{- $file | readFile | markdownify -}} +{{- else -}} +{{ $file | readFile | safeHTML }} +{{- end -}} \ No newline at end of file diff --git a/layouts/shortcodes/tip.html b/layouts/shortcodes/tip.html new file mode 100644 index 000000000..0b56ac560 --- /dev/null +++ b/layouts/shortcodes/tip.html @@ -0,0 +1,8 @@ + diff --git a/layouts/shortcodes/todo.html b/layouts/shortcodes/todo.html new file mode 100644 index 000000000..50a099267 --- /dev/null +++ b/layouts/shortcodes/todo.html @@ -0,0 +1 @@ +{{ if .Inner }}{{ end }} \ No newline at end of file diff --git a/layouts/shortcodes/warning.html b/layouts/shortcodes/warning.html new file mode 100644 index 000000000..d05057e59 --- /dev/null +++ b/layouts/shortcodes/warning.html @@ -0,0 +1,8 @@ + diff --git a/layouts/shortcodes/yt.html b/layouts/shortcodes/yt.html new file mode 100644 index 000000000..6915cec5f --- /dev/null +++ b/layouts/shortcodes/yt.html @@ -0,0 +1,11 @@ +
    + + {{if (.Get "thumbnail")}} +
    + {{else}} +
    + {{end}} +
    +{{ if (.Get "description") }} +
    {{ .Get "description" | markdownify }}
    +{{ end }} \ No newline at end of file diff --git a/livereload/connection.go b/livereload/connection.go deleted file mode 100644 index 4e94e2ee0..000000000 --- a/livereload/connection.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package livereload - -import ( - "bytes" - "sync" - - "github.com/gorilla/websocket" -) - -type connection struct { - // The websocket connection. - ws *websocket.Conn - - // Buffered channel of outbound messages. - send chan []byte - - // There is a potential data race, especially visible with large files. - // This is protected by synchronisation of the send channel's close. - closer sync.Once -} - -func (c *connection) close() { - c.closer.Do(func() { - close(c.send) - }) -} - -func (c *connection) reader() { - for { - _, message, err := c.ws.ReadMessage() - if err != nil { - break - } - if bytes.Contains(message, []byte(`"command":"hello"`)) { - c.send <- []byte(`{ - "command": "hello", - "protocols": [ "http://livereload.com/protocols/official-7" ], - "serverName": "Hugo" - }`) - } - } - c.ws.Close() -} - -func (c *connection) writer() { - for message := range c.send { - err := c.ws.WriteMessage(websocket.TextMessage, message) - if err != nil { - break - } - } - c.ws.Close() -} diff --git a/livereload/hub.go b/livereload/hub.go deleted file mode 100644 index 8ab6083ad..000000000 --- a/livereload/hub.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package livereload - -type hub struct { - // Registered connections. - connections map[*connection]bool - - // Inbound messages from the connections. - broadcast chan []byte - - // Register requests from the connections. - register chan *connection - - // Unregister requests from connections. - unregister chan *connection -} - -var wsHub = hub{ - broadcast: make(chan []byte), - register: make(chan *connection), - unregister: make(chan *connection), - connections: make(map[*connection]bool), -} - -func (h *hub) run() { - for { - select { - case c := <-h.register: - h.connections[c] = true - case c := <-h.unregister: - delete(h.connections, c) - c.close() - case m := <-h.broadcast: - for c := range h.connections { - select { - case c.send <- m: - default: - delete(h.connections, c) - c.close() - } - } - } - } -} diff --git a/livereload/livereload.go b/livereload/livereload.go deleted file mode 100644 index a5da891fc..000000000 --- a/livereload/livereload.go +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Contains an embedded version of livereload.js -// -// Copyright (c) 2010-2015 Andrey Tarantsov -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -package livereload - -import ( - "fmt" - "net/http" - "path/filepath" - - "github.com/gorilla/websocket" -) - -// Prefix to signal to LiveReload that we need to navigate to another path. -const hugoNavigatePrefix = "__hugo_navigate" - -var upgrader = &websocket.Upgrader{ReadBufferSize: 1024, WriteBufferSize: 1024} - -// Handler is a HandlerFunc handling the livereload -// Websocket interaction. -func Handler(w http.ResponseWriter, r *http.Request) { - ws, err := upgrader.Upgrade(w, r, nil) - if err != nil { - return - } - c := &connection{send: make(chan []byte, 256), ws: ws} - wsHub.register <- c - defer func() { wsHub.unregister <- c }() - go c.writer() - c.reader() -} - -// Initialize starts the Websocket Hub handling live reloads. -func Initialize() { - go wsHub.run() -} - -// ForceRefresh tells livereload to force a hard refresh. -func ForceRefresh() { - RefreshPath("/x.js") -} - -// NavigateToPath tells livereload to navigate to the given path. -// This translates to `window.location.href = path` in the client. -func NavigateToPath(path string) { - RefreshPath(hugoNavigatePrefix + path) -} - -// RefreshPath tells livereload to refresh only the given path. -// If that path points to a CSS stylesheet or an image, only the changes -// will be updated in the browser, not the entire page. -func RefreshPath(s string) { - // Tell livereload a file has changed - will force a hard refresh if not CSS or an image - urlPath := filepath.ToSlash(s) - wsHub.broadcast <- []byte(`{"command":"reload","path":"` + urlPath + `","originalPath":"","liveCSS":true,"liveImg":true}`) -} - -// ServeJS serves the liverreload.js who's reference is injected into the page. -func ServeJS(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/javascript") - w.Write(liveReloadJS()) -} - -func liveReloadJS() []byte { - return []byte(livereloadJS + hugoLiveReloadPlugin) -} - -var ( - // This is temporary patched with this PR (enables sensible error messages): - // https://github.com/livereload/livereload-js/pull/64 - // TODO(bep) replace with distribution once merged. - livereloadJS = `(function e(t,n,o){function i(s,l){if(!n[s]){if(!t[s]){var c=typeof require=="function"&&require;if(!l&&c)return c(s,!0);if(r)return r(s,!0);var a=new Error("Cannot find module '"+s+"'");throw a.code="MODULE_NOT_FOUND",a}var h=n[s]={exports:{}};t[s][0].call(h.exports,function(e){var n=t[s][1][e];return i(n?n:e)},h,h.exports,e,t,n,o)}return n[s].exports}var r=typeof require=="function"&&require;for(var s=0;s tag");return}}this.reloader=new s(this.window,this.console,l);this.connector=new t(this.options,this.WebSocket,l,{connecting:function(e){return function(){}}(this),socketConnected:function(e){return function(){}}(this),connected:function(e){return function(t){var n;if(typeof(n=e.listeners).connect==="function"){n.connect()}e.log("LiveReload is connected to "+e.options.host+":"+e.options.port+" (protocol v"+t+").");return e.analyze()}}(this),error:function(e){return function(e){if(e instanceof r){if(typeof console!=="undefined"&&console!==null){return console.log(""+e.message+".")}}else{if(typeof console!=="undefined"&&console!==null){return console.log("LiveReload internal error: "+e.message)}}}}(this),disconnected:function(e){return function(t,n){var o;if(typeof(o=e.listeners).disconnect==="function"){o.disconnect()}switch(t){case"cannot-connect":return e.log("LiveReload cannot connect to "+e.options.host+":"+e.options.port+", will retry in "+n+" sec.");case"broken":return e.log("LiveReload disconnected from "+e.options.host+":"+e.options.port+", reconnecting in "+n+" sec.");case"handshake-timeout":return e.log("LiveReload cannot connect to "+e.options.host+":"+e.options.port+" (handshake timeout), will retry in "+n+" sec.");case"handshake-failed":return e.log("LiveReload cannot connect to "+e.options.host+":"+e.options.port+" (handshake failed), will retry in "+n+" sec.");case"manual":break;case"error":break;default:return e.log("LiveReload disconnected from "+e.options.host+":"+e.options.port+" ("+t+"), reconnecting in "+n+" sec.")}}}(this),message:function(e){return function(t){switch(t.command){case"reload":return e.performReload(t);case"alert":return e.performAlert(t)}}}(this)});this.initialized=true}e.prototype.on=function(e,t){return this.listeners[e]=t};e.prototype.log=function(e){return this.console.log(""+e)};e.prototype.performReload=function(e){var t,n;this.log("LiveReload received reload request: "+JSON.stringify(e,null,2));return this.reloader.reload(e.path,{liveCSS:(t=e.liveCSS)!=null?t:true,liveImg:(n=e.liveImg)!=null?n:true,originalPath:e.originalPath||"",overrideURL:e.overrideURL||"",serverURL:"http://"+this.options.host+":"+this.options.port})};e.prototype.performAlert=function(e){return alert(e.message)};e.prototype.shutDown=function(){var e;if(!this.initialized){return}this.connector.disconnect();this.log("LiveReload disconnected.");return typeof(e=this.listeners).shutdown==="function"?e.shutdown():void 0};e.prototype.hasPlugin=function(e){return!!this.pluginIdentifiers[e]};e.prototype.addPlugin=function(e){var t;if(!this.initialized){return}if(this.hasPlugin(e.identifier)){return}this.pluginIdentifiers[e.identifier]=true;t=new e(this.window,{_livereload:this,_reloader:this.reloader,_connector:this.connector,console:this.console,Timer:l,generateCacheBustUrl:function(e){return function(t){return e.reloader.generateCacheBustUrl(t)}}(this)});this.plugins.push(t);this.reloader.addPlugin(t)};e.prototype.analyze=function(){var e,t,n,o,i,r;if(!this.initialized){return}if(!(this.connector.protocol>=7)){return}n={};r=this.plugins;for(o=0,i=r.length;o1){s.set(o[0].replace(/-/g,"_"),o.slice(1).join("="))}}}return s}}return null}}).call(this)},{}],6:[function(e,t,n){(function(){var e,t,o,i,r=[].indexOf||function(e){for(var t=0,n=this.length;t=0){this.protocol=7}else if(r.call(l.protocols,e)>=0){this.protocol=6}else{throw new i("no supported protocols found")}}return this.handlers.connected(this.protocol)}else if(this.protocol===6){l=JSON.parse(n);if(!l.length){throw new i("protocol 6 messages must be arrays")}o=l[0],c=l[1];if(o!=="refresh"){throw new i("unknown protocol 6 command")}return this.handlers.message({command:"reload",path:c.path,liveCSS:(a=c.apply_css_live)!=null?a:true})}else{l=this._parseMessage(n,["reload","alert"]);return this.handlers.message(l)}}catch(e){s=e;if(s instanceof i){return this.handlers.error(s)}else{throw s}}};n.prototype._parseMessage=function(e,t){var n,o,s;try{o=JSON.parse(e)}catch(t){n=t;throw new i("unparsable JSON",e)}if(!o.command){throw new i('missing "command" key',e)}if(s=o.command,r.call(t,s)<0){throw new i("invalid command '"+o.command+"', only valid commands are: "+t.join(", ")+")",e)}return o};return n}()}).call(this)},{}],7:[function(e,t,n){(function(){var e,t,o,i,r,s,l;l=function(e){var t,n,o;if((n=e.indexOf("#"))>=0){t=e.slice(n);e=e.slice(0,n)}else{t=""}if((n=e.indexOf("?"))>=0){o=e.slice(n);e=e.slice(0,n)}else{o=""}return{url:e,params:o,hash:t}};i=function(e){var t;e=l(e).url;if(e.indexOf("file://")===0){t=e.replace(/^file:\/\/(localhost)?/,"")}else{t=e.replace(/^([^:]+:)?\/\/([^:\/]+)(:\d*)?\//,"/")}return decodeURIComponent(t)};s=function(e,t,n){var i,r,s,l,c;i={score:0};for(l=0,c=t.length;li.score){i={object:r,score:s}}}if(i.score>0){return i}else{return null}};o=function(e,t){var n,o,i,r;e=e.replace(/^\/+/,"").toLowerCase();t=t.replace(/^\/+/,"").toLowerCase();if(e===t){return 1e4}n=e.split("/").reverse();o=t.split("/").reverse();r=Math.min(n.length,o.length);i=0;while(i0};e=[{selector:"background",styleNames:["backgroundImage"]},{selector:"border",styleNames:["borderImage","webkitBorderImage","MozBorderImage"]}];n.Reloader=t=function(){function t(e,t,n){this.window=e;this.console=t;this.Timer=n;this.document=this.window.document;this.importCacheWaitPeriod=200;this.plugins=[]}t.prototype.addPlugin=function(e){return this.plugins.push(e)};t.prototype.analyze=function(e){return results};t.prototype.reload=function(e,t){var n,o,i,r,s;this.options=t;if((o=this.options).stylesheetReloadTimeout==null){o.stylesheetReloadTimeout=15e3}s=this.plugins;for(i=0,r=s.length;i 0 { - os.Exit(-1) - } - - if commands.Hugo != nil { - if commands.Hugo.Log.LogCountForLevelsGreaterThanorEqualTo(jww.LevelError) > 0 { - os.Exit(-1) - } - } -} diff --git a/media/docshelper.go b/media/docshelper.go deleted file mode 100644 index f5afb52f0..000000000 --- a/media/docshelper.go +++ /dev/null @@ -1,17 +0,0 @@ -package media - -import ( - "github.com/gohugoio/hugo/docshelper" -) - -// This is is just some helpers used to create some JSON used in the Hugo docs. -func init() { - docsProvider := func() map[string]interface{} { - docs := make(map[string]interface{}) - - docs["types"] = DefaultTypes - return docs - } - - docshelper.AddDocProvider("media", docsProvider) -} diff --git a/media/mediaType.go b/media/mediaType.go deleted file mode 100644 index bfeeeaa9f..000000000 --- a/media/mediaType.go +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2017-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package media - -import ( - "encoding/json" - "fmt" - "sort" - "strings" - - "github.com/mitchellh/mapstructure" -) - -const ( - defaultDelimiter = "." -) - -// Type (also known as MIME type and content type) is a two-part identifier for -// file formats and format contents transmitted on the Internet. -// For Hugo's use case, we use the top-level type name / subtype name + suffix. -// One example would be image/jpeg+jpg -// If suffix is not provided, the sub type will be used. -// See // https://en.wikipedia.org/wiki/Media_type -type Type struct { - MainType string `json:"mainType"` // i.e. text - SubType string `json:"subType"` // i.e. html - Suffix string `json:"suffix"` // i.e html - Delimiter string `json:"delimiter"` // defaults to "." -} - -// FromString creates a new Type given a type sring on the form MainType/SubType and -// an optional suffix, e.g. "text/html" or "text/html+html". -func FromString(t string) (Type, error) { - t = strings.ToLower(t) - parts := strings.Split(t, "/") - if len(parts) != 2 { - return Type{}, fmt.Errorf("cannot parse %q as a media type", t) - } - mainType := parts[0] - subParts := strings.Split(parts[1], "+") - - subType := subParts[0] - var suffix string - - if len(subParts) == 1 { - suffix = subType - } else { - suffix = subParts[1] - } - - return Type{MainType: mainType, SubType: subType, Suffix: suffix, Delimiter: defaultDelimiter}, nil -} - -// Type returns a string representing the main- and sub-type of a media type, i.e. "text/css". -// Hugo will register a set of default media types. -// These can be overridden by the user in the configuration, -// by defining a media type with the same Type. -func (m Type) Type() string { - return fmt.Sprintf("%s/%s", m.MainType, m.SubType) -} - -func (m Type) String() string { - if m.Suffix != "" { - return fmt.Sprintf("%s/%s+%s", m.MainType, m.SubType, m.Suffix) - } - return fmt.Sprintf("%s/%s", m.MainType, m.SubType) -} - -// FullSuffix returns the file suffix with any delimiter prepended. -func (m Type) FullSuffix() string { - return m.Delimiter + m.Suffix -} - -var ( - CalendarType = Type{"text", "calendar", "ics", defaultDelimiter} - CSSType = Type{"text", "css", "css", defaultDelimiter} - CSVType = Type{"text", "csv", "csv", defaultDelimiter} - HTMLType = Type{"text", "html", "html", defaultDelimiter} - JavascriptType = Type{"application", "javascript", "js", defaultDelimiter} - JSONType = Type{"application", "json", "json", defaultDelimiter} - RSSType = Type{"application", "rss", "xml", defaultDelimiter} - XMLType = Type{"application", "xml", "xml", defaultDelimiter} - TextType = Type{"text", "plain", "txt", defaultDelimiter} -) - -var DefaultTypes = Types{ - CalendarType, - CSSType, - CSVType, - HTMLType, - JavascriptType, - JSONType, - RSSType, - XMLType, - TextType, -} - -func init() { - sort.Sort(DefaultTypes) -} - -type Types []Type - -func (t Types) Len() int { return len(t) } -func (t Types) Swap(i, j int) { t[i], t[j] = t[j], t[i] } -func (t Types) Less(i, j int) bool { return t[i].Type() < t[j].Type() } - -func (t Types) GetByType(tp string) (Type, bool) { - for _, tt := range t { - if strings.EqualFold(tt.Type(), tp) { - return tt, true - } - } - return Type{}, false -} - -// GetBySuffix gets a media type given as suffix, e.g. "html". -// It will return false if no format could be found, or if the suffix given -// is ambiguous. -// The lookup is case insensitive. -func (t Types) GetBySuffix(suffix string) (tp Type, found bool) { - for _, tt := range t { - if strings.EqualFold(suffix, tt.Suffix) { - if found { - // ambiguous - found = false - return - } - tp = tt - found = true - } - } - return -} - -// DecodeTypes takes a list of media type configurations and merges those, -// in the order given, with the Hugo defaults as the last resort. -func DecodeTypes(maps ...map[string]interface{}) (Types, error) { - m := make(Types, len(DefaultTypes)) - copy(m, DefaultTypes) - - for _, mm := range maps { - for k, v := range mm { - // It may be tempting to put the full media type in the key, e.g. - // "text/css+css", but that will break the logic below. - if strings.Contains(k, "+") { - return Types{}, fmt.Errorf("media type keys cannot contain any '+' chars. Valid example is %q", "text/css") - } - - found := false - for i, vv := range m { - // Match by type, i.e. "text/css" - if strings.EqualFold(k, vv.Type()) { - // Merge it with the existing - if err := mapstructure.WeakDecode(v, &m[i]); err != nil { - return m, err - } - found = true - } - } - if !found { - mediaType, err := FromString(k) - if err != nil { - return m, err - } - - if err := mapstructure.WeakDecode(v, &mediaType); err != nil { - return m, err - } - - m = append(m, mediaType) - } - } - } - - sort.Sort(m) - - return m, nil -} - -func (t Type) MarshalJSON() ([]byte, error) { - type Alias Type - return json.Marshal(&struct { - Type string `json:"type"` - String string `json:"string"` - Alias - }{ - Type: t.Type(), - String: t.String(), - Alias: (Alias)(t), - }) -} diff --git a/media/mediaType_test.go b/media/mediaType_test.go deleted file mode 100644 index 0cdecdeb1..000000000 --- a/media/mediaType_test.go +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright 2017-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package media - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestDefaultTypes(t *testing.T) { - for _, test := range []struct { - tp Type - expectedMainType string - expectedSubType string - expectedSuffix string - expectedType string - expectedString string - }{ - {CalendarType, "text", "calendar", "ics", "text/calendar", "text/calendar+ics"}, - {CSSType, "text", "css", "css", "text/css", "text/css+css"}, - {CSVType, "text", "csv", "csv", "text/csv", "text/csv+csv"}, - {HTMLType, "text", "html", "html", "text/html", "text/html+html"}, - {JavascriptType, "application", "javascript", "js", "application/javascript", "application/javascript+js"}, - {JSONType, "application", "json", "json", "application/json", "application/json+json"}, - {RSSType, "application", "rss", "xml", "application/rss", "application/rss+xml"}, - {TextType, "text", "plain", "txt", "text/plain", "text/plain+txt"}, - } { - require.Equal(t, test.expectedMainType, test.tp.MainType) - require.Equal(t, test.expectedSubType, test.tp.SubType) - require.Equal(t, test.expectedSuffix, test.tp.Suffix) - require.Equal(t, defaultDelimiter, test.tp.Delimiter) - - require.Equal(t, test.expectedType, test.tp.Type()) - require.Equal(t, test.expectedString, test.tp.String()) - - } - -} - -func TestGetByType(t *testing.T) { - types := Types{HTMLType, RSSType} - - mt, found := types.GetByType("text/HTML") - require.True(t, found) - require.Equal(t, mt, HTMLType) - - _, found = types.GetByType("text/nono") - require.False(t, found) -} - -func TestFromTypeString(t *testing.T) { - f, err := FromString("text/html") - require.NoError(t, err) - require.Equal(t, HTMLType, f) - - f, err = FromString("application/custom") - require.NoError(t, err) - require.Equal(t, Type{MainType: "application", SubType: "custom", Suffix: "custom", Delimiter: defaultDelimiter}, f) - - f, err = FromString("application/custom+pdf") - require.NoError(t, err) - require.Equal(t, Type{MainType: "application", SubType: "custom", Suffix: "pdf", Delimiter: defaultDelimiter}, f) - - _, err = FromString("noslash") - require.Error(t, err) - -} - -func TestDecodeTypes(t *testing.T) { - - var tests = []struct { - name string - maps []map[string]interface{} - shouldError bool - assert func(t *testing.T, name string, tt Types) - }{ - { - "Redefine JSON", - []map[string]interface{}{ - { - "application/json": map[string]interface{}{ - "suffix": "jsn"}}}, - false, - func(t *testing.T, name string, tt Types) { - require.Len(t, tt, len(DefaultTypes)) - json, found := tt.GetBySuffix("jsn") - require.True(t, found) - require.Equal(t, "application/json+jsn", json.String(), name) - }}, - { - "Add custom media type", - []map[string]interface{}{ - { - "text/hugo": map[string]interface{}{ - "suffix": "hgo"}}}, - false, - func(t *testing.T, name string, tt Types) { - require.Len(t, tt, len(DefaultTypes)+1) - // Make sure we have not broken the default config. - _, found := tt.GetBySuffix("json") - require.True(t, found) - - hugo, found := tt.GetBySuffix("hgo") - require.True(t, found) - require.Equal(t, "text/hugo+hgo", hugo.String(), name) - }}, - { - "Add media type invalid key", - []map[string]interface{}{ - { - "text/hugo+hgo": map[string]interface{}{}}}, - true, - func(t *testing.T, name string, tt Types) { - - }}, - } - - for _, test := range tests { - result, err := DecodeTypes(test.maps...) - if test.shouldError { - require.Error(t, err, test.name) - } else { - require.NoError(t, err, test.name) - test.assert(t, test.name, result) - } - } -} diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 000000000..c556acabf --- /dev/null +++ b/netlify.toml @@ -0,0 +1,18 @@ +[build] + publish = "public" + command = "hugo" + +[context.production.environment] + HUGO_VERSION = "0.26" + HUGO_ENV = "production" + HUGO_ENABLEGITINFO = "true" + +[context.deploy-preview.environment] + HUGO_VERSION = "0.26" + +[context.branch-deploy.environment] + HUGO_VERSION = "0.26" + +[context.next.environment] + HUGO_BASEURL = "https://next--gohugoio.netlify.com/" + HUGO_ENABLEGITINFO = "true" \ No newline at end of file diff --git a/output/docshelper.go b/output/docshelper.go deleted file mode 100644 index 9ed94b09b..000000000 --- a/output/docshelper.go +++ /dev/null @@ -1,88 +0,0 @@ -package output - -import ( - "strings" - - "fmt" - - "github.com/gohugoio/hugo/docshelper" -) - -// This is is just some helpers used to create some JSON used in the Hugo docs. -func init() { - docsProvider := func() map[string]interface{} { - docs := make(map[string]interface{}) - - docs["formats"] = DefaultFormats - docs["layouts"] = createLayoutExamples() - return docs - } - - docshelper.AddDocProvider("output", docsProvider) -} - -func createLayoutExamples() interface{} { - - type Example struct { - Example string - OutputFormat string - Suffix string - Layouts []string `json:"Template Lookup Order"` - } - - var ( - basicExamples []Example - demoLayout = "demolayout" - demoType = "demotype" - ) - - for _, example := range []struct { - name string - d LayoutDescriptor - hasTheme bool - layoutOverride string - f Format - }{ - {`AMP home, with theme "demoTheme".`, LayoutDescriptor{Kind: "home"}, true, "", AMPFormat}, - {`AMP home, French language".`, LayoutDescriptor{Kind: "home", Lang: "fr"}, false, "", AMPFormat}, - {"RSS home, no theme.", LayoutDescriptor{Kind: "home"}, false, "", RSSFormat}, - {"JSON home, no theme.", LayoutDescriptor{Kind: "home"}, false, "", JSONFormat}, - {fmt.Sprintf(`CSV regular, "layout: %s" in front matter.`, demoLayout), LayoutDescriptor{Kind: "page", Layout: demoLayout}, false, "", CSVFormat}, - {fmt.Sprintf(`JSON regular, "type: %s" in front matter.`, demoType), LayoutDescriptor{Kind: "page", Type: demoType}, false, "", JSONFormat}, - {"HTML regular.", LayoutDescriptor{Kind: "page"}, false, "", HTMLFormat}, - {"AMP regular.", LayoutDescriptor{Kind: "page"}, false, "", AMPFormat}, - {"Calendar blog section.", LayoutDescriptor{Kind: "section", Section: "blog"}, false, "", CalendarFormat}, - {"Calendar taxonomy list.", LayoutDescriptor{Kind: "taxonomy", Section: "tag"}, false, "", CalendarFormat}, - {"Calendar taxonomy term.", LayoutDescriptor{Kind: "taxonomyTerm", Section: "tag"}, false, "", CalendarFormat}, - } { - - l := NewLayoutHandler(example.hasTheme) - layouts, _ := l.For(example.d, example.layoutOverride, example.f) - - basicExamples = append(basicExamples, Example{ - Example: example.name, - OutputFormat: example.f.Name, - Suffix: example.f.MediaType.Suffix, - Layouts: makeLayoutsPresentable(layouts)}) - } - - return basicExamples - -} - -func makeLayoutsPresentable(l []string) []string { - var filtered []string - for _, ll := range l { - ll = strings.TrimPrefix(ll, "_text/") - if strings.Contains(ll, "theme/") { - ll = strings.Replace(ll, "theme/", "demoTheme/layouts/", -1) - } else { - ll = "layouts/" + ll - } - if !strings.Contains(ll, "indexes") { - filtered = append(filtered, ll) - } - } - - return filtered -} diff --git a/output/layout.go b/output/layout.go deleted file mode 100644 index ee3305758..000000000 --- a/output/layout.go +++ /dev/null @@ -1,299 +0,0 @@ -// Copyright 2017-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package output - -import ( - "fmt" - "path" - "strings" - "sync" -) - -// LayoutDescriptor describes how a layout should be chosen. This is -// typically built from a Page. -type LayoutDescriptor struct { - Type string - Section string - Kind string - Lang string - Layout string -} - -// LayoutHandler calculates the layout template to use to render a given output type. -type LayoutHandler struct { - hasTheme bool - - mu sync.RWMutex - cache map[layoutCacheKey][]string -} - -type layoutCacheKey struct { - d LayoutDescriptor - layoutOverride string - f Format -} - -// NewLayoutHandler creates a new LayoutHandler. -func NewLayoutHandler(hasTheme bool) *LayoutHandler { - return &LayoutHandler{hasTheme: hasTheme, cache: make(map[layoutCacheKey][]string)} -} - -// RSS: -// Home:"rss.xml", "_default/rss.xml", "_internal/_default/rss.xml" -// Section: "section/" + section + ".rss.xml", "_default/rss.xml", "rss.xml", "_internal/_default/rss.xml" -// Taxonomy "taxonomy/" + singular + ".rss.xml", "_default/rss.xml", "rss.xml", "_internal/_default/rss.xml" -// Tax term: taxonomy/" + singular + ".terms.rss.xml", "_default/rss.xml", "rss.xml", "_internal/_default/rss.xml" - -const ( - - // TODO(bep) variations reduce to 1 "." - - // The RSS templates doesn't map easily into the regular pages. - layoutsRSSHome = `VARIATIONS _default/VARIATIONS _internal/_default/rss.xml` - layoutsRSSSection = `section/SECTION.VARIATIONS _default/VARIATIONS VARIATIONS _internal/_default/rss.xml` - layoutsRSSTaxonomy = `taxonomy/SECTION.VARIATIONS _default/VARIATIONS VARIATIONS _internal/_default/rss.xml` - layoutsRSSTaxonomyTerm = `taxonomy/SECTION.terms.VARIATIONS _default/VARIATIONS VARIATIONS _internal/_default/rss.xml` - - layoutsHome = "index.VARIATIONS _default/list.VARIATIONS" - layoutsSection = ` -section/SECTION.VARIATIONS -SECTION/list.VARIATIONS -_default/section.VARIATIONS -_default/list.VARIATIONS -indexes/SECTION.VARIATIONS -_default/indexes.VARIATIONS -` - layoutsTaxonomy = ` -taxonomy/SECTION.VARIATIONS -indexes/SECTION.VARIATIONS -_default/taxonomy.VARIATIONS -_default/list.VARIATIONS -` - layoutsTaxonomyTerm = ` -taxonomy/SECTION.terms.VARIATIONS -_default/terms.VARIATIONS -indexes/indexes.VARIATIONS -` -) - -// For returns a layout for the given LayoutDescriptor and options. -// Layouts are rendered and cached internally. -func (l *LayoutHandler) For(d LayoutDescriptor, layoutOverride string, f Format) ([]string, error) { - - // We will get lots of requests for the same layouts, so avoid recalculations. - key := layoutCacheKey{d, layoutOverride, f} - l.mu.RLock() - if cacheVal, found := l.cache[key]; found { - l.mu.RUnlock() - return cacheVal, nil - } - l.mu.RUnlock() - - var layouts []string - - if layoutOverride != "" && d.Kind != "page" { - return layouts, fmt.Errorf("Custom layout (%q) only supported for regular pages, not kind %q", layoutOverride, d.Kind) - } - - layout := d.Layout - - if layoutOverride != "" { - layout = layoutOverride - } - - isRSS := f.Name == RSSFormat.Name - - if d.Kind == "page" { - if isRSS { - return []string{}, nil - } - layouts = regularPageLayouts(d.Type, layout, f) - } else { - if isRSS { - layouts = resolveListTemplate(d, f, - layoutsRSSHome, - layoutsRSSSection, - layoutsRSSTaxonomy, - layoutsRSSTaxonomyTerm) - } else { - layouts = resolveListTemplate(d, f, - layoutsHome, - layoutsSection, - layoutsTaxonomy, - layoutsTaxonomyTerm) - } - } - - if l.hasTheme { - layoutsWithThemeLayouts := []string{} - // First place all non internal templates - for _, t := range layouts { - if !strings.HasPrefix(t, "_internal/") { - layoutsWithThemeLayouts = append(layoutsWithThemeLayouts, t) - } - } - - // Then place theme templates with the same names - for _, t := range layouts { - if !strings.HasPrefix(t, "_internal/") { - layoutsWithThemeLayouts = append(layoutsWithThemeLayouts, "theme/"+t) - } - } - - // Lastly place internal templates - for _, t := range layouts { - if strings.HasPrefix(t, "_internal/") { - layoutsWithThemeLayouts = append(layoutsWithThemeLayouts, t) - } - } - - layouts = layoutsWithThemeLayouts - } - - layouts = prependTextPrefixIfNeeded(f, layouts...) - - l.mu.Lock() - l.cache[key] = layouts - l.mu.Unlock() - - return layouts, nil -} - -func resolveListTemplate(d LayoutDescriptor, f Format, - homeLayouts, - sectionLayouts, - taxonomyLayouts, - taxonomyTermLayouts string) []string { - var layouts []string - - switch d.Kind { - case "home": - layouts = resolveTemplate(homeLayouts, d, f) - case "section": - layouts = resolveTemplate(sectionLayouts, d, f) - case "taxonomy": - layouts = resolveTemplate(taxonomyLayouts, d, f) - case "taxonomyTerm": - layouts = resolveTemplate(taxonomyTermLayouts, d, f) - } - return layouts -} - -func resolveTemplate(templ string, d LayoutDescriptor, f Format) []string { - - // VARIATIONS will be replaced with - // .lang.name.suffix - // .name.suffix - // .lang.suffix - // .suffix - var replacementValues []string - - name := strings.ToLower(f.Name) - - if d.Lang != "" { - replacementValues = append(replacementValues, fmt.Sprintf("%s.%s.%s", d.Lang, name, f.MediaType.Suffix)) - } - - replacementValues = append(replacementValues, fmt.Sprintf("%s.%s", name, f.MediaType.Suffix)) - - if d.Lang != "" { - replacementValues = append(replacementValues, fmt.Sprintf("%s.%s", d.Lang, f.MediaType.Suffix)) - } - - isRSS := f.Name == RSSFormat.Name - - if !isRSS { - replacementValues = append(replacementValues, f.MediaType.Suffix) - } - - var layouts []string - - templFields := strings.Fields(templ) - - for _, field := range templFields { - for _, replacements := range replacementValues { - layouts = append(layouts, replaceKeyValues(field, "VARIATIONS", replacements, "SECTION", d.Section)) - } - } - - return filterDotLess(layouts) -} - -func filterDotLess(layouts []string) []string { - var filteredLayouts []string - - for _, l := range layouts { - l = strings.Trim(l, ".") - // If media type has no suffix, we have "index" type of layouts in this list, which - // doesn't make much sense. - if strings.Contains(l, ".") { - filteredLayouts = append(filteredLayouts, l) - } - } - - return filteredLayouts -} - -func prependTextPrefixIfNeeded(f Format, layouts ...string) []string { - if !f.IsPlainText { - return layouts - } - - newLayouts := make([]string, len(layouts)) - - for i, l := range layouts { - newLayouts[i] = "_text/" + l - } - - return newLayouts -} - -func replaceKeyValues(s string, oldNew ...string) string { - replacer := strings.NewReplacer(oldNew...) - return replacer.Replace(s) -} - -func regularPageLayouts(types string, layout string, f Format) []string { - var layouts []string - - if layout == "" { - layout = "single" - } - - delimiter := "." - if f.MediaType.Delimiter == "" { - delimiter = "" - } - - suffix := delimiter + f.MediaType.Suffix - name := strings.ToLower(f.Name) - - if types != "" { - t := strings.Split(types, "/") - - // Add type/layout.html - for i := range t { - search := t[:len(t)-i] - layouts = append(layouts, fmt.Sprintf("%s/%s.%s%s", strings.ToLower(path.Join(search...)), layout, name, suffix)) - layouts = append(layouts, fmt.Sprintf("%s/%s%s", strings.ToLower(path.Join(search...)), layout, suffix)) - - } - } - - // Add _default/layout.html - layouts = append(layouts, fmt.Sprintf("_default/%s.%s%s", layout, name, suffix)) - layouts = append(layouts, fmt.Sprintf("_default/%s%s", layout, suffix)) - - return filterDotLess(layouts) -} diff --git a/output/layout_base.go b/output/layout_base.go deleted file mode 100644 index fc34b08be..000000000 --- a/output/layout_base.go +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright 2017-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package output - -import ( - "fmt" - "path/filepath" - "strings" - - "github.com/gohugoio/hugo/helpers" -) - -const baseFileBase = "baseof" - -var ( - aceTemplateInnerMarkers = [][]byte{[]byte("= content")} - goTemplateInnerMarkers = [][]byte{[]byte("{{define"), []byte("{{ define")} -) - -type TemplateNames struct { - // The name used as key in the template map. Note that this will be - // prefixed with "_text/" if it should be parsed with text/template. - Name string - - OverlayFilename string - MasterFilename string -} - -type TemplateLookupDescriptor struct { - // TemplateDir is the project or theme root of the current template. - // This will be the same as WorkingDir for non-theme templates. - TemplateDir string - - // The full path to the site root. - WorkingDir string - - // Main project layout dir, defaults to "layouts" - LayoutDir string - - // The path to the template relative the the base. - // I.e. shortcodes/youtube.html - RelPath string - - // The template name prefix to look for, i.e. "theme". - Prefix string - - // The theme dir if theme active. - ThemeDir string - - // All the output formats in play. This is used to decide if text/template or - // html/template. - OutputFormats Formats - - FileExists func(filename string) (bool, error) - ContainsAny func(filename string, subslices [][]byte) (bool, error) -} - -func CreateTemplateNames(d TemplateLookupDescriptor) (TemplateNames, error) { - - name := filepath.ToSlash(d.RelPath) - - if d.Prefix != "" { - name = strings.Trim(d.Prefix, "/") + "/" + name - } - - var ( - id TemplateNames - - // This is the path to the actual template in process. This may - // be in the theme's or the project's /layouts. - baseLayoutDir = filepath.Join(d.TemplateDir, d.LayoutDir) - fullPath = filepath.Join(baseLayoutDir, d.RelPath) - - // This is always the project's layout dir. - baseWorkLayoutDir = filepath.Join(d.WorkingDir, d.LayoutDir) - - baseThemeLayoutDir string - ) - - if d.ThemeDir != "" { - baseThemeLayoutDir = filepath.Join(d.ThemeDir, "layouts") - } - - // The filename will have a suffix with an optional type indicator. - // Examples: - // index.html - // index.amp.html - // index.json - filename := filepath.Base(d.RelPath) - isPlainText := false - outputFormat, found := d.OutputFormats.FromFilename(filename) - - if found && outputFormat.IsPlainText { - isPlainText = true - } - - var ext, outFormat string - - parts := strings.Split(filename, ".") - if len(parts) > 2 { - outFormat = parts[1] - ext = parts[2] - } else if len(parts) > 1 { - ext = parts[1] - } - - filenameNoSuffix := parts[0] - - id.OverlayFilename = fullPath - id.Name = name - - if isPlainText { - id.Name = "_text/" + id.Name - } - - // Ace and Go templates may have both a base and inner template. - pathDir := filepath.Dir(fullPath) - - if ext == "amber" || strings.HasSuffix(pathDir, "partials") || strings.HasSuffix(pathDir, "shortcodes") { - // No base template support - return id, nil - } - - innerMarkers := goTemplateInnerMarkers - - var baseFilename string - - if outFormat != "" { - baseFilename = fmt.Sprintf("%s.%s.%s", baseFileBase, outFormat, ext) - } else { - baseFilename = fmt.Sprintf("%s.%s", baseFileBase, ext) - } - - if ext == "ace" { - innerMarkers = aceTemplateInnerMarkers - } - - // This may be a view that shouldn't have base template - // Have to look inside it to make sure - needsBase, err := d.ContainsAny(fullPath, innerMarkers) - if err != nil { - return id, err - } - - if needsBase { - currBaseFilename := fmt.Sprintf("%s-%s", filenameNoSuffix, baseFilename) - - templateDir := filepath.Dir(fullPath) - - // Find the base, e.g. "_default". - baseTemplatedDir := strings.TrimPrefix(templateDir, baseLayoutDir) - baseTemplatedDir = strings.TrimPrefix(baseTemplatedDir, helpers.FilePathSeparator) - - // Look for base template in the follwing order: - // 1. /-baseof.(optional)., e.g. list-baseof.(optional).. - // 2. /baseof.(optional). - // 3. _default/-baseof.(optional)., e.g. list-baseof.(optional).. - // 4. _default/baseof.(optional). - // For each of the steps above, it will first look in the project, then, if theme is set, - // in the theme's layouts folder. - // Also note that the may be both the project's layout folder and the theme's. - pairsToCheck := [][]string{ - {baseTemplatedDir, currBaseFilename}, - {baseTemplatedDir, baseFilename}, - {"_default", currBaseFilename}, - {"_default", baseFilename}, - } - - Loop: - for _, pair := range pairsToCheck { - pathsToCheck := basePathsToCheck(pair, baseLayoutDir, baseWorkLayoutDir, baseThemeLayoutDir) - - for _, pathToCheck := range pathsToCheck { - if ok, err := d.FileExists(pathToCheck); err == nil && ok { - id.MasterFilename = pathToCheck - break Loop - } - } - } - } - - return id, nil - -} - -func basePathsToCheck(path []string, layoutDir, workLayoutDir, themeLayoutDir string) []string { - // workLayoutDir will always be the most specific, so start there. - pathsToCheck := []string{filepath.Join((append([]string{workLayoutDir}, path...))...)} - - if layoutDir != "" && layoutDir != workLayoutDir { - pathsToCheck = append(pathsToCheck, filepath.Join((append([]string{layoutDir}, path...))...)) - } - - // May have a theme - if themeLayoutDir != "" && themeLayoutDir != layoutDir { - pathsToCheck = append(pathsToCheck, filepath.Join((append([]string{themeLayoutDir}, path...))...)) - - } - - return pathsToCheck - -} diff --git a/output/layout_base_test.go b/output/layout_base_test.go deleted file mode 100644 index b78f31352..000000000 --- a/output/layout_base_test.go +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright 2017-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package output - -import ( - "path/filepath" - "strings" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestLayoutBase(t *testing.T) { - - var ( - workingDir = "/sites/mysite/" - themeDir = "/themes/mytheme/" - layoutBase1 = "layouts" - layoutPath1 = "_default/single.html" - layoutPathAmp = "_default/single.amp.html" - layoutPathJSON = "_default/single.json" - ) - - for _, this := range []struct { - name string - d TemplateLookupDescriptor - needsBase bool - basePathMatchStrings string - expect TemplateNames - }{ - {"No base", TemplateLookupDescriptor{TemplateDir: workingDir, WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPath1}, false, "", - TemplateNames{ - Name: "_default/single.html", - OverlayFilename: "/sites/mysite/layouts/_default/single.html", - }}, - {"Base", TemplateLookupDescriptor{TemplateDir: workingDir, WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPath1}, true, "", - TemplateNames{ - Name: "_default/single.html", - OverlayFilename: "/sites/mysite/layouts/_default/single.html", - MasterFilename: "/sites/mysite/layouts/_default/single-baseof.html", - }}, - {"Base in theme", TemplateLookupDescriptor{TemplateDir: workingDir, WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPath1, ThemeDir: themeDir}, true, - "mytheme/layouts/_default/baseof.html", - TemplateNames{ - Name: "_default/single.html", - OverlayFilename: "/sites/mysite/layouts/_default/single.html", - MasterFilename: "/themes/mytheme/layouts/_default/baseof.html", - }}, - {"Template in theme, base in theme", TemplateLookupDescriptor{TemplateDir: themeDir, WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPath1, ThemeDir: themeDir}, true, - "mytheme/layouts/_default/baseof.html", - TemplateNames{ - Name: "_default/single.html", - OverlayFilename: "/themes/mytheme/layouts/_default/single.html", - MasterFilename: "/themes/mytheme/layouts/_default/baseof.html", - }}, - {"Template in theme, base in site", TemplateLookupDescriptor{TemplateDir: themeDir, WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPath1, ThemeDir: themeDir}, true, - "/sites/mysite/layouts/_default/baseof.html", - TemplateNames{ - Name: "_default/single.html", - OverlayFilename: "/themes/mytheme/layouts/_default/single.html", - MasterFilename: "/sites/mysite/layouts/_default/baseof.html", - }}, - {"Template in site, base in theme", TemplateLookupDescriptor{TemplateDir: workingDir, WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPath1, ThemeDir: themeDir}, true, - "/themes/mytheme", - TemplateNames{ - Name: "_default/single.html", - OverlayFilename: "/sites/mysite/layouts/_default/single.html", - MasterFilename: "/themes/mytheme/layouts/_default/single-baseof.html", - }}, - {"With prefix, base in theme", TemplateLookupDescriptor{TemplateDir: workingDir, WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPath1, - ThemeDir: themeDir, Prefix: "someprefix"}, true, - "mytheme/layouts/_default/baseof.html", - TemplateNames{ - Name: "someprefix/_default/single.html", - OverlayFilename: "/sites/mysite/layouts/_default/single.html", - MasterFilename: "/themes/mytheme/layouts/_default/baseof.html", - }}, - {"Partial", TemplateLookupDescriptor{TemplateDir: workingDir, WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: "partials/menu.html"}, true, - "mytheme/layouts/_default/baseof.html", - TemplateNames{ - Name: "partials/menu.html", - OverlayFilename: "/sites/mysite/layouts/partials/menu.html", - }}, - {"AMP, no base", TemplateLookupDescriptor{TemplateDir: workingDir, WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPathAmp}, false, "", - TemplateNames{ - Name: "_default/single.amp.html", - OverlayFilename: "/sites/mysite/layouts/_default/single.amp.html", - }}, - {"JSON, no base", TemplateLookupDescriptor{TemplateDir: workingDir, WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPathJSON}, false, "", - TemplateNames{ - Name: "_default/single.json", - OverlayFilename: "/sites/mysite/layouts/_default/single.json", - }}, - {"AMP with base", TemplateLookupDescriptor{TemplateDir: workingDir, WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPathAmp}, true, "single-baseof.html|single-baseof.amp.html", - TemplateNames{ - Name: "_default/single.amp.html", - OverlayFilename: "/sites/mysite/layouts/_default/single.amp.html", - MasterFilename: "/sites/mysite/layouts/_default/single-baseof.amp.html", - }}, - {"AMP with no match in base", TemplateLookupDescriptor{TemplateDir: workingDir, WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPathAmp}, true, "single-baseof.html", - TemplateNames{ - Name: "_default/single.amp.html", - OverlayFilename: "/sites/mysite/layouts/_default/single.amp.html", - // There is a single-baseof.html, but that makes no sense. - MasterFilename: "", - }}, - - {"JSON with base", TemplateLookupDescriptor{TemplateDir: workingDir, WorkingDir: workingDir, LayoutDir: layoutBase1, RelPath: layoutPathJSON}, true, "single-baseof.json", - TemplateNames{ - Name: "_default/single.json", - OverlayFilename: "/sites/mysite/layouts/_default/single.json", - MasterFilename: "/sites/mysite/layouts/_default/single-baseof.json", - }}, - } { - t.Run(this.name, func(t *testing.T) { - - this.basePathMatchStrings = filepath.FromSlash(this.basePathMatchStrings) - - fileExists := func(filename string) (bool, error) { - stringsToMatch := strings.Split(this.basePathMatchStrings, "|") - for _, s := range stringsToMatch { - if strings.Contains(filename, s) { - return true, nil - } - - } - return false, nil - } - - needsBase := func(filename string, subslices [][]byte) (bool, error) { - return this.needsBase, nil - } - - this.d.OutputFormats = Formats{AMPFormat, HTMLFormat, RSSFormat, JSONFormat} - this.d.WorkingDir = filepath.FromSlash(this.d.WorkingDir) - this.d.LayoutDir = filepath.FromSlash(this.d.LayoutDir) - this.d.RelPath = filepath.FromSlash(this.d.RelPath) - this.d.ContainsAny = needsBase - this.d.FileExists = fileExists - - this.expect.MasterFilename = filepath.FromSlash(this.expect.MasterFilename) - this.expect.OverlayFilename = filepath.FromSlash(this.expect.OverlayFilename) - - if strings.Contains(this.d.RelPath, "json") { - // currently the only plain text templates in this test. - this.expect.Name = "_text/" + this.expect.Name - } - - id, err := CreateTemplateNames(this.d) - - require.NoError(t, err) - require.Equal(t, this.expect, id, this.name) - - }) - } - -} diff --git a/output/layout_test.go b/output/layout_test.go deleted file mode 100644 index 6fb958c9d..000000000 --- a/output/layout_test.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2017-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package output - -import ( - "testing" - - "github.com/gohugoio/hugo/media" - - "github.com/stretchr/testify/require" -) - -func TestLayout(t *testing.T) { - - noExtNoDelimMediaType := media.TextType - noExtNoDelimMediaType.Suffix = "" - noExtNoDelimMediaType.Delimiter = "" - - noExtMediaType := media.TextType - noExtMediaType.Suffix = "" - - var ( - ampType = Format{ - Name: "AMP", - MediaType: media.HTMLType, - BaseName: "index", - } - - noExtDelimFormat = Format{ - Name: "NEM", - MediaType: noExtNoDelimMediaType, - BaseName: "_redirects", - } - noExt = Format{ - Name: "NEX", - MediaType: noExtMediaType, - BaseName: "next", - } - ) - - for _, this := range []struct { - name string - d LayoutDescriptor - hasTheme bool - layoutOverride string - tp Format - expect []string - }{ - {"Home", LayoutDescriptor{Kind: "home"}, true, "", ampType, - []string{"index.amp.html", "index.html", "_default/list.amp.html", "_default/list.html", "theme/index.amp.html", "theme/index.html"}}, - {"Home, french language", LayoutDescriptor{Kind: "home", Lang: "fr"}, true, "", ampType, - []string{"index.fr.amp.html", "index.amp.html", "index.fr.html", "index.html", "_default/list.fr.amp.html", "_default/list.amp.html", "_default/list.fr.html", "_default/list.html", "theme/index.fr.amp.html", "theme/index.amp.html", "theme/index.fr.html"}}, - {"Home, no ext or delim", LayoutDescriptor{Kind: "home"}, true, "", noExtDelimFormat, - []string{"index.nem", "_default/list.nem"}}, - {"Home, no ext", LayoutDescriptor{Kind: "home"}, true, "", noExt, - []string{"index.nex", "_default/list.nex"}}, - {"Page, no ext or delim", LayoutDescriptor{Kind: "page"}, true, "", noExtDelimFormat, - []string{"_default/single.nem", "theme/_default/single.nem"}}, - {"Section", LayoutDescriptor{Kind: "section", Section: "sect1"}, false, "", ampType, - []string{"section/sect1.amp.html", "section/sect1.html"}}, - {"Taxonomy", LayoutDescriptor{Kind: "taxonomy", Section: "tag"}, false, "", ampType, - []string{"taxonomy/tag.amp.html", "taxonomy/tag.html"}}, - {"Taxonomy term", LayoutDescriptor{Kind: "taxonomyTerm", Section: "categories"}, false, "", ampType, - []string{"taxonomy/categories.terms.amp.html", "taxonomy/categories.terms.html", "_default/terms.amp.html"}}, - {"Page", LayoutDescriptor{Kind: "page"}, true, "", ampType, - []string{"_default/single.amp.html", "_default/single.html", "theme/_default/single.amp.html"}}, - {"Page with layout", LayoutDescriptor{Kind: "page", Layout: "mylayout"}, false, "", ampType, - []string{"_default/mylayout.amp.html", "_default/mylayout.html"}}, - {"Page with layout and type", LayoutDescriptor{Kind: "page", Layout: "mylayout", Type: "myttype"}, false, "", ampType, - []string{"myttype/mylayout.amp.html", "myttype/mylayout.html", "_default/mylayout.amp.html"}}, - {"Page with layout and type with subtype", LayoutDescriptor{Kind: "page", Layout: "mylayout", Type: "myttype/mysubtype"}, false, "", ampType, - []string{"myttype/mysubtype/mylayout.amp.html", "myttype/mysubtype/mylayout.html", "myttype/mylayout.amp.html"}}, - {"Page with overridden layout", LayoutDescriptor{Kind: "page", Layout: "mylayout", Type: "myttype"}, false, "myotherlayout", ampType, - []string{"myttype/myotherlayout.amp.html", "myttype/myotherlayout.html"}}, - // RSS - {"RSS Home with theme", LayoutDescriptor{Kind: "home"}, true, "", RSSFormat, - []string{"rss.xml", "_default/rss.xml", "theme/rss.xml", "theme/_default/rss.xml", "_internal/_default/rss.xml"}}, - {"RSS Section", LayoutDescriptor{Kind: "section", Section: "sect1"}, false, "", RSSFormat, - []string{"section/sect1.rss.xml", "_default/rss.xml", "rss.xml", "_internal/_default/rss.xml"}}, - {"RSS Taxonomy", LayoutDescriptor{Kind: "taxonomy", Section: "tag"}, false, "", RSSFormat, - []string{"taxonomy/tag.rss.xml", "_default/rss.xml", "rss.xml", "_internal/_default/rss.xml"}}, - {"RSS Taxonomy term", LayoutDescriptor{Kind: "taxonomyTerm", Section: "tag"}, false, "", RSSFormat, - []string{"taxonomy/tag.terms.rss.xml", "_default/rss.xml", "rss.xml", "_internal/_default/rss.xml"}}, - {"Home plain text", LayoutDescriptor{Kind: "home"}, true, "", JSONFormat, - []string{"_text/index.json.json", "_text/index.json", "_text/_default/list.json.json", "_text/_default/list.json", "_text/theme/index.json.json", "_text/theme/index.json"}}, - {"Page plain text", LayoutDescriptor{Kind: "page"}, true, "", JSONFormat, - []string{"_text/_default/single.json.json", "_text/_default/single.json", "_text/theme/_default/single.json.json"}}, - } { - t.Run(this.name, func(t *testing.T) { - l := NewLayoutHandler(this.hasTheme) - - layouts, err := l.For(this.d, this.layoutOverride, this.tp) - - require.NoError(t, err) - require.NotNil(t, layouts) - require.True(t, len(layouts) >= len(this.expect)) - // Not checking the complete list for now ... - require.Equal(t, this.expect, layouts[:len(this.expect)]) - - if !this.hasTheme { - for _, layout := range layouts { - require.NotContains(t, layout, "theme") - } - } - }) - } - - l := NewLayoutHandler(false) - _, err := l.For(LayoutDescriptor{Kind: "taxonomyTerm", Section: "tag"}, "override", RSSFormat) - require.Error(t, err) - -} - -func BenchmarkLayout(b *testing.B) { - descriptor := LayoutDescriptor{Kind: "taxonomyTerm", Section: "categories"} - l := NewLayoutHandler(false) - - for i := 0; i < b.N; i++ { - layouts, err := l.For(descriptor, "", HTMLFormat) - require.NoError(b, err) - require.NotEmpty(b, layouts) - } -} diff --git a/output/outputFormat.go b/output/outputFormat.go deleted file mode 100644 index 4ccc28870..000000000 --- a/output/outputFormat.go +++ /dev/null @@ -1,328 +0,0 @@ -// Copyright 2017-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package output - -import ( - "encoding/json" - "fmt" - "sort" - "strings" - - "reflect" - - "github.com/mitchellh/mapstructure" - - "github.com/gohugoio/hugo/media" -) - -// Format represents an output representation, usually to a file on disk. -type Format struct { - // The Name is used as an identifier. Internal output formats (i.e. HTML and RSS) - // can be overridden by providing a new definition for those types. - Name string `json:"name"` - - MediaType media.Type `json:"mediaType"` - - // Must be set to a value when there are two or more conflicting mediatype for the same resource. - Path string `json:"path"` - - // The base output file name used when not using "ugly URLs", defaults to "index". - BaseName string `json:"baseName"` - - // The value to use for rel links - // - // See https://www.w3schools.com/tags/att_link_rel.asp - // - // AMP has a special requirement in this department, see: - // https://www.ampproject.org/docs/guides/deploy/discovery - // I.e.: - // - Rel string `json:"rel"` - - // The protocol to use, i.e. "webcal://". Defaults to the protocol of the baseURL. - Protocol string `json:"protocol"` - - // IsPlainText decides whether to use text/template or html/template - // as template parser. - IsPlainText bool `json:"isPlainText"` - - // IsHTML returns whether this format is int the HTML family. This includes - // HTML, AMP etc. This is used to decide when to create alias redirects etc. - IsHTML bool `json:"isHTML"` - - // Enable to ignore the global uglyURLs setting. - NoUgly bool `json:"noUgly"` - - // Enable if it doesn't make sense to include this format in an alternative - // format listing, CSS being one good example. - // Note that we use the term "alternative" and not "alternate" here, as it - // does not necessarily replace the other format, it is an alternative representation. - NotAlternative bool `json:"notAlternative"` -} - -var ( - // An ordered list of built-in output formats - // - // See https://www.ampproject.org/learn/overview/ - AMPFormat = Format{ - Name: "AMP", - MediaType: media.HTMLType, - BaseName: "index", - Path: "amp", - Rel: "amphtml", - IsHTML: true, - } - - CalendarFormat = Format{ - Name: "Calendar", - MediaType: media.CalendarType, - IsPlainText: true, - Protocol: "webcal://", - BaseName: "index", - Rel: "alternate", - } - - CSSFormat = Format{ - Name: "CSS", - MediaType: media.CSSType, - BaseName: "styles", - IsPlainText: true, - Rel: "stylesheet", - NotAlternative: true, - } - CSVFormat = Format{ - Name: "CSV", - MediaType: media.CSVType, - BaseName: "index", - IsPlainText: true, - Rel: "alternate", - } - - HTMLFormat = Format{ - Name: "HTML", - MediaType: media.HTMLType, - BaseName: "index", - Rel: "canonical", - IsHTML: true, - } - - JSONFormat = Format{ - Name: "JSON", - MediaType: media.JSONType, - BaseName: "index", - IsPlainText: true, - Rel: "alternate", - } - - RSSFormat = Format{ - Name: "RSS", - MediaType: media.RSSType, - BaseName: "index", - NoUgly: true, - Rel: "alternate", - } -) - -var DefaultFormats = Formats{ - AMPFormat, - CalendarFormat, - CSSFormat, - CSVFormat, - HTMLFormat, - JSONFormat, - RSSFormat, -} - -func init() { - sort.Sort(DefaultFormats) -} - -type Formats []Format - -func (f Formats) Len() int { return len(f) } -func (f Formats) Swap(i, j int) { f[i], f[j] = f[j], f[i] } -func (f Formats) Less(i, j int) bool { return f[i].Name < f[j].Name } - -// GetBySuffix gets a output format given as suffix, e.g. "html". -// It will return false if no format could be found, or if the suffix given -// is ambiguous. -// The lookup is case insensitive. -func (formats Formats) GetBySuffix(suffix string) (f Format, found bool) { - for _, ff := range formats { - if strings.EqualFold(suffix, ff.MediaType.Suffix) { - if found { - // ambiguous - found = false - return - } - f = ff - found = true - } - } - return -} - -// GetByName gets a format by its identifier name. -func (formats Formats) GetByName(name string) (f Format, found bool) { - for _, ff := range formats { - if strings.EqualFold(name, ff.Name) { - f = ff - found = true - return - } - } - return -} - -// GetByNames gets a list of formats given a list of identifiers. -func (formats Formats) GetByNames(names ...string) (Formats, error) { - var types []Format - - for _, name := range names { - tpe, ok := formats.GetByName(name) - if !ok { - return types, fmt.Errorf("OutputFormat with key %q not found", name) - } - types = append(types, tpe) - } - return types, nil -} - -// FromFilename gets a Format given a filename. -func (formats Formats) FromFilename(filename string) (f Format, found bool) { - // mytemplate.amp.html - // mytemplate.html - // mytemplate - var ext, outFormat string - - parts := strings.Split(filename, ".") - if len(parts) > 2 { - outFormat = parts[1] - ext = parts[2] - } else if len(parts) > 1 { - ext = parts[1] - } - - if outFormat != "" { - return formats.GetByName(outFormat) - } - - if ext != "" { - f, found = formats.GetBySuffix(ext) - if !found && len(parts) == 2 { - // For extensionless output formats (e.g. Netlify's _redirects) - // we must fall back to using the extension as format lookup. - f, found = formats.GetByName(ext) - } - } - return -} - -// DecodeFormats takes a list of output format configurations and merges those, -// in the order given, with the Hugo defaults as the last resort. -func DecodeFormats(mediaTypes media.Types, maps ...map[string]interface{}) (Formats, error) { - f := make(Formats, len(DefaultFormats)) - copy(f, DefaultFormats) - - for _, m := range maps { - for k, v := range m { - found := false - for i, vv := range f { - if strings.EqualFold(k, vv.Name) { - // Merge it with the existing - if err := decode(mediaTypes, v, &f[i]); err != nil { - return f, err - } - found = true - } - } - if !found { - var newOutFormat Format - newOutFormat.Name = k - if err := decode(mediaTypes, v, &newOutFormat); err != nil { - return f, err - } - - // We need values for these - if newOutFormat.BaseName == "" { - newOutFormat.BaseName = "index" - } - if newOutFormat.Rel == "" { - newOutFormat.Rel = "alternate" - } - - f = append(f, newOutFormat) - } - } - } - - sort.Sort(f) - - return f, nil -} - -func decode(mediaTypes media.Types, input, output interface{}) error { - config := &mapstructure.DecoderConfig{ - Metadata: nil, - Result: output, - WeaklyTypedInput: true, - DecodeHook: func(a reflect.Type, b reflect.Type, c interface{}) (interface{}, error) { - if a.Kind() == reflect.Map { - dataVal := reflect.Indirect(reflect.ValueOf(c)) - for _, key := range dataVal.MapKeys() { - keyStr, ok := key.Interface().(string) - if !ok { - // Not a string key - continue - } - if strings.EqualFold(keyStr, "mediaType") { - // If mediaType is a string, look it up and replace it - // in the map. - vv := dataVal.MapIndex(key) - if mediaTypeStr, ok := vv.Interface().(string); ok { - mediaType, found := mediaTypes.GetByType(mediaTypeStr) - if !found { - return c, fmt.Errorf("media type %q not found", mediaTypeStr) - } - dataVal.SetMapIndex(key, reflect.ValueOf(mediaType)) - } - } - } - } - return c, nil - }, - } - - decoder, err := mapstructure.NewDecoder(config) - if err != nil { - return err - } - - return decoder.Decode(input) -} - -func (f Format) BaseFilename() string { - return f.BaseName + "." + f.MediaType.Suffix -} - -func (f Format) MarshalJSON() ([]byte, error) { - type Alias Format - return json.Marshal(&struct { - MediaType string - Alias - }{ - MediaType: f.MediaType.String(), - Alias: (Alias)(f), - }) -} diff --git a/output/outputFormat_test.go b/output/outputFormat_test.go deleted file mode 100644 index b800d1a36..000000000 --- a/output/outputFormat_test.go +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright 2017-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package output - -import ( - "fmt" - "testing" - - "github.com/gohugoio/hugo/media" - "github.com/stretchr/testify/require" -) - -func TestDefaultTypes(t *testing.T) { - require.Equal(t, "Calendar", CalendarFormat.Name) - require.Equal(t, media.CalendarType, CalendarFormat.MediaType) - require.Equal(t, "webcal://", CalendarFormat.Protocol) - require.Empty(t, CalendarFormat.Path) - require.True(t, CalendarFormat.IsPlainText) - require.False(t, CalendarFormat.IsHTML) - - require.Equal(t, "CSS", CSSFormat.Name) - require.Equal(t, media.CSSType, CSSFormat.MediaType) - require.Empty(t, CSSFormat.Path) - require.Empty(t, CSSFormat.Protocol) // Will inherit the BaseURL protocol. - require.True(t, CSSFormat.IsPlainText) - require.False(t, CSSFormat.IsHTML) - - require.Equal(t, "CSV", CSVFormat.Name) - require.Equal(t, media.CSVType, CSVFormat.MediaType) - require.Empty(t, CSVFormat.Path) - require.Empty(t, CSVFormat.Protocol) - require.True(t, CSVFormat.IsPlainText) - require.False(t, CSVFormat.IsHTML) - - require.Equal(t, "HTML", HTMLFormat.Name) - require.Equal(t, media.HTMLType, HTMLFormat.MediaType) - require.Empty(t, HTMLFormat.Path) - require.Empty(t, HTMLFormat.Protocol) - require.False(t, HTMLFormat.IsPlainText) - require.True(t, HTMLFormat.IsHTML) - - require.Equal(t, "AMP", AMPFormat.Name) - require.Equal(t, media.HTMLType, AMPFormat.MediaType) - require.Equal(t, "amp", AMPFormat.Path) - require.Empty(t, AMPFormat.Protocol) - require.False(t, AMPFormat.IsPlainText) - require.True(t, AMPFormat.IsHTML) - - require.Equal(t, "RSS", RSSFormat.Name) - require.Equal(t, media.RSSType, RSSFormat.MediaType) - require.Empty(t, RSSFormat.Path) - require.False(t, RSSFormat.IsPlainText) - require.True(t, RSSFormat.NoUgly) - require.False(t, CalendarFormat.IsHTML) - -} - -func TestGetFormatByName(t *testing.T) { - formats := Formats{AMPFormat, CalendarFormat} - tp, _ := formats.GetByName("AMp") - require.Equal(t, AMPFormat, tp) - _, found := formats.GetByName("HTML") - require.False(t, found) - _, found = formats.GetByName("FOO") - require.False(t, found) -} - -func TestGetFormatByExt(t *testing.T) { - formats1 := Formats{AMPFormat, CalendarFormat} - formats2 := Formats{AMPFormat, HTMLFormat, CalendarFormat} - tp, _ := formats1.GetBySuffix("html") - require.Equal(t, AMPFormat, tp) - tp, _ = formats1.GetBySuffix("ics") - require.Equal(t, CalendarFormat, tp) - _, found := formats1.GetBySuffix("not") - require.False(t, found) - - // ambiguous - _, found = formats2.GetBySuffix("html") - require.False(t, found) -} - -func TestGetFormatByFilename(t *testing.T) { - noExtNoDelimMediaType := media.TextType - noExtNoDelimMediaType.Suffix = "" - noExtNoDelimMediaType.Delimiter = "" - - noExtMediaType := media.TextType - noExtMediaType.Suffix = "" - - var ( - noExtDelimFormat = Format{ - Name: "NEM", - MediaType: noExtNoDelimMediaType, - BaseName: "_redirects", - } - noExt = Format{ - Name: "NEX", - MediaType: noExtMediaType, - BaseName: "next", - } - ) - - formats := Formats{AMPFormat, HTMLFormat, noExtDelimFormat, noExt, CalendarFormat} - f, found := formats.FromFilename("my.amp.html") - require.True(t, found) - require.Equal(t, AMPFormat, f) - f, found = formats.FromFilename("my.ics") - require.True(t, found) - f, found = formats.FromFilename("my.html") - require.True(t, found) - require.Equal(t, HTMLFormat, f) - f, found = formats.FromFilename("my.nem") - require.True(t, found) - require.Equal(t, noExtDelimFormat, f) - f, found = formats.FromFilename("my.nex") - require.True(t, found) - require.Equal(t, noExt, f) - _, found = formats.FromFilename("my.css") - require.False(t, found) - -} - -func TestDecodeFormats(t *testing.T) { - - mediaTypes := media.Types{media.JSONType, media.XMLType} - - var tests = []struct { - name string - maps []map[string]interface{} - shouldError bool - assert func(t *testing.T, name string, f Formats) - }{ - { - "Redefine JSON", - []map[string]interface{}{ - { - "JsON": map[string]interface{}{ - "baseName": "myindex", - "isPlainText": "false"}}}, - false, - func(t *testing.T, name string, f Formats) { - require.Len(t, f, len(DefaultFormats), name) - json, _ := f.GetByName("JSON") - require.Equal(t, "myindex", json.BaseName) - require.Equal(t, media.JSONType, json.MediaType) - require.False(t, json.IsPlainText) - - }}, - { - "Add XML format with string as mediatype", - []map[string]interface{}{ - { - "MYXMLFORMAT": map[string]interface{}{ - "baseName": "myxml", - "mediaType": "application/xml", - }}}, - false, - func(t *testing.T, name string, f Formats) { - require.Len(t, f, len(DefaultFormats)+1, name) - xml, found := f.GetByName("MYXMLFORMAT") - require.True(t, found) - require.Equal(t, "myxml", xml.BaseName, fmt.Sprint(xml)) - require.Equal(t, media.XMLType, xml.MediaType) - - // Verify that we haven't changed the DefaultFormats slice. - json, _ := f.GetByName("JSON") - require.Equal(t, "index", json.BaseName, name) - - }}, - { - "Add format unknown mediatype", - []map[string]interface{}{ - { - "MYINVALID": map[string]interface{}{ - "baseName": "mymy", - "mediaType": "application/hugo", - }}}, - true, - func(t *testing.T, name string, f Formats) { - - }}, - { - "Add and redefine XML format", - []map[string]interface{}{ - { - "MYOTHERXMLFORMAT": map[string]interface{}{ - "baseName": "myotherxml", - "mediaType": media.XMLType, - }}, - { - "MYOTHERXMLFORMAT": map[string]interface{}{ - "baseName": "myredefined", - }}, - }, - false, - func(t *testing.T, name string, f Formats) { - require.Len(t, f, len(DefaultFormats)+1, name) - xml, found := f.GetByName("MYOTHERXMLFORMAT") - require.True(t, found) - require.Equal(t, "myredefined", xml.BaseName, fmt.Sprint(xml)) - require.Equal(t, media.XMLType, xml.MediaType) - }}, - } - - for _, test := range tests { - result, err := DecodeFormats(mediaTypes, test.maps...) - if test.shouldError { - require.Error(t, err, test.name) - } else { - require.NoError(t, err, test.name) - test.assert(t, test.name, result) - } - } -} diff --git a/parser/frontmatter.go b/parser/frontmatter.go deleted file mode 100644 index ab56b14d1..000000000 --- a/parser/frontmatter.go +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package parser - -// TODO(bep) archetype remove unused from this package. - -import ( - "bytes" - "encoding/json" - "errors" - "io" - "strings" - - "github.com/BurntSushi/toml" - "github.com/chaseadamsio/goorgeous" - - "gopkg.in/yaml.v2" -) - -// FrontmatterType represents a type of frontmatter. -type FrontmatterType struct { - // Parse decodes content into a Go interface. - Parse func([]byte) (interface{}, error) - - markstart, markend []byte // starting and ending delimiters - includeMark bool // include start and end mark in output -} - -// InterfaceToConfig encodes a given input based upon the mark and writes to w. -func InterfaceToConfig(in interface{}, mark rune, w io.Writer) error { - if in == nil { - return errors.New("input was nil") - } - - switch mark { - case rune(YAMLLead[0]): - b, err := yaml.Marshal(in) - if err != nil { - return err - } - - _, err = w.Write(b) - return err - - case rune(TOMLLead[0]): - return toml.NewEncoder(w).Encode(in) - case rune(JSONLead[0]): - b, err := json.MarshalIndent(in, "", " ") - if err != nil { - return err - } - - _, err = w.Write(b) - if err != nil { - return err - } - - _, err = w.Write([]byte{'\n'}) - return err - - default: - return errors.New("Unsupported Format provided") - } -} - -// InterfaceToFrontMatter encodes a given input into a frontmatter -// representation based upon the mark with the appropriate front matter delimiters -// surrounding the output, which is written to w. -func InterfaceToFrontMatter(in interface{}, mark rune, w io.Writer) error { - if in == nil { - return errors.New("input was nil") - } - - switch mark { - case rune(YAMLLead[0]): - _, err := w.Write([]byte(YAMLDelimUnix)) - if err != nil { - return err - } - - err = InterfaceToConfig(in, mark, w) - if err != nil { - return err - } - - _, err = w.Write([]byte(YAMLDelimUnix)) - return err - - case rune(TOMLLead[0]): - _, err := w.Write([]byte(TOMLDelimUnix)) - if err != nil { - return err - } - - err = InterfaceToConfig(in, mark, w) - - if err != nil { - return err - } - - _, err = w.Write([]byte("\n" + TOMLDelimUnix)) - return err - - default: - return InterfaceToConfig(in, mark, w) - } -} - -// FormatToLeadRune takes a given format kind and return the leading front -// matter delimiter. -func FormatToLeadRune(kind string) rune { - switch FormatSanitize(kind) { - case "yaml": - return rune([]byte(YAMLLead)[0]) - case "json": - return rune([]byte(JSONLead)[0]) - case "org": - return '#' - default: - return rune([]byte(TOMLLead)[0]) - } -} - -// FormatSanitize returns the canonical format name for a given kind. -// -// TODO(bep) move to helpers -func FormatSanitize(kind string) string { - switch strings.ToLower(kind) { - case "yaml", "yml": - return "yaml" - case "toml", "tml": - return "toml" - case "json", "js": - return "json" - case "org": - return kind - default: - return "toml" - } -} - -// DetectFrontMatter detects the type of frontmatter analysing its first character. -func DetectFrontMatter(mark rune) (f *FrontmatterType) { - switch mark { - case '-': - return &FrontmatterType{HandleYAMLMetaData, []byte(YAMLDelim), []byte(YAMLDelim), false} - case '+': - return &FrontmatterType{HandleTOMLMetaData, []byte(TOMLDelim), []byte(TOMLDelim), false} - case '{': - return &FrontmatterType{HandleJSONMetaData, []byte{'{'}, []byte{'}'}, true} - case '#': - return &FrontmatterType{HandleOrgMetaData, []byte("#+"), []byte("\n"), false} - default: - return nil - } -} - -// HandleTOMLMetaData unmarshals TOML-encoded datum and returns a Go interface -// representing the encoded data structure. -func HandleTOMLMetaData(datum []byte) (interface{}, error) { - m := map[string]interface{}{} - datum = removeTOMLIdentifier(datum) - - _, err := toml.Decode(string(datum), &m) - - return m, err - -} - -// removeTOMLIdentifier removes, if necessary, beginning and ending TOML -// frontmatter delimiters from a byte slice. -func removeTOMLIdentifier(datum []byte) []byte { - ld := len(datum) - if ld < 8 { - return datum - } - - b := bytes.TrimPrefix(datum, []byte(TOMLDelim)) - if ld-len(b) != 3 { - // No TOML prefix trimmed, so bail out - return datum - } - - b = bytes.Trim(b, "\r\n") - return bytes.TrimSuffix(b, []byte(TOMLDelim)) -} - -// HandleYAMLMetaData unmarshals YAML-encoded datum and returns a Go interface -// representing the encoded data structure. -func HandleYAMLMetaData(datum []byte) (interface{}, error) { - m := map[string]interface{}{} - err := yaml.Unmarshal(datum, &m) - return m, err -} - -// HandleJSONMetaData unmarshals JSON-encoded datum and returns a Go interface -// representing the encoded data structure. -func HandleJSONMetaData(datum []byte) (interface{}, error) { - if datum == nil { - // Package json returns on error on nil input. - // Return an empty map to be consistent with our other supported - // formats. - return make(map[string]interface{}), nil - } - - var f interface{} - err := json.Unmarshal(datum, &f) - return f, err -} - -// HandleOrgMetaData unmarshals org-mode encoded datum and returns a Go -// interface representing the encoded data structure. -func HandleOrgMetaData(datum []byte) (interface{}, error) { - return goorgeous.OrgHeaders(datum) -} diff --git a/parser/frontmatter_test.go b/parser/frontmatter_test.go deleted file mode 100644 index 2e5bdec50..000000000 --- a/parser/frontmatter_test.go +++ /dev/null @@ -1,398 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package parser - -import ( - "bytes" - "fmt" - "reflect" - "strings" - "testing" -) - -func TestInterfaceToConfig(t *testing.T) { - cases := []struct { - input interface{} - mark byte - want []byte - isErr bool - }{ - // TOML - {map[string]interface{}{}, TOMLLead[0], nil, false}, - { - map[string]interface{}{"title": "test 1"}, - TOMLLead[0], - []byte("title = \"test 1\"\n"), - false, - }, - - // YAML - {map[string]interface{}{}, YAMLLead[0], []byte("{}\n"), false}, - { - map[string]interface{}{"title": "test 1"}, - YAMLLead[0], - []byte("title: test 1\n"), - false, - }, - - // JSON - {map[string]interface{}{}, JSONLead[0], []byte("{}\n"), false}, - { - map[string]interface{}{"title": "test 1"}, - JSONLead[0], - []byte("{\n \"title\": \"test 1\"\n}\n"), - false, - }, - - // Errors - {nil, TOMLLead[0], nil, true}, - {map[string]interface{}{}, '$', nil, true}, - } - - for i, c := range cases { - var buf bytes.Buffer - - err := InterfaceToConfig(c.input, rune(c.mark), &buf) - if err != nil { - if c.isErr { - continue - } - t.Fatalf("[%d] unexpected error value: %v", i, err) - } - - if !reflect.DeepEqual(buf.Bytes(), c.want) { - t.Errorf("[%d] not equal:\nwant %q,\n got %q", i, c.want, buf.Bytes()) - } - } -} - -func TestInterfaceToFrontMatter(t *testing.T) { - cases := []struct { - input interface{} - mark rune - want []byte - isErr bool - }{ - // TOML - {map[string]interface{}{}, '+', []byte("+++\n\n+++\n"), false}, - { - map[string]interface{}{"title": "test 1"}, - '+', - []byte("+++\ntitle = \"test 1\"\n\n+++\n"), - false, - }, - - // YAML - {map[string]interface{}{}, '-', []byte("---\n{}\n---\n"), false}, // - { - map[string]interface{}{"title": "test 1"}, - '-', - []byte("---\ntitle: test 1\n---\n"), - false, - }, - - // JSON - {map[string]interface{}{}, '{', []byte("{}\n"), false}, - { - map[string]interface{}{"title": "test 1"}, - '{', - []byte("{\n \"title\": \"test 1\"\n}\n"), - false, - }, - - // Errors - {nil, '+', nil, true}, - {map[string]interface{}{}, '$', nil, true}, - } - - for i, c := range cases { - var buf bytes.Buffer - err := InterfaceToFrontMatter(c.input, c.mark, &buf) - if err != nil { - if c.isErr { - continue - } - t.Fatalf("[%d] unexpected error value: %v", i, err) - } - - if !reflect.DeepEqual(buf.Bytes(), c.want) { - t.Errorf("[%d] not equal:\nwant %q,\n got %q", i, c.want, buf.Bytes()) - } - } -} - -func TestHandleTOMLMetaData(t *testing.T) { - cases := []struct { - input []byte - want interface{} - isErr bool - }{ - {nil, map[string]interface{}{}, false}, - {[]byte("title = \"test 1\""), map[string]interface{}{"title": "test 1"}, false}, - {[]byte("a = [1, 2, 3]"), map[string]interface{}{"a": []interface{}{int64(1), int64(2), int64(3)}}, false}, - {[]byte("b = [\n[1, 2],\n[3, 4]\n]"), map[string]interface{}{"b": []interface{}{[]interface{}{int64(1), int64(2)}, []interface{}{int64(3), int64(4)}}}, false}, - // errors - {[]byte("z = [\n[1, 2]\n[3, 4]\n]"), nil, true}, - } - - for i, c := range cases { - res, err := HandleTOMLMetaData(c.input) - if err != nil { - if c.isErr { - continue - } - t.Fatalf("[%d] unexpected error value: %v", i, err) - } - - if !reflect.DeepEqual(res, c.want) { - t.Errorf("[%d] not equal: given %q\nwant %#v,\n got %#v", i, c.input, c.want, res) - } - } -} - -func TestHandleYAMLMetaData(t *testing.T) { - cases := []struct { - input []byte - want interface{} - isErr bool - }{ - {nil, map[string]interface{}{}, false}, - {[]byte("title: test 1"), map[string]interface{}{"title": "test 1"}, false}, - {[]byte("a: Easy!\nb:\n c: 2\n d: [3, 4]"), map[string]interface{}{"a": "Easy!", "b": map[interface{}]interface{}{"c": 2, "d": []interface{}{3, 4}}}, false}, - // errors - {[]byte("z = not toml"), nil, true}, - } - - for i, c := range cases { - res, err := HandleYAMLMetaData(c.input) - if err != nil { - if c.isErr { - continue - } - t.Fatalf("[%d] unexpected error value: %v", i, err) - } - - if !reflect.DeepEqual(res, c.want) { - t.Errorf("[%d] not equal: given %q\nwant %#v,\n got %#v", i, c.input, c.want, res) - } - } -} - -func TestHandleJSONMetaData(t *testing.T) { - cases := []struct { - input []byte - want interface{} - isErr bool - }{ - {nil, map[string]interface{}{}, false}, - {[]byte("{\"title\": \"test 1\"}"), map[string]interface{}{"title": "test 1"}, false}, - // errors - {[]byte("{noquotes}"), nil, true}, - } - - for i, c := range cases { - res, err := HandleJSONMetaData(c.input) - if err != nil { - if c.isErr { - continue - } - t.Fatalf("[%d] unexpected error value: %v", i, err) - } - - if !reflect.DeepEqual(res, c.want) { - t.Errorf("[%d] not equal: given %q\nwant %#v,\n got %#v", i, c.input, c.want, res) - } - } -} - -func TestHandleOrgMetaData(t *testing.T) { - cases := []struct { - input []byte - want interface{} - isErr bool - }{ - {nil, map[string]interface{}{}, false}, - {[]byte("#+title: test 1\n"), map[string]interface{}{"title": "test 1"}, false}, - } - - for i, c := range cases { - res, err := HandleOrgMetaData(c.input) - if err != nil { - if c.isErr { - continue - } - t.Fatalf("[%d] unexpected error value: %v", i, err) - } - - if !reflect.DeepEqual(res, c.want) { - t.Errorf("[%d] not equal: given %q\nwant %#v,\n got %#v", i, c.input, c.want, res) - } - } -} - -func TestFormatToLeadRune(t *testing.T) { - for i, this := range []struct { - kind string - expect rune - }{ - {"yaml", '-'}, - {"yml", '-'}, - {"toml", '+'}, - {"tml", '+'}, - {"json", '{'}, - {"js", '{'}, - {"org", '#'}, - {"unknown", '+'}, - } { - result := FormatToLeadRune(this.kind) - - if result != this.expect { - t.Errorf("[%d] got %q but expected %q", i, result, this.expect) - } - } -} - -func TestDetectFrontMatter(t *testing.T) { - cases := []struct { - mark rune - want *FrontmatterType - }{ - // funcs are uncomparable, so we ignore FrontmatterType.Parse in these tests - {'-', &FrontmatterType{nil, []byte(YAMLDelim), []byte(YAMLDelim), false}}, - {'+', &FrontmatterType{nil, []byte(TOMLDelim), []byte(TOMLDelim), false}}, - {'{', &FrontmatterType{nil, []byte("{"), []byte("}"), true}}, - {'#', &FrontmatterType{nil, []byte("#+"), []byte("\n"), false}}, - {'$', nil}, - } - - for _, c := range cases { - res := DetectFrontMatter(c.mark) - if res == nil { - if c.want == nil { - continue - } - - t.Fatalf("want %v, got %v", *c.want, res) - } - - if !reflect.DeepEqual(res.markstart, c.want.markstart) { - t.Errorf("markstart mismatch: want %v, got %v", c.want.markstart, res.markstart) - } - if !reflect.DeepEqual(res.markend, c.want.markend) { - t.Errorf("markend mismatch: want %v, got %v", c.want.markend, res.markend) - } - if !reflect.DeepEqual(res.includeMark, c.want.includeMark) { - t.Errorf("includeMark mismatch: want %v, got %v", c.want.includeMark, res.includeMark) - } - } -} - -func TestRemoveTOMLIdentifier(t *testing.T) { - cases := []struct { - input string - want string - }{ - {"a = 1", "a = 1"}, - {"a = 1\r\n", "a = 1\r\n"}, - {"+++\r\na = 1\r\n+++\r\n", "a = 1\r\n"}, - {"+++\na = 1\n+++\n", "a = 1\n"}, - {"+++\nb = \"+++ oops +++\"\n+++\n", "b = \"+++ oops +++\"\n"}, - {"+++\nc = \"\"\"+++\noops\n+++\n\"\"\"\"\n+++\n", "c = \"\"\"+++\noops\n+++\n\"\"\"\"\n"}, - {"+++\nd = 1\n+++", "d = 1\n"}, - } - - for i, c := range cases { - res := removeTOMLIdentifier([]byte(c.input)) - if string(res) != c.want { - t.Errorf("[%d] given %q\nwant: %q\n got: %q", i, c.input, c.want, res) - } - } -} - -func BenchmarkFrontmatterTags(b *testing.B) { - - for _, frontmatter := range []string{"JSON", "YAML", "YAML2", "TOML"} { - for i := 1; i < 60; i += 20 { - doBenchmarkFrontmatter(b, frontmatter, i) - } - } -} - -func doBenchmarkFrontmatter(b *testing.B, fileformat string, numTags int) { - yamlTemplate := `--- -name: "Tags" -tags: -%s ---- -` - - yaml2Template := `--- -name: "Tags" -tags: %s ---- -` - tomlTemplate := `+++ -name = "Tags" -tags = %s -+++ -` - - jsonTemplate := `{ - "name": "Tags", - "tags": [ - %s - ] -}` - name := fmt.Sprintf("%s:%d", fileformat, numTags) - b.Run(name, func(b *testing.B) { - tags := make([]string, numTags) - var ( - tagsStr string - frontmatterTemplate string - ) - for i := 0; i < numTags; i++ { - tags[i] = fmt.Sprintf("Hugo %d", i+1) - } - if fileformat == "TOML" { - frontmatterTemplate = tomlTemplate - tagsStr = strings.Replace(fmt.Sprintf("%q", tags), " ", ", ", -1) - } else if fileformat == "JSON" { - frontmatterTemplate = jsonTemplate - tagsStr = strings.Replace(fmt.Sprintf("%q", tags), " ", ", ", -1) - } else if fileformat == "YAML2" { - frontmatterTemplate = yaml2Template - tagsStr = strings.Replace(fmt.Sprintf("%q", tags), " ", ", ", -1) - } else { - frontmatterTemplate = yamlTemplate - for _, tag := range tags { - tagsStr += "\n- " + tag - } - } - - frontmatter := fmt.Sprintf(frontmatterTemplate, tagsStr) - - p := page{frontmatter: []byte(frontmatter)} - - b.ResetTimer() - for i := 0; i < b.N; i++ { - meta, err := p.Metadata() - if err != nil { - b.Fatal(err) - } - if meta == nil { - b.Fatal("Meta is nil") - } - } - }) -} diff --git a/parser/long_text_test.md b/parser/long_text_test.md deleted file mode 100644 index e87ceb8c6..000000000 --- a/parser/long_text_test.md +++ /dev/null @@ -1,263 +0,0 @@ ---- -title: The Git Book - Long Text ---- -# Getting Started # - -This chapter will be about getting started with Git. We will begin at the beginning by explaining some background on version control tools, then move on to how to get Git running on your system and finally how to get it setup to start working with. At the end of this chapter you should understand why Git is around, why you should use it and you should be all setup to do so. - -## About Version Control ## - -What is version control, and why should you care? Version control is a system that records changes to a file or set of files over time so that you can recall specific versions later. Even though the examples in this book show software source code as the files under version control, in reality any type of file on a computer can be placed under version control. - -If you are a graphic or web designer and want to keep every version of an image or layout (which you certainly would), it is very wise to use a Version Control System (VCS). A VCS allows you to: revert files back to a previous state, revert the entire project back to a previous state, review changes made over time, see who last modified something that might be causing a problem, who introduced an issue and when, and more. Using a VCS also means that if you screw things up or lose files, you can generally recover easily. In addition, you get all this for very little overhead. - -### Local Version Control Systems ### - -Many peoples version-control method of choice is to copy files into another directory (perhaps a time-stamped directory, if theyre clever). This approach is very common because it is so simple, but it is also incredibly error prone. It is easy to forget which directory youre in and accidentally write to the wrong file or copy over files you dont mean to. - -To deal with this issue, programmers long ago developed local VCSs that had a simple database that kept all the changes to files under revision control (see Figure 1-1). - -Insert 18333fig0101.png -Figure 1-1. Local version control diagram. - -One of the more popular VCS tools was a system called rcs, which is still distributed with many computers today. Even the popular Mac OS X operating system includes the rcs command when you install the Developer Tools. This tool basically works by keeping patch sets (that is, the differences between files) from one revision to another in a special format on disk; it can then recreate what any file looked like at any point in time by adding up all the patches. - -### Centralized Version Control Systems ### - -The next major issue that people encounter is that they need to collaborate with developers on other systems. To deal with this problem, Centralized Version Control Systems (CVCSs) were developed. These systems, such as CVS, Subversion, and Perforce, have a single server that contains all the versioned files, and a number of clients that check out files from that central place. For many years, this has been the standard for version control (see Figure 1-2). - -Insert 18333fig0102.png -Figure 1-2. Centralized version control diagram. - -This setup offers many advantages, especially over local VCSs. For example, everyone knows to a certain degree what everyone else on the project is doing. Administrators have fine-grained control over who can do what; and its far easier to administer a CVCS than it is to deal with local databases on every client. - -However, this setup also has some serious downsides. The most obvious is the single point of failure that the centralized server represents. If that server goes down for an hour, then during that hour nobody can collaborate at all or save versioned changes to anything theyre working on. If the hard disk the central database is on becomes corrupted, and proper backups havent been kept, you lose absolutely everythingthe entire history of the project except whatever single snapshots people happen to have on their local machines. Local VCS systems suffer from this same problemwhenever you have the entire history of the project in a single place, you risk losing everything. - -### Distributed Version Control Systems ### - -This is where Distributed Version Control Systems (DVCSs) step in. In a DVCS (such as Git, Mercurial, Bazaar or Darcs), clients dont just check out the latest snapshot of the files: they fully mirror the repository. Thus if any server dies, and these systems were collaborating via it, any of the client repositories can be copied back up to the server to restore it. Every checkout is really a full backup of all the data (see Figure 1-3). - -Insert 18333fig0103.png -Figure 1-3. Distributed version control diagram. - -Furthermore, many of these systems deal pretty well with having several remote repositories they can work with, so you can collaborate with different groups of people in different ways simultaneously within the same project. This allows you to set up several types of workflows that arent possible in centralized systems, such as hierarchical models. - -## A Short History of Git ## - -As with many great things in life, Git began with a bit of creative destruction and fiery controversy. The Linux kernel is an open source software project of fairly large scope. For most of the lifetime of the Linux kernel maintenance (19912002), changes to the software were passed around as patches and archived files. In 2002, the Linux kernel project began using a proprietary DVCS system called BitKeeper. - -In 2005, the relationship between the community that developed the Linux kernel and the commercial company that developed BitKeeper broke down, and the tools free-of-charge status was revoked. This prompted the Linux development community (and in particular Linus Torvalds, the creator of Linux) to develop their own tool based on some of the lessons they learned while using BitKeeper. Some of the goals of the new system were as follows: - -* Speed -* Simple design -* Strong support for non-linear development (thousands of parallel branches) -* Fully distributed -* Able to handle large projects like the Linux kernel efficiently (speed and data size) - -Since its birth in 2005, Git has evolved and matured to be easy to use and yet retain these initial qualities. Its incredibly fast, its very efficient with large projects, and it has an incredible branching system for non-linear development (See Chapter 3). - -## Git Basics ## - -So, what is Git in a nutshell? This is an important section to absorb, because if you understand what Git is and the fundamentals of how it works, then using Git effectively will probably be much easier for you. As you learn Git, try to clear your mind of the things you may know about other VCSs, such as Subversion and Perforce; doing so will help you avoid subtle confusion when using the tool. Git stores and thinks about information much differently than these other systems, even though the user interface is fairly similar; understanding those differences will help prevent you from becoming confused while using it. - -### Snapshots, Not Differences ### - -The major difference between Git and any other VCS (Subversion and friends included) is the way Git thinks about its data. Conceptually, most other systems store information as a list of file-based changes. These systems (CVS, Subversion, Perforce, Bazaar, and so on) think of the information they keep as a set of files and the changes made to each file over time, as illustrated in Figure 1-4. - -Insert 18333fig0104.png -Figure 1-4. Other systems tend to store data as changes to a base version of each file. - -Git doesnt think of or store its data this way. Instead, Git thinks of its data more like a set of snapshots of a mini filesystem. Every time you commit, or save the state of your project in Git, it basically takes a picture of what all your files look like at that moment and stores a reference to that snapshot. To be efficient, if files have not changed, Git doesnt store the file againjust a link to the previous identical file it has already stored. Git thinks about its data more like Figure 1-5. - -Insert 18333fig0105.png -Figure 1-5. Git stores data as snapshots of the project over time. - -This is an important distinction between Git and nearly all other VCSs. It makes Git reconsider almost every aspect of version control that most other systems copied from the previous generation. This makes Git more like a mini filesystem with some incredibly powerful tools built on top of it, rather than simply a VCS. Well explore some of the benefits you gain by thinking of your data this way when we cover Git branching in Chapter 3. - -### Nearly Every Operation Is Local ### - -Most operations in Git only need local files and resources to operate generally no information is needed from another computer on your network. If youre used to a CVCS where most operations have that network latency overhead, this aspect of Git will make you think that the gods of speed have blessed Git with unworldly powers. Because you have the entire history of the project right there on your local disk, most operations seem almost instantaneous. - -For example, to browse the history of the project, Git doesnt need to go out to the server to get the history and display it for youit simply reads it directly from your local database. This means you see the project history almost instantly. If you want to see the changes introduced between the current version of a file and the file a month ago, Git can look up the file a month ago and do a local difference calculation, instead of having to either ask a remote server to do it or pull an older version of the file from the remote server to do it locally. - -This also means that there is very little you cant do if youre offline or off VPN. If you get on an airplane or a train and want to do a little work, you can commit happily until you get to a network connection to upload. If you go home and cant get your VPN client working properly, you can still work. In many other systems, doing so is either impossible or painful. In Perforce, for example, you cant do much when you arent connected to the server; and in Subversion and CVS, you can edit files, but you cant commit changes to your database (because your database is offline). This may not seem like a huge deal, but you may be surprised what a big difference it can make. - -### Git Has Integrity ### - -Everything in Git is check-summed before it is stored and is then referred to by that checksum. This means its impossible to change the contents of any file or directory without Git knowing about it. This functionality is built into Git at the lowest levels and is integral to its philosophy. You cant lose information in transit or get file corruption without Git being able to detect it. - -The mechanism that Git uses for this checksumming is called a SHA-1 hash. This is a 40-character string composed of hexadecimal characters (09 and af) and calculated based on the contents of a file or directory structure in Git. A SHA-1 hash looks something like this: - - 24b9da6552252987aa493b52f8696cd6d3b00373 - -You will see these hash values all over the place in Git because it uses them so much. In fact, Git stores everything not by file name but in the Git database addressable by the hash value of its contents. - -### Git Generally Only Adds Data ### - -When you do actions in Git, nearly all of them only add data to the Git database. It is very difficult to get the system to do anything that is not undoable or to make it erase data in any way. As in any VCS, you can lose or mess up changes you havent committed yet; but after you commit a snapshot into Git, it is very difficult to lose, especially if you regularly push your database to another repository. - -This makes using Git a joy because we know we can experiment without the danger of severely screwing things up. For a more in-depth look at how Git stores its data and how you can recover data that seems lost, see Chapter 9. - -### The Three States ### - -Now, pay attention. This is the main thing to remember about Git if you want the rest of your learning process to go smoothly. Git has three main states that your files can reside in: committed, modified, and staged. Committed means that the data is safely stored in your local database. Modified means that you have changed the file but have not committed it to your database yet. Staged means that you have marked a modified file in its current version to go into your next commit snapshot. - -This leads us to the three main sections of a Git project: the Git directory, the working directory, and the staging area. - -Insert 18333fig0106.png -Figure 1-6. Working directory, staging area, and git directory. - -The Git directory is where Git stores the metadata and object database for your project. This is the most important part of Git, and it is what is copied when you clone a repository from another computer. - -The working directory is a single checkout of one version of the project. These files are pulled out of the compressed database in the Git directory and placed on disk for you to use or modify. - -The staging area is a simple file, generally contained in your Git directory, that stores information about what will go into your next commit. Its sometimes referred to as the index, but its becoming standard to refer to it as the staging area. - -The basic Git workflow goes something like this: - -1. You modify files in your working directory. -2. You stage the files, adding snapshots of them to your staging area. -3. You do a commit, which takes the files as they are in the staging area and stores that snapshot permanently to your Git directory. - -If a particular version of a file is in the git directory, its considered committed. If its modified but has been added to the staging area, it is staged. And if it was changed since it was checked out but has not been staged, it is modified. In Chapter 2, youll learn more about these states and how you can either take advantage of them or skip the staged part entirely. - -## Installing Git ## - -Lets get into using some Git. First things firstyou have to install it. You can get it a number of ways; the two major ones are to install it from source or to install an existing package for your platform. - -### Installing from Source ### - -If you can, its generally useful to install Git from source, because youll get the most recent version. Each version of Git tends to include useful UI enhancements, so getting the latest version is often the best route if you feel comfortable compiling software from source. It is also the case that many Linux distributions contain very old packages; so unless youre on a very up-to-date distro or are using backports, installing from source may be the best bet. - -To install Git, you need to have the following libraries that Git depends on: curl, zlib, openssl, expat, and libiconv. For example, if youre on a system that has yum (such as Fedora) or apt-get (such as a Debian based system), you can use one of these commands to install all of the dependencies: - - $ yum install curl-devel expat-devel gettext-devel \ - openssl-devel zlib-devel - - $ apt-get install libcurl4-gnutls-dev libexpat1-dev gettext \ - libz-dev libssl-dev - -When you have all the necessary dependencies, you can go ahead and grab the latest snapshot from the Git web site: - - http://git-scm.com/download - -Then, compile and install: - - $ tar -zxf git-1.7.2.2.tar.gz - $ cd git-1.7.2.2 - $ make prefix=/usr/local all - $ sudo make prefix=/usr/local install - -After this is done, you can also get Git via Git itself for updates: - - $ git clone git://git.kernel.org/pub/scm/git/git.git - -### Installing on Linux ### - -If you want to install Git on Linux via a binary installer, you can generally do so through the basic package-management tool that comes with your distribution. If youre on Fedora, you can use yum: - - $ yum install git-core - -Or if youre on a Debian-based distribution like Ubuntu, try apt-get: - - $ apt-get install git - -### Installing on Mac ### - -There are two easy ways to install Git on a Mac. The easiest is to use the graphical Git installer, which you can download from the Google Code page (see Figure 1-7): - - http://code.google.com/p/git-osx-installer - -Insert 18333fig0107.png -Figure 1-7. Git OS X installer. - -The other major way is to install Git via MacPorts (`http://www.macports.org`). If you have MacPorts installed, install Git via - - $ sudo port install git-core +svn +doc +bash_completion +gitweb - -You dont have to add all the extras, but youll probably want to include +svn in case you ever have to use Git with Subversion repositories (see Chapter 8). - -### Installing on Windows ### - -Installing Git on Windows is very easy. The msysGit project has one of the easier installation procedures. Simply download the installer exe file from the GitHub page, and run it: - - http://msysgit.github.com/ - -After its installed, you have both a command-line version (including an SSH client that will come in handy later) and the standard GUI. - -Note on Windows usage: you should use Git with the provided msysGit shell (Unix style), it allows to use the complex lines of command given in this book. If you need, for some reason, to use the native Windows shell / command line console, you have to use double quotes instead of simple quotes (for parameters with spaces in them) and you must quote the parameters ending with the circumflex accent (^) if they are last on the line, as it is a continuation symbol in Windows. - -## First-Time Git Setup ## - -Now that you have Git on your system, youll want to do a few things to customize your Git environment. You should have to do these things only once; theyll stick around between upgrades. You can also change them at any time by running through the commands again. - -Git comes with a tool called git config that lets you get and set configuration variables that control all aspects of how Git looks and operates. These variables can be stored in three different places: - -* `/etc/gitconfig` file: Contains values for every user on the system and all their repositories. If you pass the option` --system` to `git config`, it reads and writes from this file specifically. -* `~/.gitconfig` file: Specific to your user. You can make Git read and write to this file specifically by passing the `--global` option. -* config file in the git directory (that is, `.git/config`) of whatever repository youre currently using: Specific to that single repository. Each level overrides values in the previous level, so values in `.git/config` trump those in `/etc/gitconfig`. - -On Windows systems, Git looks for the `.gitconfig` file in the `$HOME` directory (`%USERPROFILE%` in Windows environment), which is `C:\Documents and Settings\$USER` or `C:\Users\$USER` for most people, depending on version (`$USER` is `%USERNAME%` in Windows environment). It also still looks for /etc/gitconfig, although its relative to the MSys root, which is wherever you decide to install Git on your Windows system when you run the installer. - -### Your Identity ### - -The first thing you should do when you install Git is to set your user name and e-mail address. This is important because every Git commit uses this information, and its immutably baked into the commits you pass around: - - $ git config --global user.name "John Doe" - $ git config --global user.email johndoe@example.com - -Again, you need to do this only once if you pass the `--global` option, because then Git will always use that information for anything you do on that system. If you want to override this with a different name or e-mail address for specific projects, you can run the command without the `--global` option when youre in that project. - -### Your Editor ### - -Now that your identity is set up, you can configure the default text editor that will be used when Git needs you to type in a message. By default, Git uses your systems default editor, which is generally Vi or Vim. If you want to use a different text editor, such as Emacs, you can do the following: - - $ git config --global core.editor emacs - -### Your Diff Tool ### - -Another useful option you may want to configure is the default diff tool to use to resolve merge conflicts. Say you want to use vimdiff: - - $ git config --global merge.tool vimdiff - -Git accepts kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge, and opendiff as valid merge tools. You can also set up a custom tool; see Chapter 7 for more information about doing that. - -### Checking Your Settings ### - -If you want to check your settings, you can use the `git config --list` command to list all the settings Git can find at that point: - - $ git config --list - user.name=Scott Chacon - user.email=schacon@gmail.com - color.status=auto - color.branch=auto - color.interactive=auto - color.diff=auto - ... - -You may see keys more than once, because Git reads the same key from different files (`/etc/gitconfig` and `~/.gitconfig`, for example). In this case, Git uses the last value for each unique key it sees. - -You can also check what Git thinks a specific keys value is by typing `git config {key}`: - - $ git config user.name - Scott Chacon - -## Getting Help ## - -If you ever need help while using Git, there are three ways to get the manual page (manpage) help for any of the Git commands: - - $ git help - $ git --help - $ man git- - -For example, you can get the manpage help for the config command by running - - $ git help config - -These commands are nice because you can access them anywhere, even offline. -If the manpages and this book arent enough and you need in-person help, you can try the `#git` or `#github` channel on the Freenode IRC server (irc.freenode.net). These channels are regularly filled with hundreds of people who are all very knowledgeable about Git and are often willing to help. - -## Summary ## - -You should have a basic understanding of what Git is and how its different from the CVCS you may have been using. You should also now have a working version of Git on your system thats set up with your personal identity. Its now time to learn some Git basics. - diff --git a/parser/page.go b/parser/page.go deleted file mode 100644 index 1537915f4..000000000 --- a/parser/page.go +++ /dev/null @@ -1,418 +0,0 @@ -// Copyright 2016n The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package parser - -import ( - "bufio" - "bytes" - "fmt" - "io" - "regexp" - "strings" - "unicode" - - "github.com/chaseadamsio/goorgeous" -) - -const ( - // TODO(bep) Do we really have to export these? - - // HTMLLead identifies the start of HTML documents. - HTMLLead = "<" - // YAMLLead identifies the start of YAML frontmatter. - YAMLLead = "-" - // YAMLDelimUnix identifies the end of YAML front matter on Unix. - YAMLDelimUnix = "---\n" - // YAMLDelimDOS identifies the end of YAML front matter on Windows. - YAMLDelimDOS = "---\r\n" - // YAMLDelim identifies the YAML front matter delimiter. - YAMLDelim = "---" - // TOMLLead identifies the start of TOML front matter. - TOMLLead = "+" - // TOMLDelimUnix identifies the end of TOML front matter on Unix. - TOMLDelimUnix = "+++\n" - // TOMLDelimDOS identifies the end of TOML front matter on Windows. - TOMLDelimDOS = "+++\r\n" - // TOMLDelim identifies the TOML front matter delimiter. - TOMLDelim = "+++" - // JSONLead identifies the start of JSON frontmatter. - JSONLead = "{" - // HTMLCommentStart identifies the start of HTML comment. - HTMLCommentStart = "" - // BOM Unicode byte order marker - BOM = '\ufeff' -) - -var ( - delims = regexp.MustCompile( - "^(" + regexp.QuoteMeta(YAMLDelim) + `\s*\n|` + regexp.QuoteMeta(TOMLDelim) + `\s*\n|` + regexp.QuoteMeta(JSONLead) + ")", - ) -) - -// Page represents a parsed content page. -type Page interface { - // FrontMatter contains the raw frontmatter with relevant delimiters. - FrontMatter() []byte - - // Content contains the raw page content. - Content() []byte - - // IsRenderable denotes that the page should be rendered. - IsRenderable() bool - - // Metadata returns the unmarshalled frontmatter data. - Metadata() (interface{}, error) -} - -// page implements the Page interface. -type page struct { - render bool - frontmatter []byte - content []byte -} - -// Content returns the raw page content. -func (p *page) Content() []byte { - return p.content -} - -// FrontMatter contains the raw frontmatter with relevant delimiters. -func (p *page) FrontMatter() []byte { - return p.frontmatter -} - -// IsRenderable denotes that the page should be rendered. -func (p *page) IsRenderable() bool { - return p.render -} - -// Metadata returns the unmarshalled frontmatter data. -func (p *page) Metadata() (meta interface{}, err error) { - frontmatter := p.FrontMatter() - - if len(frontmatter) != 0 { - fm := DetectFrontMatter(rune(frontmatter[0])) - if fm != nil { - meta, err = fm.Parse(frontmatter) - if err != nil { - return - } - } - } - return -} - -// ReadFrom reads the content from an io.Reader and constructs a page. -func ReadFrom(r io.Reader) (p Page, err error) { - reader := bufio.NewReader(r) - - // chomp BOM and assume UTF-8 - if err = chompBOM(reader); err != nil && err != io.EOF { - return - } - if err = chompWhitespace(reader); err != nil && err != io.EOF { - return - } - if err = chompFrontmatterStartComment(reader); err != nil && err != io.EOF { - return - } - - firstLine, err := peekLine(reader) - if err != nil && err != io.EOF { - return - } - - newp := new(page) - newp.render = shouldRender(firstLine) - - if newp.render && isFrontMatterDelim(firstLine) { - left, right := determineDelims(firstLine) - fm, err := extractFrontMatterDelims(reader, left, right) - if err != nil { - return nil, err - } - newp.frontmatter = fm - } else if newp.render && goorgeous.IsKeyword(firstLine) { - fm, err := goorgeous.ExtractOrgHeaders(reader) - if err != nil { - return nil, err - } - newp.frontmatter = fm - } - - content, err := extractContent(reader) - if err != nil { - return nil, err - } - - newp.content = content - - return newp, nil -} - -// chompBOM scans any leading Unicode Byte Order Markers from r. -func chompBOM(r io.RuneScanner) (err error) { - for { - c, _, err := r.ReadRune() - if err != nil { - return err - } - if c != BOM { - r.UnreadRune() - return nil - } - } -} - -// chompWhitespace scans any leading Unicode whitespace from r. -func chompWhitespace(r io.RuneScanner) (err error) { - for { - c, _, err := r.ReadRune() - if err != nil { - return err - } - if !unicode.IsSpace(c) { - r.UnreadRune() - return nil - } - } -} - -// chompFrontmatterStartComment checks r for a leading HTML comment. If a -// comment is found, it is read from r and then whitespace is trimmed from the -// beginning of r. -func chompFrontmatterStartComment(r *bufio.Reader) (err error) { - candidate, err := r.Peek(32) - if err != nil { - return err - } - - str := string(candidate) - if strings.HasPrefix(str, HTMLCommentStart) { - lineEnd := strings.IndexAny(str, "\n") - if lineEnd == -1 { - //TODO: if we can't find it, Peek more? - return nil - } - testStr := strings.TrimSuffix(str[0:lineEnd], "\r") - if strings.Contains(testStr, HTMLCommentEnd) { - return nil - } - buf := make([]byte, lineEnd) - if _, err = r.Read(buf); err != nil { - return - } - if err = chompWhitespace(r); err != nil { - return err - } - } - - return nil -} - -// chompFrontmatterEndComment checks r for a trailing HTML comment. -func chompFrontmatterEndComment(r *bufio.Reader) (err error) { - candidate, err := r.Peek(32) - if err != nil { - return err - } - - str := string(candidate) - lineEnd := strings.IndexAny(str, "\n") - if lineEnd == -1 { - return nil - } - testStr := strings.TrimSuffix(str[0:lineEnd], "\r") - if strings.Contains(testStr, HTMLCommentStart) { - return nil - } - - //TODO: if we can't find it, Peek more? - if strings.HasSuffix(testStr, HTMLCommentEnd) { - buf := make([]byte, lineEnd) - if _, err = r.Read(buf); err != nil { - return - } - if err = chompWhitespace(r); err != nil { - return err - } - } - - return nil -} - -func peekLine(r *bufio.Reader) (line []byte, err error) { - firstFive, err := r.Peek(5) - if err != nil { - return - } - idx := bytes.IndexByte(firstFive, '\n') - if idx == -1 { - return firstFive, nil - } - idx++ // include newline. - return firstFive[:idx], nil -} - -func shouldRender(lead []byte) (frontmatter bool) { - if len(lead) <= 0 { - return - } - - if bytes.Equal(lead[:1], []byte(HTMLLead)) { - return - } - return true -} - -func isFrontMatterDelim(data []byte) bool { - return delims.Match(data) -} - -func determineDelims(firstLine []byte) (left, right []byte) { - switch firstLine[0] { - case YAMLLead[0]: - return []byte(YAMLDelim), []byte(YAMLDelim) - case TOMLLead[0]: - return []byte(TOMLDelim), []byte(TOMLDelim) - case JSONLead[0]: - return []byte(JSONLead), []byte("}") - default: - panic(fmt.Sprintf("Unable to determine delims from %q", firstLine)) - } -} - -// extractFrontMatterDelims takes a frontmatter from the content bufio.Reader. -// Beginning white spaces of the bufio.Reader must be trimmed before call this -// function. -func extractFrontMatterDelims(r *bufio.Reader, left, right []byte) (fm []byte, err error) { - var ( - c byte - buf bytes.Buffer - level int - sameDelim = bytes.Equal(left, right) - inQuote bool - escapeState int - ) - // Frontmatter must start with a delimiter. To check it first, - // pre-reads beginning delimiter length - 1 bytes from Reader - for i := 0; i < len(left)-1; i++ { - if c, err = r.ReadByte(); err != nil { - return nil, fmt.Errorf("unable to read frontmatter at filepos %d: %s", buf.Len(), err) - } - if err = buf.WriteByte(c); err != nil { - return nil, err - } - } - - // Reads a character from Reader one by one and checks it matches the - // last character of one of delimiters to find the last character of - // frontmatter. If it matches, makes sure it contains the delimiter - // and if so, also checks it is followed by CR+LF or LF when YAML, - // TOML case. In JSON case, nested delimiters must be parsed and it - // is expected that the delimiter only contains one character. - for { - if c, err = r.ReadByte(); err != nil { - return nil, fmt.Errorf("unable to read frontmatter at filepos %d: %s", buf.Len(), err) - } - if err = buf.WriteByte(c); err != nil { - return nil, err - } - - switch c { - case '"': - if escapeState != 1 { - inQuote = !inQuote - } - case '\\': - escapeState++ - case left[len(left)-1]: - if sameDelim { // YAML, TOML case - if bytes.HasSuffix(buf.Bytes(), left) && (buf.Len() == len(left) || buf.Bytes()[buf.Len()-len(left)-1] == '\n') { - nextByte: - c, err = r.ReadByte() - if err != nil { - // It is ok that the end delimiter ends with EOF - if err != io.EOF || level != 1 { - return nil, fmt.Errorf("unable to read frontmatter at filepos %d: %s", buf.Len(), err) - } - } else { - switch c { - case '\n': - // ok - case ' ': - // Consume this byte and try to match again - goto nextByte - case '\r': - if err = buf.WriteByte(c); err != nil { - return nil, err - } - if c, err = r.ReadByte(); err != nil { - return nil, fmt.Errorf("unable to read frontmatter at filepos %d: %s", buf.Len(), err) - } - if c != '\n' { - return nil, fmt.Errorf("frontmatter delimiter must be followed by CR+LF or LF but those can't be found at filepos %d", buf.Len()) - } - default: - return nil, fmt.Errorf("frontmatter delimiter must be followed by CR+LF or LF but those can't be found at filepos %d", buf.Len()) - } - if err = buf.WriteByte(c); err != nil { - return nil, err - } - } - if level == 0 { - level = 1 - } else { - level = 0 - } - } - } else { // JSON case - if !inQuote { - level++ - } - } - case right[len(right)-1]: // JSON case only reaches here - if !inQuote { - level-- - } - } - - if level == 0 { - // Consumes white spaces immediately behind frontmatter - if err = chompWhitespace(r); err != nil && err != io.EOF { - return nil, err - } - if err = chompFrontmatterEndComment(r); err != nil && err != io.EOF { - return nil, err - } - - return buf.Bytes(), nil - } - - if c != '\\' { - escapeState = 0 - } - - } -} - -func extractContent(r io.Reader) (content []byte, err error) { - wr := new(bytes.Buffer) - if _, err = wr.ReadFrom(r); err != nil { - return - } - return wr.Bytes(), nil -} diff --git a/parser/page_test.go b/parser/page_test.go deleted file mode 100644 index 07d7660d4..000000000 --- a/parser/page_test.go +++ /dev/null @@ -1,130 +0,0 @@ -package parser - -import ( - "fmt" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestPage(t *testing.T) { - cases := []struct { - raw string - - content string - frontmatter string - renderable bool - metadata map[string]interface{} - }{ - { - testPageLeader + jsonPageFrontMatter + "\n" + testPageTrailer + jsonPageContent, - jsonPageContent, - jsonPageFrontMatter, - true, - map[string]interface{}{ - "title": "JSON Test 1", - "social": []interface{}{ - []interface{}{"a", "#"}, - []interface{}{"b", "#"}, - }, - }, - }, - { - testPageLeader + tomlPageFrontMatter + testPageTrailer + tomlPageContent, - tomlPageContent, - tomlPageFrontMatter, - true, - map[string]interface{}{ - "title": "TOML Test 1", - "social": []interface{}{ - []interface{}{"a", "#"}, - []interface{}{"b", "#"}, - }, - }, - }, - { - testPageLeader + yamlPageFrontMatter + testPageTrailer + yamlPageContent, - yamlPageContent, - yamlPageFrontMatter, - true, - map[string]interface{}{ - "title": "YAML Test 1", - "social": []interface{}{ - []interface{}{"a", "#"}, - []interface{}{"b", "#"}, - }, - }, - }, - { - testPageLeader + orgPageFrontMatter + orgPageContent, - orgPageContent, - orgPageFrontMatter, - true, - map[string]interface{}{ - "TITLE": "Org Test 1", - "categories": []string{"a", "b"}, - }, - }, - } - - for i, c := range cases { - p := pageMust(ReadFrom(strings.NewReader(c.raw))) - meta, err := p.Metadata() - - mesg := fmt.Sprintf("[%d]", i) - - require.Nil(t, err, mesg) - assert.Equal(t, c.content, string(p.Content()), mesg+" content") - assert.Equal(t, c.frontmatter, string(p.FrontMatter()), mesg+" frontmatter") - assert.Equal(t, c.renderable, p.IsRenderable(), mesg+" renderable") - assert.Equal(t, c.metadata, meta, mesg+" metadata") - } -} - -var ( - testWhitespace = "\t\t\n\n" - testPageLeader = "\ufeff" + testWhitespace + "\n" - - jsonPageContent = "# JSON Test\n" - jsonPageFrontMatter = `{ - "title": "JSON Test 1", - "social": [ - ["a", "#"], - ["b", "#"] - ] -}` - - tomlPageContent = "# TOML Test\n" - tomlPageFrontMatter = `+++ -title = "TOML Test 1" -social = [ - ["a", "#"], - ["b", "#"], -] -+++ -` - - yamlPageContent = "# YAML Test\n" - yamlPageFrontMatter = `--- -title: YAML Test 1 -social: - - - "a" - - "#" - - - "b" - - "#" ---- -` - - orgPageContent = "* Org Test\n" - orgPageFrontMatter = `#+TITLE: Org Test 1 -#+categories: a b -` - - pageHTMLComment = ` -` -) diff --git a/parser/parse_frontmatter_test.go b/parser/parse_frontmatter_test.go deleted file mode 100644 index 14e69abfd..000000000 --- a/parser/parse_frontmatter_test.go +++ /dev/null @@ -1,324 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package parser - -// TODO Support Mac Encoding (\r) - -import ( - "bufio" - "bytes" - "io" - "os" - "path/filepath" - "strings" - "testing" -) - -const ( - contentNoFrontmatter = "a page with no front matter" - contentWithFrontmatter = "---\ntitle: front matter\n---\nContent with front matter" - contentHTMLNoDoctype = "\n\t\n\t\n" - contentHTMLWithDoctype = "" - contentHTMLWithFrontmatter = "---\ntitle: front matter\n---\n" - contentHTML = " " - contentLinefeedAndHTML = "\n" - contentIncompleteEndFrontmatterDelim = "---\ntitle: incomplete end fm delim\n--\nincomplete frontmatter delim" - contentMissingEndFrontmatterDelim = "---\ntitle: incomplete end fm delim\nincomplete frontmatter delim" - contentSlugWorking = "---\ntitle: slug doc 2\nslug: slug-doc-2\n\n---\nslug doc 2 content" - contentSlugWorkingVariation = "---\ntitle: slug doc 3\nslug: slug-doc 3\n---\nslug doc 3 content" - contentSlugBug = "---\ntitle: slug doc 2\nslug: slug-doc-2\n---\nslug doc 2 content" - contentSlugWithJSONFrontMatter = "{\n \"categories\": \"d\",\n \"tags\": [\n \"a\", \n \"b\", \n \"c\"\n ]\n}\nJSON Front Matter with tags and categories" - contentWithJSONLooseFrontmatter = "{\n \"categories\": \"d\"\n \"tags\": [\n \"a\" \n \"b\" \n \"c\"\n ]\n}\nJSON Front Matter with tags and categories" - contentSlugWithJSONFrontMatterFirstLineOnly = "{\"categories\":\"d\",\"tags\":[\"a\",\"b\",\"c\"]}\nJSON Front Matter with tags and categories" - contentSlugWithJSONFrontMatterFirstLine = "{\"categories\":\"d\",\n \"tags\":[\"a\",\"b\",\"c\"]}\nJSON Front Matter with tags and categories" -) - -var lineEndings = []string{"\n", "\r\n"} -var delimiters = []string{"---", "+++"} - -func pageMust(p Page, err error) *page { - if err != nil { - panic(err) - } - return p.(*page) -} - -func TestDegenerateCreatePageFrom(t *testing.T) { - tests := []struct { - content string - }{ - {contentMissingEndFrontmatterDelim}, - {contentIncompleteEndFrontmatterDelim}, - } - - for _, test := range tests { - for _, ending := range lineEndings { - test.content = strings.Replace(test.content, "\n", ending, -1) - _, err := ReadFrom(strings.NewReader(test.content)) - if err == nil { - t.Errorf("Content should return an err:\n%q\n", test.content) - } - } - } -} - -func checkPageRender(t *testing.T, p *page, expected bool) { - if p.render != expected { - t.Errorf("page.render should be %t, got: %t", expected, p.render) - } -} - -func checkPageFrontMatterIsNil(t *testing.T, p *page, content string, expected bool) { - if bool(p.frontmatter == nil) != expected { - t.Logf("\n%q\n", content) - t.Errorf("page.frontmatter == nil? %t, got %t", expected, p.frontmatter == nil) - } -} - -func checkPageFrontMatterContent(t *testing.T, p *page, frontMatter string) { - if p.frontmatter == nil { - return - } - if !bytes.Equal(p.frontmatter, []byte(frontMatter)) { - t.Errorf("frontmatter mismatch\nexp: %q\ngot: %q", frontMatter, p.frontmatter) - } -} - -func checkPageContent(t *testing.T, p *page, expected string) { - if !bytes.Equal(p.content, []byte(expected)) { - t.Errorf("content mismatch\nexp: %q\ngot: %q", expected, p.content) - } -} - -func TestStandaloneCreatePageFrom(t *testing.T) { - tests := []struct { - content string - expectedMustRender bool - frontMatterIsNil bool - frontMatter string - bodycontent string - }{ - - {contentNoFrontmatter, true, true, "", "a page with no front matter"}, - {contentWithFrontmatter, true, false, "---\ntitle: front matter\n---\n", "Content with front matter"}, - {contentHTMLNoDoctype, false, true, "", "\n\t\n\t\n"}, - {contentHTMLWithDoctype, false, true, "", ""}, - {contentHTMLWithFrontmatter, true, false, "---\ntitle: front matter\n---\n", ""}, - {contentHTML, false, true, "", ""}, - {contentLinefeedAndHTML, false, true, "", ""}, - {contentSlugWithJSONFrontMatter, true, false, "{\n \"categories\": \"d\",\n \"tags\": [\n \"a\", \n \"b\", \n \"c\"\n ]\n}", "JSON Front Matter with tags and categories"}, - {contentWithJSONLooseFrontmatter, true, false, "{\n \"categories\": \"d\"\n \"tags\": [\n \"a\" \n \"b\" \n \"c\"\n ]\n}", "JSON Front Matter with tags and categories"}, - {contentSlugWithJSONFrontMatterFirstLineOnly, true, false, "{\"categories\":\"d\",\"tags\":[\"a\",\"b\",\"c\"]}", "JSON Front Matter with tags and categories"}, - {contentSlugWithJSONFrontMatterFirstLine, true, false, "{\"categories\":\"d\",\n \"tags\":[\"a\",\"b\",\"c\"]}", "JSON Front Matter with tags and categories"}, - {contentSlugWorking, true, false, "---\ntitle: slug doc 2\nslug: slug-doc-2\n\n---\n", "slug doc 2 content"}, - {contentSlugWorkingVariation, true, false, "---\ntitle: slug doc 3\nslug: slug-doc 3\n---\n", "slug doc 3 content"}, - {contentSlugBug, true, false, "---\ntitle: slug doc 2\nslug: slug-doc-2\n---\n", "slug doc 2 content"}, - } - - for _, test := range tests { - for _, ending := range lineEndings { - test.content = strings.Replace(test.content, "\n", ending, -1) - test.frontMatter = strings.Replace(test.frontMatter, "\n", ending, -1) - test.bodycontent = strings.Replace(test.bodycontent, "\n", ending, -1) - - p := pageMust(ReadFrom(strings.NewReader(test.content))) - - checkPageRender(t, p, test.expectedMustRender) - checkPageFrontMatterIsNil(t, p, test.content, test.frontMatterIsNil) - checkPageFrontMatterContent(t, p, test.frontMatter) - checkPageContent(t, p, test.bodycontent) - } - } -} - -func BenchmarkLongFormRender(b *testing.B) { - - tests := []struct { - filename string - buf []byte - }{ - {filename: "long_text_test.md"}, - } - for i, test := range tests { - path := filepath.FromSlash(test.filename) - f, err := os.Open(path) - if err != nil { - b.Fatalf("Unable to open %s: %s", path, err) - } - defer f.Close() - membuf := new(bytes.Buffer) - if _, err := io.Copy(membuf, f); err != nil { - b.Fatalf("Unable to read %s: %s", path, err) - } - tests[i].buf = membuf.Bytes() - } - - b.ResetTimer() - - for i := 0; i <= b.N; i++ { - for _, test := range tests { - ReadFrom(bytes.NewReader(test.buf)) - } - } -} - -func TestPageShouldRender(t *testing.T) { - tests := []struct { - content []byte - expected bool - }{ - {[]byte{}, false}, - {[]byte{'<'}, false}, - {[]byte{'-'}, true}, - {[]byte("--"), true}, - {[]byte("---"), true}, - {[]byte("---\n"), true}, - {[]byte{'a'}, true}, - } - - for _, test := range tests { - for _, ending := range lineEndings { - test.content = bytes.Replace(test.content, []byte("\n"), []byte(ending), -1) - if render := shouldRender(test.content); render != test.expected { - - t.Errorf("Expected %s to shouldRender = %t, got: %t", test.content, test.expected, render) - } - } - } -} - -func TestPageHasFrontMatter(t *testing.T) { - tests := []struct { - content []byte - expected bool - }{ - {[]byte{'-'}, false}, - {[]byte("--"), false}, - {[]byte("---"), false}, - {[]byte("---\n"), true}, - {[]byte("---\n"), true}, - {[]byte("--- \n"), true}, - {[]byte("--- \n"), true}, - {[]byte{'a'}, false}, - {[]byte{'{'}, true}, - {[]byte("{\n "), true}, - {[]byte{'}'}, false}, - } - for _, test := range tests { - for _, ending := range lineEndings { - test.content = bytes.Replace(test.content, []byte("\n"), []byte(ending), -1) - if isFrontMatterDelim := isFrontMatterDelim(test.content); isFrontMatterDelim != test.expected { - t.Errorf("Expected %q isFrontMatterDelim = %t, got: %t", test.content, test.expected, isFrontMatterDelim) - } - } - } -} - -func TestExtractFrontMatter(t *testing.T) { - - tests := []struct { - frontmatter string - extracted []byte - errIsNil bool - }{ - {"", nil, false}, - {"-", nil, false}, - {"---\n", nil, false}, - {"---\nfoobar", nil, false}, - {"---\nfoobar\nbarfoo\nfizbaz\n", nil, false}, - {"---\nblar\n-\n", nil, false}, - {"---\nralb\n---\n", []byte("---\nralb\n---\n"), true}, - {"---\neof\n---", []byte("---\neof\n---"), true}, - {"--- \neof\n---", []byte("---\neof\n---"), true}, - {"---\nminc\n---\ncontent", []byte("---\nminc\n---\n"), true}, - {"---\nminc\n--- \ncontent", []byte("---\nminc\n---\n"), true}, - {"--- \nminc\n--- \ncontent", []byte("---\nminc\n---\n"), true}, - {"---\ncnim\n---\ncontent\n", []byte("---\ncnim\n---\n"), true}, - {"---\ntitle: slug doc 2\nslug: slug-doc-2\n---\ncontent\n", []byte("---\ntitle: slug doc 2\nslug: slug-doc-2\n---\n"), true}, - {"---\npermalink: '/blog/title---subtitle.html'\n---\ncontent\n", []byte("---\npermalink: '/blog/title---subtitle.html'\n---\n"), true}, - } - - for _, test := range tests { - for _, ending := range lineEndings { - test.frontmatter = strings.Replace(test.frontmatter, "\n", ending, -1) - test.extracted = bytes.Replace(test.extracted, []byte("\n"), []byte(ending), -1) - for _, delim := range delimiters { - test.frontmatter = strings.Replace(test.frontmatter, "---", delim, -1) - test.extracted = bytes.Replace(test.extracted, []byte("---"), []byte(delim), -1) - line, err := peekLine(bufio.NewReader(strings.NewReader(test.frontmatter))) - if err != nil { - continue - } - l, r := determineDelims(line) - fm, err := extractFrontMatterDelims(bufio.NewReader(strings.NewReader(test.frontmatter)), l, r) - if (err == nil) != test.errIsNil { - t.Logf("\n%q\n", string(test.frontmatter)) - t.Errorf("Expected err == nil => %t, got: %t. err: %s", test.errIsNil, err == nil, err) - continue - } - if !bytes.Equal(fm, test.extracted) { - t.Errorf("Frontmatter did not match:\nexp: %q\ngot: %q", string(test.extracted), fm) - } - } - } - } -} - -func TestExtractFrontMatterDelim(t *testing.T) { - var ( - noErrExpected = true - errExpected = false - ) - tests := []struct { - frontmatter string - extracted string - errIsNil bool - }{ - {"", "", errExpected}, - {"{", "", errExpected}, - {"{}", "{}", noErrExpected}, - {"{} ", "{}", noErrExpected}, - {"{ } ", "{ }", noErrExpected}, - {"{ { }", "", errExpected}, - {"{ { } }", "{ { } }", noErrExpected}, - {"{ { } { } }", "{ { } { } }", noErrExpected}, - {"{\n{\n}\n}\n", "{\n{\n}\n}", noErrExpected}, - {"{\n \"categories\": \"d\",\n \"tags\": [\n \"a\", \n \"b\", \n \"c\"\n ]\n}\nJSON Front Matter with tags and categories", "{\n \"categories\": \"d\",\n \"tags\": [\n \"a\", \n \"b\", \n \"c\"\n ]\n}", noErrExpected}, - {"{\n \"categories\": \"d\"\n \"tags\": [\n \"a\" \n \"b\" \n \"c\"\n ]\n}\nJSON Front Matter with tags and categories", "{\n \"categories\": \"d\"\n \"tags\": [\n \"a\" \n \"b\" \n \"c\"\n ]\n}", noErrExpected}, - // Issue #3511 - {`{ "title": "{" }`, `{ "title": "{" }`, noErrExpected}, - {`{ "title": "{}" }`, `{ "title": "{}" }`, noErrExpected}, - // Issue #3661 - {`{ "title": "\"" }`, `{ "title": "\"" }`, noErrExpected}, - {`{ "title": "\"{", "other": "\"{}" }`, `{ "title": "\"{", "other": "\"{}" }`, noErrExpected}, - {`{ "title": "\"Foo\"" }`, `{ "title": "\"Foo\"" }`, noErrExpected}, - {`{ "title": "\"Foo\"\"" }`, `{ "title": "\"Foo\"\"" }`, noErrExpected}, - {`{ "url": "http:\/\/example.com\/play\/url?id=1" }`, `{ "url": "http:\/\/example.com\/play\/url?id=1" }`, noErrExpected}, - {`{ "test": "\"New\r\nString\"" }`, `{ "test": "\"New\r\nString\"" }`, noErrExpected}, - {`{ "test": "RTS\/RPG" }`, `{ "test": "RTS\/RPG" }`, noErrExpected}, - } - - for i, test := range tests { - fm, err := extractFrontMatterDelims(bufio.NewReader(strings.NewReader(test.frontmatter)), []byte("{"), []byte("}")) - if (err == nil) != test.errIsNil { - t.Logf("\n%q\n", string(test.frontmatter)) - t.Errorf("[%d] Expected err == nil => %t, got: %t. err: %s", i, test.errIsNil, err == nil, err) - continue - } - if !bytes.Equal(fm, []byte(test.extracted)) { - t.Logf("\n%q\n", string(test.frontmatter)) - t.Errorf("[%d] Frontmatter did not match:\nexp: %q\ngot: %q", i, string(test.extracted), fm) - } - } -} diff --git a/pull-docs.sh b/pull-docs.sh deleted file mode 100755 index afe8fce85..000000000 --- a/pull-docs.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -# We may extend this to also push changes in the other direction, but this is the most important step. -git subtree pull --prefix=docs/ https://github.com/gohugoio/hugoDocs.git master --squash - diff --git a/pull-theme.sh b/pull-theme.sh new file mode 100755 index 000000000..828b6cfb4 --- /dev/null +++ b/pull-theme.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +git subtree pull --prefix=themes/gohugoioTheme/ git@github.com:gohugoio/gohugoioTheme.git master --squash + diff --git a/releaser/git.go b/releaser/git.go deleted file mode 100644 index cfef434dd..000000000 --- a/releaser/git.go +++ /dev/null @@ -1,306 +0,0 @@ -// Copyright 2017-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package releaser - -import ( - "fmt" - "os/exec" - "regexp" - "sort" - "strconv" - "strings" -) - -var issueRe = regexp.MustCompile(`(?i)[Updates?|Closes?|Fix.*|See] #(\d+)`) - -const ( - notesChanges = "notesChanges" - templateChanges = "templateChanges" - coreChanges = "coreChanges" - outChanges = "outChanges" - otherChanges = "otherChanges" -) - -type changeLog struct { - Version string - Enhancements map[string]gitInfos - Fixes map[string]gitInfos - Notes gitInfos - All gitInfos - Docs gitInfos - - // Overall stats - Repo *gitHubRepo - ContributorCount int - ThemeCount int -} - -func newChangeLog(infos, docInfos gitInfos) *changeLog { - return &changeLog{ - Enhancements: make(map[string]gitInfos), - Fixes: make(map[string]gitInfos), - All: infos, - Docs: docInfos, - } -} - -func (l *changeLog) addGitInfo(isFix bool, info gitInfo, category string) { - var ( - infos gitInfos - found bool - segment map[string]gitInfos - ) - - if category == notesChanges { - l.Notes = append(l.Notes, info) - return - } else if isFix { - segment = l.Fixes - } else { - segment = l.Enhancements - } - - infos, found = segment[category] - if !found { - infos = gitInfos{} - } - - infos = append(infos, info) - segment[category] = infos -} - -func gitInfosToChangeLog(infos, docInfos gitInfos) *changeLog { - log := newChangeLog(infos, docInfos) - for _, info := range infos { - los := strings.ToLower(info.Subject) - isFix := strings.Contains(los, "fix") - var category = otherChanges - - // TODO(bep) improve - if regexp.MustCompile("(?i)deprecate").MatchString(los) { - category = notesChanges - } else if regexp.MustCompile("(?i)tpl|tplimpl:|layout").MatchString(los) { - category = templateChanges - } else if regexp.MustCompile("(?i)hugolib:").MatchString(los) { - category = coreChanges - } else if regexp.MustCompile("(?i)out(put)?:|media:|Output|Media").MatchString(los) { - category = outChanges - } - - // Trim package prefix. - colonIdx := strings.Index(info.Subject, ":") - if colonIdx != -1 && colonIdx < (len(info.Subject)/2) { - info.Subject = info.Subject[colonIdx+1:] - } - - info.Subject = strings.TrimSpace(info.Subject) - - log.addGitInfo(isFix, info, category) - } - - return log -} - -type gitInfo struct { - Hash string - Author string - Subject string - Body string - - GitHubCommit *gitHubCommit -} - -func (g gitInfo) Issues() []int { - return extractIssues(g.Body) -} - -func (g gitInfo) AuthorID() string { - if g.GitHubCommit != nil { - return g.GitHubCommit.Author.Login - } - return g.Author -} - -func extractIssues(body string) []int { - var i []int - m := issueRe.FindAllStringSubmatch(body, -1) - for _, mm := range m { - issueID, err := strconv.Atoi(mm[1]) - if err != nil { - continue - } - i = append(i, issueID) - } - return i -} - -type gitInfos []gitInfo - -func git(args ...string) (string, error) { - cmd := exec.Command("git", args...) - out, err := cmd.CombinedOutput() - if err != nil { - return "", fmt.Errorf("git failed: %q: %q (%q)", err, out, args) - } - return string(out), nil -} - -func getGitInfos(tag, repoPath string, remote bool) (gitInfos, error) { - return getGitInfosBefore("HEAD", tag, repoPath, remote) -} - -type countribCount struct { - Author string - GitHubAuthor gitHubAuthor - Count int -} - -func (c countribCount) AuthorLink() string { - if c.GitHubAuthor.HtmlURL != "" { - return fmt.Sprintf("[@%s](%s)", c.GitHubAuthor.Login, c.GitHubAuthor.HtmlURL) - } - - if !strings.Contains(c.Author, "@") { - return c.Author - } - - return c.Author[:strings.Index(c.Author, "@")] - -} - -type contribCounts []countribCount - -func (c contribCounts) Less(i, j int) bool { return c[i].Count > c[j].Count } -func (c contribCounts) Len() int { return len(c) } -func (c contribCounts) Swap(i, j int) { c[i], c[j] = c[j], c[i] } - -func (g gitInfos) ContribCountPerAuthor() contribCounts { - var c contribCounts - - counters := make(map[string]countribCount) - - for _, gi := range g { - authorID := gi.AuthorID() - if count, ok := counters[authorID]; ok { - count.Count = count.Count + 1 - counters[authorID] = count - } else { - var ghA gitHubAuthor - if gi.GitHubCommit != nil { - ghA = gi.GitHubCommit.Author - } - authorCount := countribCount{Count: 1, Author: gi.Author, GitHubAuthor: ghA} - counters[authorID] = authorCount - } - } - - for _, v := range counters { - c = append(c, v) - } - - sort.Sort(c) - return c -} - -func getGitInfosBefore(ref, tag, repoPath string, remote bool) (gitInfos, error) { - - var g gitInfos - - log, err := gitLogBefore(ref, tag, repoPath) - if err != nil { - return g, err - } - - log = strings.Trim(log, "\n\x1e'") - entries := strings.Split(log, "\x1e") - - for _, entry := range entries { - items := strings.Split(entry, "\x1f") - gi := gitInfo{ - Hash: items[0], - Author: items[1], - Subject: items[2], - Body: items[3], - } - if remote { - gc, err := fetchCommit(gi.Hash) - if err == nil { - gi.GitHubCommit = &gc - } - } - g = append(g, gi) - } - - return g, nil -} - -// Ignore autogenerated commits etc. in change log. This is a regexp. -const ignoredCommits = "releaser?:|snapcraft:|Merge commit|Squashed|Revert" - -func gitLogBefore(ref, tag, repoPath string) (string, error) { - var prevTag string - var err error - if tag != "" { - prevTag = tag - } else { - prevTag, err = gitVersionTagBefore(ref) - if err != nil { - return "", err - } - } - - defaultArgs := []string{"log", "-E", fmt.Sprintf("--grep=%s", ignoredCommits), "--invert-grep", "--pretty=format:%x1e%h%x1f%aE%x1f%s%x1f%b", "--abbrev-commit", prevTag + ".." + ref} - - var args []string - - if repoPath != "" { - args = append([]string{"-C", repoPath}, defaultArgs...) - } else { - args = defaultArgs - } - - log, err := git(args...) - if err != nil { - return ",", err - } - - return log, err -} - -func gitVersionTagBefore(ref string) (string, error) { - return gitShort("describe", "--tags", "--abbrev=0", "--always", "--match", "v[0-9]*", ref+"^") -} - -func gitLog() (string, error) { - return gitLogBefore("HEAD", "", "") -} - -func gitShort(args ...string) (output string, err error) { - output, err = git(args...) - return strings.Replace(strings.Split(output, "\n")[0], "'", "", -1), err -} - -func tagExists(tag string) (bool, error) { - out, err := git("tag", "-l", tag) - - if err != nil { - return false, err - } - - if strings.Contains(out, tag) { - return true, nil - } - - return false, nil -} diff --git a/releaser/git_test.go b/releaser/git_test.go deleted file mode 100644 index 8053f7702..000000000 --- a/releaser/git_test.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2017-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package releaser - -import ( - "os" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestGitInfos(t *testing.T) { - skipIfCI(t) - infos, err := getGitInfos("v0.20", "", false) - - require.NoError(t, err) - require.True(t, len(infos) > 0) - -} - -func TestIssuesRe(t *testing.T) { - - body := ` -This is a commit message. - -Updates #123 -Fix #345 -closes #543 -See #456 - ` - - issues := extractIssues(body) - - require.Len(t, issues, 4) - require.Equal(t, 123, issues[0]) - require.Equal(t, 543, issues[2]) - -} - -func TestGitVersionTagBefore(t *testing.T) { - skipIfCI(t) - v1, err := gitVersionTagBefore("v0.18") - require.NoError(t, err) - require.Equal(t, "v0.17", v1) -} - -func TestTagExists(t *testing.T) { - skipIfCI(t) - b1, err := tagExists("v0.18") - require.NoError(t, err) - require.True(t, b1) - - b2, err := tagExists("adfagdsfg") - require.NoError(t, err) - require.False(t, b2) - -} - -func skipIfCI(t *testing.T) { - if os.Getenv("CI") != "" { - // Travis has an ancient git with no --invert-grep: https://github.com/travis-ci/travis-ci/issues/6328 - // Also Travis clones very shallowly, making some of the tests above shaky. - t.Skip("Skip git test on Linux to make Travis happy.") - } -} diff --git a/releaser/github.go b/releaser/github.go deleted file mode 100644 index c1e7691b8..000000000 --- a/releaser/github.go +++ /dev/null @@ -1,129 +0,0 @@ -package releaser - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "os" -) - -var ( - gitHubCommitsApi = "https://api.github.com/repos/gohugoio/hugo/commits/%s" - gitHubRepoApi = "https://api.github.com/repos/gohugoio/hugo" - gitHubContributorsApi = "https://api.github.com/repos/gohugoio/hugo/contributors" -) - -type gitHubCommit struct { - Author gitHubAuthor `json:"author"` - HtmlURL string `json:"html_url"` -} - -type gitHubAuthor struct { - ID int `json:"id"` - Login string `json:"login"` - HtmlURL string `json:"html_url"` - AvatarURL string `json:"avatar_url"` -} - -type gitHubRepo struct { - ID int `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - HtmlURL string `json:"html_url"` - Stars int `json:"stargazers_count"` - Contributors []gitHubContributor -} - -type gitHubContributor struct { - ID int `json:"id"` - Login string `json:"login"` - HtmlURL string `json:"html_url"` - Contributions int `json:"contributions"` -} - -func fetchCommit(ref string) (gitHubCommit, error) { - var commit gitHubCommit - - u := fmt.Sprintf(gitHubCommitsApi, ref) - - req, err := http.NewRequest("GET", u, nil) - if err != nil { - return commit, err - } - - err = doGitHubRequest(req, &commit) - - return commit, err -} - -func fetchRepo() (gitHubRepo, error) { - var repo gitHubRepo - - req, err := http.NewRequest("GET", gitHubRepoApi, nil) - if err != nil { - return repo, err - } - - err = doGitHubRequest(req, &repo) - if err != nil { - return repo, err - } - - var contributors []gitHubContributor - page := 0 - for { - page++ - var currPage []gitHubContributor - url := fmt.Sprintf(gitHubContributorsApi+"?page=%d", page) - - req, err = http.NewRequest("GET", url, nil) - if err != nil { - return repo, err - } - - err = doGitHubRequest(req, &currPage) - if err != nil { - return repo, err - } - if len(currPage) == 0 { - break - } - - contributors = append(contributors, currPage...) - - } - - repo.Contributors = contributors - - return repo, err - -} - -func doGitHubRequest(req *http.Request, v interface{}) error { - addGitHubToken(req) - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - - if isError(resp) { - b, _ := ioutil.ReadAll(resp.Body) - return fmt.Errorf("GitHub lookup failed: %s", string(b)) - } - - return json.NewDecoder(resp.Body).Decode(v) -} - -func isError(resp *http.Response) bool { - return resp.StatusCode < 200 || resp.StatusCode > 299 -} - -func addGitHubToken(req *http.Request) { - gitHubToken := os.Getenv("GITHUB_TOKEN") - if gitHubToken != "" { - req.Header.Add("Authorization", "token "+gitHubToken) - } -} diff --git a/releaser/github_test.go b/releaser/github_test.go deleted file mode 100644 index 7feae75f5..000000000 --- a/releaser/github_test.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2017-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package releaser - -import ( - "fmt" - "os" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestGitHubLookupCommit(t *testing.T) { - skipIfNoToken(t) - commit, err := fetchCommit("793554108763c0984f1a1b1a6ee5744b560d78d0") - require.NoError(t, err) - fmt.Println(commit) -} - -func TestFetchRepo(t *testing.T) { - skipIfNoToken(t) - repo, err := fetchRepo() - require.NoError(t, err) - fmt.Println(">>", len(repo.Contributors)) -} - -func skipIfNoToken(t *testing.T) { - if os.Getenv("GITHUB_TOKEN") == "" { - t.Skip("Skip test against GitHub as no GITHUB_TOKEN set.") - } -} diff --git a/releaser/releasenotes_writer.go b/releaser/releasenotes_writer.go deleted file mode 100644 index 0c6f297a4..000000000 --- a/releaser/releasenotes_writer.go +++ /dev/null @@ -1,275 +0,0 @@ -// Copyright 2017-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package releaser implements a set of utilities and a wrapper around Goreleaser -// to help automate the Hugo release process. -package releaser - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "net/http" - "os" - "path/filepath" - "strings" - "text/template" - "time" -) - -const ( - issueLinkTemplate = "[#%d](https://github.com/gohugoio/hugo/issues/%d)" - linkTemplate = "[%s](%s)" - releaseNotesMarkdownTemplate = ` -{{- $patchRelease := isPatch . -}} -{{- $contribsPerAuthor := .All.ContribCountPerAuthor -}} -{{- $docsContribsPerAuthor := .Docs.ContribCountPerAuthor -}} -{{- if $patchRelease }} -{{ if eq (len .All) 1 }} -This is a bug-fix release with one important fix. -{{ else }} -This is a bug-fix release with a couple of important fixes. -{{ end }} -{{ else }} -This release represents **{{ len .All }} contributions by {{ len $contribsPerAuthor }} contributors** to the main Hugo code base. -{{ end -}} - -{{- if gt (len $contribsPerAuthor) 3 -}} -{{- $u1 := index $contribsPerAuthor 0 -}} -{{- $u2 := index $contribsPerAuthor 1 -}} -{{- $u3 := index $contribsPerAuthor 2 -}} -{{- $u4 := index $contribsPerAuthor 3 -}} -{{- $u1.AuthorLink }} leads the Hugo development with a significant amount of contributions, but also a big shoutout to {{ $u2.AuthorLink }}, {{ $u3.AuthorLink }}, and {{ $u4.AuthorLink }} for their ongoing contributions. -And as always a big thanks to [@digitalcraftsman](https://github.com/digitalcraftsman) for his relentless work on keeping the documentation and the themes site in pristine condition. -{{ end }} -{{- if not $patchRelease }} -Many have also been busy writing and fixing the documentation in [hugoDocs](https://github.com/gohugoio/hugoDocs), -which has received **{{ len .Docs }} contributions by {{ len $docsContribsPerAuthor }} contributors**. -{{- if gt (len $docsContribsPerAuthor) 3 -}} -{{- $u1 := index $docsContribsPerAuthor 0 -}} -{{- $u2 := index $docsContribsPerAuthor 1 -}} -{{- $u3 := index $docsContribsPerAuthor 2 -}} -{{- $u4 := index $docsContribsPerAuthor 3 }} A special thanks to {{ $u1.AuthorLink }}, {{ $u2.AuthorLink }}, {{ $u3.AuthorLink }}, and {{ $u4.AuthorLink }} for their work on the documentation site. -{{ end }} -{{ end }} -Hugo now has: - -{{ with .Repo -}} -* {{ .Stars }}+ [stars](https://github.com/gohugoio/hugo/stargazers) -* {{ len .Contributors }}+ [contributors](https://github.com/gohugoio/hugo/graphs/contributors) -{{- end -}} -{{ with .ThemeCount }} -* {{ . }}+ [themes](http://themes.gohugo.io/) -{{ end }} -{{ with .Notes }} -## Notes -{{ template "change-section" . }} -{{- end -}} -## Enhancements -{{ template "change-headers" .Enhancements -}} -## Fixes -{{ template "change-headers" .Fixes -}} - -{{ define "change-headers" }} -{{ $tmplChanges := index . "templateChanges" -}} -{{- $outChanges := index . "outChanges" -}} -{{- $coreChanges := index . "coreChanges" -}} -{{- $otherChanges := index . "otherChanges" -}} -{{- with $tmplChanges -}} -### Templates -{{ template "change-section" . }} -{{- end -}} -{{- with $outChanges -}} -### Output -{{ template "change-section" . }} -{{- end -}} -{{- with $coreChanges -}} -### Core -{{ template "change-section" . }} -{{- end -}} -{{- with $otherChanges -}} -### Other -{{ template "change-section" . }} -{{- end -}} -{{ end }} - - -{{ define "change-section" }} -{{ range . }} -{{- if .GitHubCommit -}} -* {{ .Subject }} {{ . | commitURL }} {{ . | authorURL }} {{ range .Issues }}{{ . | issue }}{{ end }} -{{ else -}} -* {{ .Subject }} {{ range .Issues }}{{ . | issue }}{{ end }} -{{ end -}} -{{- end }} -{{ end }} -` -) - -var templateFuncs = template.FuncMap{ - "isPatch": func(c changeLog) bool { - return strings.Count(c.Version, ".") > 1 - }, - "issue": func(id int) string { - return fmt.Sprintf(issueLinkTemplate, id, id) - }, - "commitURL": func(info gitInfo) string { - if info.GitHubCommit.HtmlURL == "" { - return "" - } - return fmt.Sprintf(linkTemplate, info.Hash, info.GitHubCommit.HtmlURL) - }, - "authorURL": func(info gitInfo) string { - if info.GitHubCommit.Author.Login == "" { - return "" - } - return fmt.Sprintf(linkTemplate, "@"+info.GitHubCommit.Author.Login, info.GitHubCommit.Author.HtmlURL) - }, -} - -func writeReleaseNotes(version string, infosMain, infosDocs gitInfos, to io.Writer) error { - changes := gitInfosToChangeLog(infosMain, infosDocs) - changes.Version = version - repo, err := fetchRepo() - if err == nil { - changes.Repo = &repo - } - themeCount, err := fetchThemeCount() - if err == nil { - changes.ThemeCount = themeCount - } - - tmpl, err := template.New("").Funcs(templateFuncs).Parse(releaseNotesMarkdownTemplate) - if err != nil { - return err - } - - err = tmpl.Execute(to, changes) - if err != nil { - return err - } - - return nil - -} - -func fetchThemeCount() (int, error) { - resp, err := http.Get("https://raw.githubusercontent.com/gohugoio/hugoThemes/master/.gitmodules") - if err != nil { - return 0, err - } - defer resp.Body.Close() - - b, _ := ioutil.ReadAll(resp.Body) - return bytes.Count(b, []byte("submodule")), nil -} - -func writeReleaseNotesToTmpFile(version string, infosMain, infosDocs gitInfos) (string, error) { - f, err := ioutil.TempFile("", "hugorelease") - if err != nil { - return "", err - } - - defer f.Close() - - if err := writeReleaseNotes(version, infosMain, infosDocs, f); err != nil { - return "", err - } - - return f.Name(), nil -} - -func getReleaseNotesDocsTempDirAndName(version string) (string, string) { - return hugoFilepath("temp"), fmt.Sprintf("%s-relnotes.md", version) -} - -func getReleaseNotesDocsTempFilename(version string) string { - return filepath.Join(getReleaseNotesDocsTempDirAndName(version)) -} - -func (r *ReleaseHandler) writeReleaseNotesToTemp(version string, infosMain, infosDocs gitInfos) (string, error) { - - docsTempPath, name := getReleaseNotesDocsTempDirAndName(version) - - var ( - w io.WriteCloser - ) - - if !r.try { - os.Mkdir(docsTempPath, os.ModePerm) - - f, err := os.Create(filepath.Join(docsTempPath, name)) - if err != nil { - return "", err - } - - name = f.Name() - - defer f.Close() - - w = f - - } else { - w = os.Stdout - } - - if err := writeReleaseNotes(version, infosMain, infosDocs, w); err != nil { - return "", err - } - - return name, nil - -} - -func (r *ReleaseHandler) writeReleaseNotesToDocs(title, sourceFilename string) (string, error) { - targetFilename := filepath.Base(sourceFilename) - contentDir := hugoFilepath("docs/content/news") - targetFullFilename := filepath.Join(contentDir, targetFilename) - - if r.try { - return targetFullFilename, nil - } - - os.Mkdir(contentDir, os.ModePerm) - - b, err := ioutil.ReadFile(sourceFilename) - if err != nil { - return "", err - } - - f, err := os.Create(targetFullFilename) - if err != nil { - return "", err - } - defer f.Close() - - if _, err := f.WriteString(fmt.Sprintf(` ---- -date: %s -title: %q -description: %q -categories: ["Releases"] ---- - - `, time.Now().Format("2006-01-02"), title, title)); err != nil { - return "", err - } - - if _, err := f.Write(b); err != nil { - return "", err - } - - return targetFullFilename, nil - -} diff --git a/releaser/releasenotes_writer_test.go b/releaser/releasenotes_writer_test.go deleted file mode 100644 index f3e984d55..000000000 --- a/releaser/releasenotes_writer_test.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2017-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package commands defines and implements command-line commands and flags -// used by Hugo. Commands and flags are implemented using Cobra. - -package releaser - -import ( - "bytes" - "fmt" - "os" - "testing" - - "github.com/stretchr/testify/require" -) - -func _TestReleaseNotesWriter(t *testing.T) { - if os.Getenv("CI") != "" { - // Travis has an ancient git with no --invert-grep: https://github.com/travis-ci/travis-ci/issues/6328 - t.Skip("Skip git test on CI to make Travis happy.") - } - - var b bytes.Buffer - - // TODO(bep) consider to query GitHub directly for the gitlog with author info, probably faster. - infos, err := getGitInfosBefore("HEAD", "v0.20", "", false) - require.NoError(t, err) - - require.NoError(t, writeReleaseNotes("0.21", infos, infos, &b)) - - fmt.Println(b.String()) - -} diff --git a/releaser/releaser.go b/releaser/releaser.go deleted file mode 100644 index d05683033..000000000 --- a/releaser/releaser.go +++ /dev/null @@ -1,327 +0,0 @@ -// Copyright 2017-present The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package releaser implements a set of utilities and a wrapper around Goreleaser -// to help automate the Hugo release process. -package releaser - -import ( - "errors" - "fmt" - "io/ioutil" - "log" - "os" - "os/exec" - "path/filepath" - "regexp" - "strings" - - "github.com/gohugoio/hugo/helpers" -) - -const commitPrefix = "releaser:" - -// ReleaseHandler provides functionality to release a new version of Hugo. -type ReleaseHandler struct { - cliVersion string - - // If set, we do the releases in 3 steps: - // 1: Create and write a draft release note - // 2: Prepare files for new version - // 3: Release - step int - skipPublish bool - - // Just simulate, no actual changes. - try bool - - git func(args ...string) (string, error) -} - -func (r ReleaseHandler) shouldRelease() bool { - return r.step < 1 || r.shouldContinue() -} - -func (r ReleaseHandler) shouldContinue() bool { - return r.step >= 3 -} - -func (r ReleaseHandler) shouldPrepareReleasenotes() bool { - return r.step < 1 || r.step == 1 -} - -func (r ReleaseHandler) shouldPrepareVersions() bool { - return r.step < 1 || r.step == 2 || r.step > 3 -} - -func (r ReleaseHandler) calculateVersions() (helpers.HugoVersion, helpers.HugoVersion) { - - newVersion := helpers.MustParseHugoVersion(r.cliVersion) - finalVersion := newVersion - finalVersion.PatchLevel = 0 - - newVersion.Suffix = "" - - if newVersion.PatchLevel == 0 { - finalVersion = finalVersion.Next() - } - - finalVersion.Suffix = "-DEV" - - return newVersion, finalVersion -} - -// New initialises a ReleaseHandler. -func New(version string, step int, skipPublish, try bool) *ReleaseHandler { - rh := &ReleaseHandler{cliVersion: version, step: step, skipPublish: skipPublish, try: try} - - if try { - rh.git = func(args ...string) (string, error) { - fmt.Println("git", strings.Join(args, " ")) - return "", nil - } - } else { - rh.git = git - } - - return rh -} - -// Run creates a new release. -func (r *ReleaseHandler) Run() error { - if os.Getenv("GITHUB_TOKEN") == "" { - return errors.New("GITHUB_TOKEN not set, create one here with the repo scope selected: https://github.com/settings/tokens/new") - } - - newVersion, finalVersion := r.calculateVersions() - - version := newVersion.String() - tag := "v" + version - - // Exit early if tag already exists - exists, err := tagExists(tag) - if err != nil { - return err - } - - if exists { - return fmt.Errorf("Tag %q already exists", tag) - } - - var changeLogFromTag string - - if newVersion.PatchLevel == 0 { - // There may have been patch releases between, so set the tag explicitly. - changeLogFromTag = "v" + newVersion.Prev().String() - exists, _ := tagExists(changeLogFromTag) - if !exists { - // fall back to one that exists. - changeLogFromTag = "" - } - } - - var ( - gitCommits gitInfos - gitCommitsDocs gitInfos - ) - - if r.shouldPrepareReleasenotes() || r.shouldRelease() { - gitCommits, err = getGitInfos(changeLogFromTag, "", !r.try) - if err != nil { - return err - } - gitCommitsDocs, err = getGitInfos(changeLogFromTag, "../hugoDocs", !r.try) - if err != nil { - return err - } - } - - if r.shouldPrepareReleasenotes() { - releaseNotesFile, err := r.writeReleaseNotesToTemp(version, gitCommits, gitCommitsDocs) - if err != nil { - return err - } - - if _, err := r.git("add", releaseNotesFile); err != nil { - return err - } - if _, err := r.git("commit", "-m", fmt.Sprintf("%s Add release notes draft for %s\n\n[ci skip]", commitPrefix, newVersion)); err != nil { - return err - } - } - - if r.shouldPrepareVersions() { - - // For docs, for now we assume that: - // The /docs subtree is up to date and ready to go. - // The hugoDocs/dev and hugoDocs/master must be merged manually after release. - // TODO(bep) improve this when we see how it works. - - if err := r.bumpVersions(newVersion); err != nil { - return err - } - - if _, err := r.git("commit", "-a", "-m", fmt.Sprintf("%s Bump versions for release of %s\n\n[ci skip]", commitPrefix, newVersion)); err != nil { - return err - } - } - - if !r.shouldRelease() { - fmt.Printf("Skip release ... Use --state=%d for next or --state=4 to finish\n", r.step+1) - return nil - } - - releaseNotesFile := getReleaseNotesDocsTempFilename(version) - - // Write the release notes to the docs site as well. - docFile, err := r.writeReleaseNotesToDocs(version, releaseNotesFile) - if err != nil { - return err - } - - if _, err := r.git("add", docFile); err != nil { - return err - } - if _, err := r.git("commit", "-m", fmt.Sprintf("%s Add release notes to /docs for release of %s\n\n[ci skip]", commitPrefix, newVersion)); err != nil { - return err - } - - if _, err := r.git("tag", "-a", tag, "-m", fmt.Sprintf("%s %s [ci deploy]", commitPrefix, newVersion)); err != nil { - return err - } - - if _, err := r.git("push", "origin", tag); err != nil { - return err - } - - if err := r.release(releaseNotesFile); err != nil { - return err - } - - if err := r.bumpVersions(finalVersion); err != nil { - return err - } - - if !r.try { - // No longer needed. - if err := os.Remove(releaseNotesFile); err != nil { - return err - } - } - - if _, err := r.git("commit", "-a", "-m", fmt.Sprintf("%s Prepare repository for %s\n\n[ci skip]", commitPrefix, finalVersion)); err != nil { - return err - } - - return nil -} - -func (r *ReleaseHandler) release(releaseNotesFile string) error { - if r.try { - fmt.Println("Skip goreleaser...") - return nil - } - - cmd := exec.Command("goreleaser", "--rm-dist", "--release-notes", releaseNotesFile, "--skip-publish="+fmt.Sprint(r.skipPublish)) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err := cmd.Run() - if err != nil { - return fmt.Errorf("goreleaser failed: %s", err) - } - return nil -} - -func (r *ReleaseHandler) bumpVersions(ver helpers.HugoVersion) error { - fromDev := "" - toDev := "" - - if ver.Suffix != "" { - toDev = "-DEV" - } else { - fromDev = "-DEV" - } - - if err := r.replaceInFile("helpers/hugo.go", - `Number:(\s{4,})(.*),`, fmt.Sprintf(`Number:${1}%.2f,`, ver.Number), - `PatchLevel:(\s*)(.*),`, fmt.Sprintf(`PatchLevel:${1}%d,`, ver.PatchLevel), - fmt.Sprintf(`Suffix:(\s{4,})"%s",`, fromDev), fmt.Sprintf(`Suffix:${1}"%s",`, toDev)); err != nil { - return err - } - - snapcraftGrade := "stable" - if ver.Suffix != "" { - snapcraftGrade = "devel" - } - if err := r.replaceInFile("snapcraft.yaml", - `version: "(.*)"`, fmt.Sprintf(`version: "%s"`, ver), - `grade: (.*) #`, fmt.Sprintf(`grade: %s #`, snapcraftGrade)); err != nil { - return err - } - - var minVersion string - if ver.Suffix != "" { - // People use the DEV version in daily use, and we cannot create new themes - // with the next version before it is released. - minVersion = ver.Prev().String() - } else { - minVersion = ver.String() - } - - if err := r.replaceInFile("commands/new.go", - `min_version = "(.*)"`, fmt.Sprintf(`min_version = "%s"`, minVersion)); err != nil { - return err - } - - // docs/config.toml - if err := r.replaceInFile("docs/config.toml", - `release = "(.*)"`, fmt.Sprintf(`release = "%s"`, ver)); err != nil { - return err - } - - return nil -} - -func (r *ReleaseHandler) replaceInFile(filename string, oldNew ...string) error { - fullFilename := hugoFilepath(filename) - fi, err := os.Stat(fullFilename) - if err != nil { - return err - } - - if r.try { - fmt.Printf("Replace in %q: %q\n", filename, oldNew) - return nil - } - - b, err := ioutil.ReadFile(fullFilename) - if err != nil { - return err - } - newContent := string(b) - - for i := 0; i < len(oldNew); i += 2 { - re := regexp.MustCompile(oldNew[i]) - newContent = re.ReplaceAllString(newContent, oldNew[i+1]) - } - - return ioutil.WriteFile(fullFilename, []byte(newContent), fi.Mode()) -} - -func hugoFilepath(filename string) string { - pwd, err := os.Getwd() - if err != nil { - log.Fatal(err) - } - return filepath.Join(pwd, filename) -} diff --git a/snapcraft.yaml b/snapcraft.yaml deleted file mode 100644 index 6f6adafb0..000000000 --- a/snapcraft.yaml +++ /dev/null @@ -1,37 +0,0 @@ -name: hugo -version: "0.27-DEV" -summary: Fast and Flexible Static Site Generator -description: | - Hugo is a static HTML and CSS website generator written in Go. It is - optimized for speed, easy use and configurability. Hugo takes a directory - with content and templates and renders them into a full HTML website. -confinement: strict -grade: devel # "devel" or "stable" - -apps: - hugo: - command: bin/hugo - plugs: [home, network-bind] - -parts: - hugo: - source: . - plugin: go - go-importpath: github.com/gohugoio/hugo - build-packages: - - git - - make - stage-packages: - - python-pygments - prepare: | - export GOPATH=$(dirname $SNAPCRAFT_PART_INSTALL)/go - export PATH=$GOPATH/bin:$PATH - cd $GOPATH/src/github.com/gohugoio/hugo - make vendor - make test - rm -f $GOPATH/bin/govendor - install: | - strip --remove-section=.comment --remove-section=.note $SNAPCRAFT_PART_INSTALL/bin/hugo - after: [go] - go: - source-tag: go1.8.3 diff --git a/source/content_directory_test.go b/source/content_directory_test.go deleted file mode 100644 index 4ff12af8d..000000000 --- a/source/content_directory_test.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package source - -import ( - "testing" - - "github.com/gohugoio/hugo/hugofs" - "github.com/spf13/viper" -) - -func TestIgnoreDotFilesAndDirectories(t *testing.T) { - - tests := []struct { - path string - ignore bool - ignoreFilesRegexpes interface{} - }{ - {".foobar/", true, nil}, - {"foobar/.barfoo/", true, nil}, - {"barfoo.md", false, nil}, - {"foobar/barfoo.md", false, nil}, - {"foobar/.barfoo.md", true, nil}, - {".barfoo.md", true, nil}, - {".md", true, nil}, - {"", true, nil}, - {"foobar/barfoo.md~", true, nil}, - {".foobar/barfoo.md~", true, nil}, - {"foobar~/barfoo.md", false, nil}, - {"foobar/bar~foo.md", false, nil}, - {"foobar/foo.md", true, []string{"\\.md$", "\\.boo$"}}, - {"foobar/foo.html", false, []string{"\\.md$", "\\.boo$"}}, - {"foobar/foo.md", true, []string{"^foo"}}, - {"foobar/foo.md", false, []string{"*", "\\.md$", "\\.boo$"}}, - {"foobar/.#content.md", true, []string{"/\\.#"}}, - {".#foobar.md", true, []string{"^\\.#"}}, - } - - for _, test := range tests { - - v := viper.New() - v.Set("ignoreFiles", test.ignoreFilesRegexpes) - - s := NewSourceSpec(v, hugofs.NewMem(v)) - - if ignored := s.isNonProcessablePath(test.path); test.ignore != ignored { - t.Errorf("File not ignored. Expected: %t, got: %t", test.ignore, ignored) - } - } -} diff --git a/source/file.go b/source/file.go deleted file mode 100644 index a630431c6..000000000 --- a/source/file.go +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package source - -import ( - "io" - "path/filepath" - "strings" - - "github.com/gohugoio/hugo/hugofs" - - "github.com/gohugoio/hugo/config" - "github.com/gohugoio/hugo/helpers" -) - -// SourceSpec abstracts language-specific file creation. -type SourceSpec struct { - Cfg config.Provider - Fs *hugofs.Fs - - languages map[string]interface{} - defaultContentLanguage string -} - -// NewSourceSpec initializes SourceSpec using languages from a given configuration. -func NewSourceSpec(cfg config.Provider, fs *hugofs.Fs) SourceSpec { - defaultLang := cfg.GetString("defaultContentLanguage") - languages := cfg.GetStringMap("languages") - return SourceSpec{Cfg: cfg, Fs: fs, languages: languages, defaultContentLanguage: defaultLang} -} - -// File represents a source content file. -// All paths are relative from the source directory base -type File struct { - relpath string // Original relative path, e.g. section/foo.txt - logicalName string // foo.txt - baseName string // `post` for `post.md`, also `post.en` for `post.en.md` - Contents io.Reader - section string // The first directory - dir string // The relative directory Path (minus file name) - ext string // Just the ext (eg txt) - uniqueID string // MD5 of the file's path - - translationBaseName string // `post` for `post.es.md` (if `Multilingual` is enabled.) - lang string // The language code if `Multilingual` is enabled -} - -// UniqueID is the MD5 hash of the file's path and is for most practical applications, -// Hugo content files being one of them, considered to be unique. -func (f *File) UniqueID() string { - return f.uniqueID -} - -// String returns the file's content as a string. -func (f *File) String() string { - return helpers.ReaderToString(f.Contents) -} - -// Bytes returns the file's content as a byte slice. -func (f *File) Bytes() []byte { - return helpers.ReaderToBytes(f.Contents) -} - -// BaseFileName is a filename without extension. -func (f *File) BaseFileName() string { - return f.baseName -} - -// TranslationBaseName is a filename with no extension, -// not even the optional language extension part. -func (f *File) TranslationBaseName() string { - return f.translationBaseName -} - -// Lang for this page, if `Multilingual` is enabled on your site. -func (f *File) Lang() string { - return f.lang -} - -// Section is first directory below the content root. -func (f *File) Section() string { - return f.section -} - -// LogicalName is filename and extension of the file. -func (f *File) LogicalName() string { - return f.logicalName -} - -// SetDir sets the relative directory where this file lives. -// TODO(bep) Get rid of this. -func (f *File) SetDir(dir string) { - f.dir = dir -} - -// Dir gets the name of the directory that contains this file. -// The directory is relative to the content root. -func (f *File) Dir() string { - return f.dir -} - -// Extension gets the file extension, i.e "myblogpost.md" will return "md". -func (f *File) Extension() string { - return f.ext -} - -// Ext is an alias for Extension. -func (f *File) Ext() string { - return f.Extension() -} - -// Path gets the relative path including file name and extension. -// The directory is relative to the content root. -func (f *File) Path() string { - return f.relpath -} - -// NewFileWithContents creates a new File pointer with the given relative path and -// content. The language defaults to "en". -func (sp SourceSpec) NewFileWithContents(relpath string, content io.Reader) *File { - file := sp.NewFile(relpath) - file.Contents = content - file.lang = "en" - return file -} - -// NewFile creates a new File pointer with the given relative path. -func (sp SourceSpec) NewFile(relpath string) *File { - f := &File{ - relpath: relpath, - } - - f.dir, f.logicalName = filepath.Split(f.relpath) - f.ext = strings.TrimPrefix(filepath.Ext(f.LogicalName()), ".") - f.baseName = helpers.Filename(f.LogicalName()) - - lang := strings.TrimPrefix(filepath.Ext(f.baseName), ".") - if _, ok := sp.languages[lang]; lang == "" || !ok { - f.lang = sp.defaultContentLanguage - f.translationBaseName = f.baseName - } else { - f.lang = lang - f.translationBaseName = helpers.Filename(f.baseName) - } - - f.section = helpers.GuessSection(f.Dir()) - f.uniqueID = helpers.Md5String(filepath.ToSlash(f.relpath)) - - return f -} - -// NewFileFromAbs creates a new File pointer with the given full file path path and -// content. -func (sp SourceSpec) NewFileFromAbs(base, fullpath string, content io.Reader) (f *File, err error) { - var name string - if name, err = helpers.GetRelativePath(fullpath, base); err != nil { - return nil, err - } - - return sp.NewFileWithContents(name, content), nil -} diff --git a/source/file_test.go b/source/file_test.go deleted file mode 100644 index 64ad6fb46..000000000 --- a/source/file_test.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package source - -import ( - "path/filepath" - "strings" - "testing" - - "github.com/gohugoio/hugo/hugofs" - "github.com/spf13/viper" - - "github.com/stretchr/testify/assert" -) - -func TestFileUniqueID(t *testing.T) { - ss := newTestSourceSpec() - - f1 := File{uniqueID: "123"} - f2 := ss.NewFile("a") - - assert.Equal(t, "123", f1.UniqueID()) - assert.Equal(t, "0cc175b9c0f1b6a831c399e269772661", f2.UniqueID()) - - f3 := ss.NewFile(filepath.FromSlash("test1/index.md")) - f4 := ss.NewFile(filepath.FromSlash("test2/index.md")) - - assert.NotEqual(t, f3.UniqueID(), f4.UniqueID()) - - f5l := ss.NewFile("test3/index.md") - f5w := ss.NewFile(filepath.FromSlash("test3/index.md")) - - assert.Equal(t, f5l.UniqueID(), f5w.UniqueID()) -} - -func TestFileString(t *testing.T) { - ss := newTestSourceSpec() - assert.Equal(t, "abc", ss.NewFileWithContents("a", strings.NewReader("abc")).String()) - assert.Equal(t, "", ss.NewFile("a").String()) -} - -func TestFileBytes(t *testing.T) { - ss := newTestSourceSpec() - assert.Equal(t, []byte("abc"), ss.NewFileWithContents("a", strings.NewReader("abc")).Bytes()) - assert.Equal(t, []byte(""), ss.NewFile("a").Bytes()) -} - -func newTestSourceSpec() SourceSpec { - v := viper.New() - return SourceSpec{Fs: hugofs.NewMem(v), Cfg: v} -} diff --git a/source/filesystem.go b/source/filesystem.go deleted file mode 100644 index 446d8c06d..000000000 --- a/source/filesystem.go +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright 2016 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package source - -import ( - "io" - "os" - "path/filepath" - "regexp" - "runtime" - "strings" - - "github.com/gohugoio/hugo/helpers" - "github.com/spf13/cast" - jww "github.com/spf13/jwalterweatherman" - "golang.org/x/text/unicode/norm" -) - -type Input interface { - Files() []*File -} - -type Filesystem struct { - files []*File - Base string - AvoidPaths []string - - SourceSpec -} - -func (sp SourceSpec) NewFilesystem(base string, avoidPaths ...string) *Filesystem { - return &Filesystem{SourceSpec: sp, Base: base, AvoidPaths: avoidPaths} -} - -func (f *Filesystem) FilesByExts(exts ...string) []*File { - var newFiles []*File - - if len(exts) == 0 { - return f.Files() - } - - for _, x := range f.Files() { - for _, e := range exts { - if x.Ext() == strings.TrimPrefix(e, ".") { - newFiles = append(newFiles, x) - } - } - } - return newFiles -} - -func (f *Filesystem) Files() []*File { - if len(f.files) < 1 { - f.captureFiles() - } - return f.files -} - -// add populates a file in the Filesystem.files -func (f *Filesystem) add(name string, reader io.Reader) (err error) { - var file *File - - if runtime.GOOS == "darwin" { - // When a file system is HFS+, its filepath is in NFD form. - name = norm.NFC.String(name) - } - - file, err = f.SourceSpec.NewFileFromAbs(f.Base, name, reader) - - if err == nil { - f.files = append(f.files, file) - } - return err -} - -func (f *Filesystem) captureFiles() { - walker := func(filePath string, fi os.FileInfo, err error) error { - if err != nil { - return nil - } - - b, err := f.ShouldRead(filePath, fi) - if err != nil { - return err - } - if b { - rd, err := NewLazyFileReader(f.Fs.Source, filePath) - if err != nil { - return err - } - f.add(filePath, rd) - } - return err - } - - if f.Fs == nil { - panic("Must have a fs") - } - err := helpers.SymbolicWalk(f.Fs.Source, f.Base, walker) - - if err != nil { - jww.ERROR.Println(err) - if err == helpers.ErrWalkRootTooShort { - panic("The root path is too short. If this is a test, make sure to init the content paths.") - } - } - -} - -func (f *Filesystem) ShouldRead(filePath string, fi os.FileInfo) (bool, error) { - if fi.Mode()&os.ModeSymlink == os.ModeSymlink { - link, err := filepath.EvalSymlinks(filePath) - if err != nil { - jww.ERROR.Printf("Cannot read symbolic link '%s', error was: %s", filePath, err) - return false, nil - } - linkfi, err := f.Fs.Source.Stat(link) - if err != nil { - jww.ERROR.Printf("Cannot stat '%s', error was: %s", link, err) - return false, nil - } - if !linkfi.Mode().IsRegular() { - jww.ERROR.Printf("Symbolic links for directories not supported, skipping '%s'", filePath) - } - return false, nil - } - - if fi.IsDir() { - if f.avoid(filePath) || f.isNonProcessablePath(filePath) { - return false, filepath.SkipDir - } - return false, nil - } - - if f.isNonProcessablePath(filePath) { - return false, nil - } - return true, nil -} - -func (f *Filesystem) avoid(filePath string) bool { - for _, avoid := range f.AvoidPaths { - if avoid == filePath { - return true - } - } - return false -} - -func (s SourceSpec) isNonProcessablePath(filePath string) bool { - base := filepath.Base(filePath) - if strings.HasPrefix(base, ".") || - strings.HasPrefix(base, "#") || - strings.HasSuffix(base, "~") { - return true - } - ignoreFiles := cast.ToStringSlice(s.Cfg.Get("ignoreFiles")) - if len(ignoreFiles) > 0 { - for _, ignorePattern := range ignoreFiles { - match, err := regexp.MatchString(ignorePattern, filePath) - if err != nil { - helpers.DistinctErrorLog.Printf("Invalid regexp '%s' in ignoreFiles: %s", ignorePattern, err) - return false - } else if match { - return true - } - } - } - return false -} diff --git a/source/filesystem_test.go b/source/filesystem_test.go deleted file mode 100644 index 90512ce3f..000000000 --- a/source/filesystem_test.go +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package source - -import ( - "bytes" - "path/filepath" - "runtime" - "strings" - "testing" -) - -func TestEmptySourceFilesystem(t *testing.T) { - ss := newTestSourceSpec() - src := ss.NewFilesystem("Empty") - if len(src.Files()) != 0 { - t.Errorf("new filesystem should contain 0 files.") - } -} - -type TestPath struct { - filename string - logical string - content string - section string - dir string -} - -func TestAddFile(t *testing.T) { - ss := newTestSourceSpec() - tests := platformPaths - for _, test := range tests { - base := platformBase - srcDefault := ss.NewFilesystem("") - srcWithBase := ss.NewFilesystem(base) - - for _, src := range []*Filesystem{srcDefault, srcWithBase} { - - p := test.filename - if !filepath.IsAbs(test.filename) { - p = filepath.Join(src.Base, test.filename) - } - - if err := src.add(p, bytes.NewReader([]byte(test.content))); err != nil { - if err.Error() == "source: missing base directory" { - continue - } - t.Fatalf("%s add returned an error: %s", p, err) - } - - if len(src.Files()) != 1 { - t.Fatalf("%s Files() should return 1 file", p) - } - - f := src.Files()[0] - if f.LogicalName() != test.logical { - t.Errorf("Filename (Base: %q) expected: %q, got: %q", src.Base, test.logical, f.LogicalName()) - } - - b := new(bytes.Buffer) - b.ReadFrom(f.Contents) - if b.String() != test.content { - t.Errorf("File (Base: %q) contents should be %q, got: %q", src.Base, test.content, b.String()) - } - - if f.Section() != test.section { - t.Errorf("File section (Base: %q) expected: %q, got: %q", src.Base, test.section, f.Section()) - } - - if f.Dir() != test.dir { - t.Errorf("Dir path (Base: %q) expected: %q, got: %q", src.Base, test.dir, f.Dir()) - } - } - } -} - -func TestUnicodeNorm(t *testing.T) { - if runtime.GOOS != "darwin" { - // Normalization code is only for Mac OS, since it is not necessary for other OSes. - return - } - - paths := []struct { - NFC string - NFD string - }{ - {NFC: "å", NFD: "\x61\xcc\x8a"}, - {NFC: "é", NFD: "\x65\xcc\x81"}, - } - - ss := newTestSourceSpec() - - for _, path := range paths { - src := ss.NewFilesystem("") - _ = src.add(path.NFD, strings.NewReader("")) - f := src.Files()[0] - if f.BaseFileName() != path.NFC { - t.Fatalf("file name in NFD form should be normalized (%s)", path.NFC) - } - } - -} diff --git a/source/filesystem_unix_test.go b/source/filesystem_unix_test.go deleted file mode 100644 index 560d824ae..000000000 --- a/source/filesystem_unix_test.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// +build linux darwin !windows - -package source - -// -// NOTE, any changes here need to be reflected in filesystem_windows_test.go -// -var platformBase = "/base/" -var platformPaths = []TestPath{ - {"foobar", "foobar", "aaa", "", ""}, - {"b/1file", "1file", "aaa", "b", "b/"}, - {"c/d/2file", "2file", "aaa", "c", "c/d/"}, - {"/base/e/f/3file", "3file", "aaa", "e", "e/f/"}, - {"section/foo.rss", "foo.rss", "aaa", "section", "section/"}, -} diff --git a/source/filesystem_windows_test.go b/source/filesystem_windows_test.go deleted file mode 100644 index 4662c5fd6..000000000 --- a/source/filesystem_windows_test.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package source - -// -// NOTE, any changes here need to be reflected in filesystem_linux_test.go -// - -// Note the case of the volume drive. It must be the same in all examples. -var platformBase = "C:\\foo\\" -var platformPaths = []TestPath{ - {"foobar", "foobar", "aaa", "", ""}, - {"b\\1file", "1file", "aaa", "b", "b\\"}, - {"c\\d\\2file", "2file", "aaa", "c", "c\\d\\"}, - {"C:\\foo\\e\\f\\3file", "3file", "aaa", "e", "e\\f\\"}, // note volume case is equal to platformBase - {"section\\foo.rss", "foo.rss", "aaa", "section", "section\\"}, -} diff --git a/source/inmemory.go b/source/inmemory.go deleted file mode 100644 index 387bde3b8..000000000 --- a/source/inmemory.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package source - -// ByteSource represents a source's name and content. -// It's currently only used for testing purposes. -type ByteSource struct { - Name string - Content []byte -} - -func (b *ByteSource) String() string { - return b.Name + " " + string(b.Content) -} diff --git a/source/lazy_file_reader.go b/source/lazy_file_reader.go deleted file mode 100644 index 7cc484f0b..000000000 --- a/source/lazy_file_reader.go +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// Portions Copyright 2009 The Go Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package source - -import ( - "bytes" - "errors" - "fmt" - "io" - - "github.com/spf13/afero" -) - -// LazyFileReader is an io.Reader implementation to postpone reading the file -// contents until it is really needed. It keeps filename and file contents once -// it is read. -type LazyFileReader struct { - fs afero.Fs - filename string - contents *bytes.Reader - pos int64 -} - -// NewLazyFileReader creates and initializes a new LazyFileReader of filename. -// It checks whether the file can be opened. If it fails, it returns nil and an -// error. -func NewLazyFileReader(fs afero.Fs, filename string) (*LazyFileReader, error) { - f, err := fs.Open(filename) - if err != nil { - return nil, err - } - defer f.Close() - return &LazyFileReader{fs: fs, filename: filename, contents: nil, pos: 0}, nil -} - -// Filename returns a file name which LazyFileReader keeps -func (l *LazyFileReader) Filename() string { - return l.filename -} - -// Read reads up to len(p) bytes from the LazyFileReader's file and copies them -// into p. It returns the number of bytes read and any error encountered. If -// the file is once read, it returns its contents from cache, doesn't re-read -// the file. -func (l *LazyFileReader) Read(p []byte) (n int, err error) { - if l.contents == nil { - b, err := afero.ReadFile(l.fs, l.filename) - if err != nil { - return 0, fmt.Errorf("failed to read content from %s: %s", l.filename, err.Error()) - } - l.contents = bytes.NewReader(b) - } - if _, err = l.contents.Seek(l.pos, 0); err != nil { - return 0, errors.New("failed to set read position: " + err.Error()) - } - n, err = l.contents.Read(p) - l.pos += int64(n) - return n, err -} - -// Seek implements the io.Seeker interface. Once reader contents is consumed by -// Read, WriteTo etc, to read it again, it must be rewinded by this function -func (l *LazyFileReader) Seek(offset int64, whence int) (pos int64, err error) { - if l.contents == nil { - switch whence { - case 0: - pos = offset - case 1: - pos = l.pos + offset - case 2: - fi, err := l.fs.Stat(l.filename) - if err != nil { - return 0, fmt.Errorf("failed to get %q info: %s", l.filename, err.Error()) - } - pos = fi.Size() + offset - default: - return 0, errors.New("invalid whence") - } - if pos < 0 { - return 0, errors.New("negative position") - } - } else { - pos, err = l.contents.Seek(offset, whence) - if err != nil { - return 0, err - } - } - l.pos = pos - return pos, nil -} - -// WriteTo writes data to w until all the LazyFileReader's file contents is -// drained or an error occurs. If the file is once read, it just writes its -// read cache to w, doesn't re-read the file but this method itself doesn't try -// to keep the contents in cache. -func (l *LazyFileReader) WriteTo(w io.Writer) (n int64, err error) { - if l.contents != nil { - l.contents.Seek(l.pos, 0) - if err != nil { - return 0, errors.New("failed to set read position: " + err.Error()) - } - n, err = l.contents.WriteTo(w) - l.pos += n - return n, err - } - f, err := l.fs.Open(l.filename) - if err != nil { - return 0, fmt.Errorf("failed to open %s to read content: %s", l.filename, err.Error()) - } - defer f.Close() - - fi, err := f.Stat() - if err != nil { - return 0, fmt.Errorf("failed to get %q info: %s", l.filename, err.Error()) - } - - if l.pos >= fi.Size() { - return 0, nil - } - - return l.copyBuffer(w, f, nil) -} - -// copyBuffer is the actual implementation of Copy and CopyBuffer. -// If buf is nil, one is allocated. -// -// Most of this function is copied from the Go stdlib 'io/io.go'. -func (l *LazyFileReader) copyBuffer(dst io.Writer, src io.Reader, buf []byte) (written int64, err error) { - if buf == nil { - buf = make([]byte, 32*1024) - } - for { - nr, er := src.Read(buf) - if nr > 0 { - nw, ew := dst.Write(buf[0:nr]) - if nw > 0 { - l.pos += int64(nw) - written += int64(nw) - } - if ew != nil { - err = ew - break - } - if nr != nw { - err = io.ErrShortWrite - break - } - } - if er == io.EOF { - break - } - if er != nil { - err = er - break - } - } - return written, err -} diff --git a/source/lazy_file_reader_test.go b/source/lazy_file_reader_test.go deleted file mode 100644 index 778a9513b..000000000 --- a/source/lazy_file_reader_test.go +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package source - -import ( - "bytes" - "io" - "os" - "testing" - - "github.com/spf13/afero" -) - -func TestNewLazyFileReader(t *testing.T) { - fs := afero.NewOsFs() - filename := "itdoesnotexistfile" - _, err := NewLazyFileReader(fs, filename) - if err == nil { - t.Errorf("NewLazyFileReader %s: error expected but no error is returned", filename) - } - - filename = "lazy_file_reader_test.go" - _, err = NewLazyFileReader(fs, filename) - if err != nil { - t.Errorf("NewLazyFileReader %s: %v", filename, err) - } -} - -func TestFilename(t *testing.T) { - fs := afero.NewOsFs() - filename := "lazy_file_reader_test.go" - rd, err := NewLazyFileReader(fs, filename) - if err != nil { - t.Fatalf("NewLazyFileReader %s: %v", filename, err) - } - if rd.Filename() != filename { - t.Errorf("Filename: expected filename %q, got %q", filename, rd.Filename()) - } -} - -func TestRead(t *testing.T) { - fs := afero.NewOsFs() - filename := "lazy_file_reader_test.go" - fi, err := fs.Stat(filename) - if err != nil { - t.Fatalf("os.Stat: %v", err) - } - - b, err := afero.ReadFile(fs, filename) - if err != nil { - t.Fatalf("afero.ReadFile: %v", err) - } - - rd, err := NewLazyFileReader(fs, filename) - if err != nil { - t.Fatalf("NewLazyFileReader %s: %v", filename, err) - } - - tst := func(testcase string) { - p := make([]byte, fi.Size()) - n, err := rd.Read(p) - if err != nil { - t.Fatalf("Read %s case: %v", testcase, err) - } - if int64(n) != fi.Size() { - t.Errorf("Read %s case: read bytes length expected %d, got %d", testcase, fi.Size(), n) - } - if !bytes.Equal(b, p) { - t.Errorf("Read %s case: read bytes are different from expected", testcase) - } - } - tst("No cache") - _, err = rd.Seek(0, 0) - if err != nil { - t.Fatalf("Seek: %v", err) - } - tst("Cache") -} - -func TestSeek(t *testing.T) { - type testcase struct { - seek int - offset int64 - length int - moveto int64 - expected []byte - } - fs := afero.NewOsFs() - filename := "lazy_file_reader_test.go" - b, err := afero.ReadFile(fs, filename) - if err != nil { - t.Fatalf("afero.ReadFile: %v", err) - } - - // no cache case - for i, this := range []testcase{ - {seek: os.SEEK_SET, offset: 0, length: 10, moveto: 0, expected: b[:10]}, - {seek: os.SEEK_SET, offset: 5, length: 10, moveto: 5, expected: b[5:15]}, - {seek: os.SEEK_CUR, offset: 5, length: 10, moveto: 5, expected: b[5:15]}, // current pos = 0 - {seek: os.SEEK_END, offset: -1, length: 1, moveto: int64(len(b) - 1), expected: b[len(b)-1:]}, - {seek: 3, expected: nil}, - {seek: os.SEEK_SET, offset: -1, expected: nil}, - } { - rd, err := NewLazyFileReader(fs, filename) - if err != nil { - t.Errorf("[%d] NewLazyFileReader %s: %v", i, filename, err) - continue - } - - pos, err := rd.Seek(this.offset, this.seek) - if this.expected == nil { - if err == nil { - t.Errorf("[%d] Seek didn't return an expected error", i) - } - } else { - if err != nil { - t.Errorf("[%d] Seek failed unexpectedly: %v", i, err) - continue - } - if pos != this.moveto { - t.Errorf("[%d] Seek failed to move the pointer: got %d, expected: %d", i, pos, this.moveto) - } - - buf := make([]byte, this.length) - n, err := rd.Read(buf) - if err != nil { - t.Errorf("[%d] Read failed unexpectedly: %v", i, err) - } - if !bytes.Equal(this.expected, buf[:n]) { - t.Errorf("[%d] Seek and Read got %q but expected %q", i, buf[:n], this.expected) - } - } - } - - // cache case - rd, err := NewLazyFileReader(fs, filename) - if err != nil { - t.Fatalf("NewLazyFileReader %s: %v", filename, err) - } - dummy := make([]byte, len(b)) - _, err = rd.Read(dummy) - if err != nil { - t.Fatalf("Read failed unexpectedly: %v", err) - } - - for i, this := range []testcase{ - {seek: os.SEEK_SET, offset: 0, length: 10, moveto: 0, expected: b[:10]}, - {seek: os.SEEK_SET, offset: 5, length: 10, moveto: 5, expected: b[5:15]}, - {seek: os.SEEK_CUR, offset: 1, length: 10, moveto: 16, expected: b[16:26]}, // current pos = 15 - {seek: os.SEEK_END, offset: -1, length: 1, moveto: int64(len(b) - 1), expected: b[len(b)-1:]}, - {seek: 3, expected: nil}, - {seek: os.SEEK_SET, offset: -1, expected: nil}, - } { - pos, err := rd.Seek(this.offset, this.seek) - if this.expected == nil { - if err == nil { - t.Errorf("[%d] Seek didn't return an expected error", i) - } - } else { - if err != nil { - t.Errorf("[%d] Seek failed unexpectedly: %v", i, err) - continue - } - if pos != this.moveto { - t.Errorf("[%d] Seek failed to move the pointer: got %d, expected: %d", i, pos, this.moveto) - } - - buf := make([]byte, this.length) - n, err := rd.Read(buf) - if err != nil { - t.Errorf("[%d] Read failed unexpectedly: %v", i, err) - } - if !bytes.Equal(this.expected, buf[:n]) { - t.Errorf("[%d] Seek and Read got %q but expected %q", i, buf[:n], this.expected) - } - } - } -} - -func TestWriteTo(t *testing.T) { - fs := afero.NewOsFs() - filename := "lazy_file_reader_test.go" - fi, err := fs.Stat(filename) - if err != nil { - t.Fatalf("os.Stat: %v", err) - } - - b, err := afero.ReadFile(fs, filename) - if err != nil { - t.Fatalf("afero.ReadFile: %v", err) - } - - rd, err := NewLazyFileReader(fs, filename) - if err != nil { - t.Fatalf("NewLazyFileReader %s: %v", filename, err) - } - - tst := func(testcase string, expectedSize int64, checkEqual bool) { - buf := bytes.NewBuffer(make([]byte, 0, bytes.MinRead)) - n, err := rd.WriteTo(buf) - if err != nil { - t.Fatalf("WriteTo %s case: %v", testcase, err) - } - if n != expectedSize { - t.Errorf("WriteTo %s case: written bytes length expected %d, got %d", testcase, expectedSize, n) - } - if checkEqual && !bytes.Equal(b, buf.Bytes()) { - t.Errorf("WriteTo %s case: written bytes are different from expected", testcase) - } - } - tst("No cache", fi.Size(), true) - tst("No cache 2nd", 0, false) - - p := make([]byte, fi.Size()) - _, err = rd.Read(p) - if err != nil && err != io.EOF { - t.Fatalf("Read: %v", err) - } - _, err = rd.Seek(0, 0) - if err != nil { - t.Fatalf("Seek: %v", err) - } - - tst("Cache", fi.Size(), true) -} diff --git a/static/apple-touch-icon.png b/static/apple-touch-icon.png new file mode 100644 index 000000000..50e23ce1d Binary files /dev/null and b/static/apple-touch-icon.png differ diff --git a/static/contribute/development/accept-cla.png b/static/contribute/development/accept-cla.png new file mode 100755 index 000000000..929fda6ab Binary files /dev/null and b/static/contribute/development/accept-cla.png differ diff --git a/static/contribute/development/ci-errors.png b/static/contribute/development/ci-errors.png new file mode 100755 index 000000000..95cd290b6 Binary files /dev/null and b/static/contribute/development/ci-errors.png differ diff --git a/static/contribute/development/copy-remote-url.png b/static/contribute/development/copy-remote-url.png new file mode 100755 index 000000000..9006f4a48 Binary files /dev/null and b/static/contribute/development/copy-remote-url.png differ diff --git a/static/contribute/development/forking-a-repository.png b/static/contribute/development/forking-a-repository.png new file mode 100755 index 000000000..ea132cab3 Binary files /dev/null and b/static/contribute/development/forking-a-repository.png differ diff --git a/static/contribute/development/open-pull-request.png b/static/contribute/development/open-pull-request.png new file mode 100755 index 000000000..63b504fb2 Binary files /dev/null and b/static/contribute/development/open-pull-request.png differ diff --git a/static/css/bootstrap-additions-gohugo.css b/static/css/bootstrap-additions-gohugo.css new file mode 100644 index 000000000..4128f3b8c --- /dev/null +++ b/static/css/bootstrap-additions-gohugo.css @@ -0,0 +1,65 @@ +/*! + * Bootstrap for http://gohugo.io/ + * additional property-value pairs, for selectors already in Bootstrap; + * also, additional Bootstrap-like selectors + * + * Keep all such property additions to Bootstrap v3.3.6 here. + * + * Here, maintain the same order as the original Bootstrap file. + * + * Keep any additional Bootstrap-like selectors at the bottom. + * + * Copyright 2013-2016 Steve Francia and the Hugo Authors + * + * Based on 'dist/css/bootstrap.css' from: + * + * Bootstrap v3.3.6 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +.btn { + -webkit-transition: all 0.15s; + -moz-transition: all 0.15s; + transition: all 0.15s; +} +.btn-primary:focus, +.btn-primary:active, +.btn-success:focus, +.btn-success:active, +.btn-info:focus, +.btn-info:active { + background-color: transparent; +} +.btn-default:hover, +.btn-default:active, +.btn-primary:hover, +.btn-primary:active, +.btn-success:hover, +.btn-success:active, +.btn-info:hover, +.btn-info:active { + outline: none; +} + +/* additional Bootstrap-like selectors */ + +.btn-repo { + border-color: black; + background-color: rgba(30, 30, 30, 0.8); + color: white; +} +.btn-repo:focus, +.btn-repo:active { + color: black; +} +.btn-repo:hover { + background-color: aliceblue; +} +.btn-repo:focus, +.btn-repo:active { + background-color: transparent; +} +.btn-repo:hover, +.btn-repo:active { + outline: none; +} diff --git a/static/css/bootstrap-changes-gohugo.css b/static/css/bootstrap-changes-gohugo.css new file mode 100644 index 000000000..d4ef99bcc --- /dev/null +++ b/static/css/bootstrap-changes-gohugo.css @@ -0,0 +1,136 @@ +/*! + * Bootstrap for http://gohugo.io/ + * value changes to properties in Bootstrap selectors; + * Bootstrap must already have the same properties in the same selectors. + * + * Keep all such value changes to Bootstrap v3.3.6 here. + * + * Here, maintain the same order as the original Bootstrap file. + * + * Keep all new properties, for new or existing selectors, + * in other stylesheets. + * + * Copyright 2013-2016 Steve Francia and the Hugo Authors + * + * Based on 'dist/css/bootstrap.css' from: + * + * Bootstrap v3.3.6 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +body { + font-family: Lato; + color: #2b2b2b; +} +a { + color: #ff4088; +} +a:hover, +a:focus { + color: #ff4088; +} +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: Lato; + font-weight: 400; +} +code, +kbd, +pre, +samp { + font-family: Menlo, Consolas, 'DejaVu Sans Mono', + 'Bitstream Vera Sans Mono', 'Lucida Console', Monaco, + 'Droid Sans Mono', monospace; +} +legend { + color: #2b2b2b; + border-bottom: 1px solid #c7c7cc; +} +.btn:focus { + /*outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px;*/ + outline: none; +} +.btn:hover, +.btn:focus { + color: #2b2b2b; +} +.btn-default:focus { + color: #fff; + background-color: #9e9e9e; + border-color: #9e9e9e; +} +.btn-default:hover { + color: #fff; + background-color: #9e9e9e; + border-color: #9e9e9e; +} +.btn-default:active { + color: #fff; + background-color: #9e9e9e; + border-color: #9e9e9e; +} +.btn-primary { + background-color: #007aff; + border-color: #007aff; +} +.btn-primary:focus { + color: #007aff; + border-color: #007aff; +} +.btn-primary:hover { + color: #007aff; + background-color: aliceblue; + border-color: #007aff; +} +.btn-primary:active { + color: #007aff; + border-color: #007aff; +} +.btn-success { + background-color: #4cd964; + border-color: #4cd964; +} +.btn-success:focus { + color: #4cd964; + border-color: #4cd964; +} +.btn-success:hover { + color: #4cd964; + background-color: aliceblue; + border-color: #4cd964; +} +.btn-success:active { + color: #4cd964; + border-color: #4cd964; +} +.btn-info { + background-color: #34aadc; + border-color: #34aadc; +} +.btn-info:focus { + color: #34aadc; + border-color: #34aadc; +} +.btn-info:hover { + color: #34aadc; + background-color: aliceblue; + border-color: #34aadc; +} +.btn-info:active { + color: #34aadc; + border-color: #34aadc; +} +.panel { + border: 0px solid transparent; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1); +} +.panel-body { + padding: 15px 15px 0px 15px; +} diff --git a/static/css/bootstrap-stripped-gohugo.css b/static/css/bootstrap-stripped-gohugo.css new file mode 100644 index 000000000..aafb0df53 --- /dev/null +++ b/static/css/bootstrap-stripped-gohugo.css @@ -0,0 +1,1380 @@ +/*! + * Bootstrap for http://gohugo.io/ + * with many unused property-value pairs, as well as selectors, removed + * + * Copyright 2013-2016 Steve Francia and the Hugo Authors + * + * Based on 'dist/css/bootstrap.css' from: + * + * Bootstrap v3.3.6 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ +html { + font-family: sans-serif; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} +body { + margin: 0; +} +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} +audio, +canvas, +progress, +video { + display: inline-block; + vertical-align: baseline; +} +a:active, +a:hover { + outline: 0; +} +b, +strong { + font-weight: bold; +} +dfn { + font-style: italic; +} +h1 { + margin: .67em 0; + font-size: 2em; +} +mark { + color: #000; + background: #ff0; +} +small { + font-size: 80%; +} +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} +sup { + top: -.5em; +} +sub { + bottom: -.25em; +} +img { + border: 0; +} +figure { + margin: 1em 40px; +} +hr { + height: 0; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} +pre { + overflow: auto; +} +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} +button, +input, +optgroup, +select, +textarea { + margin: 0; + font: inherit; + color: inherit; +} +button { + overflow: visible; +} +button, +select { + text-transform: none; +} +button { + -webkit-appearance: button; + cursor: pointer; +} +button::-moz-focus-inner, +input::-moz-focus-inner { + padding: 0; + border: 0; +} +input { + line-height: normal; +} +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} +fieldset { + padding: .35em .625em .75em; + margin: 0 2px; + border: 1px solid #c0c0c0; +} +legend { + padding: 0; + border: 0; +} +textarea { + overflow: auto; +} +optgroup { + font-weight: bold; +} +table { + border-spacing: 0; + border-collapse: collapse; +} +td, +th { + padding: 0; +} +/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */ +@media print { + *, + *:before, + *:after { + color: #000 !important; + text-shadow: none !important; + background: transparent !important; + -webkit-box-shadow: none !important; + box-shadow: none !important; + } + a, + a:visited { + text-decoration: underline; + } + a[href]:after { + content: " (" attr(href) ")"; + } + a[href^="#"]:after, + a[href^="javascript:"]:after { + content: ""; + } + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + thead { + display: table-header-group; + } + tr, + img { + page-break-inside: avoid; + } + img { + max-width: 100% !important; + } + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + h2, + h3 { + page-break-after: avoid; + } +} +@font-face { + font-family: 'Glyphicons Halflings'; + + src: url('../fonts/glyphicons-halflings-regular.eot'); + src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); +} +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +*:before, +*:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +html { + font-size: 10px; + + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.42857143; + color: #333; + background-color: #fff; +} +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} +a { + color: #337ab7; + text-decoration: none; +} +a:hover, +a:focus { + color: #23527c; + text-decoration: underline; +} +a:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +figure { + margin: 0; +} +img { + vertical-align: middle; +} +hr { + margin-top: 20px; + margin-bottom: 20px; + border: 0; + border-top: 1px solid #eee; +} +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: inherit; + font-weight: 500; + line-height: 1.1; + color: inherit; +} +h1, +h2, +h3 { + margin-top: 20px; + margin-bottom: 10px; +} +h4, +h5, +h6 { + margin-top: 10px; + margin-bottom: 10px; +} +h1 { + font-size: 36px; +} +h2 { + font-size: 30px; +} +h3 { + font-size: 24px; +} +h4 { + font-size: 18px; +} +h5 { + font-size: 14px; +} +h6 { + font-size: 12px; +} +p { + margin: 0 0 10px; +} +.lead { + margin-bottom: 20px; + font-size: 16px; + font-weight: 300; + line-height: 1.4; +} +@media (min-width: 768px) { + .lead { + font-size: 21px; + } +} +small, +.small { + font-size: 85%; +} +.text-center { + text-align: center; +} +ul, +ol { + margin-top: 0; + margin-bottom: 10px; +} +ul ul { + margin-bottom: 0; +} +.list-inline { + padding-left: 0; + margin-left: -5px; + list-style: none; +} +.list-inline > li { + display: inline-block; + padding-right: 5px; + padding-left: 5px; +} +dl { + margin-top: 0; + margin-bottom: 20px; +} +dt, +dd { + line-height: 1.42857143; +} +dt { + font-weight: bold; +} +dd { + margin-left: 0; +} +blockquote { + padding: 10px 20px; + margin: 0 0 20px; + font-size: 17.5px; + border-left: 5px solid #eee; +} +blockquote p:last-child, +blockquote ul:last-child, +blockquote ol:last-child { + margin-bottom: 0; +} +address { + margin-bottom: 20px; + font-style: normal; + line-height: 1.42857143; +} +code, +kbd, +pre, +samp { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; +} +code { + padding: 2px 4px; + font-size: 90%; + color: #c7254e; + background-color: #f9f2f4; + border-radius: 4px; +} +pre { + display: block; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + line-height: 1.42857143; + color: #333; + word-break: break-all; + word-wrap: break-word; + background-color: #f5f5f5; + border: 1px solid #ccc; + border-radius: 4px; +} +pre code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border-radius: 0; +} +.container { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} +@media (min-width: 768px) { + .container { + width: 750px; + } +} +@media (min-width: 992px) { + .container { + width: 970px; + } +} +@media (min-width: 1200px) { + .container { + width: 1170px; + } +} +.row { + margin-right: -15px; + margin-left: -15px; +} +.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 { + position: relative; + min-height: 1px; + padding-right: 15px; + padding-left: 15px; +} +.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 { + float: left; +} +.col-xs-12 { + width: 100%; +} +.col-xs-11 { + width: 91.66666667%; +} +.col-xs-10 { + width: 83.33333333%; +} +.col-xs-9 { + width: 75%; +} +.col-xs-8 { + width: 66.66666667%; +} +.col-xs-7 { + width: 58.33333333%; +} +.col-xs-6 { + width: 50%; +} +.col-xs-5 { + width: 41.66666667%; +} +.col-xs-4 { + width: 33.33333333%; +} +.col-xs-3 { + width: 25%; +} +.col-xs-2 { + width: 16.66666667%; +} +.col-xs-1 { + width: 8.33333333%; +} +.col-xs-pull-12 { + right: 100%; +} +.col-xs-pull-11 { + right: 91.66666667%; +} +.col-xs-pull-10 { + right: 83.33333333%; +} +.col-xs-pull-9 { + right: 75%; +} +.col-xs-pull-8 { + right: 66.66666667%; +} +.col-xs-pull-7 { + right: 58.33333333%; +} +.col-xs-pull-6 { + right: 50%; +} +.col-xs-pull-5 { + right: 41.66666667%; +} +.col-xs-pull-4 { + right: 33.33333333%; +} +.col-xs-pull-3 { + right: 25%; +} +.col-xs-pull-2 { + right: 16.66666667%; +} +.col-xs-pull-1 { + right: 8.33333333%; +} +.col-xs-pull-0 { + right: auto; +} +.col-xs-push-12 { + left: 100%; +} +.col-xs-push-11 { + left: 91.66666667%; +} +.col-xs-push-10 { + left: 83.33333333%; +} +.col-xs-push-9 { + left: 75%; +} +.col-xs-push-8 { + left: 66.66666667%; +} +.col-xs-push-7 { + left: 58.33333333%; +} +.col-xs-push-6 { + left: 50%; +} +.col-xs-push-5 { + left: 41.66666667%; +} +.col-xs-push-4 { + left: 33.33333333%; +} +.col-xs-push-3 { + left: 25%; +} +.col-xs-push-2 { + left: 16.66666667%; +} +.col-xs-push-1 { + left: 8.33333333%; +} +.col-xs-push-0 { + left: auto; +} +.col-xs-offset-12 { + margin-left: 100%; +} +.col-xs-offset-11 { + margin-left: 91.66666667%; +} +.col-xs-offset-10 { + margin-left: 83.33333333%; +} +.col-xs-offset-9 { + margin-left: 75%; +} +.col-xs-offset-8 { + margin-left: 66.66666667%; +} +.col-xs-offset-7 { + margin-left: 58.33333333%; +} +.col-xs-offset-6 { + margin-left: 50%; +} +.col-xs-offset-5 { + margin-left: 41.66666667%; +} +.col-xs-offset-4 { + margin-left: 33.33333333%; +} +.col-xs-offset-3 { + margin-left: 25%; +} +.col-xs-offset-2 { + margin-left: 16.66666667%; +} +.col-xs-offset-1 { + margin-left: 8.33333333%; +} +.col-xs-offset-0 { + margin-left: 0; +} +@media (min-width: 768px) { + .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { + float: left; + } + .col-sm-12 { + width: 100%; + } + .col-sm-11 { + width: 91.66666667%; + } + .col-sm-10 { + width: 83.33333333%; + } + .col-sm-9 { + width: 75%; + } + .col-sm-8 { + width: 66.66666667%; + } + .col-sm-7 { + width: 58.33333333%; + } + .col-sm-6 { + width: 50%; + } + .col-sm-5 { + width: 41.66666667%; + } + .col-sm-4 { + width: 33.33333333%; + } + .col-sm-3 { + width: 25%; + } + .col-sm-2 { + width: 16.66666667%; + } + .col-sm-1 { + width: 8.33333333%; + } + .col-sm-pull-12 { + right: 100%; + } + .col-sm-pull-11 { + right: 91.66666667%; + } + .col-sm-pull-10 { + right: 83.33333333%; + } + .col-sm-pull-9 { + right: 75%; + } + .col-sm-pull-8 { + right: 66.66666667%; + } + .col-sm-pull-7 { + right: 58.33333333%; + } + .col-sm-pull-6 { + right: 50%; + } + .col-sm-pull-5 { + right: 41.66666667%; + } + .col-sm-pull-4 { + right: 33.33333333%; + } + .col-sm-pull-3 { + right: 25%; + } + .col-sm-pull-2 { + right: 16.66666667%; + } + .col-sm-pull-1 { + right: 8.33333333%; + } + .col-sm-pull-0 { + right: auto; + } + .col-sm-push-12 { + left: 100%; + } + .col-sm-push-11 { + left: 91.66666667%; + } + .col-sm-push-10 { + left: 83.33333333%; + } + .col-sm-push-9 { + left: 75%; + } + .col-sm-push-8 { + left: 66.66666667%; + } + .col-sm-push-7 { + left: 58.33333333%; + } + .col-sm-push-6 { + left: 50%; + } + .col-sm-push-5 { + left: 41.66666667%; + } + .col-sm-push-4 { + left: 33.33333333%; + } + .col-sm-push-3 { + left: 25%; + } + .col-sm-push-2 { + left: 16.66666667%; + } + .col-sm-push-1 { + left: 8.33333333%; + } + .col-sm-push-0 { + left: auto; + } + .col-sm-offset-12 { + margin-left: 100%; + } + .col-sm-offset-11 { + margin-left: 91.66666667%; + } + .col-sm-offset-10 { + margin-left: 83.33333333%; + } + .col-sm-offset-9 { + margin-left: 75%; + } + .col-sm-offset-8 { + margin-left: 66.66666667%; + } + .col-sm-offset-7 { + margin-left: 58.33333333%; + } + .col-sm-offset-6 { + margin-left: 50%; + } + .col-sm-offset-5 { + margin-left: 41.66666667%; + } + .col-sm-offset-4 { + margin-left: 33.33333333%; + } + .col-sm-offset-3 { + margin-left: 25%; + } + .col-sm-offset-2 { + margin-left: 16.66666667%; + } + .col-sm-offset-1 { + margin-left: 8.33333333%; + } + .col-sm-offset-0 { + margin-left: 0; + } +} +@media (min-width: 992px) { + .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 { + float: left; + } + .col-md-12 { + width: 100%; + } + .col-md-11 { + width: 91.66666667%; + } + .col-md-10 { + width: 83.33333333%; + } + .col-md-9 { + width: 75%; + } + .col-md-8 { + width: 66.66666667%; + } + .col-md-7 { + width: 58.33333333%; + } + .col-md-6 { + width: 50%; + } + .col-md-5 { + width: 41.66666667%; + } + .col-md-4 { + width: 33.33333333%; + } + .col-md-3 { + width: 25%; + } + .col-md-2 { + width: 16.66666667%; + } + .col-md-1 { + width: 8.33333333%; + } + .col-md-pull-12 { + right: 100%; + } + .col-md-pull-11 { + right: 91.66666667%; + } + .col-md-pull-10 { + right: 83.33333333%; + } + .col-md-pull-9 { + right: 75%; + } + .col-md-pull-8 { + right: 66.66666667%; + } + .col-md-pull-7 { + right: 58.33333333%; + } + .col-md-pull-6 { + right: 50%; + } + .col-md-pull-5 { + right: 41.66666667%; + } + .col-md-pull-4 { + right: 33.33333333%; + } + .col-md-pull-3 { + right: 25%; + } + .col-md-pull-2 { + right: 16.66666667%; + } + .col-md-pull-1 { + right: 8.33333333%; + } + .col-md-pull-0 { + right: auto; + } + .col-md-push-12 { + left: 100%; + } + .col-md-push-11 { + left: 91.66666667%; + } + .col-md-push-10 { + left: 83.33333333%; + } + .col-md-push-9 { + left: 75%; + } + .col-md-push-8 { + left: 66.66666667%; + } + .col-md-push-7 { + left: 58.33333333%; + } + .col-md-push-6 { + left: 50%; + } + .col-md-push-5 { + left: 41.66666667%; + } + .col-md-push-4 { + left: 33.33333333%; + } + .col-md-push-3 { + left: 25%; + } + .col-md-push-2 { + left: 16.66666667%; + } + .col-md-push-1 { + left: 8.33333333%; + } + .col-md-push-0 { + left: auto; + } + .col-md-offset-12 { + margin-left: 100%; + } + .col-md-offset-11 { + margin-left: 91.66666667%; + } + .col-md-offset-10 { + margin-left: 83.33333333%; + } + .col-md-offset-9 { + margin-left: 75%; + } + .col-md-offset-8 { + margin-left: 66.66666667%; + } + .col-md-offset-7 { + margin-left: 58.33333333%; + } + .col-md-offset-6 { + margin-left: 50%; + } + .col-md-offset-5 { + margin-left: 41.66666667%; + } + .col-md-offset-4 { + margin-left: 33.33333333%; + } + .col-md-offset-3 { + margin-left: 25%; + } + .col-md-offset-2 { + margin-left: 16.66666667%; + } + .col-md-offset-1 { + margin-left: 8.33333333%; + } + .col-md-offset-0 { + margin-left: 0; + } +} +@media (min-width: 1200px) { + .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 { + float: left; + } + .col-lg-12 { + width: 100%; + } + .col-lg-11 { + width: 91.66666667%; + } + .col-lg-10 { + width: 83.33333333%; + } + .col-lg-9 { + width: 75%; + } + .col-lg-8 { + width: 66.66666667%; + } + .col-lg-7 { + width: 58.33333333%; + } + .col-lg-6 { + width: 50%; + } + .col-lg-5 { + width: 41.66666667%; + } + .col-lg-4 { + width: 33.33333333%; + } + .col-lg-3 { + width: 25%; + } + .col-lg-2 { + width: 16.66666667%; + } + .col-lg-1 { + width: 8.33333333%; + } + .col-lg-pull-12 { + right: 100%; + } + .col-lg-pull-11 { + right: 91.66666667%; + } + .col-lg-pull-10 { + right: 83.33333333%; + } + .col-lg-pull-9 { + right: 75%; + } + .col-lg-pull-8 { + right: 66.66666667%; + } + .col-lg-pull-7 { + right: 58.33333333%; + } + .col-lg-pull-6 { + right: 50%; + } + .col-lg-pull-5 { + right: 41.66666667%; + } + .col-lg-pull-4 { + right: 33.33333333%; + } + .col-lg-pull-3 { + right: 25%; + } + .col-lg-pull-2 { + right: 16.66666667%; + } + .col-lg-pull-1 { + right: 8.33333333%; + } + .col-lg-pull-0 { + right: auto; + } + .col-lg-push-12 { + left: 100%; + } + .col-lg-push-11 { + left: 91.66666667%; + } + .col-lg-push-10 { + left: 83.33333333%; + } + .col-lg-push-9 { + left: 75%; + } + .col-lg-push-8 { + left: 66.66666667%; + } + .col-lg-push-7 { + left: 58.33333333%; + } + .col-lg-push-6 { + left: 50%; + } + .col-lg-push-5 { + left: 41.66666667%; + } + .col-lg-push-4 { + left: 33.33333333%; + } + .col-lg-push-3 { + left: 25%; + } + .col-lg-push-2 { + left: 16.66666667%; + } + .col-lg-push-1 { + left: 8.33333333%; + } + .col-lg-push-0 { + left: auto; + } + .col-lg-offset-12 { + margin-left: 100%; + } + .col-lg-offset-11 { + margin-left: 91.66666667%; + } + .col-lg-offset-10 { + margin-left: 83.33333333%; + } + .col-lg-offset-9 { + margin-left: 75%; + } + .col-lg-offset-8 { + margin-left: 66.66666667%; + } + .col-lg-offset-7 { + margin-left: 58.33333333%; + } + .col-lg-offset-6 { + margin-left: 50%; + } + .col-lg-offset-5 { + margin-left: 41.66666667%; + } + .col-lg-offset-4 { + margin-left: 33.33333333%; + } + .col-lg-offset-3 { + margin-left: 25%; + } + .col-lg-offset-2 { + margin-left: 16.66666667%; + } + .col-lg-offset-1 { + margin-left: 8.33333333%; + } + .col-lg-offset-0 { + margin-left: 0; + } +} +table { + background-color: transparent; +} +caption { + padding-top: 8px; + padding-bottom: 8px; + color: #777; + text-align: left; +} +th { + text-align: left; +} +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; +} +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: 20px; + font-size: 21px; + line-height: inherit; + color: #333; + border: 0; + border-bottom: 1px solid #e5e5e5; +} +label { + display: inline-block; + max-width: 100%; + margin-bottom: 5px; + font-weight: bold; +} +.form-control::-moz-placeholder { + color: #999; + opacity: 1; +} +.form-control:-ms-input-placeholder { + color: #999; +} +.form-control::-webkit-input-placeholder { + color: #999; +} +.btn { + display: inline-block; + padding: 6px 12px; + margin-bottom: 0; + font-size: 14px; + font-weight: normal; + line-height: 1.42857143; + text-align: center; + white-space: nowrap; + vertical-align: middle; + -ms-touch-action: manipulation; + touch-action: manipulation; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} +.btn:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +.btn:hover, +.btn:focus { + color: #333; + text-decoration: none; +} +.btn:active { + outline: 0; +} +.btn-default { + color: #333; + background-color: #fff; + border-color: #ccc; +} +.btn-default:focus { + color: #333; + background-color: #e6e6e6; + border-color: #8c8c8c; +} +.btn-default:hover { + color: #333; + background-color: #e6e6e6; + border-color: #adadad; +} +.btn-default:active { + color: #333; + background-color: #e6e6e6; + border-color: #adadad; +} +.btn-default:active { + background-image: none; +} +.btn-primary { + color: #fff; + background-color: #337ab7; + border-color: #2e6da4; +} +.btn-primary:focus { + color: #fff; + border-color: #122b40; +} +.btn-primary:hover { + color: #fff; + background-color: #286090; + border-color: #204d74; +} +.btn-primary:active { + color: #fff; + border-color: #204d74; +} +.btn-primary:active { + background-image: none; +} +.btn-success { + color: #fff; + background-color: #5cb85c; + border-color: #4cae4c; +} +.btn-success:focus { + color: #fff; + border-color: #255625; +} +.btn-success:hover { + color: #fff; + background-color: #449d44; + border-color: #398439; +} +.btn-success:active { + color: #fff; + border-color: #398439; +} +.btn-success:active { + background-image: none; +} +.btn-info { + color: #fff; + background-color: #5bc0de; + border-color: #46b8da; +} +.btn-info:focus { + color: #fff; + border-color: #1b6d85; +} +.btn-info:hover { + color: #fff; + background-color: #31b0d5; + border-color: #269abc; +} +.btn-info:active { + color: #fff; + border-color: #269abc; +} +.btn-lg, +.btn-group-lg > .btn { + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +.btn-sm, +.btn-group-sm > .btn { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-xs, +.btn-group-xs > .btn { + padding: 1px 5px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.nav { + padding-left: 0; + margin-bottom: 0; + list-style: none; +} +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@-o-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +.panel { + margin-bottom: 20px; + background-color: #fff; + border: 1px solid transparent; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05); + box-shadow: 0 1px 1px rgba(0, 0, 0, .05); +} +.panel-body { + padding: 15px; +} +.carousel { + position: relative; +} +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; +} +.carousel-inner > .item { + position: relative; + display: none; + -webkit-transition: .6s ease-in-out left; + -o-transition: .6s ease-in-out left; + transition: .6s ease-in-out left; +} +.carousel-inner > .active { + display: block; +} +.carousel-inner > .active { + left: 0; +} +.carousel-indicators { + position: absolute; + bottom: 10px; + left: 50%; + z-index: 15; + width: 60%; + padding-left: 0; + margin-left: -30%; + text-align: center; + list-style: none; +} +.carousel-indicators li { + display: inline-block; + width: 10px; + height: 10px; + margin: 1px; + text-indent: -999px; + cursor: pointer; + background-color: #000 \9; + background-color: rgba(0, 0, 0, 0); + border: 1px solid #fff; + border-radius: 10px; +} +.carousel-indicators .active { + width: 12px; + height: 12px; + margin: 0; + background-color: #fff; +} +@media screen and (min-width: 768px) { + .carousel-indicators { + bottom: 20px; + } +} +.container:before, +.container:after, +.row:before, +.row:after, +.nav:before, +.nav:after, +.panel-body:before, +.panel-body:after { + display: table; + content: " "; +} +.container:after, +.row:after, +.nav:after, +.panel-body:after { + clear: both; +} +.pull-right { + float: right !important; +} +@-ms-viewport { + width: device-width; +} +@media (max-width: 767px) { + .hidden-xs { + display: none !important; + } +} diff --git a/static/css/content-style.css b/static/css/content-style.css new file mode 100644 index 000000000..1356b0495 --- /dev/null +++ b/static/css/content-style.css @@ -0,0 +1,84 @@ +/* Styles used by tables at the URLs: + + 1. /overview/configuration/#configure-blackfriday-rendering + 2. /templates/functions/#math + +Their HTML is in the files: + + 1. ./docs/content/overview/configuration.md + 2. ./docs/content/templates/functions.md +*/ + +table.table { + margin: 1em 0; +} +table.table-bordered tr th, +table.table-bordered tr td { + border-width: 2px; + border-color: #dddddd; + border-style: solid; + padding: 0 0.5em; +} +table.table-bordered-configuration { + max-width: 100%; +} +table.table-bordered-configuration, +table.table-bordered-configuration tr, +table.table-bordered-configuration tr th, +table.table-bordered-configuration tr td:not(.purpose-description) { + border-width: 2px; +} +table.table-bordered-configuration tr td.purpose-description { + border-width: 1px; +} +table.table-bordered-configuration, +table.table-bordered-configuration tr, +table.table-bordered-configuration tr th, +table.table-bordered-configuration tr td { + border-color: #dddddd; +} +table.table-bordered-configuration { + border-right-style: solid; + border-bottom-style: solid; +} +table.table-bordered-configuration tr, +table.table-bordered-configuration tr th, +table.table-bordered-configuration tr td:not(.purpose-description) { + border-left-style: solid; +} +table.table-bordered-configuration tr th, +table.table-bordered-configuration tr td:not(.purpose-description) { + border-top-style: solid; +} +table.table-bordered-configuration tr td.purpose-description { + border-top-style: dotted; +} +table.table-bordered-configuration tr th, +table.table-bordered-configuration tr td { + padding-top: 0; + padding-right: 0.5em; + padding-left: 0.5em; +} +table.table-bordered-configuration tr th, +table.table-bordered-configuration tr td:not(.purpose-description) { + padding-bottom: 0; +} +table.table-bordered-configuration tr td.purpose-description { + padding-bottom: 0.5em; +} +table.table-bordered-configuration tr th, +table.table-bordered-configuration tr td:not(.purpose-description) { + text-align: center; +} +table.table-bordered-configuration tr td:not(.purpose-description) code { + padding: 0; + border-radius: 0; + font-size: 14px; + background-color: inherit; + color: darkgreen; +} +table.table-bordered-configuration tr td span.purpose-title { + padding-right: 0.15em; + font-style: italic; + color: chocolate; +} diff --git a/static/css/home-page-style-responsive.css b/static/css/home-page-style-responsive.css new file mode 100644 index 000000000..46ce3eb43 --- /dev/null +++ b/static/css/home-page-style-responsive.css @@ -0,0 +1,44 @@ +/* full page image header area */ + +@media (min-width: 1024.1px) { + .header { + background-image: url('../img/desk.jpg'); + } +} +@media (max-width: 319.9px) { + .header { + background-image: url('../img/desk-sm.jpg'); + } +} +@media (max-width: 319.9px), (min-width: 1024.1px) { + .header { + background-position: center center; + -webkit-background-size: cover; + -moz-background-size: cover; + -o-background-size: cover; + background-size: cover; + background-attachment: fixed; + } +} +@media (min-width: 320px) and (max-width: 1024px) { + .header { + background-position: 0% 0%; + -webkit-background-size: 100% 100%; + -moz-background-size: 100% 100%; + -o-background-size: 100% 100%; + background-size: 100% 100%; + background-attachment: scroll; + background-clip: border-box; + background-origin: padding-box; + } +} +@media (min-width: 320px) and (max-width: 1024px) and (orientation: portrait) { + .header { + background-image: url('../img/desk-mini.jpg'); + } +} +@media (min-width: 320px) and (max-width: 1024px) and (orientation: landscape) { + .header { + background-image: url('../img/desk-wide.jpg'); + } +} diff --git a/static/css/home-page-style.css b/static/css/home-page-style.css new file mode 100644 index 000000000..4ff3bc815 --- /dev/null +++ b/static/css/home-page-style.css @@ -0,0 +1,194 @@ +/* global styles */ + +html { + width: 100%; + height: 100%; + letter-spacing: 0.5px; +} +body { + width: 100%; + height: 100%; + letter-spacing: 0.5px; + line-height: 1.6; + font-family: 'Arbutus Slab', "Helvetica Neue", "Helvetica", sans-serif !important; +} +h1, h2, h3, h4, h5, h6 { + font-family: 'Cabin', "Helvetica Neue", "Helvetica", sans-serif; +} +div.vert-text { + display: table-cell; + vertical-align: middle; + text-align: center; +} + +/* full page image header area */ + +div.buttonbox { + margin: 2em 0 4em; +} +img.logo { + padding: 2em; + width: 100%; + max-width: 35em; +} +div#main { + position: relative; + z-index: 99999; + background: rgb(255, 255, 255); + box-shadow: 0 0 15px rgba(0, 0, 0, 0.5); +} +.header { + display: table; + position: relative; + width: 100%; + height: 70%; + min-height: 70%; + z-index: 99999; + background-color: black; + background-repeat: no-repeat; +} +.header a.btn { + margin: 10px; + font-weight: 100; +} + +/* intro */ + +a:hover { + color: rgb(52, 73, 94); +} +i.callout-icon, i.lead-icon, i.point-icon { + display: inline-block; + vertical-align: middle; + text-align: center; + width: 140px; + height: 140px; + font-size: 56px; + line-height: 136px; + border-radius: 50%; + -webkit-transition: box-shadow 0.2s; + -moz-transition: box-shadow 0.2s; + transition: box-shadow 0.2s; +} +div.counterpoint { + background-color: rgb(255, 252, 244); +} +div.counterpoint, div.point { + padding: 50px 0; +} +div.counterpoint a, div.point a { + color: rgb(7,162,166); +} +div.counterpoint h2, div.point h2 { + line-height: 1.7; + font-size: 32pt; +} +a.icon-2x { + font-size: 200%; +} +i.lead-icon { + border: 3px solid #222222; +} +i.lead-icon:hover { + border: 3px solid black; + background: black; + color: #ffffff; +} +div.point { + background: rgb(96,210,211); + color: #ffffff; +} +div.point h2 > i { + color:#FF4088; +} +i.point-icon { + border: 3px solid #ffffff; +} +i.point-icon:hover { + background: #ffffff; + color: rgb(22, 203, 230); +} + +/* callout */ + +div.callout { + display: table; + table-layout: fixed; + width: 100%; + height: 420px; + padding: 50px 0; + background-color: rgb(118,156,172); + color: #ffffff; +} +i.callout-icon { + border: 3px solid #ffffff; +} +i.callout-icon:hover { + background: #ffffff; + color: rgb(249, 176, 190); +} + +/* call to action */ + +div#action.call-to-action { + padding: 30px 0px 40px; + padding: 30px 0px 50px; + width: 100%; + background-color: rgba(255, 255, 255, 0.19); + background: url('../img/gray.png'); + color: #ffffff; +} +div#action.call-to-action code { + font-size: 14pt; +} +div#action.call-to-action h1 { + padding-bottom: .5em; +} +div#action.call-to-action pre { + background-color: #545454; + color: #f9f2f4; + margin-bottom: 0; +} +div#action.call-to-action pre:hover { + background-color: #f9f2f4; + color: #545454; +} +div#action.call-to-action a.btn { + margin: 10px; +} +div#action.call-to-action a.quickstart { + font-weight: 300; + color: white; +} + +/* footer */ + +footer { + padding: 50px 0px 25px 0px; + font-size: 14px; + background: rgb(255, 255, 255); +} +footer a { + color: black; +} +footer a:focus, +footer a:hover { + text-decoration: none; + outline: none; +} +footer a:active { + color: green; +} + +/* Bootstrap addons */ + +div.owl-carousel a { + white-space: nowrap; + color: #243382; +} +div.owl-carousel blockquote { + border-left: 0px; +} +div.owl-carousel blockquote p { + font-size: 20pt; +} diff --git a/static/css/hugofont.css b/static/css/hugofont.css new file mode 100644 index 000000000..09d6ce070 --- /dev/null +++ b/static/css/hugofont.css @@ -0,0 +1,184 @@ +@font-face { + font-family: 'hugo'; + src:url('../fonts/hugo.eot'); + src:url('../fonts/hugo.eot?#iefix') format('embedded-opentype'), + url('../fonts/hugo.woff') format('woff'), + url('../fonts/hugo.ttf') format('truetype'), + url('../fonts/hugo.svg#hugo') format('svg'); + font-weight: normal; + font-style: normal; +} + +[class^="icon-"], [class*=" icon-"] { + font-family: 'hugo'; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-home:before { + content: "\21"; +} +.icon-html5:before { + content: "\23"; +} +.icon-css3:before { + content: "\24"; +} +.icon-console:before { + content: "\25"; +} +.icon-link:before { + content: "\26"; +} +.icon-fire:before { + content: "\28"; +} +.icon-check-alt:before { + content: "\29"; +} +.icon-hugo_serif:before { + content: "\e600"; +} +.icon-x-altx-alt:before { + content: "\2a"; +} +.icon-circlestar:before { + content: "\2b"; +} +.icon-file-css:before { + content: "\2c"; +} +.icon-radio-checked:before { + content: "\2e"; +} +.icon-quote:before { + content: "\44"; +} +.icon-airplane2:before { + content: "\45"; +} +.icon-heart:before { + content: "\46"; +} +.icon-rocket:before { + content: "\47"; +} +.icon-house:before { + content: "\48"; +} +.icon-arrow-right:before { + content: "\e001"; +} +.icon-arrow-left:before { + content: "\e002"; +} +.icon-flow-branch:before { + content: "\e004"; +} +.icon-pen:before { + content: "\e005"; +} +.icon-idea:before { + content: "\3b"; +} +.icon-gears:before { + content: "\3c"; +} +.icon-talking:before { + content: "\3d"; +} +.icon-tag:before { + content: "\3e"; +} +.icon-rocket2:before { + content: "\3f"; +} +.icon-octocat:before { + content: "\41"; +} +.icon-announce:before { + content: "\42"; +} +.icon-edit:before { + content: "\43"; +} +.icon-power-cord:before { + content: "\50"; +} +.icon-apple:before { + content: "\51"; +} +.icon-windows8:before { + content: "\52"; +} +.icon-tux:before { + content: "\53"; +} +.icon-file-xml:before { + content: "\54"; +} +.icon-fork:before { + content: "\55"; +} +.icon-arrow-down:before { + content: "\56"; +} +.icon-pacman:before { + content: "\e000"; +} +.icon-embed:before { + content: "\2f"; +} +.icon-code:before { + content: "\30"; +} +.icon-cc:before { + content: "\31"; +} +.icon-cc-by:before { + content: "\32"; +} +.icon-cc-nc:before { + content: "\33"; +} +.icon-beaker-alt:before { + content: "\39"; +} +.icon-w3c:before { + content: "\3a"; +} +.icon-bolt:before { + content: "\49"; +} +.icon-flow-tree:before { + content: "\4a"; +} +.icon-twitter:before { + content: "\4b"; +} +.icon-beaker:before { + content: "\4c"; +} +.icon-images:before { + content: "\4d"; +} +.icon-bubbles:before { + content: "\4e"; +} +.icon-meter2:before { + content: "\4f"; +} +.icon-hugo_sans:before { + content: "\68"; +} +.icon-spf13:before { + content: "\27"; +} diff --git a/static/css/style-responsive.css b/static/css/style-responsive.css new file mode 100644 index 000000000..58f614bb4 --- /dev/null +++ b/static/css/style-responsive.css @@ -0,0 +1,72 @@ +@media (max-width: 768px) { + + .header { + position: absolute; + } + + /*sidebar*/ + + #sidebar { + height: auto; + overflow: hidden; + position: absolute; + width: 100%; + z-index: 1001; + } + + + /* body container */ + #main-content { + margin: 0px!important; + position: none !important; + } + + .wrapper { + padding: inherit; + } + + #sidebar > ul > li { + margin: 0 10px 5px 10px; + } + #sidebar > ul > li > a { + height:35px; + line-height:35px; + padding: 0 10px; + text-align: left; + } + + #sidebar > ul > li > a, #sidebar > ul > li > ul.sub > li { + width: 100%; + } + #sidebar > ul > li > ul.sub > li > a { + background: transparent !important ; + } + + /* sidebar */ + #sidebar { + margin: 0px !important; + } + + .btn { + margin-bottom: 5px; + } + + .navigation.next { + display: none; + } + +} + +@media (max-width: 480px) { + + .notification-row { + display: none; + } +} + +@media (max-width:360px) { + + h1 { + font-size: 1.9em; + } +} diff --git a/static/css/style.css b/static/css/style.css new file mode 100644 index 000000000..312c247c9 --- /dev/null +++ b/static/css/style.css @@ -0,0 +1,684 @@ +/* Import fonts */ +@import url(//fonts.googleapis.com/css?family=Lato:300,400,700,900,300italic,400italic,700italic,900italic); + +/* ****************************** + For the github btn +****************************** */ + +.github-btn { + font-size: 11px; +} +.github-btn, +.github-btn .btn { + font-weight: bold; +} +.github-btn .btn-default { + text-shadow: 0 1px 0 #fff; + background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#e0e0e0)); + background-image: -webkit-linear-gradient(top, #ffffff, 0%, #e0e0e0, 100%); + background-image: -moz-linear-gradient(top, #ffffff 0%, #e0e0e0 100%); + background-image: linear-gradient(to bottom, #ffffff 0%, #e0e0e0 100%); + background-repeat: repeat-x; + border-color: #dbdbdb; + border-color: #ccc; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0); +} + +.github-btn .btn-default:hover, .github-btn .btn-default:focus { + background-color: #e0e0e0; + background-position: 0 -15px; + color: #333; + border-color: #adadad; +} + +.nav-github { + width: 325px; +} + .nav-github > span { + padding-right: 0.5em; + } + + .icon-github { + display: inline-block; + font-family: FontAwesome; + font-style: normal; + font-weight: normal; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + + .github-watchers .icon-github:before{ + content: "\f005"; + } + + .github-forks .icon-github:before{ + content: "\f126"; + } + +.gh-count{ + padding: 2px 5px 3px 4px; + color: #555; + text-decoration: none; + text-shadow:0 1px 0 #fff; + white-space:nowrap; + cursor:pointer; + border-radius:3px; + position:relative; + display:none; + margin-left:4px; + background-color:#fafafa; + border:1px solid #d4d4d4; +} + +.gh-count:hover,.gh-count:focus{color:#4183c4;text-decoration: none;} +.gh-count:before,.gh-count:after{content:' ';position:absolute;display:inline-block;width:0;height:0;border-color:transparent;border-style:solid} +.gh-count:before{top:50%;left:-3px;margin-top:-4px;border-width:4px 4px 4px 0;border-right-color:#fafafa} +.gh-count:after{top:50%;left:-4px;z-index:-1;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#d4d4d4} + +thead { + font-weight: bold; +} + +table { + width: 100%; +} + + +h1, h2, h3 { + margin-top: .8em; + margin-bottom: .7em; +} + +pre code { + font-size: 15px !important; + font-family: Menlo, Consolas, 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Lucida Console', Monaco, 'Droid Sans Mono', monospace; +} + +body { + color: #353b44; + background: #edece4; + font-family: 'Lato', sans-serif; + padding: 0px !important; + margin: 0px !important; + font-size: 16px !important; + font-weight: 400; +} + +h2,h3,h4,h5{ + font-weight: 700; +} + + +h1[id]:before, h2[id]:before, h3[id]:before, h4[id]:before, h5[id]:before { + display: block; + content: " "; + margin-top: -75px; + height: 75px; + visibility: hidden; +} + +label{ + font-weight: 400; +} + +.sidebar-menu .fa { + width: 30px; + text-align: center; +} + +a, a:hover, a:focus { + text-decoration: none; + outline: none; + outline: 0; +} + +img { + max-width: 100%; + height: auto; +} + +.panel-body a { + line-height: 1.1; + display: inline-block; +} +.panel-body a:after { + display: block; + content: ""; + height: 1px; + width: 0%; + background-color: #ff4088; + -webkit-transition: width 0.5s ease; + -moz-transition: width 0.5s ease; + -ms-transition: width 0.5s ease; + transition: width 0.5s ease; +} + +.panel-body a:hover:after, .panel-body a:focus:after { + width: 100%; +} + +input:focus, textarea:focus { outline: none; } +*:focus {outline: none;} +::selection { + background: #ff4088; + color: #fff; +} +::-moz-selection { + background: #ff4088; + color: #fff; +} + +#container { + width: 100%; + height: 100%; +} + +/*sidebar navigation*/ + +#sidebar { + width: 214px; + height: 100%; + position: fixed; + background: #ffffff; + overflow-y: auto; +} + + +ul.sidebar-menu , ul.sidebar-menu li ul.sub{ + margin: -2px 0 0; + padding: 0; +} + +ul.sidebar-menu { + margin-top: 60px; +} + +#sidebar > ul > li > ul.sub { + display: none; +} + +#sidebar > ul > li.active > ul.sub, #sidebar > ul > li > ul.sub > li > a { + display: block; +} + +ul.sidebar-menu li ul.sub li{ + background: #eeeeee; + margin-bottom: 0; + margin-left: 0; + margin-right: 0; +} + +ul.sidebar-menu li ul.sub li:last-child{ + border-radius: 0 0 4px 4px; + -webkit-border-radius: 0 0 4px 4px; +} + +ul.sidebar-menu li ul.sub li a { + font-size: 12px; + padding: 0 0 0 32px; + line-height: 35px; + height: 35px; + -webkit-transition: all 0.3s ease; + -moz-transition: all 0.3s ease; + -o-transition: all 0.3s ease; + -ms-transition: all 0.3s ease; + transition: all 0.3s ease; + color: #656C73; + font-size: 14px; +} + +ul.sidebar-menu li ul.sub li a:hover, ul.sidebar-menu li ul.sub li.active a { + color: #ff4088; + -webkit-transition: all 0.3s ease; + -moz-transition: all 0.3s ease; + -o-transition: all 0.3s ease; + -ms-transition: all 0.3s ease; + transition: all 0.3s ease; + display: block; +} + +ul.sidebar-menu li{ + line-height: 20px !important; +} + +ul.sidebar-menu li.sub-menu{ + line-height: 15px; + font-size: 16px; +} + +ul.sidebar-menu li a span{ + display: inline-block; +} + +ul.sidebar-menu li a{ + color: #72767D; + text-decoration: none; + display: block; + padding: 10px 0 10px 10px; + font-size: 16px; + font-weight: 400; + outline: none; + -webkit-transition: all 0.3s ease; + -moz-transition: all 0.3s ease; + -o-transition: all 0.3s ease; + -ms-transition: all 0.3s ease; + transition: all 0.3s ease; + border-right: 1px solid #D7D7D7; + border-bottom: 1px solid #D7D7D7; + white-space: nowrap; +} + +ul.sidebar-menu li.active a, ul.sidebar-menu li a:hover, ul.sidebar-menu li a:focus { + background: #eeeeee; + color: #ff4088; + display: block; + /*border-radius: 4px; + -webkit-border-radius: 4px;*/ + -webkit-transition: all 0.3s ease; + -moz-transition: all 0.3s ease; + -o-transition: all 0.3s ease; + -ms-transition: all 0.3s ease; + transition: all 0.3s ease; +} +ul.sidebar-menu li a:hover, ul.sidebar-menu li a:focus { + border-bottom: 1px solid #ff4088; +} +/*ul.sidebar-menu li.active a,*/ ul.sidebar-menu .sub-menu li.active a{ + border-bottom: 1px solid #ff4088; +} + +ul.sidebar-menu li a i { + font-size: 18px; + padding-right: 6px; + /*color: #ff4088;*/ +} + +ul.sidebar-menu li a:hover i, ul.sidebar-menu li a:focus i { + color: #ff4088; +} + +ul.sidebar-menu li.active a i { + color: #ff4088; +} + + +#sidebar ul > li > a .menu-arrow { + float: right; + margin-right: 8px; + margin-top: 6px; +} + +@-moz-document url-prefix() { + #sidebar ul > li > a .menu-arrow { + float: right; + margin-right: 8px; + margin-top: -16px; + } +} + +#main-content { + margin-left: 200px; + line-height: 1.8; + font-size: 18px; +} + +.header { + min-height: 60px; + padding: 0 10px; +} +.header { + position: fixed; + left: 0; + right: 0; + z-index: 1002; + text-align:center; +} + + +.black-bg { + background: rgba(20,20,20,0.9); + border-bottom: 1px solid #f1f2f7; +} + +.wrapper { + display: inline-block; + margin-top: 60px; + padding: 0px 15px 15px 0px; + width: 100%; +} + +a.logo { + font-size: 22px; + font-weight: 400; + color: #8E8E93; + float: left; + margin-top: 10px; + text-transform: uppercase; +} + +a.logo:hover, a.logo:focus { + text-decoration: none; + outline: none; +} + +h1.top-menu { + margin-top: -5px; +} +.title-row { + margin-top: 15px; + margin-left: 16px; + color: #EEE; +} +.notification-row { + float: right; + margin-top: 15px; + margin-left: 65px; +} + + +.top-nav { + margin-top: 15px; +} + +/*--sidebar toggle---*/ + +.toggle-nav { + float: left; + padding-right: 5px; + margin-top: 20px; + cursor: pointer; + color: gray; +} + +.toggle-nav .icon-reorder { + cursor: pointer; + display: inline-block; + font-size: 20px; +} + + +@-webkit-keyframes square { + 0% { background-position: 0 0; } + 25% { background-position: 100% 0; } + 50% { background-position: 100% 100%; } + 75% { background-position: 0 100%; } + 100% { background-position: 0 0; } +} + +@-ms-keyframes square { + 0% { background-position: 0 0; } + 25% { background-position: 100% 0; } + 50% { background-position: 100% 100%; } + 75% { background-position: 0 100%; } + 100% { background-position: 0 0; } +} + +@keyframes square { + 0% { background-position: 0 0; } + 25% { background-position: 100% 0; } + 50% { background-position: 100% 100%; } + 75% { background-position: 0 100%; } + 100% { background-position: 0 0; } +} + +.navigation { + position: absolute; + top: 0; + bottom: 0; + margin: 0; + max-width: 150px; + min-width: 90px; + width:100%; + min-height:1200px; + cursor:pointer; + display: flex; + justify-content: center; + align-content: center; + flex-direction: column; + font-size: 6em; + color: rgba(0,0,0,0.5); + text-align: center; + -webkit-transition: all 350ms ease; + transition: all 350ms ease; +} + +.navigation.next { + right:0; +} + + +.navigation:hover { + background-color: rgba(0,0,0,0.1); +} + +/* Google Custom Search box */ + +input.gsc-input, +.gsc-input-box, +.gsc-input-box-hover, +.gsc-input-box-focus, +.gsc-search-button, +.gsc-inline-block { + box-sizing: content-box; + line-height: normal; +} + +.gsc-control-cse { + padding: 0.1em 0 0.5em 1em !important; + width: 16em !important; + float: right; +} + +input.gsc-search-button-v2 { + padding: 6px 12px !important; +} + +.gsc-search-box-tools .gsc-search-box .gsc-input { + padding-right: 1px !important; +} + +/* Styled keypress from Wikipedia */ + +kbd { + border: 1px solid #aaa; + -moz-border-radius: 0.2em; + -webkit-border-radius: 0.2em; + border-radius: 0.2em; + -moz-box-shadow: 0.1em 0.2em 0.2em #ddd; + -webkit-box-shadow: 0.1em 0.2em 0.2em #ddd; + box-shadow: 0.1em 0.2em 0.2em #ddd; + background-color: #f9f9f9; + background-image: -moz-linear-gradient(top, #eee, #f9f9f9, #eee); + background-image: -o-linear-gradient(top, #eee, #f9f9f9, #eee); + background-image: -webkit-linear-gradient(top, #eee, #f9f9f9, #eee); + background-image: linear-gradient(to bottom, #eee, #f9f9f9, #eee); + padding: 0.1em 0.3em; + font-family: inherit; + font-size: 0.85em; +} + +/* For definitions of variables */ + +dl { + margin: 1em; + border-bottom: 1px solid #ccc; +} + +dt { + float: left; + clear: left; + width: 9.5em; + margin: 0.125em; + padding: 2px 4px; +} + +dd { + padding: 0.2em 0 0.2em 10em; + border-top: 1px solid #ccc; +} + +/* Prevent linebreak right after an icon */ +#main-content .fa { + display: inline; +} + +/* Logo for FreeBSD until Font Awesome adds it, see https://github.com/FortAwesome/Font-Awesome/issues/1116 */ +i.freebsd-19px:before { + content: url(/img/freebsd-19px.svg); + vertical-align: -7%; +} + +/* Responsive videos */ +.video-container { + position: relative; + padding-bottom: 56.25%; /* 16:9 */ + padding-top: 30px; + height: 0; + overflow: hidden; + margin: 20px 0; +} + +.video-container iframe, +.video-container object, +.video-container embed { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +/* Google custom search */ +.cse { + margin-top: 20px; + padding-right: 20px; +} + + +/* Table of contents */ + +.toc ul { list-style: none; margin: 0; padding: 0 5px; } +.toc ul li { display: inline; } +#TableOfContents > ul > li > ul > li > ul li { margin-right: 8px; } +#TableOfContents > ul > li > ul > li > a, #TableOfContents > ul > li > a { font-weight: bold; background-color: #eeeeee; padding: 0 10px; margin: 0 2px; } +#TableOfContents > ul > li > ul > li > a { font-style: italic; } +.toc.compact ul > li > ul > li > ul { display: none; } + +#toc { + position:fixed; + background-color: rgba(0, 0, 0, 0.1); + padding: 10px 50px 10px 20px; +} + +.showcase-container { + display: inline-block; + position: relative; + width: 100%; +} + +.showcase-container img { + border: 1px solid #555; +} + +.showcase-container h4 { + margin-top: 0; + margin-bottom: 0; +} +.dummy { + padding-top: 90%; /* Making rows line up even if img proportions off */ +} + +.thumbnail { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; +} + +@media(max-width:1200px) { + .toc { + display: none; + } +} + + +/* Footer panel */ +.footer-panel { + width: 100%; + border-top:1px #efefef solid; + line-height: 30px; + padding: 25px 0px 15px; + margin-top: 15px; + background: #f9f9f9; + display: inline-block; + float: left; +} + +.footer-panel p { + padding-left: 20px; + padding-right: 20px; + font-size: medium; + font-style: italic; +} + + +/* Search form */ +#search-input { + width: 100%; + border: 1px solid #B3B3B3; + border-radius: 3px; + padding: 5px; +} + +#search-input:focus { + border-color: #F04A9C; +} + +/* Search result wrapper */ +.algolia-autocomplete { + width: 100%; +} + +/* List of search results */ +.aa-dropdown-menu { + box-sizing: border-box; + width: 100%; + background-color: #FFFFFF; + border: 1px solid #B3B3B3; + padding: 0; + font-size: 16px; + margin: 4 0 4 0; +} + +/* Highlight terms in search result headers */ +.algolia-docsearch-suggestion--category-header .algolia-docsearch-suggestion--highlight { + background-color: #F04A9C; +} + +/* Highlight terms in search result body */ +.algolia-docsearch-suggestion--highlight { + color: #F04A9C; + font-weight: 900; +} + +/* Currently selected search result */ +.aa-cursor .algolia-docsearch-suggestion--content { + color: inherit; +} + +.aa-cursor .algolia-docsearch-suggestion { + background: #EFEFEF; + color: #353B44; +} + +.algolia-docsearch-suggestion { + font-size: 16px; + color: #9AA2AB; +} + +.algolia-docsearch-suggestion--category-header, +.algolia-docsearch-suggestion--subcategory-column { + display: none !important; +} diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 000000000..36693330b Binary files /dev/null and b/static/favicon.ico differ diff --git a/examples/blog/static/fonts/glyphicons-halflings-regular.eot b/static/fonts/glyphicons-halflings-regular.eot similarity index 100% rename from examples/blog/static/fonts/glyphicons-halflings-regular.eot rename to static/fonts/glyphicons-halflings-regular.eot diff --git a/examples/blog/static/fonts/glyphicons-halflings-regular.svg b/static/fonts/glyphicons-halflings-regular.svg similarity index 100% rename from examples/blog/static/fonts/glyphicons-halflings-regular.svg rename to static/fonts/glyphicons-halflings-regular.svg diff --git a/examples/blog/static/fonts/glyphicons-halflings-regular.ttf b/static/fonts/glyphicons-halflings-regular.ttf similarity index 100% rename from examples/blog/static/fonts/glyphicons-halflings-regular.ttf rename to static/fonts/glyphicons-halflings-regular.ttf diff --git a/examples/blog/static/fonts/glyphicons-halflings-regular.woff b/static/fonts/glyphicons-halflings-regular.woff similarity index 100% rename from examples/blog/static/fonts/glyphicons-halflings-regular.woff rename to static/fonts/glyphicons-halflings-regular.woff diff --git a/examples/blog/static/fonts/glyphicons-halflings-regular.woff2 b/static/fonts/glyphicons-halflings-regular.woff2 similarity index 100% rename from examples/blog/static/fonts/glyphicons-halflings-regular.woff2 rename to static/fonts/glyphicons-halflings-regular.woff2 diff --git a/static/fonts/hugo.eot b/static/fonts/hugo.eot new file mode 100644 index 000000000..b92f00f93 Binary files /dev/null and b/static/fonts/hugo.eot differ diff --git a/static/fonts/hugo.svg b/static/fonts/hugo.svg new file mode 100644 index 000000000..7913f7c1f --- /dev/null +++ b/static/fonts/hugo.svg @@ -0,0 +1,63 @@ + + + +Generated by IcoMoon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/static/fonts/hugo.ttf b/static/fonts/hugo.ttf new file mode 100644 index 000000000..962914d33 Binary files /dev/null and b/static/fonts/hugo.ttf differ diff --git a/static/fonts/hugo.woff b/static/fonts/hugo.woff new file mode 100644 index 000000000..4693fbe7f Binary files /dev/null and b/static/fonts/hugo.woff differ diff --git a/static/hosting-and-deployment/deployment-with-wercker/adding-a-github-pages-step.png b/static/hosting-and-deployment/deployment-with-wercker/adding-a-github-pages-step.png new file mode 100755 index 000000000..ff28a0661 Binary files /dev/null and b/static/hosting-and-deployment/deployment-with-wercker/adding-a-github-pages-step.png differ diff --git a/static/hosting-and-deployment/deployment-with-wercker/adding-the-project-to-github.png b/static/hosting-and-deployment/deployment-with-wercker/adding-the-project-to-github.png new file mode 100755 index 000000000..e1065bb00 Binary files /dev/null and b/static/hosting-and-deployment/deployment-with-wercker/adding-the-project-to-github.png differ diff --git a/static/hosting-and-deployment/deployment-with-wercker/and-we-ve-got-an-app.png b/static/hosting-and-deployment/deployment-with-wercker/and-we-ve-got-an-app.png new file mode 100755 index 000000000..7f8e10e70 Binary files /dev/null and b/static/hosting-and-deployment/deployment-with-wercker/and-we-ve-got-an-app.png differ diff --git a/static/hosting-and-deployment/deployment-with-wercker/configure-the-deploy-step.png b/static/hosting-and-deployment/deployment-with-wercker/configure-the-deploy-step.png new file mode 100755 index 000000000..550ea1bf2 Binary files /dev/null and b/static/hosting-and-deployment/deployment-with-wercker/configure-the-deploy-step.png differ diff --git a/static/hosting-and-deployment/deployment-with-wercker/creating-a-basic-hugo-site.png b/static/hosting-and-deployment/deployment-with-wercker/creating-a-basic-hugo-site.png new file mode 100755 index 000000000..78d238f88 Binary files /dev/null and b/static/hosting-and-deployment/deployment-with-wercker/creating-a-basic-hugo-site.png differ diff --git a/static/hosting-and-deployment/deployment-with-wercker/public-or-not.png b/static/hosting-and-deployment/deployment-with-wercker/public-or-not.png new file mode 100755 index 000000000..9d81a8ba4 Binary files /dev/null and b/static/hosting-and-deployment/deployment-with-wercker/public-or-not.png differ diff --git a/static/hosting-and-deployment/deployment-with-wercker/using-hugo-build.png b/static/hosting-and-deployment/deployment-with-wercker/using-hugo-build.png new file mode 100755 index 000000000..b0dbec94c Binary files /dev/null and b/static/hosting-and-deployment/deployment-with-wercker/using-hugo-build.png differ diff --git a/static/hosting-and-deployment/deployment-with-wercker/wercker-access.png b/static/hosting-and-deployment/deployment-with-wercker/wercker-access.png new file mode 100755 index 000000000..6e89c0ef3 Binary files /dev/null and b/static/hosting-and-deployment/deployment-with-wercker/wercker-access.png differ diff --git a/static/hosting-and-deployment/deployment-with-wercker/wercker-account-settings.png b/static/hosting-and-deployment/deployment-with-wercker/wercker-account-settings.png new file mode 100644 index 000000000..993a1d9e9 Binary files /dev/null and b/static/hosting-and-deployment/deployment-with-wercker/wercker-account-settings.png differ diff --git a/static/hosting-and-deployment/deployment-with-wercker/wercker-add-app.png b/static/hosting-and-deployment/deployment-with-wercker/wercker-add-app.png new file mode 100755 index 000000000..94ccef518 Binary files /dev/null and b/static/hosting-and-deployment/deployment-with-wercker/wercker-add-app.png differ diff --git a/static/hosting-and-deployment/deployment-with-wercker/wercker-git-connections.png b/static/hosting-and-deployment/deployment-with-wercker/wercker-git-connections.png new file mode 100755 index 000000000..d89c0cd8b Binary files /dev/null and b/static/hosting-and-deployment/deployment-with-wercker/wercker-git-connections.png differ diff --git a/static/hosting-and-deployment/deployment-with-wercker/wercker-search.png b/static/hosting-and-deployment/deployment-with-wercker/wercker-search.png new file mode 100755 index 000000000..d099cfd5c Binary files /dev/null and b/static/hosting-and-deployment/deployment-with-wercker/wercker-search.png differ diff --git a/static/hosting-and-deployment/deployment-with-wercker/wercker-select-owner.png b/static/hosting-and-deployment/deployment-with-wercker/wercker-select-owner.png new file mode 100755 index 000000000..111308508 Binary files /dev/null and b/static/hosting-and-deployment/deployment-with-wercker/wercker-select-owner.png differ diff --git a/static/hosting-and-deployment/deployment-with-wercker/wercker-select-repository.png b/static/hosting-and-deployment/deployment-with-wercker/wercker-select-repository.png new file mode 100755 index 000000000..e8835f21a Binary files /dev/null and b/static/hosting-and-deployment/deployment-with-wercker/wercker-select-repository.png differ diff --git a/static/hosting-and-deployment/deployment-with-wercker/wercker-sign-up-page.png b/static/hosting-and-deployment/deployment-with-wercker/wercker-sign-up-page.png new file mode 100644 index 000000000..28f469649 Binary files /dev/null and b/static/hosting-and-deployment/deployment-with-wercker/wercker-sign-up-page.png differ diff --git a/static/hosting-and-deployment/deployment-with-wercker/wercker-sign-up.png b/static/hosting-and-deployment/deployment-with-wercker/wercker-sign-up.png new file mode 100644 index 000000000..f24996889 Binary files /dev/null and b/static/hosting-and-deployment/deployment-with-wercker/wercker-sign-up.png differ diff --git a/static/hosting-and-deployment/deployment-with-wercker/werckeryml.png b/static/hosting-and-deployment/deployment-with-wercker/werckeryml.png new file mode 100755 index 000000000..be46e6136 Binary files /dev/null and b/static/hosting-and-deployment/deployment-with-wercker/werckeryml.png differ diff --git a/static/hosting-and-deployment/hosting-on-bitbucket/bitbucket-blog-post.png b/static/hosting-and-deployment/hosting-on-bitbucket/bitbucket-blog-post.png new file mode 100755 index 000000000..b78f6fd15 Binary files /dev/null and b/static/hosting-and-deployment/hosting-on-bitbucket/bitbucket-blog-post.png differ diff --git a/static/hosting-and-deployment/hosting-on-bitbucket/bitbucket-create-repo.png b/static/hosting-and-deployment/hosting-on-bitbucket/bitbucket-create-repo.png new file mode 100755 index 000000000..e97f13465 Binary files /dev/null and b/static/hosting-and-deployment/hosting-on-bitbucket/bitbucket-create-repo.png differ diff --git a/static/hosting-and-deployment/hosting-on-netlify/netlify-add-new-site.jpg b/static/hosting-and-deployment/hosting-on-netlify/netlify-add-new-site.jpg new file mode 100644 index 000000000..17698d34a Binary files /dev/null and b/static/hosting-and-deployment/hosting-on-netlify/netlify-add-new-site.jpg differ diff --git a/static/hosting-and-deployment/hosting-on-netlify/netlify-authorize-added-permissions.jpg b/static/hosting-and-deployment/hosting-on-netlify/netlify-authorize-added-permissions.jpg new file mode 100644 index 000000000..eaae924e4 Binary files /dev/null and b/static/hosting-and-deployment/hosting-on-netlify/netlify-authorize-added-permissions.jpg differ diff --git a/static/hosting-and-deployment/hosting-on-netlify/netlify-create-new-site-step-1.jpg b/static/hosting-and-deployment/hosting-on-netlify/netlify-create-new-site-step-1.jpg new file mode 100644 index 000000000..347477dd2 Binary files /dev/null and b/static/hosting-and-deployment/hosting-on-netlify/netlify-create-new-site-step-1.jpg differ diff --git a/static/hosting-and-deployment/hosting-on-netlify/netlify-create-new-site-step-2.jpg b/static/hosting-and-deployment/hosting-on-netlify/netlify-create-new-site-step-2.jpg new file mode 100644 index 000000000..18bfd6fed Binary files /dev/null and b/static/hosting-and-deployment/hosting-on-netlify/netlify-create-new-site-step-2.jpg differ diff --git a/static/hosting-and-deployment/hosting-on-netlify/netlify-create-new-site-step-3.jpg b/static/hosting-and-deployment/hosting-on-netlify/netlify-create-new-site-step-3.jpg new file mode 100644 index 000000000..6f9b6477c Binary files /dev/null and b/static/hosting-and-deployment/hosting-on-netlify/netlify-create-new-site-step-3.jpg differ diff --git a/static/hosting-and-deployment/hosting-on-netlify/netlify-deploy-published.jpg b/static/hosting-and-deployment/hosting-on-netlify/netlify-deploy-published.jpg new file mode 100644 index 000000000..ed5eaf3c8 Binary files /dev/null and b/static/hosting-and-deployment/hosting-on-netlify/netlify-deploy-published.jpg differ diff --git a/static/hosting-and-deployment/hosting-on-netlify/netlify-deploying-site.gif b/static/hosting-and-deployment/hosting-on-netlify/netlify-deploying-site.gif new file mode 100644 index 000000000..c1f27c236 Binary files /dev/null and b/static/hosting-and-deployment/hosting-on-netlify/netlify-deploying-site.gif differ diff --git a/static/hosting-and-deployment/hosting-on-netlify/netlify-first-authorize.jpg b/static/hosting-and-deployment/hosting-on-netlify/netlify-first-authorize.jpg new file mode 100644 index 000000000..748122e89 Binary files /dev/null and b/static/hosting-and-deployment/hosting-on-netlify/netlify-first-authorize.jpg differ diff --git a/static/hosting-and-deployment/hosting-on-netlify/netlify-live-site.jpg b/static/hosting-and-deployment/hosting-on-netlify/netlify-live-site.jpg new file mode 100644 index 000000000..3edc49c43 Binary files /dev/null and b/static/hosting-and-deployment/hosting-on-netlify/netlify-live-site.jpg differ diff --git a/static/hosting-and-deployment/hosting-on-netlify/netlify-push-to-deploy.jpg b/static/hosting-and-deployment/hosting-on-netlify/netlify-push-to-deploy.jpg new file mode 100644 index 000000000..f23626218 Binary files /dev/null and b/static/hosting-and-deployment/hosting-on-netlify/netlify-push-to-deploy.jpg differ diff --git a/static/hosting-and-deployment/hosting-on-netlify/netlify-signup.jpg b/static/hosting-and-deployment/hosting-on-netlify/netlify-signup.jpg new file mode 100644 index 000000000..cd9a218b4 Binary files /dev/null and b/static/hosting-and-deployment/hosting-on-netlify/netlify-signup.jpg differ diff --git a/static/hosting-and-deployment/hosting-on-netlify/tibobeijennl.jpg b/static/hosting-and-deployment/hosting-on-netlify/tibobeijennl.jpg new file mode 100644 index 000000000..ad8726820 Binary files /dev/null and b/static/hosting-and-deployment/hosting-on-netlify/tibobeijennl.jpg differ diff --git a/static/images/blog/hugo-26-poster.png b/static/images/blog/hugo-26-poster.png new file mode 100644 index 000000000..827f1f7bb Binary files /dev/null and b/static/images/blog/hugo-26-poster.png differ diff --git a/static/images/blog/hugo-http2-push.png b/static/images/blog/hugo-http2-push.png new file mode 100644 index 000000000..1ddfd4653 Binary files /dev/null and b/static/images/blog/hugo-http2-push.png differ diff --git a/static/images/contribute/development/accept-cla.png b/static/images/contribute/development/accept-cla.png new file mode 100755 index 000000000..929fda6ab Binary files /dev/null and b/static/images/contribute/development/accept-cla.png differ diff --git a/static/images/contribute/development/ci-errors.png b/static/images/contribute/development/ci-errors.png new file mode 100755 index 000000000..95cd290b6 Binary files /dev/null and b/static/images/contribute/development/ci-errors.png differ diff --git a/static/images/contribute/development/copy-remote-url.png b/static/images/contribute/development/copy-remote-url.png new file mode 100755 index 000000000..9006f4a48 Binary files /dev/null and b/static/images/contribute/development/copy-remote-url.png differ diff --git a/static/images/contribute/development/forking-a-repository.png b/static/images/contribute/development/forking-a-repository.png new file mode 100755 index 000000000..ea132cab3 Binary files /dev/null and b/static/images/contribute/development/forking-a-repository.png differ diff --git a/static/images/contribute/development/open-pull-request.png b/static/images/contribute/development/open-pull-request.png new file mode 100755 index 000000000..63b504fb2 Binary files /dev/null and b/static/images/contribute/development/open-pull-request.png differ diff --git a/static/images/gohugoio-card-1.png b/static/images/gohugoio-card-1.png new file mode 100644 index 000000000..09953aed9 Binary files /dev/null and b/static/images/gohugoio-card-1.png differ diff --git a/static/images/hosting-and-deployment/deployment-with-wercker/adding-a-github-pages-step.png b/static/images/hosting-and-deployment/deployment-with-wercker/adding-a-github-pages-step.png new file mode 100755 index 000000000..ff28a0661 Binary files /dev/null and b/static/images/hosting-and-deployment/deployment-with-wercker/adding-a-github-pages-step.png differ diff --git a/static/images/hosting-and-deployment/deployment-with-wercker/adding-the-project-to-github.png b/static/images/hosting-and-deployment/deployment-with-wercker/adding-the-project-to-github.png new file mode 100755 index 000000000..e1065bb00 Binary files /dev/null and b/static/images/hosting-and-deployment/deployment-with-wercker/adding-the-project-to-github.png differ diff --git a/static/images/hosting-and-deployment/deployment-with-wercker/and-we-ve-got-an-app.png b/static/images/hosting-and-deployment/deployment-with-wercker/and-we-ve-got-an-app.png new file mode 100755 index 000000000..7f8e10e70 Binary files /dev/null and b/static/images/hosting-and-deployment/deployment-with-wercker/and-we-ve-got-an-app.png differ diff --git a/static/images/hosting-and-deployment/deployment-with-wercker/configure-the-deploy-step.png b/static/images/hosting-and-deployment/deployment-with-wercker/configure-the-deploy-step.png new file mode 100755 index 000000000..550ea1bf2 Binary files /dev/null and b/static/images/hosting-and-deployment/deployment-with-wercker/configure-the-deploy-step.png differ diff --git a/static/images/hosting-and-deployment/deployment-with-wercker/creating-a-basic-hugo-site.png b/static/images/hosting-and-deployment/deployment-with-wercker/creating-a-basic-hugo-site.png new file mode 100755 index 000000000..78d238f88 Binary files /dev/null and b/static/images/hosting-and-deployment/deployment-with-wercker/creating-a-basic-hugo-site.png differ diff --git a/static/images/hosting-and-deployment/deployment-with-wercker/public-or-not.png b/static/images/hosting-and-deployment/deployment-with-wercker/public-or-not.png new file mode 100755 index 000000000..9d81a8ba4 Binary files /dev/null and b/static/images/hosting-and-deployment/deployment-with-wercker/public-or-not.png differ diff --git a/static/images/hosting-and-deployment/deployment-with-wercker/using-hugo-build.png b/static/images/hosting-and-deployment/deployment-with-wercker/using-hugo-build.png new file mode 100755 index 000000000..b0dbec94c Binary files /dev/null and b/static/images/hosting-and-deployment/deployment-with-wercker/using-hugo-build.png differ diff --git a/static/images/hosting-and-deployment/deployment-with-wercker/wercker-access.png b/static/images/hosting-and-deployment/deployment-with-wercker/wercker-access.png new file mode 100755 index 000000000..6e89c0ef3 Binary files /dev/null and b/static/images/hosting-and-deployment/deployment-with-wercker/wercker-access.png differ diff --git a/static/images/hosting-and-deployment/deployment-with-wercker/wercker-account-settings.png b/static/images/hosting-and-deployment/deployment-with-wercker/wercker-account-settings.png new file mode 100644 index 000000000..993a1d9e9 Binary files /dev/null and b/static/images/hosting-and-deployment/deployment-with-wercker/wercker-account-settings.png differ diff --git a/static/images/hosting-and-deployment/deployment-with-wercker/wercker-add-app.png b/static/images/hosting-and-deployment/deployment-with-wercker/wercker-add-app.png new file mode 100755 index 000000000..94ccef518 Binary files /dev/null and b/static/images/hosting-and-deployment/deployment-with-wercker/wercker-add-app.png differ diff --git a/static/images/hosting-and-deployment/deployment-with-wercker/wercker-git-connections.png b/static/images/hosting-and-deployment/deployment-with-wercker/wercker-git-connections.png new file mode 100755 index 000000000..d89c0cd8b Binary files /dev/null and b/static/images/hosting-and-deployment/deployment-with-wercker/wercker-git-connections.png differ diff --git a/static/images/hosting-and-deployment/deployment-with-wercker/wercker-search.png b/static/images/hosting-and-deployment/deployment-with-wercker/wercker-search.png new file mode 100755 index 000000000..d099cfd5c Binary files /dev/null and b/static/images/hosting-and-deployment/deployment-with-wercker/wercker-search.png differ diff --git a/static/images/hosting-and-deployment/deployment-with-wercker/wercker-select-owner.png b/static/images/hosting-and-deployment/deployment-with-wercker/wercker-select-owner.png new file mode 100755 index 000000000..111308508 Binary files /dev/null and b/static/images/hosting-and-deployment/deployment-with-wercker/wercker-select-owner.png differ diff --git a/static/images/hosting-and-deployment/deployment-with-wercker/wercker-select-repository.png b/static/images/hosting-and-deployment/deployment-with-wercker/wercker-select-repository.png new file mode 100755 index 000000000..e8835f21a Binary files /dev/null and b/static/images/hosting-and-deployment/deployment-with-wercker/wercker-select-repository.png differ diff --git a/static/images/hosting-and-deployment/deployment-with-wercker/wercker-sign-up-page.png b/static/images/hosting-and-deployment/deployment-with-wercker/wercker-sign-up-page.png new file mode 100644 index 000000000..28f469649 Binary files /dev/null and b/static/images/hosting-and-deployment/deployment-with-wercker/wercker-sign-up-page.png differ diff --git a/static/images/hosting-and-deployment/deployment-with-wercker/wercker-sign-up.png b/static/images/hosting-and-deployment/deployment-with-wercker/wercker-sign-up.png new file mode 100644 index 000000000..f24996889 Binary files /dev/null and b/static/images/hosting-and-deployment/deployment-with-wercker/wercker-sign-up.png differ diff --git a/static/images/hosting-and-deployment/deployment-with-wercker/werckeryml.png b/static/images/hosting-and-deployment/deployment-with-wercker/werckeryml.png new file mode 100755 index 000000000..be46e6136 Binary files /dev/null and b/static/images/hosting-and-deployment/deployment-with-wercker/werckeryml.png differ diff --git a/static/images/hosting-and-deployment/hosting-on-bitbucket/bitbucket-blog-post.png b/static/images/hosting-and-deployment/hosting-on-bitbucket/bitbucket-blog-post.png new file mode 100755 index 000000000..b78f6fd15 Binary files /dev/null and b/static/images/hosting-and-deployment/hosting-on-bitbucket/bitbucket-blog-post.png differ diff --git a/static/images/hosting-and-deployment/hosting-on-bitbucket/bitbucket-create-repo.png b/static/images/hosting-and-deployment/hosting-on-bitbucket/bitbucket-create-repo.png new file mode 100755 index 000000000..e97f13465 Binary files /dev/null and b/static/images/hosting-and-deployment/hosting-on-bitbucket/bitbucket-create-repo.png differ diff --git a/static/images/hosting-and-deployment/hosting-on-netlify/netlify-add-new-site.jpg b/static/images/hosting-and-deployment/hosting-on-netlify/netlify-add-new-site.jpg new file mode 100644 index 000000000..17698d34a Binary files /dev/null and b/static/images/hosting-and-deployment/hosting-on-netlify/netlify-add-new-site.jpg differ diff --git a/static/images/hosting-and-deployment/hosting-on-netlify/netlify-authorize-added-permissions.jpg b/static/images/hosting-and-deployment/hosting-on-netlify/netlify-authorize-added-permissions.jpg new file mode 100644 index 000000000..eaae924e4 Binary files /dev/null and b/static/images/hosting-and-deployment/hosting-on-netlify/netlify-authorize-added-permissions.jpg differ diff --git a/static/images/hosting-and-deployment/hosting-on-netlify/netlify-create-new-site-step-1.jpg b/static/images/hosting-and-deployment/hosting-on-netlify/netlify-create-new-site-step-1.jpg new file mode 100644 index 000000000..347477dd2 Binary files /dev/null and b/static/images/hosting-and-deployment/hosting-on-netlify/netlify-create-new-site-step-1.jpg differ diff --git a/static/images/hosting-and-deployment/hosting-on-netlify/netlify-create-new-site-step-2.jpg b/static/images/hosting-and-deployment/hosting-on-netlify/netlify-create-new-site-step-2.jpg new file mode 100644 index 000000000..18bfd6fed Binary files /dev/null and b/static/images/hosting-and-deployment/hosting-on-netlify/netlify-create-new-site-step-2.jpg differ diff --git a/static/images/hosting-and-deployment/hosting-on-netlify/netlify-create-new-site-step-3.jpg b/static/images/hosting-and-deployment/hosting-on-netlify/netlify-create-new-site-step-3.jpg new file mode 100644 index 000000000..6f9b6477c Binary files /dev/null and b/static/images/hosting-and-deployment/hosting-on-netlify/netlify-create-new-site-step-3.jpg differ diff --git a/static/images/hosting-and-deployment/hosting-on-netlify/netlify-deploy-published.jpg b/static/images/hosting-and-deployment/hosting-on-netlify/netlify-deploy-published.jpg new file mode 100644 index 000000000..ed5eaf3c8 Binary files /dev/null and b/static/images/hosting-and-deployment/hosting-on-netlify/netlify-deploy-published.jpg differ diff --git a/static/images/hosting-and-deployment/hosting-on-netlify/netlify-deploying-site.gif b/static/images/hosting-and-deployment/hosting-on-netlify/netlify-deploying-site.gif new file mode 100644 index 000000000..c1f27c236 Binary files /dev/null and b/static/images/hosting-and-deployment/hosting-on-netlify/netlify-deploying-site.gif differ diff --git a/static/images/hosting-and-deployment/hosting-on-netlify/netlify-first-authorize.jpg b/static/images/hosting-and-deployment/hosting-on-netlify/netlify-first-authorize.jpg new file mode 100644 index 000000000..748122e89 Binary files /dev/null and b/static/images/hosting-and-deployment/hosting-on-netlify/netlify-first-authorize.jpg differ diff --git a/static/images/hosting-and-deployment/hosting-on-netlify/netlify-live-site.jpg b/static/images/hosting-and-deployment/hosting-on-netlify/netlify-live-site.jpg new file mode 100644 index 000000000..3edc49c43 Binary files /dev/null and b/static/images/hosting-and-deployment/hosting-on-netlify/netlify-live-site.jpg differ diff --git a/static/images/hosting-and-deployment/hosting-on-netlify/netlify-push-to-deploy.jpg b/static/images/hosting-and-deployment/hosting-on-netlify/netlify-push-to-deploy.jpg new file mode 100644 index 000000000..f23626218 Binary files /dev/null and b/static/images/hosting-and-deployment/hosting-on-netlify/netlify-push-to-deploy.jpg differ diff --git a/static/images/hosting-and-deployment/hosting-on-netlify/netlify-signup.jpg b/static/images/hosting-and-deployment/hosting-on-netlify/netlify-signup.jpg new file mode 100644 index 000000000..cd9a218b4 Binary files /dev/null and b/static/images/hosting-and-deployment/hosting-on-netlify/netlify-signup.jpg differ diff --git a/static/images/hosting-and-deployment/hosting-on-netlify/tibobeijennl.jpg b/static/images/hosting-and-deployment/hosting-on-netlify/tibobeijennl.jpg new file mode 100644 index 000000000..ad8726820 Binary files /dev/null and b/static/images/hosting-and-deployment/hosting-on-netlify/tibobeijennl.jpg differ diff --git a/static/images/icon-custom-outputs.svg b/static/images/icon-custom-outputs.svg new file mode 100644 index 000000000..ccf581f31 --- /dev/null +++ b/static/images/icon-custom-outputs.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/images/quickstart/bookshelf-bleak-theme.png b/static/images/quickstart/bookshelf-bleak-theme.png new file mode 100755 index 000000000..ccd18c42d Binary files /dev/null and b/static/images/quickstart/bookshelf-bleak-theme.png differ diff --git a/static/images/quickstart/bookshelf-disqus.png b/static/images/quickstart/bookshelf-disqus.png new file mode 100755 index 000000000..3ce645a0c Binary files /dev/null and b/static/images/quickstart/bookshelf-disqus.png differ diff --git a/static/images/quickstart/bookshelf-new-default-image.png b/static/images/quickstart/bookshelf-new-default-image.png new file mode 100755 index 000000000..d7274c7a6 Binary files /dev/null and b/static/images/quickstart/bookshelf-new-default-image.png differ diff --git a/static/images/quickstart/bookshelf-only-picture.png b/static/images/quickstart/bookshelf-only-picture.png new file mode 100755 index 000000000..a363383bc Binary files /dev/null and b/static/images/quickstart/bookshelf-only-picture.png differ diff --git a/static/images/quickstart/bookshelf-robust-theme.png b/static/images/quickstart/bookshelf-robust-theme.png new file mode 100755 index 000000000..7c5e6b8d2 Binary files /dev/null and b/static/images/quickstart/bookshelf-robust-theme.png differ diff --git a/static/images/quickstart/bookshelf-updated-config.png b/static/images/quickstart/bookshelf-updated-config.png new file mode 100755 index 000000000..bbda606c7 Binary files /dev/null and b/static/images/quickstart/bookshelf-updated-config.png differ diff --git a/static/images/quickstart/bookshelf.png b/static/images/quickstart/bookshelf.png new file mode 100755 index 000000000..3b572adbb Binary files /dev/null and b/static/images/quickstart/bookshelf.png differ diff --git a/static/images/quickstart/default.jpg b/static/images/quickstart/default.jpg new file mode 100755 index 000000000..78d7bd28e Binary files /dev/null and b/static/images/quickstart/default.jpg differ diff --git a/static/images/quickstart/gh-pages-ui.png b/static/images/quickstart/gh-pages-ui.png new file mode 100644 index 000000000..3a7d10305 Binary files /dev/null and b/static/images/quickstart/gh-pages-ui.png differ diff --git a/static/images/site-hierarchy.svg b/static/images/site-hierarchy.svg new file mode 100644 index 000000000..7d1a043e8 --- /dev/null +++ b/static/images/site-hierarchy.svg @@ -0,0 +1,634 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/img/2626info-tn.png b/static/img/2626info-tn.png new file mode 100644 index 000000000..fb6b11757 Binary files /dev/null and b/static/img/2626info-tn.png differ diff --git a/static/img/antzucaro-tn.jpg b/static/img/antzucaro-tn.jpg new file mode 100644 index 000000000..188769c0f Binary files /dev/null and b/static/img/antzucaro-tn.jpg differ diff --git a/static/img/apperneticioblog.png b/static/img/apperneticioblog.png new file mode 100644 index 000000000..f2fcf6d96 Binary files /dev/null and b/static/img/apperneticioblog.png differ diff --git a/static/img/arresteddevops-tn.png b/static/img/arresteddevops-tn.png new file mode 100644 index 000000000..868df1d77 Binary files /dev/null and b/static/img/arresteddevops-tn.png differ diff --git a/static/img/asc-tn.jpg b/static/img/asc-tn.jpg new file mode 100644 index 000000000..a5148e236 Binary files /dev/null and b/static/img/asc-tn.jpg differ diff --git a/static/img/astrochili-tn.png b/static/img/astrochili-tn.png new file mode 100644 index 000000000..ec11741ee Binary files /dev/null and b/static/img/astrochili-tn.png differ diff --git a/static/img/aydoscom.png b/static/img/aydoscom.png new file mode 100644 index 000000000..f2cfc3982 Binary files /dev/null and b/static/img/aydoscom.png differ diff --git a/static/img/balaramadurai-net-tn.jpg b/static/img/balaramadurai-net-tn.jpg new file mode 100644 index 000000000..207a4a840 Binary files /dev/null and b/static/img/balaramadurai-net-tn.jpg differ diff --git a/static/img/barricade-tn.png b/static/img/barricade-tn.png new file mode 100644 index 000000000..96eed0fbe Binary files /dev/null and b/static/img/barricade-tn.png differ diff --git a/static/img/bepsays-tn.png b/static/img/bepsays-tn.png new file mode 100644 index 000000000..ca9119cc5 Binary files /dev/null and b/static/img/bepsays-tn.png differ diff --git a/static/img/bharathpalavalli-tn.png b/static/img/bharathpalavalli-tn.png new file mode 100644 index 000000000..bcf15ed0a Binary files /dev/null and b/static/img/bharathpalavalli-tn.png differ diff --git a/static/img/bugtrackersio-tn.jpg b/static/img/bugtrackersio-tn.jpg new file mode 100644 index 000000000..a56e94009 Binary files /dev/null and b/static/img/bugtrackersio-tn.jpg differ diff --git a/static/img/bullion-investor-com.png b/static/img/bullion-investor-com.png new file mode 100644 index 000000000..3cd78b97d Binary files /dev/null and b/static/img/bullion-investor-com.png differ diff --git a/static/img/camunda-blog.png b/static/img/camunda-blog.png new file mode 100644 index 000000000..95a004ff7 Binary files /dev/null and b/static/img/camunda-blog.png differ diff --git a/static/img/camunda-docs.png b/static/img/camunda-docs.png new file mode 100644 index 000000000..e008fdabb Binary files /dev/null and b/static/img/camunda-docs.png differ diff --git a/static/img/carnivorousplants-tn.png b/static/img/carnivorousplants-tn.png new file mode 100644 index 000000000..2e45bc013 Binary files /dev/null and b/static/img/carnivorousplants-tn.png differ diff --git a/static/img/cdnoverview-tn.png b/static/img/cdnoverview-tn.png new file mode 100644 index 000000000..a95852c45 Binary files /dev/null and b/static/img/cdnoverview-tn.png differ diff --git a/static/img/chinese-grammar-tn.png b/static/img/chinese-grammar-tn.png new file mode 100644 index 000000000..3d84184cf Binary files /dev/null and b/static/img/chinese-grammar-tn.png differ diff --git a/static/img/chingli-tn.jpg b/static/img/chingli-tn.jpg new file mode 100644 index 000000000..61ee53e04 Binary files /dev/null and b/static/img/chingli-tn.jpg differ diff --git a/static/img/chipsncookies-tn.png b/static/img/chipsncookies-tn.png new file mode 100644 index 000000000..f355cb5a4 Binary files /dev/null and b/static/img/chipsncookies-tn.png differ diff --git a/static/img/christianmendoza-tn.jpg b/static/img/christianmendoza-tn.jpg new file mode 100644 index 000000000..82b45afa4 Binary files /dev/null and b/static/img/christianmendoza-tn.jpg differ diff --git a/static/img/cinegyopen-tn.png b/static/img/cinegyopen-tn.png new file mode 100644 index 000000000..3216259fc Binary files /dev/null and b/static/img/cinegyopen-tn.png differ diff --git a/static/img/clearhaus-tn.png b/static/img/clearhaus-tn.png new file mode 100644 index 000000000..4785019a6 Binary files /dev/null and b/static/img/clearhaus-tn.png differ diff --git a/static/img/cloudshark-tn.jpg b/static/img/cloudshark-tn.jpg new file mode 100644 index 000000000..68f8018ef Binary files /dev/null and b/static/img/cloudshark-tn.jpg differ diff --git a/static/img/codingjournal-tn.png b/static/img/codingjournal-tn.png new file mode 100644 index 000000000..e2bfde580 Binary files /dev/null and b/static/img/codingjournal-tn.png differ diff --git a/static/img/consequently.jpg b/static/img/consequently.jpg new file mode 100644 index 000000000..fdb1ebd7b Binary files /dev/null and b/static/img/consequently.jpg differ diff --git a/static/img/content/archetypes/archetype-hierarchy.png b/static/img/content/archetypes/archetype-hierarchy.png new file mode 100644 index 000000000..cb0d0bcf4 Binary files /dev/null and b/static/img/content/archetypes/archetype-hierarchy.png differ diff --git a/static/img/ctlcompiled-tn.png b/static/img/ctlcompiled-tn.png new file mode 100644 index 000000000..e5137da94 Binary files /dev/null and b/static/img/ctlcompiled-tn.png differ diff --git a/static/img/danmux-tn.jpg b/static/img/danmux-tn.jpg new file mode 100644 index 000000000..e6c82a2ef Binary files /dev/null and b/static/img/danmux-tn.jpg differ diff --git a/static/img/datapipelinearchitect-tn.jpg b/static/img/datapipelinearchitect-tn.jpg new file mode 100644 index 000000000..597ecdbba Binary files /dev/null and b/static/img/datapipelinearchitect-tn.jpg differ diff --git a/static/img/davidepetilli-tn.jpg b/static/img/davidepetilli-tn.jpg new file mode 100644 index 000000000..a46fb9149 Binary files /dev/null and b/static/img/davidepetilli-tn.jpg differ diff --git a/static/img/davidrallen-tn.png b/static/img/davidrallen-tn.png new file mode 100644 index 000000000..937147dee Binary files /dev/null and b/static/img/davidrallen-tn.png differ diff --git a/static/img/davidyates-tn.png b/static/img/davidyates-tn.png new file mode 100644 index 000000000..a6a0a6143 Binary files /dev/null and b/static/img/davidyates-tn.png differ diff --git a/static/img/dbzman-online-tn.png b/static/img/dbzman-online-tn.png new file mode 100644 index 000000000..eb9d4ea0c Binary files /dev/null and b/static/img/dbzman-online-tn.png differ diff --git a/static/img/desk-mini.jpg b/static/img/desk-mini.jpg new file mode 100644 index 000000000..ff296c8f7 Binary files /dev/null and b/static/img/desk-mini.jpg differ diff --git a/static/img/desk-sm.jpg b/static/img/desk-sm.jpg new file mode 100644 index 000000000..d4a21ad55 Binary files /dev/null and b/static/img/desk-sm.jpg differ diff --git a/static/img/desk-wide.jpg b/static/img/desk-wide.jpg new file mode 100644 index 000000000..8ff17bc4d Binary files /dev/null and b/static/img/desk-wide.jpg differ diff --git a/static/img/desk.jpg b/static/img/desk.jpg new file mode 100644 index 000000000..43ecc6694 Binary files /dev/null and b/static/img/desk.jpg differ diff --git a/static/img/devmonk-tn.jpg b/static/img/devmonk-tn.jpg new file mode 100644 index 000000000..05331d119 Binary files /dev/null and b/static/img/devmonk-tn.jpg differ diff --git a/static/img/dmitriid.com.png b/static/img/dmitriid.com.png new file mode 100644 index 000000000..9c7217f6e Binary files /dev/null and b/static/img/dmitriid.com.png differ diff --git a/static/img/docs.eurie.io-tn.png b/static/img/docs.eurie.io-tn.png new file mode 100644 index 000000000..43443167b Binary files /dev/null and b/static/img/docs.eurie.io-tn.png differ diff --git a/static/img/emilyhorsman.com-tn.jpg b/static/img/emilyhorsman.com-tn.jpg new file mode 100644 index 000000000..99d2f9559 Binary files /dev/null and b/static/img/emilyhorsman.com-tn.jpg differ diff --git a/static/img/enjoyablerecipes-tn.png b/static/img/enjoyablerecipes-tn.png new file mode 100644 index 000000000..460c804bc Binary files /dev/null and b/static/img/enjoyablerecipes-tn.png differ diff --git a/static/img/esaezgil_com-tn.png b/static/img/esaezgil_com-tn.png new file mode 100644 index 000000000..f9b4087b6 Binary files /dev/null and b/static/img/esaezgil_com-tn.png differ diff --git a/static/img/esolia_com-tn.png b/static/img/esolia_com-tn.png new file mode 100644 index 000000000..4574b085f Binary files /dev/null and b/static/img/esolia_com-tn.png differ diff --git a/static/img/esolia_pro-tn.png b/static/img/esolia_pro-tn.png new file mode 100644 index 000000000..021911456 Binary files /dev/null and b/static/img/esolia_pro-tn.png differ diff --git a/static/img/fale-tn.png b/static/img/fale-tn.png new file mode 100644 index 000000000..2c5f53f84 Binary files /dev/null and b/static/img/fale-tn.png differ diff --git a/static/img/firstnameclub.png b/static/img/firstnameclub.png new file mode 100644 index 000000000..b5bf80847 Binary files /dev/null and b/static/img/firstnameclub.png differ diff --git a/static/img/fixatom-tn.png b/static/img/fixatom-tn.png new file mode 100644 index 000000000..39ded2463 Binary files /dev/null and b/static/img/fixatom-tn.png differ diff --git a/static/img/freebsd-19px.svg b/static/img/freebsd-19px.svg new file mode 100644 index 000000000..4215b83a9 --- /dev/null +++ b/static/img/freebsd-19px.svg @@ -0,0 +1,127 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + diff --git a/static/img/furqansoftware-tn.png b/static/img/furqansoftware-tn.png new file mode 100644 index 000000000..e1d0e964f Binary files /dev/null and b/static/img/furqansoftware-tn.png differ diff --git a/static/img/fxsitecompat-tn.png b/static/img/fxsitecompat-tn.png new file mode 100644 index 000000000..3df542f59 Binary files /dev/null and b/static/img/fxsitecompat-tn.png differ diff --git a/static/img/gntech-tn.png b/static/img/gntech-tn.png new file mode 100644 index 000000000..0a3fad9ba Binary files /dev/null and b/static/img/gntech-tn.png differ diff --git a/static/img/gogb-tn.jpg b/static/img/gogb-tn.jpg new file mode 100644 index 000000000..caed5cfbb Binary files /dev/null and b/static/img/gogb-tn.jpg differ diff --git a/static/img/goin5minutes-tn.png b/static/img/goin5minutes-tn.png new file mode 100644 index 000000000..eef26f110 Binary files /dev/null and b/static/img/goin5minutes-tn.png differ diff --git a/static/img/gray.png b/static/img/gray.png new file mode 100644 index 000000000..3807691d3 Binary files /dev/null and b/static/img/gray.png differ diff --git a/static/img/h10n.me-tn.png b/static/img/h10n.me-tn.png new file mode 100644 index 000000000..74bfee21b Binary files /dev/null and b/static/img/h10n.me-tn.png differ diff --git a/static/img/heimatverein-niederjosbach-tn.png b/static/img/heimatverein-niederjosbach-tn.png new file mode 100644 index 000000000..f47425b7e Binary files /dev/null and b/static/img/heimatverein-niederjosbach-tn.png differ diff --git a/static/img/horeaporutiu-tn.jpg b/static/img/horeaporutiu-tn.jpg new file mode 100644 index 000000000..7e0d0fc80 Binary files /dev/null and b/static/img/horeaporutiu-tn.jpg differ diff --git a/static/img/hugo-logo-med.png b/static/img/hugo-logo-med.png new file mode 100644 index 000000000..dcc141690 Binary files /dev/null and b/static/img/hugo-logo-med.png differ diff --git a/static/img/hugo-logo.png b/static/img/hugo-logo.png new file mode 100644 index 000000000..a4f1321b0 Binary files /dev/null and b/static/img/hugo-logo.png differ diff --git a/static/img/hugo-tn.jpg b/static/img/hugo-tn.jpg new file mode 100644 index 000000000..9ac04a38a Binary files /dev/null and b/static/img/hugo-tn.jpg differ diff --git a/static/img/hugo.png b/static/img/hugo.png new file mode 100644 index 000000000..48acf346c Binary files /dev/null and b/static/img/hugo.png differ diff --git a/static/img/hugoSM.png b/static/img/hugoSM.png new file mode 100644 index 000000000..f64f43088 Binary files /dev/null and b/static/img/hugoSM.png differ diff --git a/static/img/invincible-tn.jpg b/static/img/invincible-tn.jpg new file mode 100644 index 000000000..f8c11f82a Binary files /dev/null and b/static/img/invincible-tn.jpg differ diff --git a/static/img/invision-tn.png b/static/img/invision-tn.png new file mode 100644 index 000000000..097f71bb3 Binary files /dev/null and b/static/img/invision-tn.png differ diff --git a/static/img/jamescampbell-tn.png b/static/img/jamescampbell-tn.png new file mode 100644 index 000000000..31fb7dc28 Binary files /dev/null and b/static/img/jamescampbell-tn.png differ diff --git a/static/img/jonbeebe.net-thumbnail.png b/static/img/jonbeebe.net-thumbnail.png new file mode 100644 index 000000000..1e31d97c6 Binary files /dev/null and b/static/img/jonbeebe.net-thumbnail.png differ diff --git a/static/img/jorgennilsson-tn.png b/static/img/jorgennilsson-tn.png new file mode 100644 index 000000000..abcc54ab3 Binary files /dev/null and b/static/img/jorgennilsson-tn.png differ diff --git a/static/img/kjhealy-tn.jpg b/static/img/kjhealy-tn.jpg new file mode 100644 index 000000000..dd2561ae3 Binary files /dev/null and b/static/img/kjhealy-tn.jpg differ diff --git a/static/img/klingt-net-tn.png b/static/img/klingt-net-tn.png new file mode 100644 index 000000000..5ffd3d6a7 Binary files /dev/null and b/static/img/klingt-net-tn.png differ diff --git a/static/img/launchcode-tn.jpg b/static/img/launchcode-tn.jpg new file mode 100644 index 000000000..c422450a1 Binary files /dev/null and b/static/img/launchcode-tn.jpg differ diff --git a/static/img/leepenney-tn.jpg b/static/img/leepenney-tn.jpg new file mode 100644 index 000000000..c1085d779 Binary files /dev/null and b/static/img/leepenney-tn.jpg differ diff --git a/static/img/leowkahman-tn.png b/static/img/leowkahman-tn.png new file mode 100644 index 000000000..ae7803f57 Binary files /dev/null and b/static/img/leowkahman-tn.png differ diff --git a/static/img/lk4d4-tn.jpg b/static/img/lk4d4-tn.jpg new file mode 100644 index 000000000..687606814 Binary files /dev/null and b/static/img/lk4d4-tn.jpg differ diff --git a/static/img/losslesslife-tn.png b/static/img/losslesslife-tn.png new file mode 100644 index 000000000..cc9e286aa Binary files /dev/null and b/static/img/losslesslife-tn.png differ diff --git a/static/img/lucumt.info.png b/static/img/lucumt.info.png new file mode 100644 index 000000000..15a3a213d Binary files /dev/null and b/static/img/lucumt.info.png differ diff --git a/static/img/mariosanchez-tn.jpg b/static/img/mariosanchez-tn.jpg new file mode 100644 index 000000000..75d116c22 Binary files /dev/null and b/static/img/mariosanchez-tn.jpg differ diff --git a/static/img/mayan-edms-tn.png b/static/img/mayan-edms-tn.png new file mode 100644 index 000000000..8feca78e4 Binary files /dev/null and b/static/img/mayan-edms-tn.png differ diff --git a/static/img/michaelwhatcott-tn.jpg b/static/img/michaelwhatcott-tn.jpg new file mode 100644 index 000000000..4cb1b5e80 Binary files /dev/null and b/static/img/michaelwhatcott-tn.jpg differ diff --git a/static/img/mongodb-eng-tn.png b/static/img/mongodb-eng-tn.png new file mode 100644 index 000000000..6b223e745 Binary files /dev/null and b/static/img/mongodb-eng-tn.png differ diff --git a/static/img/mtbhomer-tn.png b/static/img/mtbhomer-tn.png new file mode 100644 index 000000000..c53f68792 Binary files /dev/null and b/static/img/mtbhomer-tn.png differ diff --git a/static/img/myearworms-tn.jpg b/static/img/myearworms-tn.jpg new file mode 100644 index 000000000..49992889b Binary files /dev/null and b/static/img/myearworms-tn.jpg differ diff --git a/static/img/neavey-tn.jpg b/static/img/neavey-tn.jpg new file mode 100644 index 000000000..6f81fcb69 Binary files /dev/null and b/static/img/neavey-tn.jpg differ diff --git a/static/img/nickoneill-tn.jpg b/static/img/nickoneill-tn.jpg new file mode 100644 index 000000000..b8f1d1ae5 Binary files /dev/null and b/static/img/nickoneill-tn.jpg differ diff --git a/static/img/ninjaducks-tn.png b/static/img/ninjaducks-tn.png new file mode 100644 index 000000000..dd70268be Binary files /dev/null and b/static/img/ninjaducks-tn.png differ diff --git a/static/img/ninya-tn.jpg b/static/img/ninya-tn.jpg new file mode 100644 index 000000000..06bba8083 Binary files /dev/null and b/static/img/ninya-tn.jpg differ diff --git a/static/img/nodesk-tn.png b/static/img/nodesk-tn.png new file mode 100644 index 000000000..76457d994 Binary files /dev/null and b/static/img/nodesk-tn.png differ diff --git a/static/img/novelist-xyz.png b/static/img/novelist-xyz.png new file mode 100644 index 000000000..c2ebed74e Binary files /dev/null and b/static/img/novelist-xyz.png differ diff --git a/static/img/npf-tn.jpg b/static/img/npf-tn.jpg new file mode 100644 index 000000000..d3eba9c8d Binary files /dev/null and b/static/img/npf-tn.jpg differ diff --git a/static/img/nutspubcrawl.jpg b/static/img/nutspubcrawl.jpg new file mode 100644 index 000000000..34862b5a2 Binary files /dev/null and b/static/img/nutspubcrawl.jpg differ diff --git a/static/img/ocul-maps.png b/static/img/ocul-maps.png new file mode 100644 index 000000000..298d55ecd Binary files /dev/null and b/static/img/ocul-maps.png differ diff --git a/static/img/petanikode.png b/static/img/petanikode.png new file mode 100644 index 000000000..3935a5c96 Binary files /dev/null and b/static/img/petanikode.png differ diff --git a/static/img/peteraba-tn.jpg b/static/img/peteraba-tn.jpg new file mode 100644 index 000000000..f30d3f042 Binary files /dev/null and b/static/img/peteraba-tn.jpg differ diff --git a/static/img/picturingjordan-tn.png b/static/img/picturingjordan-tn.png new file mode 100644 index 000000000..75e6ea115 Binary files /dev/null and b/static/img/picturingjordan-tn.png differ diff --git a/static/img/promotive.png b/static/img/promotive.png new file mode 100644 index 000000000..9f3f6209f Binary files /dev/null and b/static/img/promotive.png differ diff --git a/static/img/quickstart/bookshelf-bleak-theme.png b/static/img/quickstart/bookshelf-bleak-theme.png new file mode 100644 index 000000000..ccd18c42d Binary files /dev/null and b/static/img/quickstart/bookshelf-bleak-theme.png differ diff --git a/static/img/quickstart/bookshelf-disqus.png b/static/img/quickstart/bookshelf-disqus.png new file mode 100644 index 000000000..3ce645a0c Binary files /dev/null and b/static/img/quickstart/bookshelf-disqus.png differ diff --git a/static/img/quickstart/bookshelf-new-default-image.png b/static/img/quickstart/bookshelf-new-default-image.png new file mode 100644 index 000000000..d7274c7a6 Binary files /dev/null and b/static/img/quickstart/bookshelf-new-default-image.png differ diff --git a/static/img/quickstart/bookshelf-only-picture.png b/static/img/quickstart/bookshelf-only-picture.png new file mode 100644 index 000000000..a363383bc Binary files /dev/null and b/static/img/quickstart/bookshelf-only-picture.png differ diff --git a/static/img/quickstart/bookshelf-robust-theme.png b/static/img/quickstart/bookshelf-robust-theme.png new file mode 100644 index 000000000..7c5e6b8d2 Binary files /dev/null and b/static/img/quickstart/bookshelf-robust-theme.png differ diff --git a/static/img/quickstart/bookshelf-updated-config.png b/static/img/quickstart/bookshelf-updated-config.png new file mode 100644 index 000000000..bbda606c7 Binary files /dev/null and b/static/img/quickstart/bookshelf-updated-config.png differ diff --git a/static/img/quickstart/bookshelf.png b/static/img/quickstart/bookshelf.png new file mode 100644 index 000000000..3b572adbb Binary files /dev/null and b/static/img/quickstart/bookshelf.png differ diff --git a/static/img/quickstart/default.jpg b/static/img/quickstart/default.jpg new file mode 100644 index 000000000..78d7bd28e Binary files /dev/null and b/static/img/quickstart/default.jpg differ diff --git a/static/img/rahulrai_in-tn.png b/static/img/rahulrai_in-tn.png new file mode 100644 index 000000000..cd146dce5 Binary files /dev/null and b/static/img/rahulrai_in-tn.png differ diff --git a/static/img/rakutentech-tn.png b/static/img/rakutentech-tn.png new file mode 100644 index 000000000..04f56e314 Binary files /dev/null and b/static/img/rakutentech-tn.png differ diff --git a/static/img/rdegges-tn.png b/static/img/rdegges-tn.png new file mode 100644 index 000000000..a2e4b6c86 Binary files /dev/null and b/static/img/rdegges-tn.png differ diff --git a/static/img/readtext-tn.png b/static/img/readtext-tn.png new file mode 100644 index 000000000..9e71627b0 Binary files /dev/null and b/static/img/readtext-tn.png differ diff --git a/static/img/richardsumilang-tn.png b/static/img/richardsumilang-tn.png new file mode 100644 index 000000000..68815495f Binary files /dev/null and b/static/img/richardsumilang-tn.png differ diff --git a/static/img/rick_cogley_info-tn.jpg b/static/img/rick_cogley_info-tn.jpg new file mode 100644 index 000000000..414e0108c Binary files /dev/null and b/static/img/rick_cogley_info-tn.jpg differ diff --git a/static/img/ridingbytes-tn.png b/static/img/ridingbytes-tn.png new file mode 100644 index 000000000..624cab96d Binary files /dev/null and b/static/img/ridingbytes-tn.png differ diff --git a/static/img/robertbasic-tn.png b/static/img/robertbasic-tn.png new file mode 100644 index 000000000..5ceecfead Binary files /dev/null and b/static/img/robertbasic-tn.png differ diff --git a/static/img/sanjay-saxena-tn.png b/static/img/sanjay-saxena-tn.png new file mode 100644 index 000000000..85bc5cc58 Binary files /dev/null and b/static/img/sanjay-saxena-tn.png differ diff --git a/static/img/scottcwilson-tn.png b/static/img/scottcwilson-tn.png new file mode 100644 index 000000000..5517edf22 Binary files /dev/null and b/static/img/scottcwilson-tn.png differ diff --git a/static/img/shapeshed-tn.png b/static/img/shapeshed-tn.png new file mode 100644 index 000000000..218b96c5e Binary files /dev/null and b/static/img/shapeshed-tn.png differ diff --git a/static/img/shelan-tn.png b/static/img/shelan-tn.png new file mode 100644 index 000000000..0f7634041 Binary files /dev/null and b/static/img/shelan-tn.png differ diff --git a/static/img/siba-tn.png b/static/img/siba-tn.png new file mode 100644 index 000000000..52373df20 Binary files /dev/null and b/static/img/siba-tn.png differ diff --git a/static/img/silvergeko.jpg b/static/img/silvergeko.jpg new file mode 100644 index 000000000..19b8f98cb Binary files /dev/null and b/static/img/silvergeko.jpg differ diff --git a/static/img/softinio-tn.png b/static/img/softinio-tn.png new file mode 100644 index 000000000..ad94ae876 Binary files /dev/null and b/static/img/softinio-tn.png differ diff --git a/static/img/spf13-tn.jpg b/static/img/spf13-tn.jpg new file mode 100644 index 000000000..a987c71f9 Binary files /dev/null and b/static/img/spf13-tn.jpg differ diff --git a/static/img/steambap.png b/static/img/steambap.png new file mode 100644 index 000000000..bad21f438 Binary files /dev/null and b/static/img/steambap.png differ diff --git a/static/img/stefano.chiodino-tn.jpg b/static/img/stefano.chiodino-tn.jpg new file mode 100644 index 000000000..7747798d5 Binary files /dev/null and b/static/img/stefano.chiodino-tn.jpg differ diff --git a/static/img/stou-tn.png b/static/img/stou-tn.png new file mode 100644 index 000000000..fe449b797 Binary files /dev/null and b/static/img/stou-tn.png differ diff --git a/static/img/szymonkatra-tn.png b/static/img/szymonkatra-tn.png new file mode 100644 index 000000000..e64f2b33d Binary files /dev/null and b/static/img/szymonkatra-tn.png differ diff --git a/static/img/techmadeplain-tn.jpg b/static/img/techmadeplain-tn.jpg new file mode 100644 index 000000000..cae544861 Binary files /dev/null and b/static/img/techmadeplain-tn.jpg differ diff --git a/static/img/tendermint-tn.jpg b/static/img/tendermint-tn.jpg new file mode 100644 index 000000000..807b42d74 Binary files /dev/null and b/static/img/tendermint-tn.jpg differ diff --git a/static/img/thecodeking-tn.jpg b/static/img/thecodeking-tn.jpg new file mode 100644 index 000000000..158384d4e Binary files /dev/null and b/static/img/thecodeking-tn.jpg differ diff --git a/static/img/thehome-tn.png b/static/img/thehome-tn.png new file mode 100644 index 000000000..7b66e6215 Binary files /dev/null and b/static/img/thehome-tn.png differ diff --git a/static/img/thislittleduck-tn.png b/static/img/thislittleduck-tn.png new file mode 100644 index 000000000..0d7407d62 Binary files /dev/null and b/static/img/thislittleduck-tn.png differ diff --git a/static/img/tibobeijen-nl-tn.png b/static/img/tibobeijen-nl-tn.png new file mode 100644 index 000000000..801e34a12 Binary files /dev/null and b/static/img/tibobeijen-nl-tn.png differ diff --git a/static/img/ttsreader-tn.png b/static/img/ttsreader-tn.png new file mode 100644 index 000000000..db33322b4 Binary files /dev/null and b/static/img/ttsreader-tn.png differ diff --git a/static/img/tutorialonfly-tn.jpg b/static/img/tutorialonfly-tn.jpg new file mode 100644 index 000000000..5ff99fff6 Binary files /dev/null and b/static/img/tutorialonfly-tn.jpg differ diff --git a/static/img/tutorials/automated-deployments/adding-a-deploy-pipeline.png b/static/img/tutorials/automated-deployments/adding-a-deploy-pipeline.png new file mode 100644 index 000000000..29f637b06 Binary files /dev/null and b/static/img/tutorials/automated-deployments/adding-a-deploy-pipeline.png differ diff --git a/static/img/tutorials/automated-deployments/adding-a-deploy-step.png b/static/img/tutorials/automated-deployments/adding-a-deploy-step.png new file mode 100644 index 000000000..346187927 Binary files /dev/null and b/static/img/tutorials/automated-deployments/adding-a-deploy-step.png differ diff --git a/static/img/tutorials/automated-deployments/adding-the-project-to-github.png b/static/img/tutorials/automated-deployments/adding-the-project-to-github.png new file mode 100644 index 000000000..e1065bb00 Binary files /dev/null and b/static/img/tutorials/automated-deployments/adding-the-project-to-github.png differ diff --git a/static/img/tutorials/automated-deployments/creating-a-basic-hugo-site.png b/static/img/tutorials/automated-deployments/creating-a-basic-hugo-site.png new file mode 100644 index 000000000..78d238f88 Binary files /dev/null and b/static/img/tutorials/automated-deployments/creating-a-basic-hugo-site.png differ diff --git a/static/img/tutorials/automated-deployments/public-or-not.png b/static/img/tutorials/automated-deployments/public-or-not.png new file mode 100644 index 000000000..9d81a8ba4 Binary files /dev/null and b/static/img/tutorials/automated-deployments/public-or-not.png differ diff --git a/static/img/tutorials/automated-deployments/using-hugo-build.png b/static/img/tutorials/automated-deployments/using-hugo-build.png new file mode 100644 index 000000000..b0dbec94c Binary files /dev/null and b/static/img/tutorials/automated-deployments/using-hugo-build.png differ diff --git a/static/img/tutorials/automated-deployments/wercker-access.png b/static/img/tutorials/automated-deployments/wercker-access.png new file mode 100644 index 000000000..6e89c0ef3 Binary files /dev/null and b/static/img/tutorials/automated-deployments/wercker-access.png differ diff --git a/static/img/tutorials/automated-deployments/wercker-add-app.png b/static/img/tutorials/automated-deployments/wercker-add-app.png new file mode 100644 index 000000000..94ccef518 Binary files /dev/null and b/static/img/tutorials/automated-deployments/wercker-add-app.png differ diff --git a/static/img/tutorials/automated-deployments/wercker-git-connections.png b/static/img/tutorials/automated-deployments/wercker-git-connections.png new file mode 100644 index 000000000..d89c0cd8b Binary files /dev/null and b/static/img/tutorials/automated-deployments/wercker-git-connections.png differ diff --git a/static/img/tutorials/automated-deployments/wercker-search.png b/static/img/tutorials/automated-deployments/wercker-search.png new file mode 100644 index 000000000..d099cfd5c Binary files /dev/null and b/static/img/tutorials/automated-deployments/wercker-search.png differ diff --git a/static/img/tutorials/automated-deployments/wercker-select-repository.png b/static/img/tutorials/automated-deployments/wercker-select-repository.png new file mode 100644 index 000000000..0f7d63d98 Binary files /dev/null and b/static/img/tutorials/automated-deployments/wercker-select-repository.png differ diff --git a/static/img/tutorials/automated-deployments/wercker-sign-up-page.png b/static/img/tutorials/automated-deployments/wercker-sign-up-page.png new file mode 100644 index 000000000..55b0ebd52 Binary files /dev/null and b/static/img/tutorials/automated-deployments/wercker-sign-up-page.png differ diff --git a/static/img/tutorials/automated-deployments/wercker-sign-up.png b/static/img/tutorials/automated-deployments/wercker-sign-up.png new file mode 100644 index 000000000..9c6270061 Binary files /dev/null and b/static/img/tutorials/automated-deployments/wercker-sign-up.png differ diff --git a/static/img/tutorials/automated-deployments/werckeryml.png b/static/img/tutorials/automated-deployments/werckeryml.png new file mode 100644 index 000000000..daa392b4a Binary files /dev/null and b/static/img/tutorials/automated-deployments/werckeryml.png differ diff --git a/static/img/tutorials/hosting-on-bitbucket/bitbucket-blog-post.png b/static/img/tutorials/hosting-on-bitbucket/bitbucket-blog-post.png new file mode 100644 index 000000000..b78f6fd15 Binary files /dev/null and b/static/img/tutorials/hosting-on-bitbucket/bitbucket-blog-post.png differ diff --git a/static/img/tutorials/hosting-on-bitbucket/bitbucket-create-repo.png b/static/img/tutorials/hosting-on-bitbucket/bitbucket-create-repo.png new file mode 100644 index 000000000..e97f13465 Binary files /dev/null and b/static/img/tutorials/hosting-on-bitbucket/bitbucket-create-repo.png differ diff --git a/static/img/tutorials/how-to-contribute-to-hugo/accept-cla.png b/static/img/tutorials/how-to-contribute-to-hugo/accept-cla.png new file mode 100644 index 000000000..929fda6ab Binary files /dev/null and b/static/img/tutorials/how-to-contribute-to-hugo/accept-cla.png differ diff --git a/static/img/tutorials/how-to-contribute-to-hugo/ci-errors.png b/static/img/tutorials/how-to-contribute-to-hugo/ci-errors.png new file mode 100644 index 000000000..95cd290b6 Binary files /dev/null and b/static/img/tutorials/how-to-contribute-to-hugo/ci-errors.png differ diff --git a/static/img/tutorials/how-to-contribute-to-hugo/copy-remote-url.png b/static/img/tutorials/how-to-contribute-to-hugo/copy-remote-url.png new file mode 100644 index 000000000..9006f4a48 Binary files /dev/null and b/static/img/tutorials/how-to-contribute-to-hugo/copy-remote-url.png differ diff --git a/static/img/tutorials/how-to-contribute-to-hugo/forking-a-repository.png b/static/img/tutorials/how-to-contribute-to-hugo/forking-a-repository.png new file mode 100644 index 000000000..ea132cab3 Binary files /dev/null and b/static/img/tutorials/how-to-contribute-to-hugo/forking-a-repository.png differ diff --git a/static/img/tutorials/how-to-contribute-to-hugo/open-pull-request.png b/static/img/tutorials/how-to-contribute-to-hugo/open-pull-request.png new file mode 100644 index 000000000..63b504fb2 Binary files /dev/null and b/static/img/tutorials/how-to-contribute-to-hugo/open-pull-request.png differ diff --git a/static/img/tutswiki-tn.jpg b/static/img/tutswiki-tn.jpg new file mode 100644 index 000000000..11efb2a5b Binary files /dev/null and b/static/img/tutswiki-tn.jpg differ diff --git a/static/img/ucsb-tn.jpg b/static/img/ucsb-tn.jpg new file mode 100644 index 000000000..45962027d Binary files /dev/null and b/static/img/ucsb-tn.jpg differ diff --git a/static/img/upbeat.png b/static/img/upbeat.png new file mode 100644 index 000000000..e7a6a694c Binary files /dev/null and b/static/img/upbeat.png differ diff --git a/static/img/vamp_landingpage-tn.png b/static/img/vamp_landingpage-tn.png new file mode 100644 index 000000000..474261e0e Binary files /dev/null and b/static/img/vamp_landingpage-tn.png differ diff --git a/static/img/viglug-tn.png b/static/img/viglug-tn.png new file mode 100644 index 000000000..d18ab4023 Binary files /dev/null and b/static/img/viglug-tn.png differ diff --git a/static/img/vurt.co-tn.jpg b/static/img/vurt.co-tn.jpg new file mode 100644 index 000000000..5e7f131a0 Binary files /dev/null and b/static/img/vurt.co-tn.jpg differ diff --git a/static/img/worldtowriters-com.jpg b/static/img/worldtowriters-com.jpg new file mode 100644 index 000000000..570d06fa9 Binary files /dev/null and b/static/img/worldtowriters-com.jpg differ diff --git a/static/img/yslow-rules-tn.png b/static/img/yslow-rules-tn.png new file mode 100644 index 000000000..5c75a6943 Binary files /dev/null and b/static/img/yslow-rules-tn.png differ diff --git a/static/img/ysqi-blog.png b/static/img/ysqi-blog.png new file mode 100644 index 000000000..6dd234109 Binary files /dev/null and b/static/img/ysqi-blog.png differ diff --git a/static/img/yulinling-tn.jpg b/static/img/yulinling-tn.jpg new file mode 100644 index 000000000..bdb12f0e7 Binary files /dev/null and b/static/img/yulinling-tn.jpg differ diff --git a/static/js/livereload.js b/static/js/livereload.js new file mode 100644 index 000000000..f6c3b7f90 --- /dev/null +++ b/static/js/livereload.js @@ -0,0 +1,1155 @@ +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o tag"); + return; + } + this.reloader = new Reloader(this.window, this.console, Timer); + this.connector = new Connector(this.options, this.WebSocket, Timer, { + connecting: (function(_this) { + return function() {}; + })(this), + socketConnected: (function(_this) { + return function() {}; + })(this), + connected: (function(_this) { + return function(protocol) { + var _base; + if (typeof (_base = _this.listeners).connect === "function") { + _base.connect(); + } + _this.log("LiveReload is connected to " + _this.options.host + ":" + _this.options.port + " (protocol v" + protocol + ")."); + return _this.analyze(); + }; + })(this), + error: (function(_this) { + return function(e) { + if (e instanceof ProtocolError) { + if (typeof console !== "undefined" && console !== null) { + return console.log("" + e.message + "."); + } + } else { + if (typeof console !== "undefined" && console !== null) { + return console.log("LiveReload internal error: " + e.message); + } + } + }; + })(this), + disconnected: (function(_this) { + return function(reason, nextDelay) { + var _base; + if (typeof (_base = _this.listeners).disconnect === "function") { + _base.disconnect(); + } + switch (reason) { + case 'cannot-connect': + return _this.log("LiveReload cannot connect to " + _this.options.host + ":" + _this.options.port + ", will retry in " + nextDelay + " sec."); + case 'broken': + return _this.log("LiveReload disconnected from " + _this.options.host + ":" + _this.options.port + ", reconnecting in " + nextDelay + " sec."); + case 'handshake-timeout': + return _this.log("LiveReload cannot connect to " + _this.options.host + ":" + _this.options.port + " (handshake timeout), will retry in " + nextDelay + " sec."); + case 'handshake-failed': + return _this.log("LiveReload cannot connect to " + _this.options.host + ":" + _this.options.port + " (handshake failed), will retry in " + nextDelay + " sec."); + case 'manual': + break; + case 'error': + break; + default: + return _this.log("LiveReload disconnected from " + _this.options.host + ":" + _this.options.port + " (" + reason + "), reconnecting in " + nextDelay + " sec."); + } + }; + })(this), + message: (function(_this) { + return function(message) { + switch (message.command) { + case 'reload': + return _this.performReload(message); + case 'alert': + return _this.performAlert(message); + } + }; + })(this) + }); + } + + LiveReload.prototype.on = function(eventName, handler) { + return this.listeners[eventName] = handler; + }; + + LiveReload.prototype.log = function(message) { + return this.console.log("" + message); + }; + + LiveReload.prototype.performReload = function(message) { + var _ref, _ref1; + this.log("LiveReload received reload request: " + (JSON.stringify(message, null, 2))); + return this.reloader.reload(message.path, { + liveCSS: (_ref = message.liveCSS) != null ? _ref : true, + liveImg: (_ref1 = message.liveImg) != null ? _ref1 : true, + originalPath: message.originalPath || '', + overrideURL: message.overrideURL || '', + serverURL: "http://" + this.options.host + ":" + this.options.port + }); + }; + + LiveReload.prototype.performAlert = function(message) { + return alert(message.message); + }; + + LiveReload.prototype.shutDown = function() { + var _base; + this.connector.disconnect(); + this.log("LiveReload disconnected."); + return typeof (_base = this.listeners).shutdown === "function" ? _base.shutdown() : void 0; + }; + + LiveReload.prototype.hasPlugin = function(identifier) { + return !!this.pluginIdentifiers[identifier]; + }; + + LiveReload.prototype.addPlugin = function(pluginClass) { + var plugin; + if (this.hasPlugin(pluginClass.identifier)) { + return; + } + this.pluginIdentifiers[pluginClass.identifier] = true; + plugin = new pluginClass(this.window, { + _livereload: this, + _reloader: this.reloader, + _connector: this.connector, + console: this.console, + Timer: Timer, + generateCacheBustUrl: (function(_this) { + return function(url) { + return _this.reloader.generateCacheBustUrl(url); + }; + })(this) + }); + this.plugins.push(plugin); + this.reloader.addPlugin(plugin); + }; + + LiveReload.prototype.analyze = function() { + var plugin, pluginData, pluginsData, _i, _len, _ref; + if (!(this.connector.protocol >= 7)) { + return; + } + pluginsData = {}; + _ref = this.plugins; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + plugin = _ref[_i]; + pluginsData[plugin.constructor.identifier] = pluginData = (typeof plugin.analyze === "function" ? plugin.analyze() : void 0) || {}; + pluginData.version = plugin.constructor.version; + } + this.connector.sendCommand({ + command: 'info', + plugins: pluginsData, + url: this.window.location.href + }); + }; + + return LiveReload; + + })(); + +}).call(this); + +},{"./connector":1,"./options":5,"./reloader":7,"./timer":9}],5:[function(require,module,exports){ +(function() { + var Options; + + exports.Options = Options = (function() { + function Options() { + this.host = null; + this.port = 35729; + this.snipver = null; + this.ext = null; + this.extver = null; + this.mindelay = 1000; + this.maxdelay = 60000; + this.handshake_timeout = 5000; + } + + Options.prototype.set = function(name, value) { + if (typeof value === 'undefined') { + return; + } + if (!isNaN(+value)) { + value = +value; + } + return this[name] = value; + }; + + return Options; + + })(); + + Options.extract = function(document) { + var element, keyAndValue, m, mm, options, pair, src, _i, _j, _len, _len1, _ref, _ref1; + _ref = document.getElementsByTagName('script'); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + element = _ref[_i]; + if ((src = element.src) && (m = src.match(/^[^:]+:\/\/(.*)\/z?livereload\.js(?:\?(.*))?$/))) { + options = new Options(); + if (mm = m[1].match(/^([^\/:]+)(?::(\d+))?$/)) { + options.host = mm[1]; + if (mm[2]) { + options.port = parseInt(mm[2], 10); + } + } + if (m[2]) { + _ref1 = m[2].split('&'); + for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { + pair = _ref1[_j]; + if ((keyAndValue = pair.split('=')).length > 1) { + options.set(keyAndValue[0].replace(/-/g, '_'), keyAndValue.slice(1).join('=')); + } + } + } + return options; + } + } + return null; + }; + +}).call(this); + +},{}],6:[function(require,module,exports){ +(function() { + var PROTOCOL_6, PROTOCOL_7, Parser, ProtocolError, + __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + + exports.PROTOCOL_6 = PROTOCOL_6 = 'http://livereload.com/protocols/official-6'; + + exports.PROTOCOL_7 = PROTOCOL_7 = 'http://livereload.com/protocols/official-7'; + + exports.ProtocolError = ProtocolError = (function() { + function ProtocolError(reason, data) { + this.message = "LiveReload protocol error (" + reason + ") after receiving data: \"" + data + "\"."; + } + + return ProtocolError; + + })(); + + exports.Parser = Parser = (function() { + function Parser(handlers) { + this.handlers = handlers; + this.reset(); + } + + Parser.prototype.reset = function() { + return this.protocol = null; + }; + + Parser.prototype.process = function(data) { + var command, e, message, options, _ref; + try { + if (this.protocol == null) { + if (data.match(/^!!ver:([\d.]+)$/)) { + this.protocol = 6; + } else if (message = this._parseMessage(data, ['hello'])) { + if (!message.protocols.length) { + throw new ProtocolError("no protocols specified in handshake message"); + } else if (__indexOf.call(message.protocols, PROTOCOL_7) >= 0) { + this.protocol = 7; + } else if (__indexOf.call(message.protocols, PROTOCOL_6) >= 0) { + this.protocol = 6; + } else { + throw new ProtocolError("no supported protocols found"); + } + } + return this.handlers.connected(this.protocol); + } else if (this.protocol === 6) { + message = JSON.parse(data); + if (!message.length) { + throw new ProtocolError("protocol 6 messages must be arrays"); + } + command = message[0], options = message[1]; + if (command !== 'refresh') { + throw new ProtocolError("unknown protocol 6 command"); + } + return this.handlers.message({ + command: 'reload', + path: options.path, + liveCSS: (_ref = options.apply_css_live) != null ? _ref : true + }); + } else { + message = this._parseMessage(data, ['reload', 'alert']); + return this.handlers.message(message); + } + } catch (_error) { + e = _error; + if (e instanceof ProtocolError) { + return this.handlers.error(e); + } else { + throw e; + } + } + }; + + Parser.prototype._parseMessage = function(data, validCommands) { + var e, message, _ref; + try { + message = JSON.parse(data); + } catch (_error) { + e = _error; + throw new ProtocolError('unparsable JSON', data); + } + if (!message.command) { + throw new ProtocolError('missing "command" key', data); + } + if (_ref = message.command, __indexOf.call(validCommands, _ref) < 0) { + throw new ProtocolError("invalid command '" + message.command + "', only valid commands are: " + (validCommands.join(', ')) + ")", data); + } + return message; + }; + + return Parser; + + })(); + +}).call(this); + +},{}],7:[function(require,module,exports){ +(function() { + var IMAGE_STYLES, Reloader, numberOfMatchingSegments, pathFromUrl, pathsMatch, pickBestMatch, splitUrl; + + splitUrl = function(url) { + var hash, index, params; + if ((index = url.indexOf('#')) >= 0) { + hash = url.slice(index); + url = url.slice(0, index); + } else { + hash = ''; + } + if ((index = url.indexOf('?')) >= 0) { + params = url.slice(index); + url = url.slice(0, index); + } else { + params = ''; + } + return { + url: url, + params: params, + hash: hash + }; + }; + + pathFromUrl = function(url) { + var path; + url = splitUrl(url).url; + if (url.indexOf('file://') === 0) { + path = url.replace(/^file:\/\/(localhost)?/, ''); + } else { + path = url.replace(/^([^:]+:)?\/\/([^:\/]+)(:\d*)?\//, '/'); + } + return decodeURIComponent(path); + }; + + pickBestMatch = function(path, objects, pathFunc) { + var bestMatch, object, score, _i, _len; + bestMatch = { + score: 0 + }; + for (_i = 0, _len = objects.length; _i < _len; _i++) { + object = objects[_i]; + score = numberOfMatchingSegments(path, pathFunc(object)); + if (score > bestMatch.score) { + bestMatch = { + object: object, + score: score + }; + } + } + if (bestMatch.score > 0) { + return bestMatch; + } else { + return null; + } + }; + + numberOfMatchingSegments = function(path1, path2) { + var comps1, comps2, eqCount, len; + path1 = path1.replace(/^\/+/, '').toLowerCase(); + path2 = path2.replace(/^\/+/, '').toLowerCase(); + if (path1 === path2) { + return 10000; + } + comps1 = path1.split('/').reverse(); + comps2 = path2.split('/').reverse(); + len = Math.min(comps1.length, comps2.length); + eqCount = 0; + while (eqCount < len && comps1[eqCount] === comps2[eqCount]) { + ++eqCount; + } + return eqCount; + }; + + pathsMatch = function(path1, path2) { + return numberOfMatchingSegments(path1, path2) > 0; + }; + + IMAGE_STYLES = [ + { + selector: 'background', + styleNames: ['backgroundImage'] + }, { + selector: 'border', + styleNames: ['borderImage', 'webkitBorderImage', 'MozBorderImage'] + } + ]; + + exports.Reloader = Reloader = (function() { + function Reloader(window, console, Timer) { + this.window = window; + this.console = console; + this.Timer = Timer; + this.document = this.window.document; + this.importCacheWaitPeriod = 200; + this.plugins = []; + } + + Reloader.prototype.addPlugin = function(plugin) { + return this.plugins.push(plugin); + }; + + Reloader.prototype.analyze = function(callback) { + return results; + }; + + Reloader.prototype.reload = function(path, options) { + var plugin, _base, _i, _len, _ref; + this.options = options; + if ((_base = this.options).stylesheetReloadTimeout == null) { + _base.stylesheetReloadTimeout = 15000; + } + _ref = this.plugins; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + plugin = _ref[_i]; + if (plugin.reload && plugin.reload(path, options)) { + return; + } + } + if (options.liveCSS) { + if (path.match(/\.css$/i)) { + if (this.reloadStylesheet(path)) { + return; + } + } + } + if (options.liveImg) { + if (path.match(/\.(jpe?g|png|gif)$/i)) { + this.reloadImages(path); + return; + } + } + return this.reloadPage(); + }; + + Reloader.prototype.reloadPage = function() { + return this.window.document.location.reload(); + }; + + Reloader.prototype.reloadImages = function(path) { + var expando, img, selector, styleNames, styleSheet, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _ref3, _results; + expando = this.generateUniqueString(); + _ref = this.document.images; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + img = _ref[_i]; + if (pathsMatch(path, pathFromUrl(img.src))) { + img.src = this.generateCacheBustUrl(img.src, expando); + } + } + if (this.document.querySelectorAll) { + for (_j = 0, _len1 = IMAGE_STYLES.length; _j < _len1; _j++) { + _ref1 = IMAGE_STYLES[_j], selector = _ref1.selector, styleNames = _ref1.styleNames; + _ref2 = this.document.querySelectorAll("[style*=" + selector + "]"); + for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { + img = _ref2[_k]; + this.reloadStyleImages(img.style, styleNames, path, expando); + } + } + } + if (this.document.styleSheets) { + _ref3 = this.document.styleSheets; + _results = []; + for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) { + styleSheet = _ref3[_l]; + _results.push(this.reloadStylesheetImages(styleSheet, path, expando)); + } + return _results; + } + }; + + Reloader.prototype.reloadStylesheetImages = function(styleSheet, path, expando) { + var e, rule, rules, styleNames, _i, _j, _len, _len1; + try { + rules = styleSheet != null ? styleSheet.cssRules : void 0; + } catch (_error) { + e = _error; + } + if (!rules) { + return; + } + for (_i = 0, _len = rules.length; _i < _len; _i++) { + rule = rules[_i]; + switch (rule.type) { + case CSSRule.IMPORT_RULE: + this.reloadStylesheetImages(rule.styleSheet, path, expando); + break; + case CSSRule.STYLE_RULE: + for (_j = 0, _len1 = IMAGE_STYLES.length; _j < _len1; _j++) { + styleNames = IMAGE_STYLES[_j].styleNames; + this.reloadStyleImages(rule.style, styleNames, path, expando); + } + break; + case CSSRule.MEDIA_RULE: + this.reloadStylesheetImages(rule, path, expando); + } + } + }; + + Reloader.prototype.reloadStyleImages = function(style, styleNames, path, expando) { + var newValue, styleName, value, _i, _len; + for (_i = 0, _len = styleNames.length; _i < _len; _i++) { + styleName = styleNames[_i]; + value = style[styleName]; + if (typeof value === 'string') { + newValue = value.replace(/\burl\s*\(([^)]*)\)/, (function(_this) { + return function(match, src) { + if (pathsMatch(path, pathFromUrl(src))) { + return "url(" + (_this.generateCacheBustUrl(src, expando)) + ")"; + } else { + return match; + } + }; + })(this)); + if (newValue !== value) { + style[styleName] = newValue; + } + } + } + }; + + Reloader.prototype.reloadStylesheet = function(path) { + var imported, link, links, match, style, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1; + links = (function() { + var _i, _len, _ref, _results; + _ref = this.document.getElementsByTagName('link'); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + link = _ref[_i]; + if (link.rel.match(/^stylesheet$/i) && !link.__LiveReload_pendingRemoval) { + _results.push(link); + } + } + return _results; + }).call(this); + imported = []; + _ref = this.document.getElementsByTagName('style'); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + style = _ref[_i]; + if (style.sheet) { + this.collectImportedStylesheets(style, style.sheet, imported); + } + } + for (_j = 0, _len1 = links.length; _j < _len1; _j++) { + link = links[_j]; + this.collectImportedStylesheets(link, link.sheet, imported); + } + if (this.window.StyleFix && this.document.querySelectorAll) { + _ref1 = this.document.querySelectorAll('style[data-href]'); + for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) { + style = _ref1[_k]; + links.push(style); + } + } + this.console.log("LiveReload found " + links.length + " LINKed stylesheets, " + imported.length + " @imported stylesheets"); + match = pickBestMatch(path, links.concat(imported), (function(_this) { + return function(l) { + return pathFromUrl(_this.linkHref(l)); + }; + })(this)); + if (match) { + if (match.object.rule) { + this.console.log("LiveReload is reloading imported stylesheet: " + match.object.href); + this.reattachImportedRule(match.object); + } else { + this.console.log("LiveReload is reloading stylesheet: " + (this.linkHref(match.object))); + this.reattachStylesheetLink(match.object); + } + } else { + this.console.log("LiveReload will reload all stylesheets because path '" + path + "' did not match any specific one"); + for (_l = 0, _len3 = links.length; _l < _len3; _l++) { + link = links[_l]; + this.reattachStylesheetLink(link); + } + } + return true; + }; + + Reloader.prototype.collectImportedStylesheets = function(link, styleSheet, result) { + var e, index, rule, rules, _i, _len; + try { + rules = styleSheet != null ? styleSheet.cssRules : void 0; + } catch (_error) { + e = _error; + } + if (rules && rules.length) { + for (index = _i = 0, _len = rules.length; _i < _len; index = ++_i) { + rule = rules[index]; + switch (rule.type) { + case CSSRule.CHARSET_RULE: + continue; + case CSSRule.IMPORT_RULE: + result.push({ + link: link, + rule: rule, + index: index, + href: rule.href + }); + this.collectImportedStylesheets(link, rule.styleSheet, result); + break; + default: + break; + } + } + } + }; + + Reloader.prototype.waitUntilCssLoads = function(clone, func) { + var callbackExecuted, executeCallback, poll; + callbackExecuted = false; + executeCallback = (function(_this) { + return function() { + if (callbackExecuted) { + return; + } + callbackExecuted = true; + return func(); + }; + })(this); + clone.onload = (function(_this) { + return function() { + _this.console.log("LiveReload: the new stylesheet has finished loading"); + _this.knownToSupportCssOnLoad = true; + return executeCallback(); + }; + })(this); + if (!this.knownToSupportCssOnLoad) { + (poll = (function(_this) { + return function() { + if (clone.sheet) { + _this.console.log("LiveReload is polling until the new CSS finishes loading..."); + return executeCallback(); + } else { + return _this.Timer.start(50, poll); + } + }; + })(this))(); + } + return this.Timer.start(this.options.stylesheetReloadTimeout, executeCallback); + }; + + Reloader.prototype.linkHref = function(link) { + return link.href || link.getAttribute('data-href'); + }; + + Reloader.prototype.reattachStylesheetLink = function(link) { + var clone, parent; + if (link.__LiveReload_pendingRemoval) { + return; + } + link.__LiveReload_pendingRemoval = true; + if (link.tagName === 'STYLE') { + clone = this.document.createElement('link'); + clone.rel = 'stylesheet'; + clone.media = link.media; + clone.disabled = link.disabled; + } else { + clone = link.cloneNode(false); + } + clone.href = this.generateCacheBustUrl(this.linkHref(link)); + parent = link.parentNode; + if (parent.lastChild === link) { + parent.appendChild(clone); + } else { + parent.insertBefore(clone, link.nextSibling); + } + return this.waitUntilCssLoads(clone, (function(_this) { + return function() { + var additionalWaitingTime; + if (/AppleWebKit/.test(navigator.userAgent)) { + additionalWaitingTime = 5; + } else { + additionalWaitingTime = 200; + } + return _this.Timer.start(additionalWaitingTime, function() { + var _ref; + if (!link.parentNode) { + return; + } + link.parentNode.removeChild(link); + clone.onreadystatechange = null; + return (_ref = _this.window.StyleFix) != null ? _ref.link(clone) : void 0; + }); + }; + })(this)); + }; + + Reloader.prototype.reattachImportedRule = function(_arg) { + var href, index, link, media, newRule, parent, rule, tempLink; + rule = _arg.rule, index = _arg.index, link = _arg.link; + parent = rule.parentStyleSheet; + href = this.generateCacheBustUrl(rule.href); + media = rule.media.length ? [].join.call(rule.media, ', ') : ''; + newRule = "@import url(\"" + href + "\") " + media + ";"; + rule.__LiveReload_newHref = href; + tempLink = this.document.createElement("link"); + tempLink.rel = 'stylesheet'; + tempLink.href = href; + tempLink.__LiveReload_pendingRemoval = true; + if (link.parentNode) { + link.parentNode.insertBefore(tempLink, link); + } + return this.Timer.start(this.importCacheWaitPeriod, (function(_this) { + return function() { + if (tempLink.parentNode) { + tempLink.parentNode.removeChild(tempLink); + } + if (rule.__LiveReload_newHref !== href) { + return; + } + parent.insertRule(newRule, index); + parent.deleteRule(index + 1); + rule = parent.cssRules[index]; + rule.__LiveReload_newHref = href; + return _this.Timer.start(_this.importCacheWaitPeriod, function() { + if (rule.__LiveReload_newHref !== href) { + return; + } + parent.insertRule(newRule, index); + return parent.deleteRule(index + 1); + }); + }; + })(this)); + }; + + Reloader.prototype.generateUniqueString = function() { + return 'livereload=' + Date.now(); + }; + + Reloader.prototype.generateCacheBustUrl = function(url, expando) { + var hash, oldParams, originalUrl, params, _ref; + if (expando == null) { + expando = this.generateUniqueString(); + } + _ref = splitUrl(url), url = _ref.url, hash = _ref.hash, oldParams = _ref.params; + if (this.options.overrideURL) { + if (url.indexOf(this.options.serverURL) < 0) { + originalUrl = url; + url = this.options.serverURL + this.options.overrideURL + "?url=" + encodeURIComponent(url); + this.console.log("LiveReload is overriding source URL " + originalUrl + " with " + url); + } + } + params = oldParams.replace(/(\?|&)livereload=(\d+)/, function(match, sep) { + return "" + sep + expando; + }); + if (params === oldParams) { + if (oldParams.length === 0) { + params = "?" + expando; + } else { + params = "" + oldParams + "&" + expando; + } + } + return url + params + hash; + }; + + return Reloader; + + })(); + +}).call(this); + +},{}],8:[function(require,module,exports){ +(function() { + var CustomEvents, LiveReload, k; + + CustomEvents = require('./customevents'); + + LiveReload = window.LiveReload = new (require('./livereload').LiveReload)(window); + + for (k in window) { + if (k.match(/^LiveReloadPlugin/)) { + LiveReload.addPlugin(window[k]); + } + } + + LiveReload.addPlugin(require('./less')); + + LiveReload.on('shutdown', function() { + return delete window.LiveReload; + }); + + LiveReload.on('connect', function() { + return CustomEvents.fire(document, 'LiveReloadConnect'); + }); + + LiveReload.on('disconnect', function() { + return CustomEvents.fire(document, 'LiveReloadDisconnect'); + }); + + CustomEvents.bind(document, 'LiveReloadShutDown', function() { + return LiveReload.shutDown(); + }); + +}).call(this); + +},{"./customevents":2,"./less":3,"./livereload":4}],9:[function(require,module,exports){ +(function() { + var Timer; + + exports.Timer = Timer = (function() { + function Timer(func) { + this.func = func; + this.running = false; + this.id = null; + this._handler = (function(_this) { + return function() { + _this.running = false; + _this.id = null; + return _this.func(); + }; + })(this); + } + + Timer.prototype.start = function(timeout) { + if (this.running) { + clearTimeout(this.id); + } + this.id = setTimeout(this._handler, timeout); + return this.running = true; + }; + + Timer.prototype.stop = function() { + if (this.running) { + clearTimeout(this.id); + this.running = false; + return this.id = null; + } + }; + + return Timer; + + })(); + + Timer.start = function(timeout, func) { + return setTimeout(func, timeout); + }; + +}).call(this); + +},{}]},{},[8]); diff --git a/static/js/owl.carousel-custom.js b/static/js/owl.carousel-custom.js new file mode 100644 index 000000000..685c8e361 --- /dev/null +++ b/static/js/owl.carousel-custom.js @@ -0,0 +1,16 @@ +$('.owl-carousel').owlCarousel({ + loop:true, + margin:10, + nav:true, + autoplay:true, + autoplayHoverPause:true, + autoplayTimeout:3000, + responsive:{ + 0:{ + items:1 + }, + 1000:{ + items:3 + }, + } +}) diff --git a/static/js/scripts.js b/static/js/scripts.js new file mode 100644 index 000000000..ef6074f55 --- /dev/null +++ b/static/js/scripts.js @@ -0,0 +1,283 @@ +function initializeJS() { + + //tool tips + jQuery('.tooltips').tooltip(); + + //popovers + jQuery('.popovers').popover(); + + //sidebar dropdown menu + jQuery('#sidebar .sub-menu > a').click(function () { + // Close previous open submenu + var last = jQuery('.sub.open', jQuery('#sidebar')); + jQuery(last).slideUp(200); + jQuery(last).removeClass("open"); + jQuery('.menu-arrow', jQuery(last).parent()).addClass('fa-angle-right'); + jQuery('.menu-arrow', jQuery(last).parent()).removeClass('fa-angle-down'); + + // Toggle current submenu + var sub = jQuery(this).next(); + if (sub.is(":visible")) { + jQuery('.menu-arrow', this).addClass('fa-angle-right'); + jQuery('.menu-arrow', this).removeClass('fa-angle-down'); + sub.slideUp(200); + jQuery(sub).removeClass("open") + } else { + jQuery('.menu-arrow', this).addClass('fa-angle-down'); + jQuery('.menu-arrow', this).removeClass('fa-angle-right'); + sub.slideDown(200); + jQuery(sub).addClass("open") + } + + // Center menu on screen + var o = (jQuery(this).offset()); + diff = 200 - o.top; + if(diff>0) + jQuery("#sidebar").scrollTo("-="+Math.abs(diff),500); + else + jQuery("#sidebar").scrollTo("+="+Math.abs(diff),500); + }); + + + // sidebar menu toggle + jQuery(function() { + function responsiveView() { + var wSize = jQuery(window).width(); + if (wSize <= 768) { + jQuery('#container').addClass('sidebar-close'); + jQuery('#sidebar > ul').hide(); + } + + if (wSize > 768) { + jQuery('#container').removeClass('sidebar-close'); + jQuery('#sidebar > ul').show(); + } + } + jQuery(window).on('load', responsiveView); + jQuery(window).on('resize', responsiveView); + }); + + jQuery('.toggle-nav').click(function () { + if (jQuery('#sidebar > ul').is(":visible") === true) { + jQuery('#main-content').css({ + 'margin-left': '0px' + }); + jQuery('#sidebar').css({ + 'margin-left': '-180px' + }); + jQuery('#sidebar > ul').hide(); + jQuery("#container").addClass("sidebar-closed"); + } else { + jQuery('#main-content').css({ + 'margin-left': '180px' + }); + jQuery('#sidebar > ul').show(); + jQuery('#sidebar').css({ + 'margin-left': '0' + }); + jQuery("#container").removeClass("sidebar-closed"); + } + }); + + //bar chart + if (jQuery(".custom-custom-bar-chart")) { + jQuery(".bar").each(function () { + var i = jQuery(this).find(".value").html(); + jQuery(this).find(".value").html(""); + jQuery(this).find(".value").animate({ + height: i + }, 2000) + }) + } + +} + +(function(){ + var caches = {}; + $.fn.showGithub = function(user, repo, type, count){ + $(this).each(function(){ + var $e = $(this); + var user = $e.data('user') || user, + repo = $e.data('repo') || repo, + type = $e.data('type') || type || 'watch', + count = $e.data('count') == 'true' || count || true; + var $mainButton = $e.html(' ').find('.github-btn'), + $button = $mainButton.find('.btn'), + $text = $mainButton.find('.gh-text'), + $counter = $mainButton.find('.gh-count'); + + function addCommas(a) { + return String(a).replace(/(\d)(?=(\d{3})+$)/g, '$1,'); + } + + function callback(a) { + if (type == 'watch') { + $counter.html(addCommas(a.watchers)); + } else { + if (type == 'fork') { + $counter.html(addCommas(a.forks)); + } else { + if (type == 'follow') { + $counter.html(addCommas(a.followers)); + } + } + } + + if (count) { + $counter.css('display', 'inline-block'); + } + } + + function jsonp(url) { + var ctx = caches[url] || {}; + caches[url] = ctx; + if(ctx.onload || ctx.data){ + if(ctx.data){ + callback(ctx.data); + } else { + setTimeout(jsonp, 500, url); + } + }else{ + ctx.onload = true; + $.getJSON(url, function(a){ + ctx.onload = false; + ctx.data = a; + callback(a); + }); + } + } + + var urlBase = 'https://github.com/' + user + '/' + repo; + + $button.attr('href', urlBase + '/'); + + if (type == 'watch') { + $mainButton.addClass('github-watchers'); + $text.html('Star'); + $counter.attr('href', urlBase + '/stargazers'); + } else { + if (type == 'fork') { + $mainButton.addClass('github-forks'); + $text.html('Fork'); + $counter.attr('href', urlBase + '/network'); + } else { + if (type == 'follow') { + $mainButton.addClass('github-me'); + $text.html('Follow @' + user); + $button.attr('href', 'https://github.com/' + user); + $counter.attr('href', 'https://github.com/' + user + '/followers'); + } + } + } + + if (type == 'follow') { + jsonp('https://api.github.com/users/' + user); + } else { + jsonp('https://api.github.com/repos/' + user + '/' + repo); + } + + }); + }; + + })(); + + +(function($){ + (function(){ + var caches = {}; + $.fn.showGithub = function(user, repo, type, count){ + + $(this).each(function(){ + var $e = $(this); + + var user = $e.data('user') || user, + repo = $e.data('repo') || repo, + type = $e.data('type') || type || 'watch', + count = $e.data('count') == 'true' || count || true; + + var $mainButton = $e.html(' ').find('.github-btn'), + $button = $mainButton.find('.btn'), + $text = $mainButton.find('.gh-text'), + $counter = $mainButton.find('.gh-count'); + + function addCommas(a) { + return String(a).replace(/(\d)(?=(\d{3})+$)/g, '$1,'); + } + + function callback(a) { + if (type == 'watch') { + $counter.html(addCommas(a.watchers)); + } else { + if (type == 'fork') { + $counter.html(addCommas(a.forks)); + } else { + if (type == 'follow') { + $counter.html(addCommas(a.followers)); + } + } + } + + if (count) { + $counter.css('display', 'inline-block'); + } + } + + function jsonp(url) { + var ctx = caches[url] || {}; + caches[url] = ctx; + if(ctx.onload || ctx.data){ + if(ctx.data){ + callback(ctx.data); + } else { + setTimeout(jsonp, 500, url); + } + }else{ + ctx.onload = true; + $.getJSON(url, function(a){ + ctx.onload = false; + ctx.data = a; + callback(a); + }); + } + } + + var urlBase = 'https://github.com/' + user + '/' + repo; + + $button.attr('href', urlBase + '/'); + + if (type == 'watch') { + $mainButton.addClass('github-watchers'); + $text.html('Star'); + $counter.attr('href', urlBase + '/stargazers'); + } else { + if (type == 'fork') { + $mainButton.addClass('github-forks'); + $text.html('Fork'); + $counter.attr('href', urlBase + '/network'); + } else { + if (type == 'follow') { + $mainButton.addClass('github-me'); + $text.html('@' + user); + $button.attr('href', 'https://github.com/' + user); + $counter.attr('href', 'https://github.com/' + user + '/followers'); + } + } + } + + if (type == 'follow') { + jsonp('https://api.github.com/users/' + user); + } else { + jsonp('https://api.github.com/repos/' + user + '/' + repo); + } + + }); + }; + + })(); +})(jQuery); + +jQuery(document).ready(function(){ + initializeJS(); + $('[rel=show-github]').showGithub(); +}); + diff --git a/static/quickstart/bookshelf-bleak-theme.png b/static/quickstart/bookshelf-bleak-theme.png new file mode 100755 index 000000000..ccd18c42d Binary files /dev/null and b/static/quickstart/bookshelf-bleak-theme.png differ diff --git a/static/quickstart/bookshelf-disqus.png b/static/quickstart/bookshelf-disqus.png new file mode 100755 index 000000000..3ce645a0c Binary files /dev/null and b/static/quickstart/bookshelf-disqus.png differ diff --git a/static/quickstart/bookshelf-new-default-image.png b/static/quickstart/bookshelf-new-default-image.png new file mode 100755 index 000000000..d7274c7a6 Binary files /dev/null and b/static/quickstart/bookshelf-new-default-image.png differ diff --git a/static/quickstart/bookshelf-only-picture.png b/static/quickstart/bookshelf-only-picture.png new file mode 100755 index 000000000..a363383bc Binary files /dev/null and b/static/quickstart/bookshelf-only-picture.png differ diff --git a/static/quickstart/bookshelf-robust-theme.png b/static/quickstart/bookshelf-robust-theme.png new file mode 100755 index 000000000..7c5e6b8d2 Binary files /dev/null and b/static/quickstart/bookshelf-robust-theme.png differ diff --git a/static/quickstart/bookshelf-updated-config.png b/static/quickstart/bookshelf-updated-config.png new file mode 100755 index 000000000..bbda606c7 Binary files /dev/null and b/static/quickstart/bookshelf-updated-config.png differ diff --git a/static/quickstart/bookshelf.png b/static/quickstart/bookshelf.png new file mode 100755 index 000000000..3b572adbb Binary files /dev/null and b/static/quickstart/bookshelf.png differ diff --git a/static/quickstart/default.jpg b/static/quickstart/default.jpg new file mode 100755 index 000000000..78d7bd28e Binary files /dev/null and b/static/quickstart/default.jpg differ diff --git a/static/quickstart/gh-pages-ui.png b/static/quickstart/gh-pages-ui.png new file mode 100644 index 000000000..3a7d10305 Binary files /dev/null and b/static/quickstart/gh-pages-ui.png differ diff --git a/static/share/hugo-tall.png b/static/share/hugo-tall.png new file mode 100644 index 000000000..001ce5eb3 Binary files /dev/null and b/static/share/hugo-tall.png differ diff --git a/static/share/made-with-hugo-dark.png b/static/share/made-with-hugo-dark.png new file mode 100644 index 000000000..c6cadf283 Binary files /dev/null and b/static/share/made-with-hugo-dark.png differ diff --git a/static/share/made-with-hugo-long-dark.png b/static/share/made-with-hugo-long-dark.png new file mode 100644 index 000000000..1e49995fb Binary files /dev/null and b/static/share/made-with-hugo-long-dark.png differ diff --git a/static/share/made-with-hugo-long.png b/static/share/made-with-hugo-long.png new file mode 100644 index 000000000..c5df534cf Binary files /dev/null and b/static/share/made-with-hugo-long.png differ diff --git a/static/share/made-with-hugo.png b/static/share/made-with-hugo.png new file mode 100644 index 000000000..52dfd19e5 Binary files /dev/null and b/static/share/made-with-hugo.png differ diff --git a/static/share/powered-by-hugo-dark.png b/static/share/powered-by-hugo-dark.png new file mode 100644 index 000000000..a8e2ebc80 Binary files /dev/null and b/static/share/powered-by-hugo-dark.png differ diff --git a/static/share/powered-by-hugo-long-dark.png b/static/share/powered-by-hugo-long-dark.png new file mode 100644 index 000000000..1b760b1bf Binary files /dev/null and b/static/share/powered-by-hugo-long-dark.png differ diff --git a/static/share/powered-by-hugo-long.png b/static/share/powered-by-hugo-long.png new file mode 100644 index 000000000..37131359d Binary files /dev/null and b/static/share/powered-by-hugo-long.png differ diff --git a/static/share/powered-by-hugo.png b/static/share/powered-by-hugo.png new file mode 100644 index 000000000..27ff099d5 Binary files /dev/null and b/static/share/powered-by-hugo.png differ diff --git a/static/vendor/OwlCarousel2/LICENSE b/static/vendor/OwlCarousel2/LICENSE new file mode 100644 index 000000000..7162d578b --- /dev/null +++ b/static/vendor/OwlCarousel2/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2014 Owl + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/static/vendor/OwlCarousel2/css/owl.carousel.css b/static/vendor/OwlCarousel2/css/owl.carousel.css new file mode 100644 index 000000000..aaf80dd13 --- /dev/null +++ b/static/vendor/OwlCarousel2/css/owl.carousel.css @@ -0,0 +1,179 @@ +/* + * Owl Carousel - Animate Plugin + */ +.owl-carousel .animated { + -webkit-animation-duration: 1000ms; + animation-duration: 1000ms; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; } +.owl-carousel .owl-animated-in { + z-index: 0; } +.owl-carousel .owl-animated-out { + z-index: 1; } +.owl-carousel .fadeOut { + -webkit-animation-name: fadeOut; + animation-name: fadeOut; } + +@-webkit-keyframes fadeOut { + 0% { + opacity: 1; } + + 100% { + opacity: 0; } } + +@keyframes fadeOut { + 0% { + opacity: 1; } + + 100% { + opacity: 0; } } + +/* + * Owl Carousel - Auto Height Plugin + */ +.owl-height { + -webkit-transition: height 500ms ease-in-out; + -moz-transition: height 500ms ease-in-out; + -ms-transition: height 500ms ease-in-out; + -o-transition: height 500ms ease-in-out; + transition: height 500ms ease-in-out; } + +/* + * Core Owl Carousel CSS File + */ +.owl-carousel { + display: none; + width: 100%; + -webkit-tap-highlight-color: transparent; + /* position relative and z-index fix webkit rendering fonts issue */ + position: relative; + z-index: 1; } + .owl-carousel .owl-stage { + position: relative; + -ms-touch-action: pan-Y; } + .owl-carousel .owl-stage:after { + content: "."; + display: block; + clear: both; + visibility: hidden; + line-height: 0; + height: 0; } + .owl-carousel .owl-stage-outer { + position: relative; + overflow: hidden; + /* fix for flashing background */ + -webkit-transform: translate3d(0px, 0px, 0px); } + .owl-carousel .owl-item { + position: relative; + min-height: 1px; + float: left; + -webkit-backface-visibility: hidden; + -webkit-tap-highlight-color: transparent; + -webkit-touch-callout: none; } + .owl-carousel .owl-item img { + display: block; + width: 100%; + -webkit-transform-style: preserve-3d; } + .owl-carousel .owl-nav.disabled, .owl-carousel .owl-dots.disabled { + display: none; } + .owl-carousel .owl-nav .owl-prev, .owl-carousel .owl-nav .owl-next, .owl-carousel .owl-dot { + cursor: pointer; + cursor: hand; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } + .owl-carousel.owl-loaded { + display: block; } + .owl-carousel.owl-loading { + opacity: 0; + display: block; } + .owl-carousel.owl-hidden { + opacity: 0; } + .owl-carousel.owl-refresh .owl-item { + display: none; } + .owl-carousel.owl-drag .owl-item { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } + .owl-carousel.owl-grab { + cursor: move; + cursor: -webkit-grab; + cursor: -o-grab; + cursor: -ms-grab; + cursor: grab; } + .owl-carousel.owl-rtl { + direction: rtl; } + .owl-carousel.owl-rtl .owl-item { + float: right; } + +/* No Js */ +.no-js .owl-carousel { + display: block; } + +/* + * Owl Carousel - Lazy Load Plugin + */ +.owl-carousel .owl-item .owl-lazy { + opacity: 0; + -webkit-transition: opacity 400ms ease; + -moz-transition: opacity 400ms ease; + -ms-transition: opacity 400ms ease; + -o-transition: opacity 400ms ease; + transition: opacity 400ms ease; } +.owl-carousel .owl-item img { + transform-style: preserve-3d; } + +/* + * Owl Carousel - Video Plugin + */ +.owl-carousel .owl-video-wrapper { + position: relative; + height: 100%; + background: #000; } +.owl-carousel .owl-video-play-icon { + position: absolute; + height: 80px; + width: 80px; + left: 50%; + top: 50%; + margin-left: -40px; + margin-top: -40px; + background: url("owl.video.play.png") no-repeat; + cursor: pointer; + z-index: 1; + -webkit-backface-visibility: hidden; + -webkit-transition: scale 100ms ease; + -moz-transition: scale 100ms ease; + -ms-transition: scale 100ms ease; + -o-transition: scale 100ms ease; + transition: scale 100ms ease; } +.owl-carousel .owl-video-play-icon:hover { + -webkit-transition: scale(1.3, 1.3); + -moz-transition: scale(1.3, 1.3); + -ms-transition: scale(1.3, 1.3); + -o-transition: scale(1.3, 1.3); + transition: scale(1.3, 1.3); } +.owl-carousel .owl-video-playing .owl-video-tn, .owl-carousel .owl-video-playing .owl-video-play-icon { + display: none; } +.owl-carousel .owl-video-tn { + opacity: 0; + height: 100%; + background-position: center center; + background-repeat: no-repeat; + -webkit-background-size: contain; + -moz-background-size: contain; + -o-background-size: contain; + background-size: contain; + -webkit-transition: opacity 400ms ease; + -moz-transition: opacity 400ms ease; + -ms-transition: opacity 400ms ease; + -o-transition: opacity 400ms ease; + transition: opacity 400ms ease; } +.owl-carousel .owl-video-frame { + position: relative; + z-index: 1; + height: 100%; + width: 100%; } diff --git a/static/vendor/OwlCarousel2/css/owl.theme.default.css b/static/vendor/OwlCarousel2/css/owl.theme.default.css new file mode 100644 index 000000000..dcd4c82ae --- /dev/null +++ b/static/vendor/OwlCarousel2/css/owl.theme.default.css @@ -0,0 +1,51 @@ +/* + * Default theme - Owl Carousel CSS File + */ +.owl-theme .owl-nav { + margin-top: 10px; + text-align: center; + -webkit-tap-highlight-color: transparent; } + .owl-theme .owl-nav [class*='owl-'] { + color: #FFF; + font-size: 14px; + margin: 5px; + padding: 4px 7px; + background: #D6D6D6; + display: inline-block; + cursor: pointer; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; } + .owl-theme .owl-nav [class*='owl-']:hover { + background: #869791; + color: #FFF; + text-decoration: none; } + .owl-theme .owl-nav .disabled { + opacity: 0.5; + cursor: default; } +.owl-theme .owl-nav.disabled + .owl-dots { + margin-top: 10px; } +.owl-theme .owl-dots { + text-align: center; + -webkit-tap-highlight-color: transparent; } + .owl-theme .owl-dots .owl-dot { + display: inline-block; + zoom: 1; + *display: inline; } + .owl-theme .owl-dots .owl-dot span { + width: 10px; + height: 10px; + margin: 5px 7px; + background: #D6D6D6; + display: block; + -webkit-backface-visibility: visible; + -webkit-transition: opacity 200ms ease; + -moz-transition: opacity 200ms ease; + -ms-transition: opacity 200ms ease; + -o-transition: opacity 200ms ease; + transition: opacity 200ms ease; + -webkit-border-radius: 30px; + -moz-border-radius: 30px; + border-radius: 30px; } + .owl-theme .owl-dots .owl-dot.active span, .owl-theme .owl-dots .owl-dot:hover span { + background: #869791; } diff --git a/static/vendor/OwlCarousel2/js/owl.carousel.min.js b/static/vendor/OwlCarousel2/js/owl.carousel.min.js new file mode 100644 index 000000000..cd327896d --- /dev/null +++ b/static/vendor/OwlCarousel2/js/owl.carousel.min.js @@ -0,0 +1,2 @@ +!function(a,b,c,d){function e(b,c){this.settings=null,this.options=a.extend({},e.Defaults,c),this.$element=a(b),this._handlers={},this._plugins={},this._supress={},this._current=null,this._speed=null,this._coordinates=[],this._breakpoint=null,this._width=null,this._items=[],this._clones=[],this._mergers=[],this._widths=[],this._invalidated={},this._pipe=[],this._drag={time:null,target:null,pointer:null,stage:{start:null,current:null},direction:null},this._states={current:{},tags:{initializing:["busy"],animating:["busy"],dragging:["interacting"]}},a.each(["onResize","onThrottledResize"],a.proxy(function(b,c){this._handlers[c]=a.proxy(this[c],this)},this)),a.each(e.Plugins,a.proxy(function(a,b){this._plugins[a.charAt(0).toLowerCase()+a.slice(1)]=new b(this)},this)),a.each(e.Workers,a.proxy(function(b,c){this._pipe.push({filter:c.filter,run:a.proxy(c.run,this)})},this)),this.setup(),this.initialize()}e.Defaults={items:3,loop:!1,center:!1,rewind:!1,mouseDrag:!0,touchDrag:!0,pullDrag:!0,freeDrag:!1,margin:0,stagePadding:0,merge:!1,mergeFit:!0,autoWidth:!1,startPosition:0,rtl:!1,smartSpeed:250,fluidSpeed:!1,dragEndSpeed:!1,responsive:{},responsiveRefreshRate:200,responsiveBaseElement:b,fallbackEasing:"swing",info:!1,nestedItemSelector:!1,itemElement:"div",stageElement:"div",refreshClass:"owl-refresh",loadedClass:"owl-loaded",loadingClass:"owl-loading",rtlClass:"owl-rtl",responsiveClass:"owl-responsive",dragClass:"owl-drag",itemClass:"owl-item",stageClass:"owl-stage",stageOuterClass:"owl-stage-outer",grabClass:"owl-grab"},e.Width={Default:"default",Inner:"inner",Outer:"outer"},e.Type={Event:"event",State:"state"},e.Plugins={},e.Workers=[{filter:["width","settings"],run:function(){this._width=this.$element.width()}},{filter:["width","items","settings"],run:function(a){a.current=this._items&&this._items[this.relative(this._current)]}},{filter:["items","settings"],run:function(){this.$stage.children(".cloned").remove()}},{filter:["width","items","settings"],run:function(a){var b=this.settings.margin||"",c=!this.settings.autoWidth,d=this.settings.rtl,e={width:"auto","margin-left":d?b:"","margin-right":d?"":b};!c&&this.$stage.children().css(e),a.css=e}},{filter:["width","items","settings"],run:function(a){var b=(this.width()/this.settings.items).toFixed(3)-this.settings.margin,c=null,d=this._items.length,e=!this.settings.autoWidth,f=[];for(a.items={merge:!1,width:b};d--;)c=this._mergers[d],c=this.settings.mergeFit&&Math.min(c,this.settings.items)||c,a.items.merge=c>1||a.items.merge,f[d]=e?b*c:this._items[d].width();this._widths=f}},{filter:["items","settings"],run:function(){var b=[],c=this._items,d=this.settings,e=Math.max(2*d.items,4),f=2*Math.ceil(c.length/2),g=d.loop&&c.length?d.rewind?e:Math.max(e,f):0,h="",i="";for(g/=2;g--;)b.push(this.normalize(b.length/2,!0)),h+=c[b[b.length-1]][0].outerHTML,b.push(this.normalize(c.length-1-(b.length-1)/2,!0)),i=c[b[b.length-1]][0].outerHTML+i;this._clones=b,a(h).addClass("cloned").appendTo(this.$stage),a(i).addClass("cloned").prependTo(this.$stage)}},{filter:["width","items","settings"],run:function(){for(var a=this.settings.rtl?1:-1,b=this._clones.length+this._items.length,c=-1,d=0,e=0,f=[];++cc;c++)a=this._coordinates[c-1]||0,b=Math.abs(this._coordinates[c])+f*e,(this.op(a,"<=",g)&&this.op(a,">",h)||this.op(b,"<",g)&&this.op(b,">",h))&&i.push(c);this.$stage.children(".active").removeClass("active"),this.$stage.children(":eq("+i.join("), :eq(")+")").addClass("active"),this.settings.center&&(this.$stage.children(".center").removeClass("center"),this.$stage.children().eq(this.current()).addClass("center"))}}],e.prototype.initialize=function(){if(this.enter("initializing"),this.trigger("initialize"),this.$element.toggleClass(this.settings.rtlClass,this.settings.rtl),this.settings.autoWidth&&!this.is("pre-loading")){var b,c,e;b=this.$element.find("img"),c=this.settings.nestedItemSelector?"."+this.settings.nestedItemSelector:d,e=this.$element.children(c).width(),b.length&&0>=e&&this.preloadAutoWidthImages(b)}this.$element.addClass(this.options.loadingClass),this.$stage=a("<"+this.settings.stageElement+' class="'+this.settings.stageClass+'"/>').wrap('
    '),this.$element.append(this.$stage.parent()),this.replace(this.$element.children().not(this.$stage.parent())),this.$element.is(":visible")?this.refresh():this.invalidate("width"),this.$element.removeClass(this.options.loadingClass).addClass(this.options.loadedClass),this.registerEventHandlers(),this.leave("initializing"),this.trigger("initialized")},e.prototype.setup=function(){var b=this.viewport(),c=this.options.responsive,d=-1,e=null;c?(a.each(c,function(a){b>=a&&a>d&&(d=Number(a))}),e=a.extend({},this.options,c[d]),delete e.responsive,e.responsiveClass&&this.$element.attr("class",this.$element.attr("class").replace(new RegExp("("+this.options.responsiveClass+"-)\\S+\\s","g"),"$1"+d))):e=a.extend({},this.options),(null===this.settings||this._breakpoint!==d)&&(this.trigger("change",{property:{name:"settings",value:e}}),this._breakpoint=d,this.settings=e,this.invalidate("settings"),this.trigger("changed",{property:{name:"settings",value:this.settings}}))},e.prototype.optionsLogic=function(){this.settings.autoWidth&&(this.settings.stagePadding=!1,this.settings.merge=!1)},e.prototype.prepare=function(b){var c=this.trigger("prepare",{content:b});return c.data||(c.data=a("<"+this.settings.itemElement+"/>").addClass(this.options.itemClass).append(b)),this.trigger("prepared",{content:c.data}),c.data},e.prototype.update=function(){for(var b=0,c=this._pipe.length,d=a.proxy(function(a){return this[a]},this._invalidated),e={};c>b;)(this._invalidated.all||a.grep(this._pipe[b].filter,d).length>0)&&this._pipe[b].run(e),b++;this._invalidated={},!this.is("valid")&&this.enter("valid")},e.prototype.width=function(a){switch(a=a||e.Width.Default){case e.Width.Inner:case e.Width.Outer:return this._width;default:return this._width-2*this.settings.stagePadding+this.settings.margin}},e.prototype.refresh=function(){this.enter("refreshing"),this.trigger("refresh"),this.setup(),this.optionsLogic(),this.$element.addClass(this.options.refreshClass),this.update(),this.$element.removeClass(this.options.refreshClass),this.leave("refreshing"),this.trigger("refreshed")},e.prototype.onThrottledResize=function(){b.clearTimeout(this.resizeTimer),this.resizeTimer=b.setTimeout(this._handlers.onResize,this.settings.responsiveRefreshRate)},e.prototype.onResize=function(){return this._items.length?this._width===this.$element.width()?!1:this.$element.is(":visible")?(this.enter("resizing"),this.trigger("resize").isDefaultPrevented()?(this.leave("resizing"),!1):(this.invalidate("width"),this.refresh(),this.leave("resizing"),void this.trigger("resized"))):!1:!1},e.prototype.registerEventHandlers=function(){a.support.transition&&this.$stage.on(a.support.transition.end+".owl.core",a.proxy(this.onTransitionEnd,this)),this.settings.responsive!==!1&&this.on(b,"resize",this._handlers.onThrottledResize),this.settings.mouseDrag&&(this.$element.addClass(this.options.dragClass),this.$stage.on("mousedown.owl.core",a.proxy(this.onDragStart,this)),this.$stage.on("dragstart.owl.core selectstart.owl.core",function(){return!1})),this.settings.touchDrag&&(this.$stage.on("touchstart.owl.core",a.proxy(this.onDragStart,this)),this.$stage.on("touchcancel.owl.core",a.proxy(this.onDragEnd,this)))},e.prototype.onDragStart=function(b){var d=null;3!==b.which&&(a.support.transform?(d=this.$stage.css("transform").replace(/.*\(|\)| /g,"").split(","),d={x:d[16===d.length?12:4],y:d[16===d.length?13:5]}):(d=this.$stage.position(),d={x:this.settings.rtl?d.left+this.$stage.width()-this.width()+this.settings.margin:d.left,y:d.top}),this.is("animating")&&(a.support.transform?this.animate(d.x):this.$stage.stop(),this.invalidate("position")),this.$element.toggleClass(this.options.grabClass,"mousedown"===b.type),this.speed(0),this._drag.time=(new Date).getTime(),this._drag.target=a(b.target),this._drag.stage.start=d,this._drag.stage.current=d,this._drag.pointer=this.pointer(b),a(c).on("mouseup.owl.core touchend.owl.core",a.proxy(this.onDragEnd,this)),a(c).one("mousemove.owl.core touchmove.owl.core",a.proxy(function(b){var d=this.difference(this._drag.pointer,this.pointer(b));a(c).on("mousemove.owl.core touchmove.owl.core",a.proxy(this.onDragMove,this)),Math.abs(d.x)0^this.settings.rtl?"left":"right";a(c).off(".owl.core"),this.$element.removeClass(this.options.grabClass),(0!==d.x&&this.is("dragging")||!this.is("valid"))&&(this.speed(this.settings.dragEndSpeed||this.settings.smartSpeed),this.current(this.closest(e.x,0!==d.x?f:this._drag.direction)),this.invalidate("position"),this.update(),this._drag.direction=f,(Math.abs(d.x)>3||(new Date).getTime()-this._drag.time>300)&&this._drag.target.one("click.owl.core",function(){return!1})),this.is("dragging")&&(this.leave("dragging"),this.trigger("dragged"))},e.prototype.closest=function(b,c){var d=-1,e=30,f=this.width(),g=this.coordinates();return this.settings.freeDrag||a.each(g,a.proxy(function(a,h){return b>h-e&&h+e>b?d=a:this.op(b,"<",h)&&this.op(b,">",g[a+1]||h-f)&&(d="left"===c?a+1:a),-1===d},this)),this.settings.loop||(this.op(b,">",g[this.minimum()])?d=b=this.minimum():this.op(b,"<",g[this.maximum()])&&(d=b=this.maximum())),d},e.prototype.animate=function(b){var c=this.speed()>0;this.is("animating")&&this.onTransitionEnd(),c&&(this.enter("animating"),this.trigger("translate")),a.support.transform3d&&a.support.transition?this.$stage.css({transform:"translate3d("+b+"px,0px,0px)",transition:this.speed()/1e3+"s"}):c?this.$stage.animate({left:b+"px"},this.speed(),this.settings.fallbackEasing,a.proxy(this.onTransitionEnd,this)):this.$stage.css({left:b+"px"})},e.prototype.is=function(a){return this._states.current[a]&&this._states.current[a]>0},e.prototype.current=function(a){if(a===d)return this._current;if(0===this._items.length)return d;if(a=this.normalize(a),this._current!==a){var b=this.trigger("change",{property:{name:"position",value:a}});b.data!==d&&(a=this.normalize(b.data)),this._current=a,this.invalidate("position"),this.trigger("changed",{property:{name:"position",value:this._current}})}return this._current},e.prototype.invalidate=function(b){return"string"===a.type(b)&&(this._invalidated[b]=!0,this.is("valid")&&this.leave("valid")),a.map(this._invalidated,function(a,b){return b})},e.prototype.reset=function(a){a=this.normalize(a),a!==d&&(this._speed=0,this._current=a,this.suppress(["translate","translated"]),this.animate(this.coordinates(a)),this.release(["translate","translated"]))},e.prototype.normalize=function(b,c){var e=this._items.length,f=c?0:this._clones.length;return!a.isNumeric(b)||1>e?b=d:(0>b||b>=e+f)&&(b=((b-f/2)%e+e)%e+f/2),b},e.prototype.relative=function(a){return a-=this._clones.length/2,this.normalize(a,!0)},e.prototype.maximum=function(a){var b,c=this.settings,d=this._coordinates.length,e=Math.abs(this._coordinates[d-1])-this._width,f=-1;if(c.loop)d=this._clones.length/2+this._items.length-1;else if(c.autoWidth||c.merge)for(;d-f>1;)Math.abs(this._coordinates[b=d+f>>1])0)-(0>e),g=this._items.length,h=this.minimum(),i=this.maximum();this.settings.loop?(!this.settings.rewind&&Math.abs(e)>g/2&&(e+=-1*f*g),a=c+e,d=((a-h)%g+g)%g+h,d!==a&&i>=d-e&&d-e>0&&(c=d-e,a=d,this.reset(c))):this.settings.rewind?(i+=1,a=(a%i+i)%i):a=Math.max(h,Math.min(i,a)),this.speed(this.duration(c,a,b)),this.current(a),this.$element.is(":visible")&&this.update()},e.prototype.next=function(a){a=a||!1,this.to(this.relative(this.current())+1,a)},e.prototype.prev=function(a){a=a||!1,this.to(this.relative(this.current())-1,a)},e.prototype.onTransitionEnd=function(a){return a!==d&&(a.stopPropagation(),(a.target||a.srcElement||a.originalTarget)!==this.$stage.get(0))?!1:(this.leave("animating"),void this.trigger("translated"))},e.prototype.viewport=function(){var d;if(this.options.responsiveBaseElement!==b)d=a(this.options.responsiveBaseElement).width();else if(b.innerWidth)d=b.innerWidth;else{if(!c.documentElement||!c.documentElement.clientWidth)throw"Can not detect viewport width.";d=c.documentElement.clientWidth}return d},e.prototype.replace=function(b){this.$stage.empty(),this._items=[],b&&(b=b instanceof jQuery?b:a(b)),this.settings.nestedItemSelector&&(b=b.find("."+this.settings.nestedItemSelector)),b.filter(function(){return 1===this.nodeType}).each(a.proxy(function(a,b){b=this.prepare(b),this.$stage.append(b),this._items.push(b),this._mergers.push(1*b.find("[data-merge]").andSelf("[data-merge]").attr("data-merge")||1)},this)),this.reset(a.isNumeric(this.settings.startPosition)?this.settings.startPosition:0),this.invalidate("items")},e.prototype.add=function(b,c){var e=this.relative(this._current);c=c===d?this._items.length:this.normalize(c,!0),b=b instanceof jQuery?b:a(b),this.trigger("add",{content:b,position:c}),b=this.prepare(b),0===this._items.length||c===this._items.length?(0===this._items.length&&this.$stage.append(b),0!==this._items.length&&this._items[c-1].after(b),this._items.push(b),this._mergers.push(1*b.find("[data-merge]").andSelf("[data-merge]").attr("data-merge")||1)):(this._items[c].before(b),this._items.splice(c,0,b),this._mergers.splice(c,0,1*b.find("[data-merge]").andSelf("[data-merge]").attr("data-merge")||1)),this._items[e]&&this.reset(this._items[e].index()),this.invalidate("items"),this.trigger("added",{content:b,position:c})},e.prototype.remove=function(a){a=this.normalize(a,!0),a!==d&&(this.trigger("remove",{content:this._items[a],position:a}),this._items[a].remove(),this._items.splice(a,1),this._mergers.splice(a,1),this.invalidate("items"),this.trigger("removed",{content:null,position:a}))},e.prototype.preloadAutoWidthImages=function(b){b.each(a.proxy(function(b,c){this.enter("pre-loading"),c=a(c),a(new Image).one("load",a.proxy(function(a){c.attr("src",a.target.src),c.css("opacity",1),this.leave("pre-loading"),!this.is("pre-loading")&&!this.is("initializing")&&this.refresh()},this)).attr("src",c.attr("src")||c.attr("data-src")||c.attr("data-src-retina"))},this))},e.prototype.destroy=function(){this.$element.off(".owl.core"),this.$stage.off(".owl.core"),a(c).off(".owl.core"),this.settings.responsive!==!1&&(b.clearTimeout(this.resizeTimer),this.off(b,"resize",this._handlers.onThrottledResize));for(var d in this._plugins)this._plugins[d].destroy();this.$stage.children(".cloned").remove(),this.$stage.unwrap(),this.$stage.children().contents().unwrap(),this.$stage.children().unwrap(),this.$element.removeClass(this.options.refreshClass).removeClass(this.options.loadingClass).removeClass(this.options.loadedClass).removeClass(this.options.rtlClass).removeClass(this.options.dragClass).removeClass(this.options.grabClass).attr("class",this.$element.attr("class").replace(new RegExp(this.options.responsiveClass+"-\\S+\\s","g"),"")).removeData("owl.carousel")},e.prototype.op=function(a,b,c){var d=this.settings.rtl;switch(b){case"<":return d?a>c:c>a;case">":return d?c>a:a>c;case">=":return d?c>=a:a>=c;case"<=":return d?a>=c:c>=a}},e.prototype.on=function(a,b,c,d){a.addEventListener?a.addEventListener(b,c,d):a.attachEvent&&a.attachEvent("on"+b,c)},e.prototype.off=function(a,b,c,d){a.removeEventListener?a.removeEventListener(b,c,d):a.detachEvent&&a.detachEvent("on"+b,c)},e.prototype.trigger=function(b,c,d,f,g){var h={item:{count:this._items.length,index:this.current()}},i=a.camelCase(a.grep(["on",b,d],function(a){return a}).join("-").toLowerCase()),j=a.Event([b,"owl",d||"carousel"].join(".").toLowerCase(),a.extend({relatedTarget:this},h,c));return this._supress[b]||(a.each(this._plugins,function(a,b){b.onTrigger&&b.onTrigger(j)}),this.register({type:e.Type.Event,name:b}),this.$element.trigger(j),this.settings&&"function"==typeof this.settings[i]&&this.settings[i].call(this,j)),j},e.prototype.enter=function(b){a.each([b].concat(this._states.tags[b]||[]),a.proxy(function(a,b){this._states.current[b]===d&&(this._states.current[b]=0),this._states.current[b]++},this))},e.prototype.leave=function(b){a.each([b].concat(this._states.tags[b]||[]),a.proxy(function(a,b){this._states.current[b]--},this))},e.prototype.register=function(b){if(b.type===e.Type.Event){if(a.event.special[b.name]||(a.event.special[b.name]={}),!a.event.special[b.name].owl){var c=a.event.special[b.name]._default;a.event.special[b.name]._default=function(a){return!c||!c.apply||a.namespace&&-1!==a.namespace.indexOf("owl")?a.namespace&&a.namespace.indexOf("owl")>-1:c.apply(this,arguments)},a.event.special[b.name].owl=!0}}else b.type===e.Type.State&&(this._states.tags[b.name]?this._states.tags[b.name]=this._states.tags[b.name].concat(b.tags):this._states.tags[b.name]=b.tags,this._states.tags[b.name]=a.grep(this._states.tags[b.name],a.proxy(function(c,d){return a.inArray(c,this._states.tags[b.name])===d},this)))},e.prototype.suppress=function(b){a.each(b,a.proxy(function(a,b){this._supress[b]=!0},this))},e.prototype.release=function(b){a.each(b,a.proxy(function(a,b){delete this._supress[b]},this))},e.prototype.pointer=function(a){var c={x:null,y:null};return a=a.originalEvent||a||b.event,a=a.touches&&a.touches.length?a.touches[0]:a.changedTouches&&a.changedTouches.length?a.changedTouches[0]:a,a.pageX?(c.x=a.pageX,c.y=a.pageY):(c.x=a.clientX,c.y=a.clientY),c},e.prototype.difference=function(a,b){return{x:a.x-b.x,y:a.y-b.y}},a.fn.owlCarousel=function(b){var c=Array.prototype.slice.call(arguments,1);return this.each(function(){var d=a(this),f=d.data("owl.carousel");f||(f=new e(this,"object"==typeof b&&b),d.data("owl.carousel",f),a.each(["next","prev","to","destroy","refresh","replace","add","remove"],function(b,c){f.register({type:e.Type.Event,name:c}),f.$element.on(c+".owl.carousel.core",a.proxy(function(a){a.namespace&&a.relatedTarget!==this&&(this.suppress([c]),f[c].apply(this,[].slice.call(arguments,1)),this.release([c]))},f))})),"string"==typeof b&&"_"!==b.charAt(0)&&f[b].apply(f,c)})},a.fn.owlCarousel.Constructor=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){var e=function(b){this._core=b,this._interval=null,this._visible=null,this._handlers={"initialized.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.autoRefresh&&this.watch()},this)},this._core.options=a.extend({},e.Defaults,this._core.options),this._core.$element.on(this._handlers)};e.Defaults={autoRefresh:!0,autoRefreshInterval:500},e.prototype.watch=function(){this._interval||(this._visible=this._core.$element.is(":visible"),this._interval=b.setInterval(a.proxy(this.refresh,this),this._core.settings.autoRefreshInterval))},e.prototype.refresh=function(){this._core.$element.is(":visible")!==this._visible&&(this._visible=!this._visible,this._core.$element.toggleClass("owl-hidden",!this._visible),this._visible&&this._core.invalidate("width")&&this._core.refresh())},e.prototype.destroy=function(){var a,c;b.clearInterval(this._interval);for(a in this._handlers)this._core.$element.off(a,this._handlers[a]);for(c in Object.getOwnPropertyNames(this))"function"!=typeof this[c]&&(this[c]=null)},a.fn.owlCarousel.Constructor.Plugins.AutoRefresh=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){var e=function(b){this._core=b,this._loaded=[],this._handlers={"initialized.owl.carousel change.owl.carousel":a.proxy(function(b){if(b.namespace&&this._core.settings&&this._core.settings.lazyLoad&&(b.property&&"position"==b.property.name||"initialized"==b.type))for(var c=this._core.settings,d=c.center&&Math.ceil(c.items/2)||c.items,e=c.center&&-1*d||0,f=(b.property&&b.property.value||this._core.current())+e,g=this._core.clones().length,h=a.proxy(function(a,b){this.load(b)},this);e++-1||(e.each(a.proxy(function(c,d){var e,f=a(d),g=b.devicePixelRatio>1&&f.attr("data-src-retina")||f.attr("data-src");this._core.trigger("load",{element:f,url:g},"lazy"),f.is("img")?f.one("load.owl.lazy",a.proxy(function(){f.css("opacity",1),this._core.trigger("loaded",{element:f,url:g},"lazy")},this)).attr("src",g):(e=new Image,e.onload=a.proxy(function(){f.css({"background-image":"url("+g+")",opacity:"1"}),this._core.trigger("loaded",{element:f,url:g},"lazy")},this),e.src=g)},this)),this._loaded.push(d.get(0)))},e.prototype.destroy=function(){var a,b;for(a in this.handlers)this._core.$element.off(a,this.handlers[a]);for(b in Object.getOwnPropertyNames(this))"function"!=typeof this[b]&&(this[b]=null)},a.fn.owlCarousel.Constructor.Plugins.Lazy=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){var e=function(b){this._core=b,this._handlers={"initialized.owl.carousel refreshed.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.autoHeight&&this.update()},this),"changed.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.autoHeight&&"position"==a.property.name&&this.update()},this),"loaded.owl.lazy":a.proxy(function(a){a.namespace&&this._core.settings.autoHeight&&a.element.closest("."+this._core.settings.itemClass).index()===this._core.current()&&this.update()},this)},this._core.options=a.extend({},e.Defaults,this._core.options),this._core.$element.on(this._handlers)};e.Defaults={autoHeight:!1,autoHeightClass:"owl-height"},e.prototype.update=function(){var b=this._core._current,c=b+this._core.settings.items,d=this._core.$stage.children().toArray().slice(b,c);heights=[],maxheight=0,a.each(d,function(b,c){heights.push(a(c).height())}),maxheight=Math.max.apply(null,heights),this._core.$stage.parent().height(maxheight).addClass(this._core.settings.autoHeightClass)},e.prototype.destroy=function(){var a,b;for(a in this._handlers)this._core.$element.off(a,this._handlers[a]);for(b in Object.getOwnPropertyNames(this))"function"!=typeof this[b]&&(this[b]=null)},a.fn.owlCarousel.Constructor.Plugins.AutoHeight=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){var e=function(b){this._core=b,this._videos={},this._playing=null,this._handlers={"initialized.owl.carousel":a.proxy(function(a){a.namespace&&this._core.register({type:"state",name:"playing",tags:["interacting"]})},this),"resize.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.video&&this.isInFullScreen()&&a.preventDefault()},this),"refreshed.owl.carousel":a.proxy(function(a){a.namespace&&this._core.is("resizing")&&this._core.$stage.find(".cloned .owl-video-frame").remove()},this),"changed.owl.carousel":a.proxy(function(a){a.namespace&&"position"===a.property.name&&this._playing&&this.stop()},this),"prepared.owl.carousel":a.proxy(function(b){if(b.namespace){var c=a(b.content).find(".owl-video");c.length&&(c.css("display","none"),this.fetch(c,a(b.content)))}},this)},this._core.options=a.extend({},e.Defaults,this._core.options),this._core.$element.on(this._handlers),this._core.$element.on("click.owl.video",".owl-video-play-icon",a.proxy(function(a){this.play(a)},this))};e.Defaults={video:!1,videoHeight:!1,videoWidth:!1},e.prototype.fetch=function(a,b){var c=a.attr("data-vimeo-id")?"vimeo":"youtube",d=a.attr("data-vimeo-id")||a.attr("data-youtube-id"),e=a.attr("data-width")||this._core.settings.videoWidth,f=a.attr("data-height")||this._core.settings.videoHeight,g=a.attr("href");if(!g)throw new Error("Missing video URL.");if(d=g.match(/(http:|https:|)\/\/(player.|www.)?(vimeo\.com|youtu(be\.com|\.be|be\.googleapis\.com))\/(video\/|embed\/|watch\?v=|v\/)?([A-Za-z0-9._%-]*)(\&\S+)?/),d[3].indexOf("youtu")>-1)c="youtube";else{if(!(d[3].indexOf("vimeo")>-1))throw new Error("Video URL not supported.");c="vimeo"}d=d[6],this._videos[g]={type:c,id:d,width:e,height:f},b.attr("data-video",g),this.thumbnail(a,this._videos[g])},e.prototype.thumbnail=function(b,c){var d,e,f,g=c.width&&c.height?'style="width:'+c.width+"px;height:"+c.height+'px;"':"",h=b.find("img"),i="src",j="",k=this._core.settings,l=function(a){e='
    ',d=k.lazyLoad?'
    ':'
    ',b.after(d),b.after(e)};return b.wrap('
    "),this._core.settings.lazyLoad&&(i="data-src",j="owl-lazy"),h.length?(l(h.attr(i)),h.remove(),!1):void("youtube"===c.type?(f="http://img.youtube.com/vi/"+c.id+"/hqdefault.jpg",l(f)):"vimeo"===c.type&&a.ajax({type:"GET",url:"http://vimeo.com/api/v2/video/"+c.id+".json",jsonp:"callback",dataType:"jsonp",success:function(a){f=a[0].thumbnail_large,l(f)}}))},e.prototype.stop=function(){this._core.trigger("stop",null,"video"),this._playing.find(".owl-video-frame").remove(),this._playing.removeClass("owl-video-playing"),this._playing=null,this._core.leave("playing"),this._core.trigger("stopped",null,"video")},e.prototype.play=function(b){var c,d=a(b.target),e=d.closest("."+this._core.settings.itemClass),f=this._videos[e.attr("data-video")],g=f.width||"100%",h=f.height||this._core.$stage.height();this._playing||(this._core.enter("playing"),this._core.trigger("play",null,"video"),e=this._core.items(this._core.relative(e.index())),this._core.reset(e.index()),"youtube"===f.type?c='':"vimeo"===f.type&&(c=''),a('
    '+c+"
    ").insertAfter(e.find(".owl-video")),this._playing=e.addClass("owl-video-playing"))},e.prototype.isInFullScreen=function(){var b=c.fullscreenElement||c.mozFullScreenElement||c.webkitFullscreenElement;return b&&a(b).parent().hasClass("owl-video-frame")},e.prototype.destroy=function(){var a,b;this._core.$element.off("click.owl.video");for(a in this._handlers)this._core.$element.off(a,this._handlers[a]);for(b in Object.getOwnPropertyNames(this))"function"!=typeof this[b]&&(this[b]=null)},a.fn.owlCarousel.Constructor.Plugins.Video=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){var e=function(b){this.core=b,this.core.options=a.extend({},e.Defaults,this.core.options),this.swapping=!0,this.previous=d,this.next=d,this.handlers={"change.owl.carousel":a.proxy(function(a){a.namespace&&"position"==a.property.name&&(this.previous=this.core.current(),this.next=a.property.value)},this),"drag.owl.carousel dragged.owl.carousel translated.owl.carousel":a.proxy(function(a){a.namespace&&(this.swapping="translated"==a.type)},this),"translate.owl.carousel":a.proxy(function(a){a.namespace&&this.swapping&&(this.core.options.animateOut||this.core.options.animateIn)&&this.swap()},this)},this.core.$element.on(this.handlers)};e.Defaults={animateOut:!1,animateIn:!1},e.prototype.swap=function(){if(1===this.core.settings.items&&a.support.animation&&a.support.transition){this.core.speed(0);var b,c=a.proxy(this.clear,this),d=this.core.$stage.children().eq(this.previous),e=this.core.$stage.children().eq(this.next),f=this.core.settings.animateIn,g=this.core.settings.animateOut;this.core.current()!==this.previous&&(g&&(b=this.core.coordinates(this.previous)-this.core.coordinates(this.next),d.one(a.support.animation.end,c).css({left:b+"px"}).addClass("animated owl-animated-out").addClass(g)),f&&e.one(a.support.animation.end,c).addClass("animated owl-animated-in").addClass(f))}},e.prototype.clear=function(b){a(b.target).css({left:""}).removeClass("animated owl-animated-out owl-animated-in").removeClass(this.core.settings.animateIn).removeClass(this.core.settings.animateOut),this.core.onTransitionEnd()},e.prototype.destroy=function(){var a,b;for(a in this.handlers)this.core.$element.off(a,this.handlers[a]);for(b in Object.getOwnPropertyNames(this))"function"!=typeof this[b]&&(this[b]=null)},a.fn.owlCarousel.Constructor.Plugins.Animate=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){var e=function(b){this._core=b,this._interval=null,this._paused=!1,this._handlers={"changed.owl.carousel":a.proxy(function(a){a.namespace&&"settings"===a.property.name&&(this._core.settings.autoplay?this.play():this.stop())},this),"initialized.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.autoplay&&this.play()},this),"play.owl.autoplay":a.proxy(function(a,b,c){a.namespace&&this.play(b,c)},this),"stop.owl.autoplay":a.proxy(function(a){a.namespace&&this.stop()},this),"mouseover.owl.autoplay":a.proxy(function(){this._core.settings.autoplayHoverPause&&this._core.is("rotating")&&this.pause()},this),"mouseleave.owl.autoplay":a.proxy(function(){ +this._core.settings.autoplayHoverPause&&this._core.is("rotating")&&this.play()},this)},this._core.$element.on(this._handlers),this._core.options=a.extend({},e.Defaults,this._core.options)};e.Defaults={autoplay:!1,autoplayTimeout:5e3,autoplayHoverPause:!1,autoplaySpeed:!1},e.prototype.play=function(d,e){this._paused=!1,this._core.is("rotating")||(this._core.enter("rotating"),this._interval=b.setInterval(a.proxy(function(){this._paused||this._core.is("busy")||this._core.is("interacting")||c.hidden||this._core.next(e||this._core.settings.autoplaySpeed)},this),d||this._core.settings.autoplayTimeout))},e.prototype.stop=function(){this._core.is("rotating")&&(b.clearInterval(this._interval),this._core.leave("rotating"))},e.prototype.pause=function(){this._core.is("rotating")&&(this._paused=!0)},e.prototype.destroy=function(){var a,b;this.stop();for(a in this._handlers)this._core.$element.off(a,this._handlers[a]);for(b in Object.getOwnPropertyNames(this))"function"!=typeof this[b]&&(this[b]=null)},a.fn.owlCarousel.Constructor.Plugins.autoplay=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){"use strict";var e=function(b){this._core=b,this._initialized=!1,this._pages=[],this._controls={},this._templates=[],this.$element=this._core.$element,this._overrides={next:this._core.next,prev:this._core.prev,to:this._core.to},this._handlers={"prepared.owl.carousel":a.proxy(function(b){b.namespace&&this._core.settings.dotsData&&this._templates.push('
    '+a(b.content).find("[data-dot]").andSelf("[data-dot]").attr("data-dot")+"
    ")},this),"added.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.dotsData&&this._templates.splice(a.position,0,this._templates.pop())},this),"remove.owl.carousel":a.proxy(function(a){a.namespace&&this._core.settings.dotsData&&this._templates.splice(a.position,1)},this),"changed.owl.carousel":a.proxy(function(a){a.namespace&&"position"==a.property.name&&this.draw()},this),"initialized.owl.carousel":a.proxy(function(a){a.namespace&&!this._initialized&&(this._core.trigger("initialize",null,"navigation"),this.initialize(),this.update(),this.draw(),this._initialized=!0,this._core.trigger("initialized",null,"navigation"))},this),"refreshed.owl.carousel":a.proxy(function(a){a.namespace&&this._initialized&&(this._core.trigger("refresh",null,"navigation"),this.update(),this.draw(),this._core.trigger("refreshed",null,"navigation"))},this)},this._core.options=a.extend({},e.Defaults,this._core.options),this.$element.on(this._handlers)};e.Defaults={nav:!1,navText:["prev","next"],navSpeed:!1,navElement:"div",navContainer:!1,navContainerClass:"owl-nav",navClass:["owl-prev","owl-next"],slideBy:1,dotClass:"owl-dot",dotsClass:"owl-dots",dots:!0,dotsEach:!1,dotsData:!1,dotsSpeed:!1,dotsContainer:!1},e.prototype.initialize=function(){var b,c=this._core.settings;this._controls.$relative=(c.navContainer?a(c.navContainer):a("
    ").addClass(c.navContainerClass).appendTo(this.$element)).addClass("disabled"),this._controls.$previous=a("<"+c.navElement+">").addClass(c.navClass[0]).html(c.navText[0]).prependTo(this._controls.$relative).on("click",a.proxy(function(a){this.prev(c.navSpeed)},this)),this._controls.$next=a("<"+c.navElement+">").addClass(c.navClass[1]).html(c.navText[1]).appendTo(this._controls.$relative).on("click",a.proxy(function(a){this.next(c.navSpeed)},this)),c.dotsData||(this._templates=[a("
    ").addClass(c.dotClass).append(a("")).prop("outerHTML")]),this._controls.$absolute=(c.dotsContainer?a(c.dotsContainer):a("
    ").addClass(c.dotsClass).appendTo(this.$element)).addClass("disabled"),this._controls.$absolute.on("click","div",a.proxy(function(b){var d=a(b.target).parent().is(this._controls.$absolute)?a(b.target).index():a(b.target).parent().index();b.preventDefault(),this.to(d,c.dotsSpeed)},this));for(b in this._overrides)this._core[b]=a.proxy(this[b],this)},e.prototype.destroy=function(){var a,b,c,d;for(a in this._handlers)this.$element.off(a,this._handlers[a]);for(b in this._controls)this._controls[b].remove();for(d in this.overides)this._core[d]=this._overrides[d];for(c in Object.getOwnPropertyNames(this))"function"!=typeof this[c]&&(this[c]=null)},e.prototype.update=function(){var a,b,c,d=this._core.clones().length/2,e=d+this._core.items().length,f=this._core.maximum(!0),g=this._core.settings,h=g.center||g.autoWidth||g.dotsData?1:g.dotsEach||g.items;if("page"!==g.slideBy&&(g.slideBy=Math.min(g.slideBy,g.items)),g.dots||"page"==g.slideBy)for(this._pages=[],a=d,b=0,c=0;e>a;a++){if(b>=h||0===b){if(this._pages.push({start:Math.min(f,a-d),end:a-d+h-1}),Math.min(f,a-d)===f)break;b=0,++c}b+=this._core.mergers(this._core.relative(a))}},e.prototype.draw=function(){var b,c=this._core.settings,d=this._core.items().length<=c.items,e=this._core.relative(this._core.current()),f=c.loop||c.rewind;this._controls.$relative.toggleClass("disabled",!c.nav||d),c.nav&&(this._controls.$previous.toggleClass("disabled",!f&&e<=this._core.minimum(!0)),this._controls.$next.toggleClass("disabled",!f&&e>=this._core.maximum(!0))),this._controls.$absolute.toggleClass("disabled",!c.dots||d),c.dots&&(b=this._pages.length-this._controls.$absolute.children().length,c.dotsData&&0!==b?this._controls.$absolute.html(this._templates.join("")):b>0?this._controls.$absolute.append(new Array(b+1).join(this._templates[0])):0>b&&this._controls.$absolute.children().slice(b).remove(),this._controls.$absolute.find(".active").removeClass("active"),this._controls.$absolute.children().eq(a.inArray(this.current(),this._pages)).addClass("active"))},e.prototype.onTrigger=function(b){var c=this._core.settings;b.page={index:a.inArray(this.current(),this._pages),count:this._pages.length,size:c&&(c.center||c.autoWidth||c.dotsData?1:c.dotsEach||c.items)}},e.prototype.current=function(){var b=this._core.relative(this._core.current());return a.grep(this._pages,a.proxy(function(a,c){return a.start<=b&&a.end>=b},this)).pop()},e.prototype.getPosition=function(b){var c,d,e=this._core.settings;return"page"==e.slideBy?(c=a.inArray(this.current(),this._pages),d=this._pages.length,b?++c:--c,c=this._pages[(c%d+d)%d].start):(c=this._core.relative(this._core.current()),d=this._core.items().length,b?c+=e.slideBy:c-=e.slideBy),c},e.prototype.next=function(b){a.proxy(this._overrides.to,this._core)(this.getPosition(!0),b)},e.prototype.prev=function(b){a.proxy(this._overrides.to,this._core)(this.getPosition(!1),b)},e.prototype.to=function(b,c,d){var e;d?a.proxy(this._overrides.to,this._core)(b,c):(e=this._pages.length,a.proxy(this._overrides.to,this._core)(this._pages[(b%e+e)%e].start,c))},a.fn.owlCarousel.Constructor.Plugins.Navigation=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){"use strict";var e=function(c){this._core=c,this._hashes={},this.$element=this._core.$element,this._handlers={"initialized.owl.carousel":a.proxy(function(c){c.namespace&&"URLHash"===this._core.settings.startPosition&&a(b).trigger("hashchange.owl.navigation")},this),"prepared.owl.carousel":a.proxy(function(b){if(b.namespace){var c=a(b.content).find("[data-hash]").andSelf("[data-hash]").attr("data-hash");if(!c)return;this._hashes[c]=b.content}},this),"changed.owl.carousel":a.proxy(function(c){if(c.namespace&&"position"===c.property.name){var d=this._core.items(this._core.relative(this._core.current())),e=a.map(this._hashes,function(a,b){return a===d?b:null}).join();if(!e||b.location.hash.slice(1)===e)return;b.location.hash=e}},this)},this._core.options=a.extend({},e.Defaults,this._core.options),this.$element.on(this._handlers),a(b).on("hashchange.owl.navigation",a.proxy(function(a){var c=b.location.hash.substring(1),e=this._core.$stage.children(),f=this._hashes[c]&&e.index(this._hashes[c]);f!==d&&f!==this._core.current()&&this._core.to(this._core.relative(f),!1,!0)},this))};e.Defaults={URLhashListener:!1},e.prototype.destroy=function(){var c,d;a(b).off("hashchange.owl.navigation");for(c in this._handlers)this._core.$element.off(c,this._handlers[c]);for(d in Object.getOwnPropertyNames(this))"function"!=typeof this[d]&&(this[d]=null)},a.fn.owlCarousel.Constructor.Plugins.Hash=e}(window.Zepto||window.jQuery,window,document),function(a,b,c,d){function e(b,c){var e=!1,f=b.charAt(0).toUpperCase()+b.slice(1);return a.each((b+" "+h.join(f+" ")+f).split(" "),function(a,b){return g[b]!==d?(e=c?b:!0,!1):void 0}),e}function f(a){return e(a,!0)}var g=a("").get(0).style,h="Webkit Moz O ms".split(" "),i={transition:{end:{WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"}},animation:{end:{WebkitAnimation:"webkitAnimationEnd",MozAnimation:"animationend",OAnimation:"oAnimationEnd",animation:"animationend"}}},j={csstransforms:function(){return!!e("transform")},csstransforms3d:function(){return!!e("perspective")},csstransitions:function(){return!!e("transition")},cssanimations:function(){return!!e("animation")}};j.csstransitions()&&(a.support.transition=new String(f("transition")),a.support.transition.end=i.transition.end[a.support.transition]),j.cssanimations()&&(a.support.animation=new String(f("animation")),a.support.animation.end=i.animation.end[a.support.animation]),j.csstransforms()&&(a.support.transform=new String(f("transform")),a.support.transform3d=j.csstransforms3d())}(window.Zepto||window.jQuery,window,document); \ No newline at end of file diff --git a/static/vendor/OwlCarousel2/notes.txt b/static/vendor/OwlCarousel2/notes.txt new file mode 100644 index 000000000..eeb8f7a89 --- /dev/null +++ b/static/vendor/OwlCarousel2/notes.txt @@ -0,0 +1,12 @@ +Version: 2.0.0-beta.3 + +Source: + https://github.com/OwlCarousel2/OwlCarousel2 + +Files (there): + dist/assets/owl.carousel.css + dist/assets/owl.theme.default.css + dist/owl.carousel.js + +Roadmap (from August 22, 2016): + https://github.com/OwlCarousel2/OwlCarousel2/issues/1538 diff --git a/static/vendor/dieulot/js/instantclick.min.js b/static/vendor/dieulot/js/instantclick.min.js new file mode 100644 index 000000000..a2539d884 --- /dev/null +++ b/static/vendor/dieulot/js/instantclick.min.js @@ -0,0 +1,13 @@ +/* InstantClick 3.1.0 | (C) 2014 Alexandre Dieulot | http://instantclick.io/license */ +var InstantClick=function(d,e){function w(a){var b=a.indexOf("#");return 0>b?a:a.substr(0,b)}function z(a){for(;a&&"A"!=a.nodeName;)a=a.parentNode;return a}function A(a){var b=e.protocol+"//"+e.host;if(!(b=a.target||a.hasAttribute("download")||0!=a.href.indexOf(b+"/")||-1+new Date-500||(a=z(a.target))&&A(a)&&x(a.href)}function N(a){G>+new Date-500||(a=z(a.target))&&A(a)&&(a.addEventListener("mouseout",T),H?(O=a.href,l=setTimeout(x,H)):x(a.href))}function U(a){G=+new Date;(a=z(a.target))&&A(a)&&(D?a.removeEventListener("mousedown", +M):a.removeEventListener("mouseover",N),x(a.href))}function V(a){var b=z(a.target);!b||!A(b)||1p.readyState)&&0!=p.status){q.ready=+new Date-q.start;if(p.getResponseHeader("Content-Type").match(/\/(x|ht|xht)ml/)){var a=d.implementation.createHTMLDocument("");a.documentElement.innerHTML=p.responseText.replace(//gi,"");y=a.title; +u=a.body;var b=t("receive",r,u,y);b&&("body"in b&&(u=b.body),"title"in b&&(y=b.title));b=w(r);h[b]={body:u,title:y,scrollY:b in h?h[b].scrollY:0};for(var a=a.head.children,b=0,c,g=a.length-1;0<=g;g--)if(c=a[g],c.hasAttribute("data-instant-track")){c=c.getAttribute("href")||c.getAttribute("src")||c.innerHTML;for(var e=E.length-1;0<=e;e--)E[e]==c&&b++}b!=E.length&&(F=!0)}else F=!0;m&&(m=!1,P(r))}}function L(a){d.body.addEventListener("touchstart",U,!0);D?d.body.addEventListener("mousedown",M,!0):d.body.addEventListener("mouseover", +N,!0);d.body.addEventListener("click",V,!0);if(!a){a=d.body.getElementsByTagName("script");var b,c,g,e;i=0;for(j=a.length;i+new Date-(q.start+q.display)||(l&&(clearTimeout(l),l=!1),a||(a=O),v&&(a==r||m))||(v=!0,m=!1,r=a,F=u=!1,q={start:+new Date},t("fetch"), +p.open("GET",a),p.send())}function P(a){"display"in q||(q.display=+new Date-q.start);l||!v?l&&r&&r!=a?e.href=a:(x(a),C.start(0,!0),t("wait"),m=!0):m?e.href=a:F?e.href=r:u?(h[k].scrollY=pageYOffset,m=v=!1,K(y,u,r)):(C.start(0,!0),t("wait"),m=!0)}var I=navigator.userAgent,S=-1b;b++)a[b]+"Transform"in h.style&&(k=a[b]+"Transform");var c="transition";if(!(c in h.style))for(b=0;3>b;b++)a[b]+"Transition"in +h.style&&(c="-"+a[b].toLowerCase()+"-"+c);a=d.createElement("style");a.innerHTML="#instantclick{position:"+(Q?"absolute":"fixed")+";top:0;left:0;width:100%;pointer-events:none;z-index:2147483647;"+c+":opacity .25s .1s}.instantclick-bar{background:#29d;width:100%;margin-left:-100%;height:2px;"+c+":all .25s}";d.head.appendChild(a);Q&&(m(),addEventListener("resize",m),addEventListener("scroll",m))},start:a,done:e}}(),R="pushState"in history&&(!I.match("Android")||I.match("Chrome/"))&&"file:"!=e.protocol; +return{supported:R,init:function(){if(!k)if(R){for(var a=arguments.length-1;0<=a;a--){var b=arguments[a];!0===b?J=!0:"mousedown"==b?D=!0:"number"==typeof b&&(H=b)}k=w(e.href);h[k]={body:d.body,title:d.title,scrollY:pageYOffset};for(var b=d.head.children,c,a=b.length-1;0<=a;a--)c=b[a],c.hasAttribute("data-instant-track")&&(c=c.getAttribute("href")||c.getAttribute("src")||c.innerHTML,E.push(c));p=new XMLHttpRequest;p.addEventListener("readystatechange",W);L(!0);C.init();t("change",!0);addEventListener("popstate", +function(){var a=w(e.href);a!=k&&(a in h?(h[k].scrollY=pageYOffset,k=a,K(h[a].title,h[a].body,!1,h[a].scrollY)):e.href=e.href)})}else t("change",!0)},on:function(a,b){B[a].push(b)}}}(document,location); diff --git a/static/vendor/flesler/js/jquery.scrollTo.min.js b/static/vendor/flesler/js/jquery.scrollTo.min.js new file mode 100644 index 000000000..65a020d92 --- /dev/null +++ b/static/vendor/flesler/js/jquery.scrollTo.min.js @@ -0,0 +1,7 @@ +/** + * Copyright (c) 2007-2015 Ariel Flesler - afleslergmailcom | http://flesler.blogspot.com + * Licensed under MIT + * @author Ariel Flesler + * @version 2.1.2 + */ +;(function(f){"use strict";"function"===typeof define&&define.amd?define(["jquery"],f):"undefined"!==typeof module&&module.exports?module.exports=f(require("jquery")):f(jQuery)})(function($){"use strict";function n(a){return!a.nodeName||-1!==$.inArray(a.nodeName.toLowerCase(),["iframe","#document","html","body"])}function h(a){return $.isFunction(a)||$.isPlainObject(a)?a:{top:a,left:a}}var p=$.scrollTo=function(a,d,b){return $(window).scrollTo(a,d,b)};p.defaults={axis:"xy",duration:0,limit:!0};$.fn.scrollTo=function(a,d,b){"object"=== typeof d&&(b=d,d=0);"function"===typeof b&&(b={onAfter:b});"max"===a&&(a=9E9);b=$.extend({},p.defaults,b);d=d||b.duration;var u=b.queue&&1=f[g]?0:Math.min(f[g],n));!a&&1li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"} diff --git a/examples/blog/static/fonts/FontAwesome.otf b/static/vendor/font-awesome/fonts/FontAwesome.otf similarity index 100% rename from examples/blog/static/fonts/FontAwesome.otf rename to static/vendor/font-awesome/fonts/FontAwesome.otf diff --git a/examples/blog/static/fonts/fontawesome-webfont.eot b/static/vendor/font-awesome/fonts/fontawesome-webfont.eot similarity index 100% rename from examples/blog/static/fonts/fontawesome-webfont.eot rename to static/vendor/font-awesome/fonts/fontawesome-webfont.eot diff --git a/examples/blog/static/fonts/fontawesome-webfont.svg b/static/vendor/font-awesome/fonts/fontawesome-webfont.svg similarity index 100% rename from examples/blog/static/fonts/fontawesome-webfont.svg rename to static/vendor/font-awesome/fonts/fontawesome-webfont.svg diff --git a/examples/blog/static/fonts/fontawesome-webfont.ttf b/static/vendor/font-awesome/fonts/fontawesome-webfont.ttf similarity index 100% rename from examples/blog/static/fonts/fontawesome-webfont.ttf rename to static/vendor/font-awesome/fonts/fontawesome-webfont.ttf diff --git a/examples/blog/static/fonts/fontawesome-webfont.woff b/static/vendor/font-awesome/fonts/fontawesome-webfont.woff similarity index 100% rename from examples/blog/static/fonts/fontawesome-webfont.woff rename to static/vendor/font-awesome/fonts/fontawesome-webfont.woff diff --git a/examples/blog/static/fonts/fontawesome-webfont.woff2 b/static/vendor/font-awesome/fonts/fontawesome-webfont.woff2 similarity index 100% rename from examples/blog/static/fonts/fontawesome-webfont.woff2 rename to static/vendor/font-awesome/fonts/fontawesome-webfont.woff2 diff --git a/static/vendor/highlightjs/css/monokai-sublime.css b/static/vendor/highlightjs/css/monokai-sublime.css new file mode 100644 index 000000000..2864170da --- /dev/null +++ b/static/vendor/highlightjs/css/monokai-sublime.css @@ -0,0 +1,83 @@ +/* + +Monokai Sublime style. Derived from Monokai by noformnocontent http://nn.mit-license.org/ + +*/ + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + background: #23241f; +} + +.hljs, +.hljs-tag, +.hljs-subst { + color: #f8f8f2; +} + +.hljs-strong, +.hljs-emphasis { + color: #a8a8a2; +} + +.hljs-bullet, +.hljs-quote, +.hljs-number, +.hljs-regexp, +.hljs-literal, +.hljs-link { + color: #ae81ff; +} + +.hljs-code, +.hljs-title, +.hljs-section, +.hljs-selector-class { + color: #a6e22e; +} + +.hljs-strong { + font-weight: bold; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-name, +.hljs-attr { + color: #f92672; +} + +.hljs-symbol, +.hljs-attribute { + color: #66d9ef; +} + +.hljs-params, +.hljs-class .hljs-title { + color: #f8f8f2; +} + +.hljs-string, +.hljs-type, +.hljs-built_in, +.hljs-builtin-name, +.hljs-selector-id, +.hljs-selector-attr, +.hljs-selector-pseudo, +.hljs-addition, +.hljs-variable, +.hljs-template-variable { + color: #e6db74; +} + +.hljs-comment, +.hljs-deletion, +.hljs-meta { + color: #75715e; +} diff --git a/static/vendor/highlightjs/js/highlight.pack.js b/static/vendor/highlightjs/js/highlight.pack.js new file mode 100644 index 000000000..ab4712ecf --- /dev/null +++ b/static/vendor/highlightjs/js/highlight.pack.js @@ -0,0 +1,3 @@ +/*! highlight.js v9.0.0 | BSD3 License | git.io/hljslicense */ +!function(e){"undefined"!=typeof exports?e(exports):(self.hljs=e({}),"function"==typeof define&&define.amd&&define("hljs",[],function(){return self.hljs}))}(function(e){function t(e){return e.replace(/&/gm,"&").replace(//gm,">")}function r(e){return e.nodeName.toLowerCase()}function n(e,t){var r=e&&e.exec(t);return r&&0==r.index}function a(e){return/^(no-?highlight|plain|text)$/i.test(e)}function i(e){var t,r,n,i=e.className+" ";if(i+=e.parentNode?e.parentNode.className:"",r=/\blang(?:uage)?-([\w-]+)\b/i.exec(i))return w(r[1])?r[1]:"no-highlight";for(i=i.split(/\s+/),t=0,n=i.length;n>t;t++)if(w(i[t])||a(i[t]))return i[t]}function o(e,t){var r,n={};for(r in e)n[r]=e[r];if(t)for(r in t)n[r]=t[r];return n}function c(e){var t=[];return function n(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3==i.nodeType?a+=i.nodeValue.length:1==i.nodeType&&(t.push({event:"start",offset:a,node:i}),a=n(i,a),r(i).match(/br|hr|img|input/)||t.push({event:"stop",offset:a,node:i}));return a}(e,0),t}function s(e,n,a){function i(){return e.length&&n.length?e[0].offset!=n[0].offset?e[0].offset"}function c(e){b+=""}function s(e){("start"==e.event?o:c)(e.node)}for(var l=0,b="",u=[];e.length||n.length;){var d=i();if(b+=t(a.substr(l,d[0].offset-l)),l=d[0].offset,d==e){u.reverse().forEach(c);do s(d.splice(0,1)[0]),d=i();while(d==e&&d.length&&d[0].offset==l);u.reverse().forEach(o)}else"start"==d[0].event?u.push(d[0].node):u.pop(),s(d.splice(0,1)[0])}return b+t(a.substr(l))}function l(e){function t(e){return e&&e.source||e}function r(r,n){return new RegExp(t(r),"m"+(e.cI?"i":"")+(n?"g":""))}function n(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var c={},s=function(t,r){e.cI&&(r=r.toLowerCase()),r.split(" ").forEach(function(e){var r=e.split("|");c[r[0]]=[t,r[1]?Number(r[1]):1]})};"string"==typeof a.k?s("keyword",a.k):Object.keys(a.k).forEach(function(e){s(e,a.k[e])}),a.k=c}a.lR=r(a.l||/\b\w+\b/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=r(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=r(a.e)),a.tE=t(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=r(a.i)),void 0===a.r&&(a.r=1),a.c||(a.c=[]);var l=[];a.c.forEach(function(e){e.v?e.v.forEach(function(t){l.push(o(e,t))}):l.push("self"==e?a:e)}),a.c=l,a.c.forEach(function(e){n(e,a)}),a.starts&&n(a.starts,i);var b=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(t).filter(Boolean);a.t=b.length?r(b.join("|"),!0):{exec:function(){return null}}}}n(e)}function b(e,r,a,i){function o(e,t){for(var r=0;r";return i+=e+'">',i+t+o}function f(){if(!x.k)return t(S);var e="",r=0;x.lR.lastIndex=0;for(var n=x.lR.exec(S);n;){e+=t(S.substr(r,n.index-r));var a=d(x,n);a?(E+=a[1],e+=p(a[0],t(n[0]))):e+=t(n[0]),r=x.lR.lastIndex,n=x.lR.exec(S)}return e+t(S.substr(r))}function m(){var e="string"==typeof x.sL;if(e&&!k[x.sL])return t(S);var r=e?b(x.sL,S,!0,_[x.sL]):u(S,x.sL.length?x.sL:void 0);return x.r>0&&(E+=r.r),e&&(_[x.sL]=r.top),p(r.language,r.value,!1,!0)}function g(){return void 0!==x.sL?m():f()}function h(e,r){var n=e.cN?p(e.cN,"",!0):"";e.rB?(M+=n,S=""):e.eB?(M+=t(r)+n,S=""):(M+=n,S=r),x=Object.create(e,{parent:{value:x}})}function N(e,r){if(S+=e,void 0===r)return M+=g(),0;var n=o(r,x);if(n)return M+=g(),h(n,r),n.rB?0:r.length;var a=c(x,r);if(a){var i=x;i.rE||i.eE||(S+=r),M+=g();do x.cN&&(M+=""),E+=x.r,x=x.parent;while(x!=a.parent);return i.eE&&(M+=t(r)),S="",a.starts&&h(a.starts,""),i.rE?0:r.length}if(s(r,x))throw new Error('Illegal lexeme "'+r+'" for mode "'+(x.cN||"")+'"');return S+=r,r.length||1}var v=w(e);if(!v)throw new Error('Unknown language: "'+e+'"');l(v);var C,x=i||v,_={},M="";for(C=x;C!=v;C=C.parent)C.cN&&(M=p(C.cN,"",!0)+M);var S="",E=0;try{for(var $,A,z=0;;){if(x.t.lastIndex=z,$=x.t.exec(r),!$)break;A=N(r.substr(z,$.index-z),$[0]),z=$.index+A}for(N(r.substr(z)),C=x;C.parent;C=C.parent)C.cN&&(M+="");return{r:E,value:M,language:e,top:x}}catch(B){if(-1!=B.message.indexOf("Illegal"))return{r:0,value:t(r)};throw B}}function u(e,r){r=r||y.languages||Object.keys(k);var n={r:0,value:t(e)},a=n;return r.forEach(function(t){if(w(t)){var r=b(t,e,!1);r.language=t,r.r>a.r&&(a=r),r.r>n.r&&(a=n,n=r)}}),a.language&&(n.second_best=a),n}function d(e){return y.tabReplace&&(e=e.replace(/^((<[^>]+>|\t)+)/gm,function(e,t){return t.replace(/\t/g,y.tabReplace)})),y.useBR&&(e=e.replace(/\n/g,"
    ")),e}function p(e,t,r){var n=t?C[t]:r,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(n)&&a.push(n),a.join(" ").trim()}function f(e){var t=i(e);if(!a(t)){var r;y.useBR?(r=document.createElementNS("http://www.w3.org/1999/xhtml","div"),r.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):r=e;var n=r.textContent,o=t?b(t,n,!0):u(n),l=c(r);if(l.length){var f=document.createElementNS("http://www.w3.org/1999/xhtml","div");f.innerHTML=o.value,o.value=s(l,c(f),n)}o.value=d(o.value),e.innerHTML=o.value,e.className=p(e.className,t,o.language),e.result={language:o.language,re:o.r},o.second_best&&(e.second_best={language:o.second_best.language,re:o.second_best.r})}}function m(e){y=o(y,e)}function g(){if(!g.called){g.called=!0;var e=document.querySelectorAll("pre code");Array.prototype.forEach.call(e,f)}}function h(){addEventListener("DOMContentLoaded",g,!1),addEventListener("load",g,!1)}function N(t,r){var n=k[t]=r(e);n.aliases&&n.aliases.forEach(function(e){C[e]=t})}function v(){return Object.keys(k)}function w(e){return e=(e||"").toLowerCase(),k[e]||k[C[e]]}var y={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},k={},C={};return e.highlight=b,e.highlightAuto=u,e.fixMarkup=d,e.highlightBlock=f,e.configure=m,e.initHighlighting=g,e.initHighlightingOnLoad=h,e.registerLanguage=N,e.listLanguages=v,e.getLanguage=w,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|like)\b/},e.C=function(t,r,n){var a=e.inherit({cN:"comment",b:t,e:r,c:[]},n||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.registerLanguage("apache",function(e){var t={cN:"number",b:"[\\$%]\\d+"};return{aliases:["apacheconf"],cI:!0,c:[e.HCM,{cN:"section",b:""},{cN:"attribute",b:/\w+/,r:0,k:{nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{e:/$/,r:0,k:{literal:"on off all"},c:[{cN:"meta",b:"\\s\\[",e:"\\]$"},{cN:"variable",b:"[\\$%]\\{",e:"\\}",c:["self",t]},t,e.QSM]}}],i:/\S/}}),e.registerLanguage("xml",function(e){var t="[A-Za-z0-9\\._:-]+",r={b:/<\?(php)?(?!\w)/,e:/\?>/,sL:"php"},n={eW:!0,i:/]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xsl","plist"],cI:!0,c:[{cN:"meta",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},e.C("",{r:10}),{b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"tag",b:"|$)",e:">",k:{name:"style"},c:[n],starts:{e:"",rE:!0,sL:["css","xml"]}},{cN:"tag",b:"|$)",e:">",k:{name:"script"},c:[n],starts:{e:"",rE:!0,sL:["actionscript","javascript","handlebars","xml"]}},r,{cN:"meta",b:/<\?\w+/,e:/\?>/,r:10},{cN:"tag",b:"",c:[{cN:"name",b:/[^\/><\s]+/,r:0},n]}]}}),e.registerLanguage("asciidoc",function(e){return{aliases:["adoc"],c:[e.C("^/{4,}\\n","\\n/{4,}$",{r:10}),e.C("^//","$",{r:0}),{cN:"title",b:"^\\.\\w.*$"},{b:"^[=\\*]{4,}\\n",e:"\\n^[=\\*]{4,}$",r:10},{cN:"section",r:10,v:[{b:"^(={1,5}) .+?( \\1)?$"},{b:"^[^\\[\\]\\n]+?\\n[=\\-~\\^\\+]{2,}$"}]},{cN:"meta",b:"^:.+?:",e:"\\s",eE:!0,r:10},{cN:"meta",b:"^\\[.+?\\]$",r:0},{cN:"quote",b:"^_{4,}\\n",e:"\\n_{4,}$",r:10},{cN:"code",b:"^[\\-\\.]{4,}\\n",e:"\\n[\\-\\.]{4,}$",r:10},{b:"^\\+{4,}\\n",e:"\\n\\+{4,}$",c:[{b:"<",e:">",sL:"xml",r:0}],r:10},{cN:"bullet",b:"^(\\*+|\\-+|\\.+|[^\\n]+?::)\\s+"},{cN:"symbol",b:"^(NOTE|TIP|IMPORTANT|WARNING|CAUTION):\\s+",r:10},{cN:"strong",b:"\\B\\*(?![\\*\\s])",e:"(\\n{2}|\\*)",c:[{b:"\\\\*\\w",r:0}]},{cN:"emphasis",b:"\\B'(?!['\\s])",e:"(\\n{2}|')",c:[{b:"\\\\'\\w",r:0}],r:0},{cN:"emphasis",b:"_(?![_\\s])",e:"(\\n{2}|_)",r:0},{cN:"string",v:[{b:"``.+?''"},{b:"`.+?'"}]},{cN:"code",b:"(`.+?`|\\+.+?\\+)",r:0},{cN:"code",b:"^[ \\t]",e:"$",r:0},{b:"^'{3,}[ \\t]*$",r:10},{b:"(link:)?(http|https|ftp|file|irc|image:?):\\S+\\[.*?\\]",rB:!0,c:[{b:"(link|image:?):",r:0},{cN:"link",b:"\\w",e:"[^\\[]+",r:0},{cN:"string",b:"\\[",e:"\\]",eB:!0,eE:!0,r:0}],r:10}]}}),e.registerLanguage("bash",function(e){var t={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)}/}]},r={cN:"string",b:/"/,e:/"/,c:[e.BE,t,{cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]}]},n={cN:"string",b:/'/,e:/'/};return{aliases:["sh","zsh"],l:/-?[a-z\.]+/,k:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"meta",b:/^#![^\n]+sh\s*$/,r:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:!0,c:[e.inherit(e.TM,{b:/\w[\w\d_]*/})],r:0},e.HCM,r,n,t]}}),e.registerLanguage("coffeescript",function(e){var t={keyword:"in if for while finally new do return else break catch instanceof throw try this switch continue typeof delete debugger super then unless until loop of by when and or is isnt not",literal:"true false null undefined yes no on off",built_in:"npm require console print module global window document"},r="[A-Za-z$_][0-9A-Za-z$_]*",n={cN:"subst",b:/#\{/,e:/}/,k:t},a=[e.BNM,e.inherit(e.CNM,{starts:{e:"(\\s*/)?",r:0}}),{cN:"string",v:[{b:/'''/,e:/'''/,c:[e.BE]},{b:/'/,e:/'/,c:[e.BE]},{b:/"""/,e:/"""/,c:[e.BE,n]},{b:/"/,e:/"/,c:[e.BE,n]}]},{cN:"regexp",v:[{b:"///",e:"///",c:[n,e.HCM]},{b:"//[gim]*",r:0},{b:/\/(?![ *])(\\\/|.)*?\/[gim]*(?=\W|$)/}]},{b:"@"+r},{b:"`",e:"`",eB:!0,eE:!0,sL:"javascript"}];n.c=a;var i=e.inherit(e.TM,{b:r}),o="(\\(.*\\))?\\s*\\B[-=]>",c={cN:"params",b:"\\([^\\(]",rB:!0,c:[{b:/\(/,e:/\)/,k:t,c:["self"].concat(a)}]};return{aliases:["coffee","cson","iced"],k:t,i:/\/\*/,c:a.concat([e.C("###","###"),e.HCM,{cN:"function",b:"^\\s*"+r+"\\s*=\\s*"+o,e:"[-=]>",rB:!0,c:[i,c]},{b:/[:\(,=]\s*/,r:0,c:[{cN:"function",b:o,e:"[-=]>",rB:!0,c:[c]}]},{cN:"class",bK:"class",e:"$",i:/[:="\[\]]/,c:[{bK:"extends",eW:!0,i:/[:="\[\]]/,c:[i]},i]},{b:r+":",e:":",rB:!0,rE:!0,r:0}])}}),e.registerLanguage("css",function(e){var t="[a-zA-Z-][a-zA-Z0-9_-]*",r={b:/[A-Z\_\.\-]+\s*:/,rB:!0,e:";",eW:!0,c:[{cN:"attribute",b:/\S/,e:":",eE:!0,starts:{eW:!0,eE:!0,c:[{b:/[\w-]+\s*\(/,rB:!0,c:[{cN:"built_in",b:/[\w-]+/}]},e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"number",b:"#[0-9A-Fa-f]+"},{cN:"meta",b:"!important"}]}}]};return{cI:!0,i:/[=\/|'\$]/,c:[e.CBCM,{cN:"selector-id",b:/#[A-Za-z0-9_-]+/},{cN:"selector-class",b:/\.[A-Za-z0-9_-]+/},{cN:"selector-attr",b:/\[/,e:/\]/,i:"$"},{cN:"selector-pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"']+/},{b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{b:"@",e:"[{;]",c:[{cN:"keyword",b:/\S+/},{b:/\s/,eW:!0,eE:!0,r:0,c:[e.ASM,e.QSM,e.CSSNM]}]},{cN:"selector-tag",b:t,r:0},{b:"{",e:"}",i:/\S/,c:[e.CBCM,r]}]}}),e.registerLanguage("diff",function(e){return{aliases:["patch"],c:[{cN:"meta",r:10,v:[{b:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{b:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{b:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{cN:"comment",v:[{b:/Index: /,e:/$/},{b:/=====/,e:/=====$/},{b:/^\-\-\-/,e:/$/},{b:/^\*{3} /,e:/$/},{b:/^\+\+\+/,e:/$/},{b:/\*{5}/,e:/\*{5}$/}]},{cN:"addition",b:"^\\+",e:"$"},{cN:"deletion",b:"^\\-",e:"$"},{cN:"addition",b:"^\\!",e:"$"}]}}),e.registerLanguage("django",function(e){var t={b:/\|[A-Za-z]+:?/,k:{name:"truncatewords removetags linebreaksbr yesno get_digit timesince random striptags filesizeformat escape linebreaks length_is ljust rjust cut urlize fix_ampersands title floatformat capfirst pprint divisibleby add make_list unordered_list urlencode timeuntil urlizetrunc wordcount stringformat linenumbers slice date dictsort dictsortreversed default_if_none pluralize lower join center default truncatewords_html upper length phone2numeric wordwrap time addslashes slugify first escapejs force_escape iriencode last safe safeseq truncatechars localize unlocalize localtime utc timezone"},c:[e.QSM,e.ASM]};return{aliases:["jinja"],cI:!0,sL:"xml",c:[e.C(/\{%\s*comment\s*%}/,/\{%\s*endcomment\s*%}/),e.C(/\{#/,/#}/),{cN:"template-tag",b:/\{%/,e:/%}/,c:[{cN:"name",b:/\w+/,k:{name:"comment endcomment load templatetag ifchanged endifchanged if endif firstof for endfor ifnotequal endifnotequal widthratio extends include spaceless endspaceless regroup ifequal endifequal ssi now with cycle url filter endfilter debug block endblock else autoescape endautoescape csrf_token empty elif endwith static trans blocktrans endblocktrans get_static_prefix get_media_prefix plural get_current_language language get_available_languages get_current_language_bidi get_language_info get_language_info_list localize endlocalize localtime endlocaltime timezone endtimezone get_current_timezone verbatim"},starts:{eW:!0,k:"in by as",c:[t],r:0}}]},{cN:"template-variable",b:/\{\{/,e:/}}/,c:[t]}]}}),e.registerLanguage("dockerfile",function(e){return{aliases:["docker"],cI:!0,k:"from maintainer cmd expose add copy entrypoint volume user workdir onbuild run env label",c:[e.HCM,{k:"run cmd entrypoint volume add copy workdir onbuild label",b:/^ *(onbuild +)?(run|cmd|entrypoint|volume|add|copy|workdir|label) +/,starts:{e:/[^\\]\n/,sL:"bash"}},{k:"from maintainer expose env user onbuild",b:/^ *(onbuild +)?(from|maintainer|expose|env|user|onbuild) +/,e:/[^\\]\n/,c:[e.ASM,e.QSM,e.NM,e.HCM]}]}}),e.registerLanguage("dos",function(e){var t=e.C(/@?rem\b/,/$/,{r:10}),r={cN:"symbol",b:"^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)",r:0};return{aliases:["bat","cmd"],cI:!0,i:/\/\*/,k:{keyword:"if else goto for in do call exit not exist errorlevel defined equ neq lss leq gtr geq",built_in:"prn nul lpt3 lpt2 lpt1 con com4 com3 com2 com1 aux shift cd dir echo setlocal endlocal set pause copy append assoc at attrib break cacls cd chcp chdir chkdsk chkntfs cls cmd color comp compact convert date dir diskcomp diskcopy doskey erase fs find findstr format ftype graftabl help keyb label md mkdir mode more move path pause print popd pushd promt rd recover rem rename replace restore rmdir shiftsort start subst time title tree type ver verify vol ping net ipconfig taskkill xcopy ren del"},c:[{cN:"variable",b:/%%[^ ]|%[^ ]+?%|![^ ]+?!/},{cN:"function",b:r.b,e:"goto:eof",c:[e.inherit(e.TM,{b:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),t]},{cN:"number",b:"\\b\\d+",r:0},t]}}),e.registerLanguage("go",function(e){var t={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",literal:"true false iota nil",built_in:"append cap close complex copy imag len make new panic print println real recover delete"};return{aliases:["golang"],k:t,i:">|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",r="and false then defined module in return redo if BEGIN retry end for true self when next until do begin unless END rescue nil else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",n={cN:"doctag",b:"@[A-Za-z]+"},a={b:"#<",e:">"},i=[e.C("#","$",{c:[n]}),e.C("^\\=begin","^\\=end",{c:[n],r:10}),e.C("^__END__","\\n$")],o={cN:"subst",b:"#\\{",e:"}",k:r},c={cN:"string",c:[e.BE,o],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/`/,e:/`/},{b:"%[qQwWx]?\\(",e:"\\)"},{b:"%[qQwWx]?\\[",e:"\\]"},{b:"%[qQwWx]?{",e:"}"},{b:"%[qQwWx]?<",e:">"},{b:"%[qQwWx]?/",e:"/"},{b:"%[qQwWx]?%",e:"%"},{b:"%[qQwWx]?-",e:"-"},{b:"%[qQwWx]?\\|",e:"\\|"},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/}]},s={cN:"params",b:"\\(",e:"\\)",endsParent:!0,k:r},l=[c,a,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[e.inherit(e.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{b:"<\\s*",c:[{b:"("+e.IR+"::)?"+e.IR}]}].concat(i)},{cN:"function",bK:"def",e:"$|;",c:[e.inherit(e.TM,{b:t}),s].concat(i)},{cN:"symbol",b:e.UIR+"(\\!|\\?)?:",r:0},{cN:"symbol",b:":",c:[c,{b:t}],r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{b:"("+e.RSR+")\\s*",c:[a,{cN:"regexp",c:[e.BE,o],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}].concat(i),r:0}].concat(i);o.c=l,s.c=l;var b="[>?]>",u="[\\w#]+\\(\\w+\\):\\d+:\\d+>",d="(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>",p=[{b:/^\s*=>/,starts:{e:"$",c:l}},{cN:"meta",b:"^("+b+"|"+u+"|"+d+")",starts:{e:"$",c:l}}];return{aliases:["rb","gemspec","podspec","thor","irb"],k:r,i:/\/\*/,c:i.concat(p).concat(l)}}),e.registerLanguage("haml",function(e){return{cI:!0,c:[{cN:"meta",b:"^!!!( (5|1\\.1|Strict|Frameset|Basic|Mobile|RDFa|XML\\b.*))?$",r:10},e.C("^\\s*(!=#|=#|-#|/).*$",!1,{r:0}),{b:"^\\s*(-|=|!=)(?!#)",starts:{e:"\\n",sL:"ruby"}},{cN:"tag",b:"^\\s*%",c:[{cN:"selector-tag",b:"\\w+"},{cN:"selector-id",b:"#[\\w-]+"},{cN:"selector-class",b:"\\.[\\w-]+"},{b:"{\\s*",e:"\\s*}",c:[{b:":\\w+\\s*=>",e:",\\s+",rB:!0,eW:!0,c:[{cN:"attr",b:":\\w+"},e.ASM,e.QSM,{b:"\\w+",r:0}]}]},{b:"\\(\\s*",e:"\\s*\\)",eE:!0,c:[{b:"\\w+\\s*=",e:"\\s+",rB:!0,eW:!0,c:[{cN:"attr",b:"\\w+",r:0},e.ASM,e.QSM,{b:"\\w+",r:0}]}]}]},{b:"^\\s*[=~]\\s*"},{b:"#{",starts:{e:"}",sL:"ruby"}}]}}),e.registerLanguage("handlebars",function(e){var t={"builtin-name":"each in with if else unless bindattr action collection debugger log outlet template unbound view yield"};return{aliases:["hbs","html.hbs","html.handlebars"],cI:!0,sL:"xml",c:[e.C("{{!(--)?","(--)?}}"),{cN:"template-tag",b:/\{\{[#\/]/,e:/\}\}/,c:[{cN:"name",b:/[a-zA-Z\.-]+/,k:t,starts:{eW:!0,r:0,c:[e.QSM]}}]},{cN:"template-variable",b:/\{\{/,e:/\}\}/,k:t}]}}),e.registerLanguage("http",function(e){var t="HTTP/[0-9\\.]+";return{aliases:["https"],i:"\\S",c:[{b:"^"+t,e:"$",c:[{cN:"number",b:"\\b\\d{3}\\b"}]},{b:"^[A-Z]+ (.*?) "+t+"$",rB:!0,e:"$",c:[{cN:"string",b:" ",e:" ",eB:!0,eE:!0},{b:t},{cN:"keyword",b:"[A-Z]+"}]},{cN:"attribute",b:"^\\w",e:": ",eE:!0,i:"\\n|\\s|=",starts:{e:"$",r:0}},{b:"\\n\\n",starts:{sL:[],eW:!0}}]}}),e.registerLanguage("ini",function(e){var t={cN:"string",c:[e.BE],v:[{b:"'''",e:"'''",r:10},{b:'"""',e:'"""',r:10},{b:'"',e:'"'},{b:"'",e:"'"}]};return{aliases:["toml"],cI:!0,i:/\S/,c:[e.C(";","$"),e.HCM,{cN:"section",b:/^\s*\[+/,e:/\]+/},{b:/^[a-z0-9\[\]_-]+\s*=\s*/,e:"$",rB:!0,c:[{cN:"attr",b:/[a-z0-9\[\]_-]+/},{b:/=/,eW:!0,r:0,c:[{cN:"literal",b:/\bon|off|true|false|yes|no\b/},{cN:"variable",v:[{b:/\$[\w\d"][\w\d_]*/},{b:/\$\{(.*?)}/}]},t,{cN:"number",b:/([\+\-]+)?[\d]+_[\d_]+/},e.NM]}]}]}}),e.registerLanguage("javascript",function(e){return{aliases:["js"],k:{keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},c:[{cN:"meta",r:10,b:/^\s*['"]use (strict|asm)['"]/},e.ASM,e.QSM,{cN:"string",b:"`",e:"`",c:[e.BE,{cN:"subst",b:"\\$\\{",e:"\\}"}]},e.CLCM,e.CBCM,{cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{b:/\s*[);\]]/,r:0,sL:"xml"}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:[e.CLCM,e.CBCM]}],i:/\[|%/},{b:/\$[(.]/},{b:"\\."+e.IR,r:0},{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]},{bK:"constructor",e:/\{/,eE:!0}],i:/#/}}),e.registerLanguage("json",function(e){var t={literal:"true false null"},r=[e.QSM,e.CNM],n={e:",",eW:!0,eE:!0,c:r,k:t},a={b:"{",e:"}",c:[{cN:"attr",b:'\\s*"',e:'"\\s*:\\s*',eB:!0,eE:!0,c:[e.BE],i:"\\n",starts:n}],i:"\\S"},i={b:"\\[",e:"\\]",c:[e.inherit(n)],i:"\\S"};return r.splice(r.length,0,a,i),{c:r,k:t,i:"\\S"}}),e.registerLanguage("less",function(e){var t="[\\w-]+",r="("+t+"|@{"+t+"})",n=[],a=[],i=function(e){return{cN:"string",b:"~?"+e+".*?"+e}},o=function(e,t,r){return{cN:e,b:t,r:r}},c={b:"\\(",e:"\\)",c:a,r:0};a.push(e.CLCM,e.CBCM,i("'"),i('"'),e.CSSNM,{b:"(url|data-uri)\\(",starts:{cN:"string",e:"[\\)\\n]",eE:!0}},o("number","#[0-9A-Fa-f]+\\b"),c,o("variable","@@?"+t,10),o("variable","@{"+t+"}"),o("built_in","~?`[^`]*?`"),{cN:"attribute",b:t+"\\s*:",e:":",rB:!0,eE:!0},{cN:"meta",b:"!important"});var s=a.concat({b:"{",e:"}",c:n}),l={bK:"when",eW:!0,c:[{bK:"and not"}].concat(a)},b={cN:"attribute",b:r,e:":",eE:!0,c:[e.CLCM,e.CBCM],i:/\S/,starts:{e:"[;}]",rE:!0,c:a,i:"[<=$]"}},u={cN:"keyword",b:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",starts:{e:"[;{}]",rE:!0,c:a,r:0}},d={cN:"variable",v:[{b:"@"+t+"\\s*:",r:15},{b:"@"+t}],starts:{e:"[;}]",rE:!0,c:s}},p={v:[{b:"[\\.#:&\\[]",e:"[;{}]"},{b:r+"[^;]*{",e:"{"}],rB:!0,rE:!0,i:"[<='$\"]",c:[e.CLCM,e.CBCM,l,o("keyword","all\\b"),o("variable","@{"+t+"}"),o("selector-tag",r+"%?",0),o("selector-id","#"+r),o("selector-class","\\."+r,0),o("selector-tag","&",0),{cN:"selector-attr",b:"\\[",e:"\\]"},{b:"\\(",e:"\\)",c:s},{b:"!important"}]};return n.push(e.CLCM,e.CBCM,u,d,p,b),{cI:!0,i:"[=>'/<($\"]",c:n}}),e.registerLanguage("makefile",function(e){var t={cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]};return{aliases:["mk","mak"],c:[e.HCM,{b:/^\w+\s*\W*=/,rB:!0,r:0,starts:{e:/\s*\W*=/,eE:!0,starts:{e:/$/,r:0,c:[t]}}},{cN:"section",b:/^[\w]+:\s*$/},{cN:"meta",b:/^\.PHONY:/,e:/$/,k:{"meta-keyword":".PHONY"},l:/[\.\w]+/},{b:/^\t+/,e:/$/,r:0,c:[e.QSM,t]}]}}),e.registerLanguage("markdown",function(e){return{aliases:["md","mkdown","mkd"],c:[{cN:"section",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",r:0},{cN:"bullet",b:"^([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",r:0}]},{cN:"quote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"`.+?`"},{b:"^( {4}| )",e:"$",r:0}]},{b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].*?[\\)\\]]",rB:!0,c:[{cN:"string",b:"\\[",e:"\\]",eB:!0,rE:!0,r:0},{cN:"link",b:"\\]\\(",e:"\\)",eB:!0,eE:!0},{cN:"symbol",b:"\\]\\[",e:"\\]",eB:!0,eE:!0}],r:10},{b:"^\\[.+\\]:",rB:!0,c:[{cN:"symbol",b:"\\[",e:"\\]:",eB:!0,eE:!0,starts:{cN:"link",e:"$"}}]}]}}),e.registerLanguage("nginx",function(e){var t={cN:"variable",v:[{b:/\$\d+/},{b:/\$\{/,e:/}/},{b:"[\\$\\@]"+e.UIR}]},r={eW:!0,l:"[a-z/_]+",k:{literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},r:0,i:"=>",c:[e.HCM,{cN:"string",c:[e.BE,t],v:[{b:/"/,e:/"/},{b:/'/,e:/'/}]},{b:"([a-z]+):/",e:"\\s",eW:!0,eE:!0,c:[t]},{cN:"regexp",c:[e.BE,t],v:[{b:"\\s\\^",e:"\\s|{|;",rE:!0},{b:"~\\*?\\s+",e:"\\s|{|;",rE:!0},{b:"\\*(\\.[a-z\\-]+)+"},{b:"([a-z\\-]+\\.)+\\*"}]},{cN:"number",b:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{cN:"number",b:"\\b\\d+[kKmMgGdshdwy]*\\b",r:0},t]};return{aliases:["nginxconf"],c:[e.HCM,{b:e.UIR+"\\s+{",rB:!0,e:"{",c:[{cN:"section",b:e.UIR}],r:0},{b:e.UIR+"\\s",e:";|{",rB:!0,c:[{cN:"attribute",b:e.UIR,starts:r}],r:0}],i:"[^\\s\\}]"}}),e.registerLanguage("php",function(e){var t={b:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},r={cN:"meta",b:/<\?(php)?|\?>/},n={cN:"string",c:[e.BE,r],v:[{b:'b"',e:'"'},{b:"b'",e:"'"},e.inherit(e.ASM,{i:null}),e.inherit(e.QSM,{i:null})]},a={v:[e.BNM,e.CNM]};return{aliases:["php3","php4","php5","php6"],cI:!0,k:"and include_once list abstract global private echo interface as static endswitch array null if endwhile or const for endforeach self var while isset public protected exit foreach throw elseif include __FILE__ empty require_once do xor return parent clone use __CLASS__ __LINE__ else break print eval new catch __METHOD__ case exception default die require __FUNCTION__ enddeclare final try switch continue endfor endif declare unset true false trait goto instanceof insteadof __DIR__ __NAMESPACE__ yield finally",c:[e.CLCM,e.HCM,e.C("/\\*","\\*/",{c:[{cN:"doctag",b:"@[A-Za-z]+"},r]}),e.C("__halt_compiler.+?;",!1,{eW:!0,k:"__halt_compiler",l:e.UIR}),{cN:"string",b:/<<<['"]?\w+['"]?$/,e:/^\w+;?$/,c:[e.BE,{cN:"subst",v:[{b:/\$\w+/},{b:/\{\$/,e:/\}/}]}]},r,t,{b:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{cN:"function",bK:"function",e:/[;{]/,eE:!0,i:"\\$|\\[|%",c:[e.UTM,{cN:"params",b:"\\(",e:"\\)",c:["self",t,e.CBCM,n,a]}]},{cN:"class",bK:"class interface",e:"{",eE:!0,i:/[:\(\$"]/,c:[{bK:"extends implements"},e.UTM]},{bK:"namespace",e:";",i:/[\.']/,c:[e.UTM]},{bK:"use",e:";",c:[e.UTM]},{b:"=>"},n,a]}}),e.registerLanguage("powershell",function(e){var t={b:"`[\\s\\S]",r:0},r={cN:"variable",v:[{b:/\$[\w\d][\w\d_:]*/}]},n={cN:"literal",b:/\$(null|true|false)\b/},a={cN:"string",b:/"/,e:/"/,c:[t,r,{cN:"variable",b:/\$[A-z]/,e:/[^A-z]/}]},i={cN:"string",b:/'/,e:/'/};return{aliases:["ps"],l:/-?[A-z\.\-]+/,cI:!0,k:{keyword:"if else foreach return function do while until elseif begin for trap data dynamicparam end break throw param continue finally in switch exit filter try process catch",built_in:"Add-Content Add-History Add-Member Add-PSSnapin Clear-Content Clear-Item Clear-Item Property Clear-Variable Compare-Object ConvertFrom-SecureString Convert-Path ConvertTo-Html ConvertTo-SecureString Copy-Item Copy-ItemProperty Export-Alias Export-Clixml Export-Console Export-Csv ForEach-Object Format-Custom Format-List Format-Table Format-Wide Get-Acl Get-Alias Get-AuthenticodeSignature Get-ChildItem Get-Command Get-Content Get-Credential Get-Culture Get-Date Get-EventLog Get-ExecutionPolicy Get-Help Get-History Get-Host Get-Item Get-ItemProperty Get-Location Get-Member Get-PfxCertificate Get-Process Get-PSDrive Get-PSProvider Get-PSSnapin Get-Service Get-TraceSource Get-UICulture Get-Unique Get-Variable Get-WmiObject Group-Object Import-Alias Import-Clixml Import-Csv Invoke-Expression Invoke-History Invoke-Item Join-Path Measure-Command Measure-Object Move-Item Move-ItemProperty New-Alias New-Item New-ItemProperty New-Object New-PSDrive New-Service New-TimeSpan New-Variable Out-Default Out-File Out-Host Out-Null Out-Printer Out-String Pop-Location Push-Location Read-Host Remove-Item Remove-ItemProperty Remove-PSDrive Remove-PSSnapin Remove-Variable Rename-Item Rename-ItemProperty Resolve-Path Restart-Service Resume-Service Select-Object Select-String Set-Acl Set-Alias Set-AuthenticodeSignature Set-Content Set-Date Set-ExecutionPolicy Set-Item Set-ItemProperty Set-Location Set-PSDebug Set-Service Set-TraceSource Set-Variable Sort-Object Split-Path Start-Service Start-Sleep Start-Transcript Stop-Process Stop-Service Stop-Transcript Suspend-Service Tee-Object Test-Path Trace-Command Update-FormatData Update-TypeData Where-Object Write-Debug Write-Error Write-Host Write-Output Write-Progress Write-Verbose Write-Warning",nomarkup:"-ne -eq -lt -gt -ge -le -not -like -notlike -match -notmatch -contains -notcontains -in -notin -replace"},c:[e.HCM,e.NM,a,i,n,r]}}),e.registerLanguage("python",function(e){var t={cN:"meta",b:/^(>>>|\.\.\.) /},r={cN:"string",c:[e.BE],v:[{b:/(u|b)?r?'''/,e:/'''/,c:[t],r:10},{b:/(u|b)?r?"""/,e:/"""/,c:[t],r:10},{b:/(u|r|ur)'/,e:/'/,r:10},{b:/(u|r|ur)"/,e:/"/,r:10},{b:/(b|br)'/,e:/'/},{b:/(b|br)"/,e:/"/},e.ASM,e.QSM]},n={cN:"number",r:0,v:[{b:e.BNR+"[lLjJ]?"},{b:"\\b(0o[0-7]+)[lLjJ]?"},{b:e.CNR+"[lLjJ]?"}]},a={cN:"params",b:/\(/,e:/\)/,c:["self",t,n,r]};return{aliases:["py","gyp"],k:{keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10 None True False",built_in:"Ellipsis NotImplemented"},i:/(<\/|->|\?)/,c:[t,n,r,e.HCM,{v:[{cN:"function",bK:"def",r:10},{cN:"class",bK:"class"}],e:/:/,i:/[${=;\n,]/,c:[e.UTM,a]},{cN:"meta",b:/^[\t ]*@/,e:/$/},{b:/\b(print|exec)\(/}]}}),e.registerLanguage("scss",function(e){var t="[a-zA-Z-][a-zA-Z0-9_-]*",r={cN:"variable",b:"(\\$"+t+")\\b"},n={cN:"number",b:"#[0-9A-Fa-f]+"};({cN:"attribute",b:"[A-Z\\_\\.\\-]+",e:":",eE:!0,i:"[^\\s]",starts:{eW:!0,eE:!0,c:[n,e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"meta",b:"!important"}]}});return{cI:!0,i:"[=/|']",c:[e.CLCM,e.CBCM,{cN:"selector-id",b:"\\#[A-Za-z0-9_-]+",r:0},{cN:"selector-class",b:"\\.[A-Za-z0-9_-]+",r:0},{cN:"selector-attr",b:"\\[",e:"\\]",i:"$"},{cN:"selector-tag",b:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b", +r:0},{b:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{b:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},r,{cN:"attribute",b:"\\b(z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",i:"[^\\s]"},{b:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{b:":",e:";",c:[r,n,e.CSSNM,e.QSM,e.ASM,{cN:"meta",b:"!important"}]},{b:"@",e:"[{;]",k:"mixin include extend for if else each while charset import debug media page content font-face namespace warn",c:[r,e.QSM,e.ASM,n,e.CSSNM,{b:"\\s[A-Za-z0-9_.-]+",r:0}]}]}}),e.registerLanguage("tex",function(e){var t={cN:"tag",b:/\\/,r:0,c:[{cN:"name",v:[{b:/[a-zA-Zа-яА-я]+[*]?/},{b:/[^a-zA-Zа-яА-я0-9]/}],starts:{eW:!0,r:0,c:[{cN:"string",v:[{b:/\[/,e:/\]/},{b:/\{/,e:/\}/}]},{b:/\s*=\s*/,eW:!0,r:0,c:[{cN:"number",b:/-?\d*\.?\d+(pt|pc|mm|cm|in|dd|cc|ex|em)?/}]}]}}]};return{c:[t,{cN:"formula",c:[t],r:0,v:[{b:/\$\$/,e:/\$\$/},{b:/\$/,e:/\$/}]},e.C("%","$",{r:0})]}}),e.registerLanguage("yaml",function(e){var t={literal:"{ } true false yes no Yes No True False null"},r="^[ \\-]*",n="[a-zA-Z_][\\w\\-]*",a={cN:"attr",v:[{b:r+n+":"},{b:r+'"'+n+'":'},{b:r+"'"+n+"':"}]},i={cN:"template-variable",v:[{b:"{{",e:"}}"},{b:"%{",e:"}"}]},o={cN:"string",r:0,v:[{b:/'/,e:/'/},{b:/"/,e:/"/}],c:[e.BE,i]};return{cI:!0,aliases:["yml","YAML","yaml"],c:[a,{cN:"meta",b:"^---s*$",r:10},{cN:"string",b:"[\\|>] *$",rE:!0,c:o.c,e:a.v[0].b},{b:"<%[%=-]?",e:"[%-]?%>",sL:"ruby",eB:!0,eE:!0,r:0},{cN:"type",b:"!!"+e.UIR},{cN:"meta",b:"&"+e.UIR+"$"},{cN:"meta",b:"\\*"+e.UIR+"$"},{cN:"bullet",b:"^ *-",r:0},o,e.HCM,e.CNM],k:t}}),e}); \ No newline at end of file diff --git a/static/vendor/highlightjs/notes.txt b/static/vendor/highlightjs/notes.txt new file mode 100644 index 000000000..43475e5f9 --- /dev/null +++ b/static/vendor/highlightjs/notes.txt @@ -0,0 +1,13 @@ +Version: 9.0.0 + +Source: + https://github.com/isagalaev/highlight.js + +Also see Hugo commit: + 7cf7f85ad66a1cd35ab4e6026ed6b9da95c53c34. + +Files (there): + src/styles/monokai-sublime.css + +Files (we build): + highlight.pack.js diff --git a/static/vendor/jquery/js/jquery-2.1.4.min.js b/static/vendor/jquery/js/jquery-2.1.4.min.js new file mode 100644 index 000000000..49990d6e1 --- /dev/null +++ b/static/vendor/jquery/js/jquery-2.1.4.min.js @@ -0,0 +1,4 @@ +/*! jQuery v2.1.4 | (c) 2005, 2015 jQuery Foundation, Inc. | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)+1>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b="length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,aa=/[+~]/,ba=/'|\\/g,ca=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),da=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ea=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fa){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(ba,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+ra(o[l]);w=aa.test(a)&&pa(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",ea,!1):e.attachEvent&&e.attachEvent("onunload",ea)),p=!f(g),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="
    ",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?la(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ca,da),a[3]=(a[3]||a[4]||a[5]||"").replace(ca,da),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ca,da).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(ca,da),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return W.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(ca,da).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:oa(function(){return[0]}),last:oa(function(a,b){return[b-1]}),eq:oa(function(a,b,c){return[0>c?c+b:c]}),even:oa(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:oa(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:oa(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:oa(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function sa(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function ta(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ua(a,b,c){for(var d=0,e=b.length;e>d;d++)ga(a,b[d],c);return c}function va(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wa(a,b,c,d,e,f){return d&&!d[u]&&(d=wa(d)),e&&!e[u]&&(e=wa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ua(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:va(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=va(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=va(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sa(function(a){return a===b},h,!0),l=sa(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sa(ta(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wa(i>1&&ta(m),i>1&&ra(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xa(a.slice(i,e)),f>e&&xa(a=a.slice(e)),f>e&&ra(a))}m.push(c)}return ta(m)}function ya(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=va(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&ga.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,ya(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ca,da),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ca,da),aa.test(j[0].type)&&pa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&ra(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,aa.test(a)&&pa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+K.uid++}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){ +return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b)},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthx",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,ba=/<([\w:]+)/,ca=/<|&#?\w+;/,da=/<(?:script|style|link)/i,ea=/checked\s*(?:[^=]|=\s*.checked.)/i,fa=/^$|\/(?:java|ecma)script/i,ga=/^true\/(.*)/,ha=/^\s*\s*$/g,ia={option:[1,""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};ia.optgroup=ia.option,ia.tbody=ia.tfoot=ia.colgroup=ia.caption=ia.thead,ia.th=ia.td;function ja(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function ka(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function la(a){var b=ga.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function ma(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function na(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function oa(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pa(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=oa(h),f=oa(a),d=0,e=f.length;e>d;d++)pa(f[d],g[d]);if(b)if(c)for(f=f||oa(a),g=g||oa(h),d=0,e=f.length;e>d;d++)na(f[d],g[d]);else na(a,h);return g=oa(h,"script"),g.length>0&&ma(g,!i&&oa(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(ca.test(e)){f=f||k.appendChild(b.createElement("div")),g=(ba.exec(e)||["",""])[1].toLowerCase(),h=ia[g]||ia._default,f.innerHTML=h[1]+e.replace(aa,"<$1>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=oa(k.appendChild(e),"script"),i&&ma(f),c)){j=0;while(e=f[j++])fa.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(oa(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&ma(oa(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(oa(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!da.test(a)&&!ia[(ba.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(aa,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(oa(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(oa(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&ea.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(oa(c,"script"),ka),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,oa(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,la),j=0;g>j;j++)h=f[j],fa.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(ha,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qa,ra={};function sa(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function ta(a){var b=l,c=ra[a];return c||(c=sa(a,b),"none"!==c&&c||(qa=(qa||n(" -
    {{ else }} -
    - -
    -{{ end }}`) - t.addInternalShortcode("vimeo.html", `{{ if .IsNamedParams }}
    - -
    {{ else }} -
    - -
    -{{ end }}`) - t.addInternalShortcode("gist.html", ``) - t.addInternalShortcode("tweet.html", `{{ (getJSON "https://api.twitter.com/1/statuses/oembed.json?id=" (index .Params 0)).html | safeHTML }}`) - t.addInternalShortcode("instagram.html", `{{ if len .Params | eq 2 }}{{ if eq (.Get 1) "hidecaption" }}{{ with getJSON "https://api.instagram.com/oembed/?url=https://instagram.com/p/" (index .Params 0) "/&hidecaption=1" }}{{ .html | safeHTML }}{{ end }}{{ end }}{{ else }}{{ with getJSON "https://api.instagram.com/oembed/?url=https://instagram.com/p/" (index .Params 0) "/&hidecaption=0" }}{{ .html | safeHTML }}{{ end }}{{ end }}`) -} - -func (t *templateHandler) embedTemplates() { - - t.addInternalTemplate("_default", "rss.xml", ` - - {{ if eq .Title .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{.}} on {{ end }}{{ .Site.Title }}{{ end }} - {{ .Permalink }} - Recent content {{ if ne .Title .Site.Title }}{{ with .Title }}in {{.}} {{ end }}{{ end }}on {{ .Site.Title }} - Hugo -- gohugo.io{{ with .Site.LanguageCode }} - {{.}}{{end}}{{ with .Site.Author.email }} - {{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}{{end}}{{ with .Site.Author.email }} - {{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}{{end}}{{ with .Site.Copyright }} - {{.}}{{end}}{{ if not .Date.IsZero }} - {{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}{{ end }} - {{ with .OutputFormats.Get "RSS" }} - {{ printf "" .Permalink .MediaType | safeHTML }} - {{ end }} - {{ range .Data.Pages }} - - {{ .Title }} - {{ .Permalink }} - {{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }} - {{ with .Site.Author.email }}{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}{{end}} - {{ .Permalink }} - {{ .Summary | html }} - - {{ end }} - -`) - - t.addInternalTemplate("_default", "sitemap.xml", ` - {{ range .Data.Pages }} - - {{ .Permalink }}{{ if not .Lastmod.IsZero }} - {{ safeHTML ( .Lastmod.Format "2006-01-02T15:04:05-07:00" ) }}{{ end }}{{ with .Sitemap.ChangeFreq }} - {{ . }}{{ end }}{{ if ge .Sitemap.Priority 0.0 }} - {{ .Sitemap.Priority }}{{ end }}{{ if .IsTranslated }}{{ range .Translations }} - {{ end }} - {{ end }} - - {{ end }} -`) - - // For multilanguage sites - t.addInternalTemplate("_default", "sitemapindex.xml", ` - {{ range . }} - - {{ .SitemapAbsURL }} - {{ if not .LastChange.IsZero }} - {{ .LastChange.Format "2006-01-02T15:04:05-07:00" | safeHTML }} - {{ end }} - - {{ end }} - -`) - - t.addInternalTemplate("", "pagination.html", `{{ $pag := $.Paginator }} -{{ if gt $pag.TotalPages 1 }} -
      - {{ with $pag.First }} -
    • - -
    • - {{ end }} -
    • - -
    • - {{ $.Scratch.Set "__paginator.ellipsed" false }} - {{ range $pag.Pagers }} - {{ $right := sub .TotalPages .PageNumber }} - {{ $showNumber := or (le .PageNumber 3) (eq $right 0) }} - {{ $showNumber := or $showNumber (and (gt .PageNumber (sub $pag.PageNumber 2)) (lt .PageNumber (add $pag.PageNumber 2))) }} - {{ if $showNumber }} - {{ $.Scratch.Set "__paginator.ellipsed" false }} - {{ $.Scratch.Set "__paginator.shouldEllipse" false }} - {{ else }} - {{ $.Scratch.Set "__paginator.shouldEllipse" (not ($.Scratch.Get "__paginator.ellipsed") ) }} - {{ $.Scratch.Set "__paginator.ellipsed" true }} - {{ end }} - {{ if $showNumber }} -
    • {{ .PageNumber }}
    • - {{ else if ($.Scratch.Get "__paginator.shouldEllipse") }} -
    • - {{ end }} - {{ end }} -
    • - -
    • - {{ with $pag.Last }} -
    • - -
    • - {{ end }} -
    -{{ end }}`) - - t.addInternalTemplate("", "disqus.html", `{{ if .Site.DisqusShortname }}
    - - -comments powered by Disqus{{end}}`) - - // Add SEO & Social metadata - t.addInternalTemplate("", "opengraph.html", ` - - - -{{ with .Params.images }}{{ range first 6 . }} - -{{ end }}{{ end }} - -{{ if .IsPage }} -{{ if not .PublishDate.IsZero }} -{{ else if not .Date.IsZero }}{{ end }} -{{ if not .Lastmod.IsZero }}{{ end }} -{{ else }} -{{ if not .Date.IsZero }}{{ end }} -{{ end }}{{ with .Params.audio }} -{{ end }}{{ with .Params.locale }} -{{ end }}{{ with .Site.Params.title }} -{{ end }}{{ with .Params.videos }} -{{ range .Params.videos }} - -{{ end }}{{ end }} - - -{{ $permalink := .Permalink }} -{{ $siteSeries := .Site.Taxonomies.series }}{{ with .Params.series }} -{{ range $name := . }} - {{ $series := index $siteSeries $name }} - {{ range $page := first 6 $series.Pages }} - {{ if ne $page.Permalink $permalink }}{{ end }} - {{ end }} -{{ end }}{{ end }} - -{{ if .IsPage }} -{{ range .Site.Authors }}{{ with .Social.facebook }} -{{ end }}{{ with .Site.Social.facebook }} -{{ end }} - -{{ with .Params.tags }}{{ range first 6 . }} - {{ end }}{{ end }} -{{ end }}{{ end }} - - -{{ with .Site.Social.facebook_admin }}{{ end }}`) - - t.addInternalTemplate("", "twitter_cards.html", `{{- with $.Param "images" -}} - - -{{ else -}} - -{{- end -}} - - -{{ with .Site.Social.twitter -}} - -{{ end -}} -{{ range .Site.Authors }} -{{ with .twitter -}} - -{{ end -}} -{{ end -}}`) - - t.addInternalTemplate("", "google_news.html", `{{ if .IsPage }}{{ with .Params.news_keywords }} - -{{ end }}{{ end }}`) - - t.addInternalTemplate("", "schema.html", `{{ with .Site.Social.GooglePlus }}{{ end }} - - - -{{if .IsPage}}{{ $ISO8601 := "2006-01-02T15:04:05-07:00" }}{{ if not .PublishDate.IsZero }} -{{ end }} -{{ if not .Date.IsZero }}{{ end }} - -{{ with .Params.images }}{{ range first 6 . }} - -{{ end }}{{ end }} - - - -{{ end }}`) - - t.addInternalTemplate("", "google_analytics.html", `{{ with .Site.GoogleAnalytics }} - -{{ end }}`) - - t.addInternalTemplate("", "google_analytics_async.html", `{{ with .Site.GoogleAnalytics }} - - -{{ end }}`) - - t.addInternalTemplate("_default", "robots.txt", "User-agent: *") -} diff --git a/tpl/tplimpl/template_funcs.go b/tpl/tplimpl/template_funcs.go deleted file mode 100644 index 309a7b85f..000000000 --- a/tpl/tplimpl/template_funcs.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2017-present The Hugo Authors. All rights reserved. -// -// Portions Copyright The Go Authors. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tplimpl - -import ( - "html/template" - - "github.com/gohugoio/hugo/tpl/internal" - - // Init the namespaces - _ "github.com/gohugoio/hugo/tpl/cast" - _ "github.com/gohugoio/hugo/tpl/collections" - _ "github.com/gohugoio/hugo/tpl/compare" - _ "github.com/gohugoio/hugo/tpl/crypto" - _ "github.com/gohugoio/hugo/tpl/data" - _ "github.com/gohugoio/hugo/tpl/encoding" - _ "github.com/gohugoio/hugo/tpl/fmt" - _ "github.com/gohugoio/hugo/tpl/images" - _ "github.com/gohugoio/hugo/tpl/inflect" - _ "github.com/gohugoio/hugo/tpl/lang" - _ "github.com/gohugoio/hugo/tpl/math" - _ "github.com/gohugoio/hugo/tpl/os" - _ "github.com/gohugoio/hugo/tpl/partials" - _ "github.com/gohugoio/hugo/tpl/safe" - _ "github.com/gohugoio/hugo/tpl/strings" - _ "github.com/gohugoio/hugo/tpl/time" - _ "github.com/gohugoio/hugo/tpl/transform" - _ "github.com/gohugoio/hugo/tpl/urls" -) - -func (t *templateFuncster) initFuncMap() { - funcMap := template.FuncMap{} - - // Merge the namespace funcs - for _, nsf := range internal.TemplateFuncsNamespaceRegistry { - ns := nsf(t.Deps) - if _, exists := funcMap[ns.Name]; exists { - panic(ns.Name + " is a duplicate template func") - } - funcMap[ns.Name] = ns.Context - for _, mm := range ns.MethodMappings { - for _, alias := range mm.Aliases { - if _, exists := funcMap[alias]; exists { - panic(alias + " is a duplicate template func") - } - funcMap[alias] = mm.Method - } - - } - } - - t.funcMap = funcMap - t.Tmpl.(*templateHandler).setFuncs(funcMap) -} diff --git a/tpl/tplimpl/template_funcs_test.go b/tpl/tplimpl/template_funcs_test.go deleted file mode 100644 index 04f4464ec..000000000 --- a/tpl/tplimpl/template_funcs_test.go +++ /dev/null @@ -1,266 +0,0 @@ -// Copyright 2016 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tplimpl - -import ( - "bytes" - "fmt" - "path/filepath" - "reflect" - "testing" - - "io/ioutil" - "log" - "os" - - "github.com/gohugoio/hugo/config" - "github.com/gohugoio/hugo/deps" - "github.com/gohugoio/hugo/helpers" - "github.com/gohugoio/hugo/hugofs" - "github.com/gohugoio/hugo/i18n" - "github.com/gohugoio/hugo/tpl" - "github.com/gohugoio/hugo/tpl/internal" - "github.com/spf13/afero" - jww "github.com/spf13/jwalterweatherman" - "github.com/spf13/viper" - "github.com/stretchr/testify/require" -) - -var ( - logger = jww.NewNotepad(jww.LevelFatal, jww.LevelFatal, os.Stdout, ioutil.Discard, "", log.Ldate|log.Ltime) -) - -func newDepsConfig(cfg config.Provider) deps.DepsCfg { - l := helpers.NewLanguage("en", cfg) - l.Set("i18nDir", "i18n") - return deps.DepsCfg{ - Language: l, - Cfg: cfg, - Fs: hugofs.NewMem(l), - Logger: logger, - TemplateProvider: DefaultTemplateProvider, - TranslationProvider: i18n.NewTranslationProvider(), - } -} - -func TestTemplateFuncsExamples(t *testing.T) { - t.Parallel() - - workingDir := "/home/hugo" - - v := viper.New() - - v.Set("workingDir", workingDir) - v.Set("multilingual", true) - v.Set("baseURL", "http://mysite.com/hugo/") - v.Set("CurrentContentLanguage", helpers.NewLanguage("en", v)) - - fs := hugofs.NewMem(v) - - afero.WriteFile(fs.Source, filepath.Join(workingDir, "README.txt"), []byte("Hugo Rocks!"), 0755) - - depsCfg := newDepsConfig(v) - depsCfg.Fs = fs - d, err := deps.New(depsCfg) - require.NoError(t, err) - - var data struct { - Title string - Section string - Params map[string]interface{} - } - - data.Title = "**BatMan**" - data.Section = "blog" - data.Params = map[string]interface{}{"langCode": "en"} - - for _, nsf := range internal.TemplateFuncsNamespaceRegistry { - ns := nsf(d) - for _, mm := range ns.MethodMappings { - for i, example := range mm.Examples { - in, expected := example[0], example[1] - d.WithTemplate = func(templ tpl.TemplateHandler) error { - require.NoError(t, templ.AddTemplate("test", in)) - require.NoError(t, templ.AddTemplate("partials/header.html", "Hugo Rocks!")) - return nil - } - require.NoError(t, d.LoadResources()) - - var b bytes.Buffer - require.NoError(t, d.Tmpl.Lookup("test").Execute(&b, &data)) - if b.String() != expected { - t.Fatalf("%s[%d]: got %q expected %q", ns.Name, i, b.String(), expected) - } - } - } - } - -} - -// TODO(bep) it would be dandy to put this one into the partials package, but -// we have some package cycle issues to solve first. -func TestPartialCached(t *testing.T) { - t.Parallel() - testCases := []struct { - name string - partial string - tmpl string - variant string - }{ - // name and partial should match between test cases. - {"test1", "{{ .Title }} seq: {{ shuffle (seq 1 20) }}", `{{ partialCached "test1" . }}`, ""}, - {"test1", "{{ .Title }} seq: {{ shuffle (seq 1 20) }}", `{{ partialCached "test1" . "%s" }}`, "header"}, - {"test1", "{{ .Title }} seq: {{ shuffle (seq 1 20) }}", `{{ partialCached "test1" . "%s" }}`, "footer"}, - {"test1", "{{ .Title }} seq: {{ shuffle (seq 1 20) }}", `{{ partialCached "test1" . "%s" }}`, "header"}, - } - - var data struct { - Title string - Section string - Params map[string]interface{} - } - - data.Title = "**BatMan**" - data.Section = "blog" - data.Params = map[string]interface{}{"langCode": "en"} - - for i, tc := range testCases { - var tmp string - if tc.variant != "" { - tmp = fmt.Sprintf(tc.tmpl, tc.variant) - } else { - tmp = tc.tmpl - } - - config := newDepsConfig(viper.New()) - - config.WithTemplate = func(templ tpl.TemplateHandler) error { - err := templ.AddTemplate("testroot", tmp) - if err != nil { - return err - } - err = templ.AddTemplate("partials/"+tc.name, tc.partial) - if err != nil { - return err - } - - return nil - } - - de, err := deps.New(config) - require.NoError(t, err) - require.NoError(t, de.LoadResources()) - - buf := new(bytes.Buffer) - templ := de.Tmpl.Lookup("testroot") - err = templ.Execute(buf, &data) - if err != nil { - t.Fatalf("[%d] error executing template: %s", i, err) - } - - for j := 0; j < 10; j++ { - buf2 := new(bytes.Buffer) - err := templ.Execute(buf2, nil) - if err != nil { - t.Fatalf("[%d] error executing template 2nd time: %s", i, err) - } - - if !reflect.DeepEqual(buf, buf2) { - t.Fatalf("[%d] cached results do not match:\nResult 1:\n%q\nResult 2:\n%q", i, buf, buf2) - } - } - } -} - -func BenchmarkPartial(b *testing.B) { - config := newDepsConfig(viper.New()) - config.WithTemplate = func(templ tpl.TemplateHandler) error { - err := templ.AddTemplate("testroot", `{{ partial "bench1" . }}`) - if err != nil { - return err - } - err = templ.AddTemplate("partials/bench1", `{{ shuffle (seq 1 10) }}`) - if err != nil { - return err - } - - return nil - } - - de, err := deps.New(config) - require.NoError(b, err) - require.NoError(b, de.LoadResources()) - - buf := new(bytes.Buffer) - tmpl := de.Tmpl.Lookup("testroot") - - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - if err := tmpl.Execute(buf, nil); err != nil { - b.Fatalf("error executing template: %s", err) - } - buf.Reset() - } -} - -func BenchmarkPartialCached(b *testing.B) { - config := newDepsConfig(viper.New()) - config.WithTemplate = func(templ tpl.TemplateHandler) error { - err := templ.AddTemplate("testroot", `{{ partialCached "bench1" . }}`) - if err != nil { - return err - } - err = templ.AddTemplate("partials/bench1", `{{ shuffle (seq 1 10) }}`) - if err != nil { - return err - } - - return nil - } - - de, err := deps.New(config) - require.NoError(b, err) - require.NoError(b, de.LoadResources()) - - buf := new(bytes.Buffer) - tmpl := de.Tmpl.Lookup("testroot") - - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - if err := tmpl.Execute(buf, nil); err != nil { - b.Fatalf("error executing template: %s", err) - } - buf.Reset() - } -} - -func newTestFuncster() *templateFuncster { - return newTestFuncsterWithViper(viper.New()) -} - -func newTestFuncsterWithViper(v *viper.Viper) *templateFuncster { - config := newDepsConfig(v) - d, err := deps.New(config) - if err != nil { - panic(err) - } - - if err := d.LoadResources(); err != nil { - panic(err) - } - - return d.Tmpl.(*templateHandler).html.funcster -} diff --git a/tpl/transform/init.go b/tpl/transform/init.go deleted file mode 100644 index c0e9b2d5d..000000000 --- a/tpl/transform/init.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2017 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package transform - -import ( - "github.com/gohugoio/hugo/deps" - "github.com/gohugoio/hugo/tpl/internal" -) - -const name = "transform" - -func init() { - f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { - ctx := New(d) - - ns := &internal.TemplateFuncsNamespace{ - Name: name, - Context: func(args ...interface{}) interface{} { return ctx }, - } - - ns.AddMethodMapping(ctx.Emojify, - []string{"emojify"}, - [][2]string{ - {`{{ "I :heart: Hugo" | emojify }}`, `I ❤️ Hugo`}, - }, - ) - - ns.AddMethodMapping(ctx.Highlight, - []string{"highlight"}, - [][2]string{}, - ) - - ns.AddMethodMapping(ctx.HTMLEscape, - []string{"htmlEscape"}, - [][2]string{ - { - `{{ htmlEscape "Cathal Garvey & The Sunshine Band " | safeHTML}}`, - `Cathal Garvey & The Sunshine Band <cathal@foo.bar>`}, - { - `{{ htmlEscape "Cathal Garvey & The Sunshine Band "}}`, - `Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;`}, - { - `{{ htmlEscape "Cathal Garvey & The Sunshine Band " | htmlUnescape | safeHTML }}`, - `Cathal Garvey & The Sunshine Band `}, - }, - ) - - ns.AddMethodMapping(ctx.HTMLUnescape, - []string{"htmlUnescape"}, - [][2]string{ - { - `{{ htmlUnescape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>" | safeHTML}}`, - `Cathal Garvey & The Sunshine Band `}, - { - `{{"Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;" | htmlUnescape | htmlUnescape | safeHTML}}`, - `Cathal Garvey & The Sunshine Band `}, - { - `{{"Cathal Garvey &amp; The Sunshine Band &lt;cathal@foo.bar&gt;" | htmlUnescape | htmlUnescape }}`, - `Cathal Garvey & The Sunshine Band <cathal@foo.bar>`}, - { - `{{ htmlUnescape "Cathal Garvey & The Sunshine Band <cathal@foo.bar>" | htmlEscape | safeHTML }}`, - `Cathal Garvey & The Sunshine Band <cathal@foo.bar>`}, - }, - ) - - ns.AddMethodMapping(ctx.Markdownify, - []string{"markdownify"}, - [][2]string{ - {`{{ .Title | markdownify}}`, `BatMan`}, - }, - ) - - ns.AddMethodMapping(ctx.Plainify, - []string{"plainify"}, - [][2]string{ - {`{{ plainify "Hello world, gophers!" }}`, `Hello world, gophers!`}, - }, - ) - - return ns - - } - - internal.AddTemplateFuncsNamespace(f) -} diff --git a/tpl/transform/init_test.go b/tpl/transform/init_test.go deleted file mode 100644 index 8ac20366c..000000000 --- a/tpl/transform/init_test.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2017 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package transform - -import ( - "testing" - - "github.com/gohugoio/hugo/deps" - "github.com/gohugoio/hugo/tpl/internal" - "github.com/stretchr/testify/require" -) - -func TestInit(t *testing.T) { - var found bool - var ns *internal.TemplateFuncsNamespace - - for _, nsf := range internal.TemplateFuncsNamespaceRegistry { - ns = nsf(&deps.Deps{}) - if ns.Name == name { - found = true - break - } - } - - require.True(t, found) - require.IsType(t, &Namespace{}, ns.Context()) -} diff --git a/tpl/transform/transform.go b/tpl/transform/transform.go deleted file mode 100644 index b41b41b9c..000000000 --- a/tpl/transform/transform.go +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2017 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package transform - -import ( - "bytes" - "html" - "html/template" - - "github.com/gohugoio/hugo/deps" - "github.com/gohugoio/hugo/helpers" - "github.com/spf13/cast" -) - -// New returns a new instance of the transform-namespaced template functions. -func New(deps *deps.Deps) *Namespace { - return &Namespace{ - deps: deps, - } -} - -// Namespace provides template functions for the "transform" namespace. -type Namespace struct { - deps *deps.Deps -} - -// Emojify returns a copy of s with all emoji codes replaced with actual emojis. -// -// See http://www.emoji-cheat-sheet.com/ -func (ns *Namespace) Emojify(s interface{}) (template.HTML, error) { - ss, err := cast.ToStringE(s) - if err != nil { - return "", err - } - - return template.HTML(helpers.Emojify([]byte(ss))), nil -} - -// Highlight returns a copy of s as an HTML string with syntax -// highlighting applied. -func (ns *Namespace) Highlight(s interface{}, lang, opts string) (template.HTML, error) { - ss, err := cast.ToStringE(s) - if err != nil { - return "", err - } - - return template.HTML(helpers.Highlight(ns.deps.Cfg, html.UnescapeString(ss), lang, opts)), nil -} - -// HTMLEscape returns a copy of s with reserved HTML characters escaped. -func (ns *Namespace) HTMLEscape(s interface{}) (string, error) { - ss, err := cast.ToStringE(s) - if err != nil { - return "", err - } - - return html.EscapeString(ss), nil -} - -// HTMLUnescape returns a copy of with HTML escape requences converted to plain -// text. -func (ns *Namespace) HTMLUnescape(s interface{}) (string, error) { - ss, err := cast.ToStringE(s) - if err != nil { - return "", err - } - - return html.UnescapeString(ss), nil -} - -var markdownTrimPrefix = []byte("

    ") -var markdownTrimSuffix = []byte("

    \n") - -// Markdownify renders a given input from Markdown to HTML. -func (ns *Namespace) Markdownify(s interface{}) (template.HTML, error) { - ss, err := cast.ToStringE(s) - if err != nil { - return "", err - } - - m := ns.deps.ContentSpec.RenderBytes( - &helpers.RenderingContext{ - Cfg: ns.deps.Cfg, - Content: []byte(ss), - PageFmt: "markdown", - Config: ns.deps.ContentSpec.NewBlackfriday(), - }, - ) - m = bytes.TrimPrefix(m, markdownTrimPrefix) - m = bytes.TrimSuffix(m, markdownTrimSuffix) - - return template.HTML(m), nil -} - -// Plainify returns a copy of s with all HTML tags removed. -func (ns *Namespace) Plainify(s interface{}) (string, error) { - ss, err := cast.ToStringE(s) - if err != nil { - return "", err - } - - return helpers.StripHTML(ss), nil -} diff --git a/tpl/transform/transform_test.go b/tpl/transform/transform_test.go deleted file mode 100644 index b50d7530c..000000000 --- a/tpl/transform/transform_test.go +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright 2017 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package transform - -import ( - "fmt" - "html/template" - "testing" - - "github.com/gohugoio/hugo/config" - "github.com/gohugoio/hugo/deps" - "github.com/gohugoio/hugo/helpers" - "github.com/gohugoio/hugo/hugofs" - "github.com/spf13/viper" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -type tstNoStringer struct{} - -func TestEmojify(t *testing.T) { - t.Parallel() - - ns := New(newDeps(viper.New())) - - for i, test := range []struct { - s interface{} - expect interface{} - }{ - {":notamoji:", template.HTML(":notamoji:")}, - {"I :heart: Hugo", template.HTML("I ❤️ Hugo")}, - // errors - {tstNoStringer{}, false}, - } { - errMsg := fmt.Sprintf("[%d] %s", i, test.s) - - result, err := ns.Emojify(test.s) - - if b, ok := test.expect.(bool); ok && !b { - require.Error(t, err, errMsg) - continue - } - - require.NoError(t, err, errMsg) - assert.Equal(t, test.expect, result, errMsg) - } -} - -func TestHighlight(t *testing.T) { - t.Parallel() - - ns := New(newDeps(viper.New())) - - for i, test := range []struct { - s interface{} - lang string - opts string - expect interface{} - }{ - {"func boo() {}", "go", "", "boo"}, - {tstNoStringer{}, "go", "", false}, - } { - errMsg := fmt.Sprintf("[%d]", i) - - result, err := ns.Highlight(test.s, test.lang, test.opts) - - if b, ok := test.expect.(bool); ok && !b { - require.Error(t, err, errMsg) - continue - } - - require.NoError(t, err, errMsg) - assert.Contains(t, result, "boo", errMsg) - } -} - -func TestHTMLEscape(t *testing.T) { - t.Parallel() - - ns := New(newDeps(viper.New())) - - for i, test := range []struct { - s interface{} - expect interface{} - }{ - {`"Foo & Bar's Diner" `, `"Foo & Bar's Diner" <y@z>`}, - {"Hugo & Caddy > Wordpress & Apache", "Hugo & Caddy > Wordpress & Apache"}, - // errors - {tstNoStringer{}, false}, - } { - errMsg := fmt.Sprintf("[%d] %s", i, test.s) - - result, err := ns.HTMLEscape(test.s) - - if b, ok := test.expect.(bool); ok && !b { - require.Error(t, err, errMsg) - continue - } - - require.NoError(t, err, errMsg) - assert.Equal(t, test.expect, result, errMsg) - } -} - -func TestHTMLUnescape(t *testing.T) { - t.Parallel() - - ns := New(newDeps(viper.New())) - - for i, test := range []struct { - s interface{} - expect interface{} - }{ - {`"Foo & Bar's Diner" <y@z>`, `"Foo & Bar's Diner" `}, - {"Hugo & Caddy > Wordpress & Apache", "Hugo & Caddy > Wordpress & Apache"}, - // errors - {tstNoStringer{}, false}, - } { - errMsg := fmt.Sprintf("[%d] %s", i, test.s) - - result, err := ns.HTMLUnescape(test.s) - - if b, ok := test.expect.(bool); ok && !b { - require.Error(t, err, errMsg) - continue - } - - require.NoError(t, err, errMsg) - assert.Equal(t, test.expect, result, errMsg) - } -} - -func TestMarkdownify(t *testing.T) { - t.Parallel() - - ns := New(newDeps(viper.New())) - - for i, test := range []struct { - s interface{} - expect interface{} - }{ - {"Hello **World!**", template.HTML("Hello World!")}, - {[]byte("Hello Bytes **World!**"), template.HTML("Hello Bytes World!")}, - {tstNoStringer{}, false}, - } { - errMsg := fmt.Sprintf("[%d] %s", i, test.s) - - result, err := ns.Markdownify(test.s) - - if b, ok := test.expect.(bool); ok && !b { - require.Error(t, err, errMsg) - continue - } - - require.NoError(t, err, errMsg) - assert.Equal(t, test.expect, result, errMsg) - } -} - -func TestPlainify(t *testing.T) { - t.Parallel() - - ns := New(newDeps(viper.New())) - - for i, test := range []struct { - s interface{} - expect interface{} - }{ - {"Note: blah blah", "Note: blah blah"}, - // errors - {tstNoStringer{}, false}, - } { - errMsg := fmt.Sprintf("[%d] %s", i, test.s) - - result, err := ns.Plainify(test.s) - - if b, ok := test.expect.(bool); ok && !b { - require.Error(t, err, errMsg) - continue - } - - require.NoError(t, err, errMsg) - assert.Equal(t, test.expect, result, errMsg) - } -} - -func newDeps(cfg config.Provider) *deps.Deps { - l := helpers.NewLanguage("en", cfg) - l.Set("i18nDir", "i18n") - return &deps.Deps{ - Cfg: cfg, - Fs: hugofs.NewMem(l), - ContentSpec: helpers.NewContentSpec(l), - } -} diff --git a/tpl/urls/init.go b/tpl/urls/init.go deleted file mode 100644 index c48fe8ca1..000000000 --- a/tpl/urls/init.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2017 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package urls - -import ( - "github.com/gohugoio/hugo/deps" - "github.com/gohugoio/hugo/tpl/internal" -) - -const name = "urls" - -func init() { - f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { - ctx := New(d) - - ns := &internal.TemplateFuncsNamespace{ - Name: name, - Context: func(args ...interface{}) interface{} { return ctx }, - } - - ns.AddMethodMapping(ctx.AbsURL, - []string{"absURL"}, - [][2]string{}, - ) - - ns.AddMethodMapping(ctx.AbsLangURL, - []string{"absLangURL"}, - [][2]string{}, - ) - ns.AddMethodMapping(ctx.Ref, - []string{"ref"}, - [][2]string{}, - ) - ns.AddMethodMapping(ctx.RelURL, - []string{"relURL"}, - [][2]string{}, - ) - ns.AddMethodMapping(ctx.RelLangURL, - []string{"relLangURL"}, - [][2]string{}, - ) - ns.AddMethodMapping(ctx.RelRef, - []string{"relref"}, - [][2]string{}, - ) - ns.AddMethodMapping(ctx.URLize, - []string{"urlize"}, - [][2]string{}, - ) - - return ns - - } - - internal.AddTemplateFuncsNamespace(f) -} diff --git a/tpl/urls/init_test.go b/tpl/urls/init_test.go deleted file mode 100644 index 6630f13d3..000000000 --- a/tpl/urls/init_test.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2017 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package urls - -import ( - "testing" - - "github.com/gohugoio/hugo/deps" - "github.com/gohugoio/hugo/tpl/internal" - "github.com/stretchr/testify/require" -) - -func TestInit(t *testing.T) { - var found bool - var ns *internal.TemplateFuncsNamespace - - for _, nsf := range internal.TemplateFuncsNamespaceRegistry { - ns = nsf(&deps.Deps{}) - if ns.Name == name { - found = true - break - } - } - - require.True(t, found) - require.IsType(t, &Namespace{}, ns.Context()) -} diff --git a/tpl/urls/urls.go b/tpl/urls/urls.go deleted file mode 100644 index 817ed8b15..000000000 --- a/tpl/urls/urls.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2017 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package urls - -import ( - "errors" - "html/template" - - "github.com/gohugoio/hugo/deps" - "github.com/spf13/cast" -) - -// New returns a new instance of the urls-namespaced template functions. -func New(deps *deps.Deps) *Namespace { - return &Namespace{ - deps: deps, - } -} - -// Namespace provides template functions for the "urls" namespace. -type Namespace struct { - deps *deps.Deps -} - -// AbsURL takes a given string and converts it to an absolute URL. -func (ns *Namespace) AbsURL(a interface{}) (template.HTML, error) { - s, err := cast.ToStringE(a) - if err != nil { - return "", nil - } - - return template.HTML(ns.deps.PathSpec.AbsURL(s, false)), nil -} - -// RelURL takes a given string and prepends the relative path according to a -// page's position in the project directory structure. -func (ns *Namespace) RelURL(a interface{}) (template.HTML, error) { - s, err := cast.ToStringE(a) - if err != nil { - return "", nil - } - - return template.HTML(ns.deps.PathSpec.RelURL(s, false)), nil -} - -// URLize returns the given argument formatted as URL. -func (ns *Namespace) URLize(a interface{}) (string, error) { - s, err := cast.ToStringE(a) - if err != nil { - return "", nil - } - return ns.deps.PathSpec.URLize(s), nil -} - -type reflinker interface { - Ref(refs ...string) (string, error) - RelRef(refs ...string) (string, error) -} - -// Ref returns the absolute URL path to a given content item. -func (ns *Namespace) Ref(in interface{}, refs ...string) (template.HTML, error) { - p, ok := in.(reflinker) - if !ok { - return "", errors.New("invalid Page received in Ref") - } - s, err := p.Ref(refs...) - return template.HTML(s), err -} - -// RelRef returns the relative URL path to a given content item. -func (ns *Namespace) RelRef(in interface{}, refs ...string) (template.HTML, error) { - p, ok := in.(reflinker) - if !ok { - return "", errors.New("invalid Page received in RelRef") - } - s, err := p.RelRef(refs...) - return template.HTML(s), err -} - -// RelLangURL takes a given string and prepends the relative path according to a -// page's position in the project directory structure and the current language. -func (ns *Namespace) RelLangURL(a interface{}) (template.HTML, error) { - s, err := cast.ToStringE(a) - if err != nil { - return "", err - } - - return template.HTML(ns.deps.PathSpec.RelURL(s, true)), nil -} - -// AbsLangURL takes a given string and converts it to an absolute URL according -// to a page's position in the project directory structure and the current -// language. -func (ns *Namespace) AbsLangURL(a interface{}) (template.HTML, error) { - s, err := cast.ToStringE(a) - if err != nil { - return "", err - } - - return template.HTML(ns.deps.PathSpec.AbsURL(s, true)), nil -} diff --git a/transform/absurl.go b/transform/absurl.go deleted file mode 100644 index 255ac33b6..000000000 --- a/transform/absurl.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package transform - -var ar = newAbsURLReplacer() - -// AbsURL replaces relative URLs with absolute ones -// in HTML files, using the baseURL setting. -var AbsURL = func(ct contentTransformer) { - ar.replaceInHTML(ct) -} - -// AbsURLInXML replaces relative URLs with absolute ones -// in XML files, using the baseURL setting. -var AbsURLInXML = func(ct contentTransformer) { - ar.replaceInXML(ct) -} diff --git a/transform/absurlreplacer.go b/transform/absurlreplacer.go deleted file mode 100644 index c659a94e8..000000000 --- a/transform/absurlreplacer.go +++ /dev/null @@ -1,312 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package transform - -import ( - "bytes" - "io" - "unicode/utf8" -) - -type matchState int - -const ( - matchStateNone matchState = iota - matchStateWhitespace - matchStatePartial - matchStateFull -) - -type absurllexer struct { - // the source to absurlify - content []byte - // the target for the new absurlified content - w io.Writer - - // path may be set to a "." relative path - path []byte - - pos int // input position - start int // item start position - width int // width of last element - - matchers []absURLMatcher - - ms matchState - matches [3]bool // track matches of the 3 prefixes - idx int // last index in matches checked - -} - -type stateFunc func(*absurllexer) stateFunc - -// prefix is how to identify and which func to handle the replacement. -type prefix struct { - r []rune - f func(l *absurllexer) -} - -// new prefixes can be added below, but note: -// - the matches array above must be expanded. -// - the prefix must with the current logic end with '=' -var prefixes = []*prefix{ - {r: []rune{'s', 'r', 'c', '='}, f: checkCandidateBase}, - {r: []rune{'h', 'r', 'e', 'f', '='}, f: checkCandidateBase}, - {r: []rune{'s', 'r', 'c', 's', 'e', 't', '='}, f: checkCandidateSrcset}, -} - -type absURLMatcher struct { - match []byte - quote []byte -} - -// match check rune inside word. Will be != ' '. -func (l *absurllexer) match(r rune) { - - var found bool - - // note, the prefixes can start off on the same foot, i.e. - // src and srcset. - if l.ms == matchStateWhitespace { - l.idx = 0 - for j, p := range prefixes { - if r == p.r[l.idx] { - l.matches[j] = true - found = true - // checkMatchState will only return true when r=='=', so - // we can safely ignore the return value here. - l.checkMatchState(r, j) - } - } - - if !found { - l.ms = matchStateNone - } - - return - } - - l.idx++ - for j, m := range l.matches { - // still a match? - if m { - if prefixes[j].r[l.idx] == r { - found = true - if l.checkMatchState(r, j) { - return - } - } else { - l.matches[j] = false - } - } - } - - if !found { - l.ms = matchStateNone - } -} - -func (l *absurllexer) checkMatchState(r rune, idx int) bool { - if r == '=' { - l.ms = matchStateFull - for k := range l.matches { - if k != idx { - l.matches[k] = false - } - } - return true - } - - l.ms = matchStatePartial - - return false -} - -func (l *absurllexer) emit() { - l.w.Write(l.content[l.start:l.pos]) - l.start = l.pos -} - -// handle URLs in src and href. -func checkCandidateBase(l *absurllexer) { - for _, m := range l.matchers { - if !bytes.HasPrefix(l.content[l.pos:], m.match) { - continue - } - // check for schemaless URLs - posAfter := l.pos + len(m.match) - if posAfter >= len(l.content) { - return - } - r, _ := utf8.DecodeRune(l.content[posAfter:]) - if r == '/' { - // schemaless: skip - return - } - if l.pos > l.start { - l.emit() - } - l.pos += len(m.match) - l.w.Write(m.quote) - l.w.Write(l.path) - l.start = l.pos - } -} - -// handle URLs in srcset. -func checkCandidateSrcset(l *absurllexer) { - // special case, not frequent (me think) - for _, m := range l.matchers { - if !bytes.HasPrefix(l.content[l.pos:], m.match) { - continue - } - - // check for schemaless URLs - posAfter := l.pos + len(m.match) - if posAfter >= len(l.content) { - return - } - r, _ := utf8.DecodeRune(l.content[posAfter:]) - if r == '/' { - // schemaless: skip - continue - } - - posLastQuote := bytes.Index(l.content[l.pos+1:], m.quote) - - // safe guard - if posLastQuote < 0 || posLastQuote > 2000 { - return - } - - if l.pos > l.start { - l.emit() - } - - section := l.content[l.pos+len(m.quote) : l.pos+posLastQuote+1] - - fields := bytes.Fields(section) - l.w.Write(m.quote) - for i, f := range fields { - if f[0] == '/' { - l.w.Write(l.path) - l.w.Write(f[1:]) - - } else { - l.w.Write(f) - } - - if i < len(fields)-1 { - l.w.Write([]byte(" ")) - } - } - - l.w.Write(m.quote) - l.pos += len(section) + (len(m.quote) * 2) - l.start = l.pos - } -} - -// main loop -func (l *absurllexer) replace() { - contentLength := len(l.content) - var r rune - - for { - if l.pos >= contentLength { - l.width = 0 - break - } - - var width = 1 - r = rune(l.content[l.pos]) - if r >= utf8.RuneSelf { - r, width = utf8.DecodeRune(l.content[l.pos:]) - } - l.width = width - l.pos += l.width - if r == ' ' { - l.ms = matchStateWhitespace - } else if l.ms != matchStateNone { - l.match(r) - if l.ms == matchStateFull { - var p *prefix - for i, m := range l.matches { - if m { - p = prefixes[i] - l.matches[i] = false - } - } - l.ms = matchStateNone - p.f(l) - } - } - } - - // Done! - if l.pos > l.start { - l.emit() - } -} - -func doReplace(ct contentTransformer, matchers []absURLMatcher) { - - lexer := &absurllexer{ - content: ct.Content(), - w: ct, - path: ct.Path(), - matchers: matchers} - - lexer.replace() -} - -type absURLReplacer struct { - htmlMatchers []absURLMatcher - xmlMatchers []absURLMatcher -} - -func newAbsURLReplacer() *absURLReplacer { - - // HTML - dqHTMLMatch := []byte("\"/") - sqHTMLMatch := []byte("'/") - - // XML - dqXMLMatch := []byte(""/") - sqXMLMatch := []byte("'/") - - dqHTML := []byte("\"") - sqHTML := []byte("'") - - dqXML := []byte(""") - sqXML := []byte("'") - - return &absURLReplacer{ - htmlMatchers: []absURLMatcher{ - {dqHTMLMatch, dqHTML}, - {sqHTMLMatch, sqHTML}, - }, - xmlMatchers: []absURLMatcher{ - {dqXMLMatch, dqXML}, - {sqXMLMatch, sqXML}, - }} -} - -func (au *absURLReplacer) replaceInHTML(ct contentTransformer) { - doReplace(ct, au.htmlMatchers) -} - -func (au *absURLReplacer) replaceInXML(ct contentTransformer) { - doReplace(ct, au.xmlMatchers) -} diff --git a/transform/chain.go b/transform/chain.go deleted file mode 100644 index f9c99a04a..000000000 --- a/transform/chain.go +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package transform - -import ( - "bytes" - "io" - - bp "github.com/gohugoio/hugo/bufferpool" -) - -type trans func(rw contentTransformer) - -type link trans - -type chain []link - -// NewChain creates a chained content transformer given the provided transforms. -func NewChain(trs ...link) chain { - return trs -} - -// NewEmptyTransforms creates a new slice of transforms with a capacity of 20. -func NewEmptyTransforms() []link { - return make([]link, 0, 20) -} - -// contentTransformer is an interface that enables rotation of pooled buffers -// in the transformer chain. -type contentTransformer interface { - Path() []byte - Content() []byte - io.Writer -} - -// Implements contentTransformer -// Content is read from the from-buffer and rewritten to to the to-buffer. -type fromToBuffer struct { - path []byte - from *bytes.Buffer - to *bytes.Buffer -} - -func (ft fromToBuffer) Path() []byte { - return ft.path -} - -func (ft fromToBuffer) Write(p []byte) (n int, err error) { - return ft.to.Write(p) -} - -func (ft fromToBuffer) Content() []byte { - return ft.from.Bytes() -} - -func (c *chain) Apply(w io.Writer, r io.Reader, p []byte) error { - - b1 := bp.GetBuffer() - defer bp.PutBuffer(b1) - - if _, err := b1.ReadFrom(r); err != nil { - return err - } - - if len(*c) == 0 { - _, err := b1.WriteTo(w) - return err - } - - b2 := bp.GetBuffer() - defer bp.PutBuffer(b2) - - fb := &fromToBuffer{path: p, from: b1, to: b2} - - for i, tr := range *c { - if i > 0 { - if fb.from == b1 { - fb.from = b2 - fb.to = b1 - fb.to.Reset() - } else { - fb.from = b1 - fb.to = b2 - fb.to.Reset() - } - } - - tr(fb) - } - - _, err := fb.to.WriteTo(w) - return err -} diff --git a/transform/chain_test.go b/transform/chain_test.go deleted file mode 100644 index 7b770ed67..000000000 --- a/transform/chain_test.go +++ /dev/null @@ -1,258 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package transform - -import ( - "bytes" - "path/filepath" - "strings" - "testing" - - "github.com/gohugoio/hugo/helpers" - "github.com/stretchr/testify/assert" -) - -const ( - h5JsContentDoubleQuote = "" - h5JsContentSingleQuote = "" - h5JsContentAbsURL = "" - h5JsContentAbsURLSchemaless = "" - corectOutputSrcHrefSq = "" - - h5XMLXontentAbsURL = "<p><a href="/foobar">foobar</a></p> <p>A video: <iframe src='/foo'></iframe></p>" - correctOutputSrcHrefInXML = "<p><a href="http://base/foobar">foobar</a></p> <p>A video: <iframe src='http://base/foo'></iframe></p>" - h5XMLContentGuarded = "<p><a href="//foobar">foobar</a></p> <p>A video: <iframe src='//foo'></iframe></p>" -) - -const ( - // additional sanity tests for replacements testing - replace1 = "No replacements." - replace2 = "ᚠᛇᚻ ᛒᛦᚦ ᚠᚱᚩᚠᚢᚱ\nᚠᛁᚱᚪ ᚷᛖᚻᚹᛦᛚᚳᚢᛗ" - replace3 = `End of file: src="/` - replace4 = `End of file: srcset="/` - replace5 = `Srcsett with no closing quote: srcset="/img/small.jpg do be do be do.` - - // Issue: 816, schemaless links combined with others - replaceSchemalessHTML = `Pre. src='//schemaless' src='/normal' Schemaless. normal. Post.` - replaceSchemalessHTMLCorrect = `Pre. src='//schemaless' src='http://base/normal' Schemaless. normal. Post.` - replaceSchemalessXML = `Pre. src='//schemaless' src='/normal' Schemaless. normal. Post.` - replaceSchemalessXMLCorrect = `Pre. src='//schemaless' src='http://base/normal' Schemaless. normal. Post.` -) - -const ( - // srcset= - srcsetBasic = `Pre. text` - srcsetBasicCorrect = `Pre. text` - srcsetSingleQuote = `Pre. text POST.` - srcsetSingleQuoteCorrect = `Pre. text POST.` - srcsetXMLBasic = `Pre. "text"` - srcsetXMLBasicCorrect = `Pre. "text"` - srcsetXMLSingleQuote = `Pre. "text"` - srcsetXMLSingleQuoteCorrect = `Pre. "text"` - srcsetVariations = `Pre. -Missing start quote: text src='/img/foo.jpg'> FOO. - -schemaless: -schemaless2: text src='http://base/img/foo.jpg'> FOO. - -schemaless: -schemaless2: `, helpers.CurrentHugoVersion) - -// HugoGeneratorInject injects a meta generator tag for Hugo if none present. -func HugoGeneratorInject(ct contentTransformer) { - if metaTagsCheck.Match(ct.Content()) { - if _, err := ct.Write(ct.Content()); err != nil { - helpers.DistinctWarnLog.Println("Failed to inject Hugo generator tag:", err) - } - return - } - - head := "" - replace := []byte(fmt.Sprintf("%s\n\t%s", head, hugoGeneratorTag)) - newcontent := bytes.Replace(ct.Content(), []byte(head), replace, 1) - - if len(newcontent) == len(ct.Content()) { - head := "" - replace := []byte(fmt.Sprintf("%s\n\t%s", head, hugoGeneratorTag)) - newcontent = bytes.Replace(ct.Content(), []byte(head), replace, 1) - } - - if _, err := ct.Write(newcontent); err != nil { - helpers.DistinctWarnLog.Println("Failed to inject Hugo generator tag:", err) - } - -} diff --git a/transform/hugogeneratorinject_test.go b/transform/hugogeneratorinject_test.go deleted file mode 100644 index d37fea24e..000000000 --- a/transform/hugogeneratorinject_test.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2016 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package transform - -import ( - "bytes" - "strings" - "testing" -) - -func TestHugoGeneratorInject(t *testing.T) { - hugoGeneratorTag = "META" - for i, this := range []struct { - in string - expect string - }{ - {` - -`, ` - META - -`}, - {` - -`, ` - META - -`}, - {``, ``}, - {``, ``}, - {``, ``}, - {``, ``}, - {"", ""}, - {"", ""}, - {"", "\n\tMETA"}, - } { - in := strings.NewReader(this.in) - out := new(bytes.Buffer) - - tr := NewChain(HugoGeneratorInject) - tr.Apply(out, in, []byte("")) - - if out.String() != this.expect { - t.Errorf("[%d] Expected \n%q got \n%q", i, this.expect, out.String()) - } - } - -} diff --git a/transform/livereloadinject.go b/transform/livereloadinject.go deleted file mode 100644 index 4efd0151d..000000000 --- a/transform/livereloadinject.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package transform - -import ( - "bytes" - "fmt" -) - -// LiveReloadInject returns a function that can be used -// to inject a script tag for the livereload JavaScript in a HTML document. -func LiveReloadInject(port int) func(ct contentTransformer) { - return func(ct contentTransformer) { - endBodyTag := "" - match := []byte(endBodyTag) - replaceTemplate := `%s` - replace := []byte(fmt.Sprintf(replaceTemplate, port, endBodyTag)) - - newcontent := bytes.Replace(ct.Content(), match, replace, 1) - if len(newcontent) == len(ct.Content()) { - endBodyTag = "" - replace := []byte(fmt.Sprintf(replaceTemplate, port, endBodyTag)) - match := []byte(endBodyTag) - newcontent = bytes.Replace(ct.Content(), match, replace, 1) - } - - ct.Write(newcontent) - } -} diff --git a/transform/livereloadinject_test.go b/transform/livereloadinject_test.go deleted file mode 100644 index 3337243bd..000000000 --- a/transform/livereloadinject_test.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package transform - -import ( - "bytes" - "fmt" - "strings" - "testing" -) - -func TestLiveReloadInject(t *testing.T) { - doTestLiveReloadInject(t, "") - doTestLiveReloadInject(t, "") -} - -func doTestLiveReloadInject(t *testing.T, bodyEndTag string) { - out := new(bytes.Buffer) - in := strings.NewReader(bodyEndTag) - - tr := NewChain(LiveReloadInject(1313)) - tr.Apply(out, in, []byte("path")) - - expected := fmt.Sprintf(`%s`, bodyEndTag) - if string(out.Bytes()) != expected { - t.Errorf("Expected %s got %s", expected, string(out.Bytes())) - } -} diff --git a/utils/utils.go b/utils/utils.go deleted file mode 100644 index 87357a26e..000000000 --- a/utils/utils.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2016 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package utils - -import ( - "os" - - jww "github.com/spf13/jwalterweatherman" -) - -// CheckErr logs the messages given and then the error. -// TODO(bep) Remove this package. -func CheckErr(logger *jww.Notepad, err error, s ...string) { - if err == nil { - return - } - if len(s) == 0 { - logger.CRITICAL.Println(err) - return - } - for _, message := range s { - logger.ERROR.Println(message) - } - logger.ERROR.Println(err) -} - -// StopOnErr exits on any error after logging it. -func StopOnErr(logger *jww.Notepad, err error, s ...string) { - if err == nil { - return - } - - defer os.Exit(-1) - - if len(s) == 0 { - newMessage := err.Error() - // Printing an empty string results in a error with - // no message, no bueno. - if newMessage != "" { - logger.CRITICAL.Println(newMessage) - } - } - for _, message := range s { - if message != "" { - logger.CRITICAL.Println(message) - } - } -} diff --git a/vendor/vendor.json b/vendor/vendor.json deleted file mode 100644 index 169fa4195..000000000 --- a/vendor/vendor.json +++ /dev/null @@ -1,405 +0,0 @@ -{ - "comment": "", - "ignore": "test", - "package": [ - { - "checksumSHA1": "Pc2ORQp+VY3Un/dkh4QwLC7R6lE=", - "path": "github.com/BurntSushi/toml", - "revision": "a368813c5e648fee92e5f6c30e3944ff9d5e8895", - "revisionTime": "2017-06-26T11:06:00Z" - }, - { - "checksumSHA1": "NX4v3cbkXAJxFlrncqT9yEUBuoA=", - "path": "github.com/PuerkitoBio/purell", - "revision": "b938d81255b5473c57635324295cb0fe398c7a58", - "revisionTime": "2017-03-24T13:41:32Z" - }, - { - "checksumSHA1": "pvmScnaMFuAVLTxxOWjhGZBgPkg=", - "path": "github.com/PuerkitoBio/urlesc", - "revision": "bbf7a2afc14f93e1e0a5c06df524fbd75e5031e5", - "revisionTime": "2017-03-24T14:02:28Z" - }, - { - "checksumSHA1": "7yrV1Gzr1ajco1xJ1gsyqRDTY2U=", - "path": "github.com/bep/gitmap", - "revision": "de8030ebafb76c6e84d50ee6d143382637c00598", - "revisionTime": "2017-06-13T14:57:45Z" - }, - { - "checksumSHA1": "NKoZRlZix5wzCfN0rTg29GtKZRU=", - "path": "github.com/chaseadamsio/goorgeous", - "revision": "677defd0e024333503d8c946dd4ba3f32ad3e5d2", - "revisionTime": "2017-04-27T12:50:10Z" - }, - { - "checksumSHA1": "CCfIpyo6sGH8gnEdP/0vGSi4ywA=", - "path": "github.com/cpuguy83/go-md2man/md2man", - "revision": "23709d0847197db6021a51fdb193e66e9222d4e7", - "revisionTime": "2017-06-03T12:52:39Z" - }, - { - "checksumSHA1": "OFu4xJEIjiI8Suu+j/gabfp+y6Q=", - "origin": "github.com/stretchr/testify/vendor/github.com/davecgh/go-spew/spew", - "path": "github.com/davecgh/go-spew/spew", - "revision": "05e8a0eda380579888eb53c394909df027f06991", - "revisionTime": "2017-07-13T16:51:06Z" - }, - { - "checksumSHA1": "Gg9/TEWjO52UKbEQ6fRB/bpcOW8=", - "path": "github.com/dchest/cssmin", - "revision": "fb8d9b44afdc258bfff6052d3667521babcb2239", - "revisionTime": "2015-12-10T17:00:30Z" - }, - { - "checksumSHA1": "248k9rTfZ4kAknuomoKsdBG9zCU=", - "path": "github.com/eknkc/amber", - "revision": "b8bd8b03e4f747e33f092617225e9fa8076c0448", - "revisionTime": "2017-07-16T09:37:48Z" - }, - { - "checksumSHA1": "gYrgVVwi7z6acHR4xsOnJNQLTS0=", - "path": "github.com/eknkc/amber/parser", - "revision": "b8bd8b03e4f747e33f092617225e9fa8076c0448", - "revisionTime": "2017-07-16T09:37:48Z" - }, - { - "checksumSHA1": "jGqodjjIGBLUCcAenqMYloWpBks=", - "path": "github.com/fortytw2/leaktest", - "revision": "3b724c3d7b8729a35bf4e577f71653aec6e53513", - "revisionTime": "2017-07-15T21:17:39Z" - }, - { - "checksumSHA1": "x2Km0Qy3WgJJnV19Zv25VwTJcBM=", - "path": "github.com/fsnotify/fsnotify", - "revision": "4da3e2cfbabc9f751898f250b49f2439785783a1", - "revisionTime": "2017-03-29T04:21:07Z" - }, - { - "checksumSHA1": "8KawKW6FJguwWz5cPvMg/ChmepE=", - "path": "github.com/gorilla/websocket", - "revision": "a69d9f6de432e2c6b296a947d8a5ee88f68522cf", - "revisionTime": "2017-07-18T20:23:41Z" - }, - { - "checksumSHA1": "Cas2nprG6pWzf05A2F/OlnjUu2Y=", - "path": "github.com/hashicorp/go-immutable-radix", - "revision": "8aac2701530899b64bdea735a1de8da899815220", - "revisionTime": "2017-07-25T22:12:15Z" - }, - { - "checksumSHA1": "9hffs0bAIU6CquiRhKQdzjHnKt0=", - "path": "github.com/hashicorp/golang-lru/simplelru", - "revision": "0a025b7e63adc15a622f29b0b2c4c3848243bbf6", - "revisionTime": "2016-08-13T22:13:03Z" - }, - { - "checksumSHA1": "7JBkp3EZoc0MSbiyWfzVhO4RYoY=", - "path": "github.com/hashicorp/hcl", - "revision": "392dba7d905ed5d04a5794ba89f558b27e2ba1ca", - "revisionTime": "2017-05-05T08:58:37Z" - }, - { - "checksumSHA1": "XQmjDva9JCGGkIecOgwtBEMCJhU=", - "path": "github.com/hashicorp/hcl/hcl/ast", - "revision": "392dba7d905ed5d04a5794ba89f558b27e2ba1ca", - "revisionTime": "2017-05-05T08:58:37Z" - }, - { - "checksumSHA1": "teokXoyRXEJ0vZHOWBD11l5YFNI=", - "path": "github.com/hashicorp/hcl/hcl/parser", - "revision": "392dba7d905ed5d04a5794ba89f558b27e2ba1ca", - "revisionTime": "2017-05-05T08:58:37Z" - }, - { - "checksumSHA1": "z6wdP4mRw4GVjShkNHDaOWkbxS0=", - "path": "github.com/hashicorp/hcl/hcl/scanner", - "revision": "392dba7d905ed5d04a5794ba89f558b27e2ba1ca", - "revisionTime": "2017-05-05T08:58:37Z" - }, - { - "checksumSHA1": "oS3SCN9Wd6D8/LG0Yx1fu84a7gI=", - "path": "github.com/hashicorp/hcl/hcl/strconv", - "revision": "392dba7d905ed5d04a5794ba89f558b27e2ba1ca", - "revisionTime": "2017-05-05T08:58:37Z" - }, - { - "checksumSHA1": "c6yprzj06ASwCo18TtbbNNBHljA=", - "path": "github.com/hashicorp/hcl/hcl/token", - "revision": "392dba7d905ed5d04a5794ba89f558b27e2ba1ca", - "revisionTime": "2017-05-05T08:58:37Z" - }, - { - "checksumSHA1": "PwlfXt7mFS8UYzWxOK5DOq0yxS0=", - "path": "github.com/hashicorp/hcl/json/parser", - "revision": "392dba7d905ed5d04a5794ba89f558b27e2ba1ca", - "revisionTime": "2017-05-05T08:58:37Z" - }, - { - "checksumSHA1": "YdvFsNOMSWMLnY6fcliWQa0O5Fw=", - "path": "github.com/hashicorp/hcl/json/scanner", - "revision": "392dba7d905ed5d04a5794ba89f558b27e2ba1ca", - "revisionTime": "2017-05-05T08:58:37Z" - }, - { - "checksumSHA1": "fNlXQCQEnb+B3k5UDL/r15xtSJY=", - "path": "github.com/hashicorp/hcl/json/token", - "revision": "392dba7d905ed5d04a5794ba89f558b27e2ba1ca", - "revisionTime": "2017-05-05T08:58:37Z" - }, - { - "checksumSHA1": "40vJyUB4ezQSn/NSadsKEOrudMc=", - "path": "github.com/inconshreveable/mousetrap", - "revision": "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75", - "revisionTime": "2014-10-17T20:07:13Z" - }, - { - "checksumSHA1": "ywE9KA40kVq0qKcAIqLgpoA0su4=", - "path": "github.com/jdkato/prose/internal/util", - "revision": "c24611cae00c16858e611ef77226dd2f7502759f", - "revisionTime": "2017-07-29T20:17:14Z" - }, - { - "checksumSHA1": "SpQ8EpkRvM9fAxEXQAy7Qy/L0Ig=", - "path": "github.com/jdkato/prose/transform", - "revision": "c24611cae00c16858e611ef77226dd2f7502759f", - "revisionTime": "2017-07-29T20:17:14Z" - }, - { - "checksumSHA1": "gEjGS03N1eysvpQ+FCHTxPcbxXc=", - "path": "github.com/kardianos/osext", - "revision": "ae77be60afb1dcacde03767a8c37337fad28ac14", - "revisionTime": "2017-05-10T13:15:34Z" - }, - { - "checksumSHA1": "u6Fh5nWSW70Yi2/hq/zxPinakD4=", - "path": "github.com/kyokomi/emoji", - "revision": "ddd4753eac3f6480ca86b16cc6c98d26a0935d17", - "revisionTime": "2017-05-19T01:14:27Z" - }, - { - "checksumSHA1": "up35HgA0/UxlprRI0fUSpiGhZvg=", - "path": "github.com/magiconair/properties", - "revision": "be5ece7dd465ab0765a9682137865547526d1dfb", - "revisionTime": "2017-07-10T12:48:59Z" - }, - { - "checksumSHA1": "2XtmBo7p3fb4n4kTvI9lZhQra+o=", - "path": "github.com/markbates/inflect", - "revision": "6cacb66d100482ef7cc366289ccb156020e57e76", - "revisionTime": "2017-04-11T19:10:01Z" - }, - { - "checksumSHA1": "Q0kIzeNiYBp4e336hnORWNTV80A=", - "path": "github.com/miekg/mmark", - "revision": "fd2f6c1403b37925bd7fe13af05853b8ae58ee5f", - "revisionTime": "2017-07-10T20:28:41Z" - }, - { - "checksumSHA1": "EHjhpHipgm+XGccrRAms9AW3Ewk=", - "path": "github.com/mitchellh/mapstructure", - "revision": "d0303fe809921458f417bcf828397a65db30a7e4", - "revisionTime": "2017-05-23T03:00:23Z" - }, - { - "checksumSHA1": "gDe7nlx3FyCVxLkARgl0VAntDRk=", - "path": "github.com/nicksnyder/go-i18n/i18n/bundle", - "revision": "3e70a1a463008cea6726380c908b1a6a8bdf7b24", - "revisionTime": "2017-05-12T15:20:54Z" - }, - { - "checksumSHA1": "+XOg99I1zdmBRUb04ZswvzQ2WS0=", - "path": "github.com/nicksnyder/go-i18n/i18n/language", - "revision": "3e70a1a463008cea6726380c908b1a6a8bdf7b24", - "revisionTime": "2017-05-12T15:20:54Z" - }, - { - "checksumSHA1": "WZOU406In2hs8FJOHWqV8PWkJKs=", - "path": "github.com/nicksnyder/go-i18n/i18n/translation", - "revision": "3e70a1a463008cea6726380c908b1a6a8bdf7b24", - "revisionTime": "2017-05-12T15:20:54Z" - }, - { - "checksumSHA1": "F1IYMLBLAZaTOWnmXsgaxTGvrWI=", - "path": "github.com/pelletier/go-buffruneio", - "revision": "c37440a7cf42ac63b919c752ca73a85067e05992", - "revisionTime": "2017-02-27T22:03:11Z" - }, - { - "checksumSHA1": "zZg0J0MqvnqXVYo644QDvnUinrc=", - "path": "github.com/pelletier/go-toml", - "revision": "69d355db5304c0f7f809a2edc054553e7142f016", - "revisionTime": "2017-06-28T01:26:37Z" - }, - { - "checksumSHA1": "zKKp5SZ3d3ycKe4EKMNT0BqAWBw=", - "origin": "github.com/stretchr/testify/vendor/github.com/pmezard/go-difflib/difflib", - "path": "github.com/pmezard/go-difflib/difflib", - "revision": "05e8a0eda380579888eb53c394909df027f06991", - "revisionTime": "2017-07-13T16:51:06Z" - }, - { - "checksumSHA1": "OZpv1QDSDG23bzSk/7auPQRB2Qg=", - "path": "github.com/russross/blackfriday", - "revision": "4048872b16cc0fc2c5fd9eacf0ed2c2fedaa0c8c", - "revisionTime": "2017-07-28T17:53:26Z" - }, - { - "checksumSHA1": "7xtI4YHA5UU4Ra/A5tBZ6rxzqCg=", - "path": "github.com/shurcooL/sanitized_anchor_name", - "revision": "541ff5ee47f1dddf6a5281af78307d921524bcb5", - "revisionTime": "2017-05-15T01:32:39Z" - }, - { - "checksumSHA1": "lBehULzb2/kIK3wZ0gz2yNmHq9s=", - "path": "github.com/spf13/afero", - "revision": "9be650865eab0c12963d8753212f4f9c66cdcf12", - "revisionTime": "2017-02-17T16:41:46Z" - }, - { - "checksumSHA1": "5KRbEQ28dDaQmKwAYTD0if/aEvg=", - "path": "github.com/spf13/afero/mem", - "revision": "9be650865eab0c12963d8753212f4f9c66cdcf12", - "revisionTime": "2017-02-17T16:41:46Z" - }, - { - "checksumSHA1": "Sq0QP4JywTr7UM4hTK1cjCi7jec=", - "path": "github.com/spf13/cast", - "revision": "acbeb36b902d72a7a4c18e8f3241075e7ab763e4", - "revisionTime": "2017-04-13T08:50:28Z" - }, - { - "checksumSHA1": "43IRTqKhvUH3i2J0UV0iUcIvucw=", - "path": "github.com/spf13/cobra", - "revision": "34594c771f2c18301dc152640ad40ece28795373", - "revisionTime": "2017-07-24T19:36:59Z" - }, - { - "checksumSHA1": "9umiInkHtYqaxDZwVUd2U1cHp7k=", - "path": "github.com/spf13/cobra/doc", - "revision": "34594c771f2c18301dc152640ad40ece28795373", - "revisionTime": "2017-07-24T19:36:59Z" - }, - { - "checksumSHA1": "TmmnmNJgWmRK0eMo+43gTzVA8zI=", - "path": "github.com/spf13/fsync", - "revision": "12a01e648f05a938100a26858d2d59a120307a18", - "revisionTime": "2017-03-20T14:25:52Z" - }, - { - "checksumSHA1": "mZa8ukZwyAZ86E3oVjhnnKSlizs=", - "path": "github.com/spf13/jwalterweatherman", - "revision": "0efa5202c04663c757d84f90f5219c1250baf94f", - "revisionTime": "2017-05-23T09:39:43Z" - }, - { - "checksumSHA1": "zLJY+lsX1e5OO6gRxQd5RfKgdQY=", - "path": "github.com/spf13/nitro", - "revision": "24d7ef30a12da0bdc5e2eb370a79c659ddccf0e8", - "revisionTime": "2013-10-03T13:43:07Z" - }, - { - "checksumSHA1": "STxYqRb4gnlSr3mRpT+Igfdz/kM=", - "path": "github.com/spf13/pflag", - "revision": "e57e3eeb33f795204c1ca35f56c44f83227c6e66", - "revisionTime": "2017-05-08T18:43:26Z" - }, - { - "checksumSHA1": "XeB0yeQkOMI+CWrllrbTrg4iOEs=", - "path": "github.com/spf13/viper", - "revision": "25b30aa063fc18e48662b86996252eabdcf2f0c7", - "revisionTime": "2017-07-23T05:52:07Z" - }, - { - "checksumSHA1": "J+ppEq6nYMKxhB9+c+MD1x9UwSc=", - "path": "github.com/stretchr/testify/assert", - "revision": "05e8a0eda380579888eb53c394909df027f06991", - "revisionTime": "2017-07-13T16:51:06Z" - }, - { - "checksumSHA1": "7vs6dSc1PPGBKyzb/SCIyeMJPLQ=", - "path": "github.com/stretchr/testify/require", - "revision": "05e8a0eda380579888eb53c394909df027f06991", - "revisionTime": "2017-07-13T16:51:06Z" - }, - { - "checksumSHA1": "U4bWGZ3c9p8FHrgU5l4l5i7A6bo=", - "path": "github.com/yosssi/ace", - "revision": "ea038f4770b6746c3f8f84f14fa60d9fe1205b56", - "revisionTime": "2016-07-28T07:45:28Z" - }, - { - "checksumSHA1": "zdekzNuFGSoxAZ8cURGsrhBObZs=", - "path": "golang.org/x/image/riff", - "revision": "426cfd8eeb6e08ab1932954e09e3c2cb2bc6e36d", - "revisionTime": "2017-05-14T06:33:48Z" - }, - { - "checksumSHA1": "ebUbLKyTEaupuKj5KsceDfkn+UA=", - "path": "golang.org/x/image/vp8", - "revision": "426cfd8eeb6e08ab1932954e09e3c2cb2bc6e36d", - "revisionTime": "2017-05-14T06:33:48Z" - }, - { - "checksumSHA1": "MF/A3WDD30iVwlktK0itZ0PTJho=", - "path": "golang.org/x/image/vp8l", - "revision": "426cfd8eeb6e08ab1932954e09e3c2cb2bc6e36d", - "revisionTime": "2017-05-14T06:33:48Z" - }, - { - "checksumSHA1": "wwirbKM4d69iWA4s9JwpXTsda3A=", - "path": "golang.org/x/image/webp", - "revision": "426cfd8eeb6e08ab1932954e09e3c2cb2bc6e36d", - "revisionTime": "2017-05-14T06:33:48Z" - }, - { - "checksumSHA1": "g/Z/Ka0VgJESgQK7/SJCjm/aX0I=", - "path": "golang.org/x/net/idna", - "revision": "f5079bd7f6f74e23c4d65efa0f4ce14cbd6a3c0f", - "revisionTime": "2017-07-19T21:11:51Z" - }, - { - "checksumSHA1": "K3C9lD5XgLfXd25MtZe3Uw5BRoA=", - "path": "golang.org/x/sys/unix", - "revision": "35ef4487ce0a1ea5d4b616ffe71e34febe723695", - "revisionTime": "2017-07-27T13:28:57Z" - }, - { - "checksumSHA1": "faFDXp++cLjLBlvsr+izZ+go1WU=", - "path": "golang.org/x/text/secure/bidirule", - "revision": "836efe42bb4aa16aaa17b9c155d8813d336ed720", - "revisionTime": "2017-07-09T00:38:22Z" - }, - { - "checksumSHA1": "ziMb9+ANGRJSSIuxYdRbA+cDRBQ=", - "path": "golang.org/x/text/transform", - "revision": "836efe42bb4aa16aaa17b9c155d8813d336ed720", - "revisionTime": "2017-07-09T00:38:22Z" - }, - { - "checksumSHA1": "MRLtTu/vpd18le8/HPLxOdjO5RE=", - "path": "golang.org/x/text/unicode/bidi", - "revision": "836efe42bb4aa16aaa17b9c155d8813d336ed720", - "revisionTime": "2017-07-09T00:38:22Z" - }, - { - "checksumSHA1": "kKylzIrLEnH8NKyeVAL0dq5gjVQ=", - "path": "golang.org/x/text/unicode/norm", - "revision": "836efe42bb4aa16aaa17b9c155d8813d336ed720", - "revisionTime": "2017-07-09T00:38:22Z" - }, - { - "checksumSHA1": "mfeW9NCKg58o6w5bbXPvpixpIw0=", - "path": "golang.org/x/text/width", - "revision": "836efe42bb4aa16aaa17b9c155d8813d336ed720", - "revisionTime": "2017-07-09T00:38:22Z" - }, - { - "checksumSHA1": "o20lmjzBQyKD5LfLZ3OhUoMkLds=", - "path": "gopkg.in/yaml.v2", - "revision": "25c4ec802a7d637f88d584ab26798e94ad14c13b", - "revisionTime": "2017-07-21T12:20:51Z" - } - ], - "rootPath": "github.com/gohugoio/hugo" -} diff --git a/watcher/batcher.go b/watcher/batcher.go deleted file mode 100644 index 6f4b276cf..000000000 --- a/watcher/batcher.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2015 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package watcher - -import ( - "time" - - "github.com/fsnotify/fsnotify" -) - -// Batcher batches file watch events in a given interval. -type Batcher struct { - *fsnotify.Watcher - interval time.Duration - done chan struct{} - - Events chan []fsnotify.Event // Events are returned on this channel -} - -// New creates and starts a Batcher with the given time interval. -func New(interval time.Duration) (*Batcher, error) { - watcher, err := fsnotify.NewWatcher() - - batcher := &Batcher{} - batcher.Watcher = watcher - batcher.interval = interval - batcher.done = make(chan struct{}, 1) - batcher.Events = make(chan []fsnotify.Event, 1) - - if err == nil { - go batcher.run() - } - - return batcher, err -} - -func (b *Batcher) run() { - tick := time.Tick(b.interval) - evs := make([]fsnotify.Event, 0) -OuterLoop: - for { - select { - case ev := <-b.Watcher.Events: - evs = append(evs, ev) - case <-tick: - if len(evs) == 0 { - continue - } - b.Events <- evs - evs = make([]fsnotify.Event, 0) - case <-b.done: - break OuterLoop - } - } - close(b.done) -} - -// Close stops the watching of the files. -func (b *Batcher) Close() { - b.done <- struct{}{} - b.Watcher.Close() -}