ref: 6e16449e5fca5df192de04430af26a46ea9ceb2d
parent: 50a1d6f3f155ab837310e00ffb309a9199773c73
author: spf13 <[email protected]>
date: Thu Jul 4 07:32:55 EDT 2013
adding hugo
--- /dev/null
+++ b/LICENSE.md
@@ -1,0 +1,67 @@
+Simple Public License (SimPL-2.0)
+=================================
+
+Preamble
+--------
+
+This Simple Public License 2.0 (SimPL-2.0 for short) is a plain language
+implementation of GPL 2.0. The words are different, but the goal is the
+same - to guarantee for all users the freedom to share and change
+software. If anyone wonders about the meaning of the SimPL, they should
+interpret it as consistent with GPL 2.0.
+
+
+Simple Public License (SimPL) 2.0
+=================================
+
+The SimPL applies to the software's source and object code and comes
+with any rights that I have in it (other than trademarks). You agree to
+the SimPL by copying, distributing, or making a derivative work of the
+software.
+
+ You get the royalty free right to:
+
+- Use the software for any purpose;
+- Make derivative works of it (this is called a "Derived Work");
+- Copy and distribute it and any Derived Work.
+
+If you distribute the software or a Derived Work, you must give back to
+the community by:
+
+- Prominently noting the date of any changes you make;
+- Leaving other people's copyright notices, warranty disclaimers, and
+ license terms in place;
+- Providing the source code, build scripts, installation scripts, and
+ interface definitions in a form that is easy to get and best to
+ modify;
+- Licensing it to everyone under SimPL, or substantially similar terms
+ (such as GPL 2.0), without adding further restrictions to the rights
+ provided;
+- Conspicuously announcing that it is available under that license.
+
+There are some things that you must shoulder:
+
+- You get NO WARRANTIES. None of any kind;
+- If the software damages you in any way, you may only recover direct
+ damages up to the amount you paid for it (that is zero if you did
+ not pay anything). You may not recover any other damages, including
+ those called "consequential damages." (The state or country where
+ you live may not allow you to limit your liability in this way, so
+ this may not apply to you);
+
+The SimPL continues perpetually, except that your license rights end
+automatically if:
+
+- You do not abide by the "give back to the community" terms (your
+ licensees get to keep their rights if they abide);
+- Anyone prevents you from distributing the software under the terms
+ of the SimPL.
+
+License for the License
+-----------------------
+
+You may do anything that you want with the SimPL text; it's a license
+form to use in any way that you find helpful. To avoid confusion,
+however, if you change the terms in any way then you may not call your
+license the Simple Public License or the SimPL (but feel free to
+acknowledge that your license is "based on the Simple Public License").
--- /dev/null
+++ b/docs/config.json
@@ -1,0 +1,4 @@
+{
+ "Indexes" : {"tag": "tags"},
+ "BaseUrl" : "http://localhost"
+}
--- /dev/null
+++ b/docs/content/doc/configuration.md
@@ -1,0 +1,19 @@
+{
+ "title": "Configuring Hugo",
+ "Pubdate": "2013-07-01"
+}
+
+The directory structure and templates provide the majority of the
+configuration for a site. In fact a config file isn't even needed for many websites
+since the defaults used follow commonly used patterns.
+
+The following is an example of a config file with the default values
+
+ {
+ "SourceDir" : "content",
+ "LayoutDir" : "layouts",
+ "PublishDir" : "public",
+ "BuildDrafts" : false,
+ "Tags" : { "category" : "categories", "tag" : "tags" },
+ "BaseUrl" : "http://yourSite.com/"
+ }
--- /dev/null
+++ b/docs/content/doc/contributing.md
@@ -1,0 +1,10 @@
+{
+ "title": "Contributing to Hugo",
+ "Pubdate": "2013-07-01"
+}
+
+1. Fork it from https://github.com/spf13/hugo
+2. Create your feature branch (`git checkout -b my-new-feature`)
+3. Commit your changes (`git commit -am 'Add some feature'`)
+4. Push to the branch (`git push origin my-new-feature`)
+5. Create new Pull Request
--- /dev/null
+++ b/docs/content/doc/contributors.md
@@ -1,0 +1,9 @@
+{
+ "title": "Contributors",
+ "Pubdate": "2013-07-01"
+}
+
+Hugo was built with love and golang by:
+
+* [spf13](https://github.com/spf13)
+
--- /dev/null
+++ b/docs/content/doc/example.md
@@ -1,0 +1,40 @@
+{
+ "title": "Example Content File",
+ "Pubdate": "2013-07-01"
+}
+
+Somethings are better shown than explained. The following is a very basic example of a content file:
+
+**mysite/project/nitro.md <- http://mysite.com/project/nitro.html**
+
+ {
+ "Title": "Nitro : A quick and simple profiler for golang",
+ "Description": "",
+ "Keywords": [ "Development", "golang", "profiling" ],
+ "Tags": [ "Development", "golang", "profiling" ],
+ "Pubdate": "2013-06-19",
+ "Topics": [ "Development", "GoLang" ],
+ "Slug": "nitro",
+ "project_url": "http://github.com/spf13/nitro"
+ }
+
+ # Nitro
+
+ Quick and easy performance analyzer library for golang.
+
+ ## Overview
+
+ Nitro is a quick and easy performance analyzer library for golang.
+ It is useful for comparing A/B against different drafts of functions
+ or different functions.
+
+ ## Implementing Nitro
+
+ Using Nitro is simple. First use go get to install the latest version
+ of the library.
+
+ $ go get github.com/spf13/nitro
+
+ Next include nitro in your application.
+
+
--- /dev/null
+++ b/docs/content/doc/front-matter.md
@@ -1,0 +1,38 @@
+{
+ "title": "Front Matter",
+ "Pubdate": "2013-07-01"
+}
+
+The front matter is one of the features that gives Hugo it's strength. It enables
+you to include the meta data of the content right with it. Hugo supports a few
+different formats. The main format supported is JSON. Here is an 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" ],
+ "Pubdate": "2012-04-06",
+ "Categories": [ "Development", "VIM" ],
+ "Slug": "spf13-vim-3-0-release-and-new-website"
+ }
+
+### Variables
+There are a few predefined variables that Hugo is aware of and utilizes. The user can also create
+any variable they want to. These will be placed into the `.Params` variable available to the templates.
+
+#### Required
+
+**Title** The title for the content. <br>
+**Description** The description for the content.<br>
+**Pubdate** The date the content will be sorted by.<br>
+**Indexes** These will use the field name of the plural form of the index (see tags and categories above)
+
+#### Optional
+
+**Draft** If true the content will not be rendered unless `hugo` is called with -d<br>
+**Type** The type of the content (will be derived from the directory automatically if unset).<br>
+**Slug** The token to appear in the tail of the url.<br>
+ *or*<br>
+**Url** The full path to the content from the web root.<br>
+*If neither is present the filename will be used.*
+
--- /dev/null
+++ b/docs/content/doc/installing.md
@@ -1,0 +1,18 @@
+{
+ "title": "Installing Hugo",
+ "Pubdate": "2013-07-01"
+}
+
+Installation is very easy. Simply download the appropriate version for your
+platform.
+
+Hugo is written in GoLang with support for Windows, Linux and OSX.
+
+<div class="alert alert-info">
+Please make sure that you place the executable in your path. `/usr/local/bin`
+is the most probable location.
+</div>
+
+
+Hugo doesn't have any external dependencies, but can benefit from external
+programs.
--- /dev/null
+++ b/docs/content/doc/license.md
@@ -1,0 +1,75 @@
+{
+ "title": "License",
+ "Pubdate": "2013-07-01"
+}
+
+Hugo is released under the Simple Public License.
+
+
+Simple Public License (SimPL-2.0)
+=================================
+
+Preamble
+--------
+
+This Simple Public License 2.0 (SimPL-2.0 for short) is a plain language
+implementation of GPL 2.0. The words are different, but the goal is the
+same - to guarantee for all users the freedom to share and change
+software. If anyone wonders about the meaning of the SimPL, they should
+interpret it as consistent with GPL 2.0.
+
+
+Simple Public License (SimPL) 2.0
+=================================
+
+The SimPL applies to the software's source and object code and comes
+with any rights that I have in it (other than trademarks). You agree to
+the SimPL by copying, distributing, or making a derivative work of the
+software.
+
+ You get the royalty free right to:
+
+- Use the software for any purpose;
+- Make derivative works of it (this is called a "Derived Work");
+- Copy and distribute it and any Derived Work.
+
+If you distribute the software or a Derived Work, you must give back to
+the community by:
+
+- Prominently noting the date of any changes you make;
+- Leaving other people's copyright notices, warranty disclaimers, and
+ license terms in place;
+- Providing the source code, build scripts, installation scripts, and
+ interface definitions in a form that is easy to get and best to
+ modify;
+- Licensing it to everyone under SimPL, or substantially similar terms
+ (such as GPL 2.0), without adding further restrictions to the rights
+ provided;
+- Conspicuously announcing that it is available under that license.
+
+There are some things that you must shoulder:
+
+- You get NO WARRANTIES. None of any kind;
+- If the software damages you in any way, you may only recover direct
+ damages up to the amount you paid for it (that is zero if you did
+ not pay anything). You may not recover any other damages, including
+ those called "consequential damages." (The state or country where
+ you live may not allow you to limit your liability in this way, so
+ this may not apply to you);
+
+The SimPL continues perpetually, except that your license rights end
+automatically if:
+
+- You do not abide by the "give back to the community" terms (your
+ licensees get to keep their rights if they abide);
+- Anyone prevents you from distributing the software under the terms
+ of the SimPL.
+
+License for the License
+-----------------------
+
+You may do anything that you want with the SimPL text; it's a license
+form to use in any way that you find helpful. To avoid confusion,
+however, if you change the terms in any way then you may not call your
+license the Simple Public License or the SimPL (but feel free to
+acknowledge that your license is "based on the Simple Public License").
--- /dev/null
+++ b/docs/content/doc/organization.md
@@ -1,0 +1,22 @@
+{
+ "title": "Organization",
+ "Pubdate": "2013-07-01"
+}
+
+Hugo uses markdown files with headers commonly called the front matter. Hugo respects the organization
+that you provide for your content to minimize any extra configuration, though this can be overridden
+by additional configuration in the front matter.
+
+## Organization
+In Hugo the content should be arranged in the same way they are intended for the rendered website.
+Without any additional configuration the following will just work.
+
+ .
+ └── content
+ ├── post
+ | ├── firstpost.md // <- http://site.com/post/firstpost.html
+ | └── secondpost.md // <- http://site.com/post/secondpost.html
+ └── quote
+ ├── first.md // <- http://site.com/quote/first.html
+ └── second.md // <- http://site.com/quote/second.html
+
--- /dev/null
+++ b/docs/content/doc/release-notes.md
@@ -1,0 +1,14 @@
+{
+ "title": "Release Notes",
+ "Pubdate": "2013-07-01"
+
+}
+
+* **0.7.0** July 4, 2013
+ * Hugo now includes a simple server
+ * First public release
+* **0.6.0** July 2, 2013
+ * Hugo includes an example documentation site which it builds
+* **0.5.0** June 25, 2013
+ * Hugo is quite usable and able to build spf13.com
+
--- /dev/null
+++ b/docs/content/doc/roadmap.md
@@ -1,0 +1,18 @@
+{
+ "title": "Roadmap",
+ "Pubdate": "2013-07-01"
+}
+
+In no particular order, here is what I'm working on:
+
+ * Pagination
+ * Support for top level pages (other than homepage)
+ * Series support
+ * Syntax highlighting
+ * Previous & Next
+ * Related Posts
+ * Support for TOML front matter
+ * Proper YAML support for front matter
+ * Support for other formats
+
+
--- /dev/null
+++ b/docs/content/doc/shortcodes.md
@@ -1,0 +1,76 @@
+{
+ "title": "Shortcodes",
+ "Pubdate": "2013-07-01"
+}
+
+Because Hugo uses markdown for it's content format, it was clear that there's a lot of things that
+markdown doesn't support well. This is good, the simple nature of markdown is exactly why we chose it.
+
+However we cannot accept being constrained by our simple format. Also unacceptable is writing raw
+html in our markdown every time we want to include unsupported content such as a video. To do
+so is in complete opposition to the intent of using a bare bones format for our content and
+utilizing templates to apply styling for display.
+
+To avoid both of these limitations Hugo has full support for shortcodes.
+
+### What is a shortcode?
+A shortcode is a simple snippet inside a markdown file that Hugo will render using a template.
+
+Short codes are designated by the opening and closing characters of '{{%' and '%}}' respectively.
+Short codes are space delimited. The first word is always the name of the shortcode. Following the
+name are the parameters. The author of the shortcode can choose if the short code
+will use positional parameters or named parameters (but not both). A good rule of thumb is that if a
+short code has a single required value in the case of the youtube example below then positional
+works very well. For more complex layouts with optional parameters named parameters work best.
+
+The format for named parameters models that of html with the format name="value"
+
+### Example: youtube
+*Example has an extra space so Hugo doesn't actually render it*
+
+ {{ % youtube 09jf3ow9jfw %}}
+
+This would be rendered as
+
+ <div class="embed video-player">
+ <iframe class="youtube-player" type="text/html"
+ width="640" height="385"
+ src="http://www.youtube.com/embed/09jf3ow9jfw"
+ allowfullscreen frameborder="0">
+ </iframe>
+ </div>
+
+### Example: image with caption
+*Example has an extra space so Hugo doesn't actually render it*
+
+ {{ % img src="/media/spf13.jpg" title="Steve Francia" %}}
+
+Would be rendered as:
+
+ <figure >
+ <img src="/media/spf13.jpg" />
+ <figcaption>
+ <h4>Steve Francia</h4>
+ </figcaption>
+ </figure>
+
+
+### Creating a shortcode
+
+All that you need to do to create a shortcode is place a template in the layouts/shortcodes directory.
+
+The template name will be the name of the shortcode.
+
+**Inside the template**
+
+To access a parameter by either position or name the index method can be used.
+
+ {{ index .Params 0 }}
+ or
+ {{ index .Params "class" }}
+
+To check if a parameter has been provided use the isset method provided by Hugo.
+
+ {{ if isset .Params "class"}} class="{{ index .Params "class"}}" {{ end }}
+
+
--- /dev/null
+++ b/docs/content/doc/source-directory.md
@@ -1,0 +1,54 @@
+{
+ "title": "Source Directory Organization",
+ "Pubdate": "2013-07-01"
+}
+
+Hugo takes a single directory and uses it as the input for creating a complete website.
+
+Hugo has a very small amount of configuration, while remaining highly customizable.
+It accomplishes by assuming that you will only provide templates with the intent of
+using them.
+
+An example directory may look like:
+
+ .
+ ├── config.json
+ ├── content
+ | ├── post
+ | | ├── firstpost.md
+ | | └── secondpost.md
+ | └── quote
+ | | ├── first.md
+ | | └── second.md
+ ├── layouts
+ | ├── chrome
+ | | ├── header.html
+ | | └── footer.html
+ | ├── indexes
+ | | ├── category.html
+ | | ├── post.html
+ | | ├── quote.html
+ | | └── tag.html
+ | ├── post
+ | | ├── li.html
+ | | ├── single.html
+ | | └── summary.html
+ | ├── quote
+ | | ├── li.html
+ | | ├── single.html
+ | | └── summary.html
+ | ├── shortcodes
+ | | ├── img.html
+ | | ├── vimeo.html
+ | | └── youtube.html
+ | ├── index.html
+ | └── rss.xml
+ └── public
+
+This directory structure tells us a lot about this site:
+
+1. the website intends to have two different types of content, posts and quotes.
+2. It will also apply two different indexes to that content, categories and tags.
+3. It will be displaying content in 3 different views, a list, a summary and a full page view.
+
+Included with the repository is an this example site ready to be rendered.
--- /dev/null
+++ b/docs/content/doc/templates.md
@@ -1,0 +1,66 @@
+{
+ "title": "Templates",
+ "Pubdate": "2013-07-01"
+}
+
+Hugo uses the excellent golang html/template library for it's template engine. It is an extremely
+lightweight engine that provides a very small amount of logic. In our
+experience that it is just the right amount of logic to be able to create a good static website
+
+This document will not cover how to use golang templates, but the [golang docs](http://golang.org/pkg/html/template/)
+provide a good introduction.
+
+### Template roles
+
+There are 5 different kinds of templates that Hugo works with.
+
+#### index.html
+This file must exist in the layouts directory. It is the template used to render the
+homepage of your site.
+
+#### rss.xml
+This file must exist in the layouts directory. It will be used to render all rss documents.
+The one provided in the example application will generate an ATOM format.
+
+*Important: Hugo will automatically add the following header line to this file.*
+
+ <?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+
+#### Indexes
+An index is a page that list multiple pieces of content. If you think of a typical blog, the tag
+pages are good examples of indexes.
+
+
+#### Content Type(s)
+Hugo supports multiple types of content. Another way of looking at this is that Hugo has the ability
+to render content in a variety of ways as determined by the type.
+
+#### Chrome
+Chrome is simply the decoration of your site. It's not a requirement to have this, but in practice
+it's very convenient. Hugo doesn't know anything about Chrome, it's simply a convention that you may
+likely find beneficial. As you create the rest of your templates you will include templates from the
+/layout/chrome directory. I've found it helpful to include a header and footer template
+in Chrome so I can include those in the other full page layouts (index.html, indexes/ type/single.html).
+
+### Adding a new content type
+
+Adding a type is easy.
+
+**Step 1:**
+Create a directory with the name of the type in layouts.Type is always singular. *Eg /layouts/post*.
+
+**Step 2:**
+Create a file called single.html inside your directory. *Eg /layouts/post/single.html*.
+
+**Step 3:**
+Create a file with the same name as your directory in /layouts/indexes/. *Eg /layouts/index/post.html*.
+
+**Step 4:**
+Many sites support rendering content in a few different ways, for instance a single page view and a
+summary view to be used when displaying a list of contents on a single page. Hugo makes no assumptions
+here about how you want to display your content, and will support as many different views of a content
+type as your site requires. All that is required for these additional views is that a template
+exists in each layout/type directory with the same name.
+
+For these, reviewing this example site will be very helpful in order to understand how these types work.
+
--- /dev/null
+++ b/docs/content/doc/usage.md
@@ -1,0 +1,52 @@
+{
+ "title": "Using Hugo",
+ "Pubdate": "2013-07-01"
+}
+
+Make sure either hugo is in your path or provide a path to it.
+
+ $ hugo --help
+ usage: hugo [flags] []
+ -b="": hostname (and path) to the root eg. http://spf13.com/
+ -c="config.json": config file (default is path/config.json)
+ -d=false: include content marked as draft
+ -h=false: show this help
+ -k=false: analyze content and provide feedback
+ -p="": filesystem path to read files relative from
+ -w=false: watch filesystem for changes and recreate as needed
+ -s=false: a (very) simple webserver
+ -p="1313": port for webserver to run on
+
+## Common Usage Example:
+
+The most common use is probably to run hugo with your current
+directory being the input directory.
+
+
+ $ hugo
+ > X pages created
+ > Y indicies created
+
+
+If you are working on things and want to see the changes
+immediately, tell Hugo to watch for changes.
+<br>
+**It will
+recreate the site faster than you can tab over to
+your browser to view the changes.**
+
+ $ hugo -p ~/mysite -w
+ Watching for changes. Press ctrl+c to stop
+ 15 pages created
+ 0 tags created
+
+Hugo can even run a server and create your site at the same time!
+
+ $hugo -p ~/mysite -w -s
+ Watching for changes. Press ctrl+c to stop
+ 15 pages created
+ 0 tags created
+ Web Server is available at http://localhost:1313
+ Press ctrl+c to stop
+
+
--- /dev/null
+++ b/docs/content/doc/variables.md
@@ -1,0 +1,29 @@
+{
+ "title": "Variables",
+ "Pubdate": "2013-07-01"
+}
+
+Hugo makes a set of values available to the templates. Go templates are context based. The following
+are available in the context for the templates.
+
+**.Title** The title for the content. <br>
+**.Description** The description for the content.<br>
+**.Keywords** The meta keywords for this content.<br>
+**.Date** The date the content is published on.<br>
+**.Indexes** These will use the field name of the plural form of the index (see tags and categories above)<br>
+**.Permalink** The Permanent link for this page.<br>
+**.FuzzyWordCount** The approximate number of words in the content.<br>
+**.RSSLink** Link to the indexes' rss link <br>
+
+Any value defined in the front matter, including indexes will be made available under `.Params`.
+Take for example I'm using tags and categories as my indexes. The following would be how I would access them:
+
+**.Params.Tags** <br>
+**.Params.Categories** <br>
+
+Also available is `.Site` which has the following:
+
+**.Site.BaseUrl** The base URL for the site as defined in the config.json file.<br>
+**.Site.Indexes** The names of the indexes of the site.<br>
+**.Site.LastChange** The date of the last change of the most recent content.<br>
+**.Site.Recent** Array of all content ordered by Date, newest first<br>
--- /dev/null
+++ b/docs/layouts/chrome/footer.html
@@ -1,0 +1,12 @@
+ </div>
+ </div>
+ <hr>
+ <footer id="footer">
+ <p class="pull-right"><a href="#top">Back to top</a></p>
+ Made by <a href="http://spf13.com">Steve Francia</a>.<br>
+ Code licensed under the <a href="https://github.com/spf13/hugo/blob/master/LICENSE.md">Simple Public License 2.0</a>.<br>
+ </footer>
+ </div>
+ </body>
+
+</html>
--- /dev/null
+++ b/docs/layouts/chrome/header.html
@@ -1,0 +1,20 @@
+<!doctype html>
+<html>
+ <head>
+ <title>{{ .Title }}</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
+ {{ template "chrome/includes.html" . }}
+ </head>
+ <body>
+ <div class="navbar"></div>
+ <div class="container-fluid">
+ <div class="row-fluid">
+ <div class="span3">
+ <div class="well" style="background-color: #222; color: #ccc;">
+ <h1>Hugo</h1>
+ <p>A Fast and Flexible Static Site Generator built with love by <a href="http://spf13.com">spf13</a> in GO</p>
+ </div>
+ {{ template "chrome/menu.html" . }}
+ </div>
+ <div class="span9">
--- /dev/null
+++ b/docs/layouts/chrome/includes.html
@@ -1,0 +1,2 @@
+ <link rel="stylesheet" href="/static/css/bootstrap.min.css">
+ <link rel="stylesheet" href="/static/css/bootstrap-responsive.css">
--- /dev/null
+++ b/docs/layouts/chrome/menu.html
@@ -1,0 +1,28 @@
+ <ul class="nav nav-list">
+ <li> <a href="/">Home</a> </li>
+ <li class="divider"></li>
+ <li class="nav-header">Getting Started</li>
+ <li> <a href="/doc/installing.html">Installing Hugo</a> </li>
+ <li> <a href="/doc/usage.html">Usage</a> </li>
+ <li> <a href="/doc/configuration.html">Configuration</a> </li>
+ <li> <a href="/doc/source-directory.html">Input Directory Layout</a> </li>
+ <li class="divider"></li>
+ <li class="nav-header">Layout</li>
+ <li> <a href="/doc/templates.html">Templates</a> </li>
+ <li> <a href="/doc/variables.html">Variables</a> </li>
+ <li class="divider"></li>
+ <li class="nav-header">Content</li>
+ <li> <a href="/doc/organization.html">Organization</a> </li>
+ <li> <a href="/doc/front-matter.html">Front Matter</a> </li>
+ <li> <a href="/doc/example.html">Example</a> </li>
+ <li class="divider"></li>
+ <li class="nav-header">Extras</li>
+ <li> <a href="/doc/shortcodes.html">ShortCodes</a> </li>
+ <li class="divider"></li>
+ <li class="nav-header">Meta</li>
+ <li> <a href="/doc/release-notes.html">Release Notes</a> </li>
+ <li> <a href="/doc/roadmap.html">Roadmap</a> </li>
+ <li> <a href="/doc/contributing.html">Contributing</a> </li>
+ <li> <a href="/doc/contributors.html">Contributors</a> </li>
+ <li> <a href="/doc/license.html">License</a> </li>
+ </ul>
--- /dev/null
+++ b/docs/layouts/doc/single.html
@@ -1,0 +1,4 @@
+{{ template "chrome/header.html" . }}
+ <h1>{{ .Title }}</h1>
+ {{ .Content }}
+{{ template "chrome/footer.html" . }}
--- /dev/null
+++ b/docs/layouts/index.html
@@ -1,0 +1,46 @@
+<!doctype html>
+<html>
+ <head>
+ <title>Hugo Static Site Generator written in GO lang</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
+ {{ template "chrome/includes.html" . }}
+ </head>
+ <body>
+ <div class="navbar"></div>
+ <div class="container-fluid">
+ <div class="row-fluid">
+ <div class="span3">
+ {{ template "chrome/menu.html" . }}
+ </div>
+ <div class="span9">
+
+ <div class="hero-unit" style="background-color: #222; color: #ccc;">
+ <h1>Hugo</h1>
+ <p>A Fast and Flexible Static Site Generator built with love by <a href="http://spf13.com">spf13</a> in GO</p>
+ <p>
+ <a class="btn btn-large btn-success" href="/doc/installing.html">Get Started</a>
+ </p>
+ </div>
+ <div class="row-fluid">
+ <div class="span4">
+ <h2>Fast
+ <br>
+ </h2>
+ <p>Written in GoLang for speed, Hugo is significantly faster than other
+ static site generators. It's so fast that it will render the site in
+ less time than it takes to switch to your browser and reload.</p>
+ </div>
+ <div class="span4">
+ <h2>Flexible</h2>
+ <p>Hugo is made to be very flexible. Define your own content types. Define
+ your own indexes. Build your own templates, shortcodes and more.</p>
+ </div>
+ <div class="span4">
+ <h2>Fun</h2>
+ <p>Hugo is more fun than you can shake a stick at. Hugo removes all
+ the cruft of building a site allowing you to focus on creating the
+ best site possible.</p>
+ </div>
+ </div>
+{{ template "chrome/footer.html" }}
binary files /dev/null b/docs/public/static/.DS_Store differ
--- /dev/null
+++ b/docs/public/static/css/bootstrap-responsive.css
@@ -1,0 +1,1109 @@
+/*!
+ * Bootstrap Responsive v2.3.1
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */
+
+.clearfix {
+ *zoom: 1;
+}
+
+.clearfix:before,
+.clearfix:after {
+ display: table;
+ line-height: 0;
+ content: "";
+}
+
+.clearfix:after {
+ clear: both;
+}
+
+.hide-text {
+ font: 0/0 a;
+ color: transparent;
+ text-shadow: none;
+ background-color: transparent;
+ border: 0;
+}
+
+.input-block-level {
+ display: block;
+ width: 100%;
+ min-height: 30px;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+@-ms-viewport {
+ width: device-width;
+}
+
+.hidden {
+ display: none;
+ visibility: hidden;
+}
+
+.visible-phone {
+ display: none !important;
+}
+
+.visible-tablet {
+ display: none !important;
+}
+
+.hidden-desktop {
+ display: none !important;
+}
+
+.visible-desktop {
+ display: inherit !important;
+}
+
+@media (min-width: 768px) and (max-width: 979px) {
+ .hidden-desktop {
+ display: inherit !important;
+ }
+ .visible-desktop {
+ display: none !important ;
+ }
+ .visible-tablet {
+ display: inherit !important;
+ }
+ .hidden-tablet {
+ display: none !important;
+ }
+}
+
+@media (max-width: 767px) {
+ .hidden-desktop {
+ display: inherit !important;
+ }
+ .visible-desktop {
+ display: none !important;
+ }
+ .visible-phone {
+ display: inherit !important;
+ }
+ .hidden-phone {
+ display: none !important;
+ }
+}
+
+.visible-print {
+ display: none !important;
+}
+
+@media print {
+ .visible-print {
+ display: inherit !important;
+ }
+ .hidden-print {
+ display: none !important;
+ }
+}
+
+@media (min-width: 1200px) {
+ .row {
+ margin-left: -30px;
+ *zoom: 1;
+ }
+ .row:before,
+ .row:after {
+ display: table;
+ line-height: 0;
+ content: "";
+ }
+ .row:after {
+ clear: both;
+ }
+ [class*="span"] {
+ float: left;
+ min-height: 1px;
+ margin-left: 30px;
+ }
+ .container,
+ .navbar-static-top .container,
+ .navbar-fixed-top .container,
+ .navbar-fixed-bottom .container {
+ width: 1170px;
+ }
+ .span12 {
+ width: 1170px;
+ }
+ .span11 {
+ width: 1070px;
+ }
+ .span10 {
+ width: 970px;
+ }
+ .span9 {
+ width: 870px;
+ }
+ .span8 {
+ width: 770px;
+ }
+ .span7 {
+ width: 670px;
+ }
+ .span6 {
+ width: 570px;
+ }
+ .span5 {
+ width: 470px;
+ }
+ .span4 {
+ width: 370px;
+ }
+ .span3 {
+ width: 270px;
+ }
+ .span2 {
+ width: 170px;
+ }
+ .span1 {
+ width: 70px;
+ }
+ .offset12 {
+ margin-left: 1230px;
+ }
+ .offset11 {
+ margin-left: 1130px;
+ }
+ .offset10 {
+ margin-left: 1030px;
+ }
+ .offset9 {
+ margin-left: 930px;
+ }
+ .offset8 {
+ margin-left: 830px;
+ }
+ .offset7 {
+ margin-left: 730px;
+ }
+ .offset6 {
+ margin-left: 630px;
+ }
+ .offset5 {
+ margin-left: 530px;
+ }
+ .offset4 {
+ margin-left: 430px;
+ }
+ .offset3 {
+ margin-left: 330px;
+ }
+ .offset2 {
+ margin-left: 230px;
+ }
+ .offset1 {
+ margin-left: 130px;
+ }
+ .row-fluid {
+ width: 100%;
+ *zoom: 1;
+ }
+ .row-fluid:before,
+ .row-fluid:after {
+ display: table;
+ line-height: 0;
+ content: "";
+ }
+ .row-fluid:after {
+ clear: both;
+ }
+ .row-fluid [class*="span"] {
+ display: block;
+ float: left;
+ width: 100%;
+ min-height: 30px;
+ margin-left: 2.564102564102564%;
+ *margin-left: 2.5109110747408616%;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ .row-fluid [class*="span"]:first-child {
+ margin-left: 0;
+ }
+ .row-fluid .controls-row [class*="span"] + [class*="span"] {
+ margin-left: 2.564102564102564%;
+ }
+ .row-fluid .span12 {
+ width: 100%;
+ *width: 99.94680851063829%;
+ }
+ .row-fluid .span11 {
+ width: 91.45299145299145%;
+ *width: 91.39979996362975%;
+ }
+ .row-fluid .span10 {
+ width: 82.90598290598291%;
+ *width: 82.8527914166212%;
+ }
+ .row-fluid .span9 {
+ width: 74.35897435897436%;
+ *width: 74.30578286961266%;
+ }
+ .row-fluid .span8 {
+ width: 65.81196581196582%;
+ *width: 65.75877432260411%;
+ }
+ .row-fluid .span7 {
+ width: 57.26495726495726%;
+ *width: 57.21176577559556%;
+ }
+ .row-fluid .span6 {
+ width: 48.717948717948715%;
+ *width: 48.664757228587014%;
+ }
+ .row-fluid .span5 {
+ width: 40.17094017094017%;
+ *width: 40.11774868157847%;
+ }
+ .row-fluid .span4 {
+ width: 31.623931623931625%;
+ *width: 31.570740134569924%;
+ }
+ .row-fluid .span3 {
+ width: 23.076923076923077%;
+ *width: 23.023731587561375%;
+ }
+ .row-fluid .span2 {
+ width: 14.52991452991453%;
+ *width: 14.476723040552828%;
+ }
+ .row-fluid .span1 {
+ width: 5.982905982905983%;
+ *width: 5.929714493544281%;
+ }
+ .row-fluid .offset12 {
+ margin-left: 105.12820512820512%;
+ *margin-left: 105.02182214948171%;
+ }
+ .row-fluid .offset12:first-child {
+ margin-left: 102.56410256410257%;
+ *margin-left: 102.45771958537915%;
+ }
+ .row-fluid .offset11 {
+ margin-left: 96.58119658119658%;
+ *margin-left: 96.47481360247316%;
+ }
+ .row-fluid .offset11:first-child {
+ margin-left: 94.01709401709402%;
+ *margin-left: 93.91071103837061%;
+ }
+ .row-fluid .offset10 {
+ margin-left: 88.03418803418803%;
+ *margin-left: 87.92780505546462%;
+ }
+ .row-fluid .offset10:first-child {
+ margin-left: 85.47008547008548%;
+ *margin-left: 85.36370249136206%;
+ }
+ .row-fluid .offset9 {
+ margin-left: 79.48717948717949%;
+ *margin-left: 79.38079650845607%;
+ }
+ .row-fluid .offset9:first-child {
+ margin-left: 76.92307692307693%;
+ *margin-left: 76.81669394435352%;
+ }
+ .row-fluid .offset8 {
+ margin-left: 70.94017094017094%;
+ *margin-left: 70.83378796144753%;
+ }
+ .row-fluid .offset8:first-child {
+ margin-left: 68.37606837606839%;
+ *margin-left: 68.26968539734497%;
+ }
+ .row-fluid .offset7 {
+ margin-left: 62.393162393162385%;
+ *margin-left: 62.28677941443899%;
+ }
+ .row-fluid .offset7:first-child {
+ margin-left: 59.82905982905982%;
+ *margin-left: 59.72267685033642%;
+ }
+ .row-fluid .offset6 {
+ margin-left: 53.84615384615384%;
+ *margin-left: 53.739770867430444%;
+ }
+ .row-fluid .offset6:first-child {
+ margin-left: 51.28205128205128%;
+ *margin-left: 51.175668303327875%;
+ }
+ .row-fluid .offset5 {
+ margin-left: 45.299145299145295%;
+ *margin-left: 45.1927623204219%;
+ }
+ .row-fluid .offset5:first-child {
+ margin-left: 42.73504273504273%;
+ *margin-left: 42.62865975631933%;
+ }
+ .row-fluid .offset4 {
+ margin-left: 36.75213675213675%;
+ *margin-left: 36.645753773413354%;
+ }
+ .row-fluid .offset4:first-child {
+ margin-left: 34.18803418803419%;
+ *margin-left: 34.081651209310785%;
+ }
+ .row-fluid .offset3 {
+ margin-left: 28.205128205128204%;
+ *margin-left: 28.0987452264048%;
+ }
+ .row-fluid .offset3:first-child {
+ margin-left: 25.641025641025642%;
+ *margin-left: 25.53464266230224%;
+ }
+ .row-fluid .offset2 {
+ margin-left: 19.65811965811966%;
+ *margin-left: 19.551736679396257%;
+ }
+ .row-fluid .offset2:first-child {
+ margin-left: 17.094017094017094%;
+ *margin-left: 16.98763411529369%;
+ }
+ .row-fluid .offset1 {
+ margin-left: 11.11111111111111%;
+ *margin-left: 11.004728132387708%;
+ }
+ .row-fluid .offset1:first-child {
+ margin-left: 8.547008547008547%;
+ *margin-left: 8.440625568285142%;
+ }
+ input,
+ textarea,
+ .uneditable-input {
+ margin-left: 0;
+ }
+ .controls-row [class*="span"] + [class*="span"] {
+ margin-left: 30px;
+ }
+ input.span12,
+ textarea.span12,
+ .uneditable-input.span12 {
+ width: 1156px;
+ }
+ input.span11,
+ textarea.span11,
+ .uneditable-input.span11 {
+ width: 1056px;
+ }
+ input.span10,
+ textarea.span10,
+ .uneditable-input.span10 {
+ width: 956px;
+ }
+ input.span9,
+ textarea.span9,
+ .uneditable-input.span9 {
+ width: 856px;
+ }
+ input.span8,
+ textarea.span8,
+ .uneditable-input.span8 {
+ width: 756px;
+ }
+ input.span7,
+ textarea.span7,
+ .uneditable-input.span7 {
+ width: 656px;
+ }
+ input.span6,
+ textarea.span6,
+ .uneditable-input.span6 {
+ width: 556px;
+ }
+ input.span5,
+ textarea.span5,
+ .uneditable-input.span5 {
+ width: 456px;
+ }
+ input.span4,
+ textarea.span4,
+ .uneditable-input.span4 {
+ width: 356px;
+ }
+ input.span3,
+ textarea.span3,
+ .uneditable-input.span3 {
+ width: 256px;
+ }
+ input.span2,
+ textarea.span2,
+ .uneditable-input.span2 {
+ width: 156px;
+ }
+ input.span1,
+ textarea.span1,
+ .uneditable-input.span1 {
+ width: 56px;
+ }
+ .thumbnails {
+ margin-left: -30px;
+ }
+ .thumbnails > li {
+ margin-left: 30px;
+ }
+ .row-fluid .thumbnails {
+ margin-left: 0;
+ }
+}
+
+@media (min-width: 768px) and (max-width: 979px) {
+ .row {
+ margin-left: -20px;
+ *zoom: 1;
+ }
+ .row:before,
+ .row:after {
+ display: table;
+ line-height: 0;
+ content: "";
+ }
+ .row:after {
+ clear: both;
+ }
+ [class*="span"] {
+ float: left;
+ min-height: 1px;
+ margin-left: 20px;
+ }
+ .container,
+ .navbar-static-top .container,
+ .navbar-fixed-top .container,
+ .navbar-fixed-bottom .container {
+ width: 724px;
+ }
+ .span12 {
+ width: 724px;
+ }
+ .span11 {
+ width: 662px;
+ }
+ .span10 {
+ width: 600px;
+ }
+ .span9 {
+ width: 538px;
+ }
+ .span8 {
+ width: 476px;
+ }
+ .span7 {
+ width: 414px;
+ }
+ .span6 {
+ width: 352px;
+ }
+ .span5 {
+ width: 290px;
+ }
+ .span4 {
+ width: 228px;
+ }
+ .span3 {
+ width: 166px;
+ }
+ .span2 {
+ width: 104px;
+ }
+ .span1 {
+ width: 42px;
+ }
+ .offset12 {
+ margin-left: 764px;
+ }
+ .offset11 {
+ margin-left: 702px;
+ }
+ .offset10 {
+ margin-left: 640px;
+ }
+ .offset9 {
+ margin-left: 578px;
+ }
+ .offset8 {
+ margin-left: 516px;
+ }
+ .offset7 {
+ margin-left: 454px;
+ }
+ .offset6 {
+ margin-left: 392px;
+ }
+ .offset5 {
+ margin-left: 330px;
+ }
+ .offset4 {
+ margin-left: 268px;
+ }
+ .offset3 {
+ margin-left: 206px;
+ }
+ .offset2 {
+ margin-left: 144px;
+ }
+ .offset1 {
+ margin-left: 82px;
+ }
+ .row-fluid {
+ width: 100%;
+ *zoom: 1;
+ }
+ .row-fluid:before,
+ .row-fluid:after {
+ display: table;
+ line-height: 0;
+ content: "";
+ }
+ .row-fluid:after {
+ clear: both;
+ }
+ .row-fluid [class*="span"] {
+ display: block;
+ float: left;
+ width: 100%;
+ min-height: 30px;
+ margin-left: 2.7624309392265194%;
+ *margin-left: 2.709239449864817%;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ .row-fluid [class*="span"]:first-child {
+ margin-left: 0;
+ }
+ .row-fluid .controls-row [class*="span"] + [class*="span"] {
+ margin-left: 2.7624309392265194%;
+ }
+ .row-fluid .span12 {
+ width: 100%;
+ *width: 99.94680851063829%;
+ }
+ .row-fluid .span11 {
+ width: 91.43646408839778%;
+ *width: 91.38327259903608%;
+ }
+ .row-fluid .span10 {
+ width: 82.87292817679558%;
+ *width: 82.81973668743387%;
+ }
+ .row-fluid .span9 {
+ width: 74.30939226519337%;
+ *width: 74.25620077583166%;
+ }
+ .row-fluid .span8 {
+ width: 65.74585635359117%;
+ *width: 65.69266486422946%;
+ }
+ .row-fluid .span7 {
+ width: 57.18232044198895%;
+ *width: 57.12912895262725%;
+ }
+ .row-fluid .span6 {
+ width: 48.61878453038674%;
+ *width: 48.56559304102504%;
+ }
+ .row-fluid .span5 {
+ width: 40.05524861878453%;
+ *width: 40.00205712942283%;
+ }
+ .row-fluid .span4 {
+ width: 31.491712707182323%;
+ *width: 31.43852121782062%;
+ }
+ .row-fluid .span3 {
+ width: 22.92817679558011%;
+ *width: 22.87498530621841%;
+ }
+ .row-fluid .span2 {
+ width: 14.3646408839779%;
+ *width: 14.311449394616199%;
+ }
+ .row-fluid .span1 {
+ width: 5.801104972375691%;
+ *width: 5.747913483013988%;
+ }
+ .row-fluid .offset12 {
+ margin-left: 105.52486187845304%;
+ *margin-left: 105.41847889972962%;
+ }
+ .row-fluid .offset12:first-child {
+ margin-left: 102.76243093922652%;
+ *margin-left: 102.6560479605031%;
+ }
+ .row-fluid .offset11 {
+ margin-left: 96.96132596685082%;
+ *margin-left: 96.8549429881274%;
+ }
+ .row-fluid .offset11:first-child {
+ margin-left: 94.1988950276243%;
+ *margin-left: 94.09251204890089%;
+ }
+ .row-fluid .offset10 {
+ margin-left: 88.39779005524862%;
+ *margin-left: 88.2914070765252%;
+ }
+ .row-fluid .offset10:first-child {
+ margin-left: 85.6353591160221%;
+ *margin-left: 85.52897613729868%;
+ }
+ .row-fluid .offset9 {
+ margin-left: 79.8342541436464%;
+ *margin-left: 79.72787116492299%;
+ }
+ .row-fluid .offset9:first-child {
+ margin-left: 77.07182320441989%;
+ *margin-left: 76.96544022569647%;
+ }
+ .row-fluid .offset8 {
+ margin-left: 71.2707182320442%;
+ *margin-left: 71.16433525332079%;
+ }
+ .row-fluid .offset8:first-child {
+ margin-left: 68.50828729281768%;
+ *margin-left: 68.40190431409427%;
+ }
+ .row-fluid .offset7 {
+ margin-left: 62.70718232044199%;
+ *margin-left: 62.600799341718584%;
+ }
+ .row-fluid .offset7:first-child {
+ margin-left: 59.94475138121547%;
+ *margin-left: 59.838368402492065%;
+ }
+ .row-fluid .offset6 {
+ margin-left: 54.14364640883978%;
+ *margin-left: 54.037263430116376%;
+ }
+ .row-fluid .offset6:first-child {
+ margin-left: 51.38121546961326%;
+ *margin-left: 51.27483249088986%;
+ }
+ .row-fluid .offset5 {
+ margin-left: 45.58011049723757%;
+ *margin-left: 45.47372751851417%;
+ }
+ .row-fluid .offset5:first-child {
+ margin-left: 42.81767955801105%;
+ *margin-left: 42.71129657928765%;
+ }
+ .row-fluid .offset4 {
+ margin-left: 37.01657458563536%;
+ *margin-left: 36.91019160691196%;
+ }
+ .row-fluid .offset4:first-child {
+ margin-left: 34.25414364640884%;
+ *margin-left: 34.14776066768544%;
+ }
+ .row-fluid .offset3 {
+ margin-left: 28.45303867403315%;
+ *margin-left: 28.346655695309746%;
+ }
+ .row-fluid .offset3:first-child {
+ margin-left: 25.69060773480663%;
+ *margin-left: 25.584224756083227%;
+ }
+ .row-fluid .offset2 {
+ margin-left: 19.88950276243094%;
+ *margin-left: 19.783119783707537%;
+ }
+ .row-fluid .offset2:first-child {
+ margin-left: 17.12707182320442%;
+ *margin-left: 17.02068884448102%;
+ }
+ .row-fluid .offset1 {
+ margin-left: 11.32596685082873%;
+ *margin-left: 11.219583872105325%;
+ }
+ .row-fluid .offset1:first-child {
+ margin-left: 8.56353591160221%;
+ *margin-left: 8.457152932878806%;
+ }
+ input,
+ textarea,
+ .uneditable-input {
+ margin-left: 0;
+ }
+ .controls-row [class*="span"] + [class*="span"] {
+ margin-left: 20px;
+ }
+ input.span12,
+ textarea.span12,
+ .uneditable-input.span12 {
+ width: 710px;
+ }
+ input.span11,
+ textarea.span11,
+ .uneditable-input.span11 {
+ width: 648px;
+ }
+ input.span10,
+ textarea.span10,
+ .uneditable-input.span10 {
+ width: 586px;
+ }
+ input.span9,
+ textarea.span9,
+ .uneditable-input.span9 {
+ width: 524px;
+ }
+ input.span8,
+ textarea.span8,
+ .uneditable-input.span8 {
+ width: 462px;
+ }
+ input.span7,
+ textarea.span7,
+ .uneditable-input.span7 {
+ width: 400px;
+ }
+ input.span6,
+ textarea.span6,
+ .uneditable-input.span6 {
+ width: 338px;
+ }
+ input.span5,
+ textarea.span5,
+ .uneditable-input.span5 {
+ width: 276px;
+ }
+ input.span4,
+ textarea.span4,
+ .uneditable-input.span4 {
+ width: 214px;
+ }
+ input.span3,
+ textarea.span3,
+ .uneditable-input.span3 {
+ width: 152px;
+ }
+ input.span2,
+ textarea.span2,
+ .uneditable-input.span2 {
+ width: 90px;
+ }
+ input.span1,
+ textarea.span1,
+ .uneditable-input.span1 {
+ width: 28px;
+ }
+}
+
+@media (max-width: 767px) {
+ body {
+ padding-right: 20px;
+ padding-left: 20px;
+ }
+ .navbar-fixed-top,
+ .navbar-fixed-bottom,
+ .navbar-static-top {
+ margin-right: -20px;
+ margin-left: -20px;
+ }
+ .container-fluid {
+ padding: 0;
+ }
+ .dl-horizontal dt {
+ float: none;
+ width: auto;
+ clear: none;
+ text-align: left;
+ }
+ .dl-horizontal dd {
+ margin-left: 0;
+ }
+ .container {
+ width: auto;
+ }
+ .row-fluid {
+ width: 100%;
+ }
+ .row,
+ .thumbnails {
+ margin-left: 0;
+ }
+ .thumbnails > li {
+ float: none;
+ margin-left: 0;
+ }
+ [class*="span"],
+ .uneditable-input[class*="span"],
+ .row-fluid [class*="span"] {
+ display: block;
+ float: none;
+ width: 100%;
+ margin-left: 0;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ .span12,
+ .row-fluid .span12 {
+ width: 100%;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ .row-fluid [class*="offset"]:first-child {
+ margin-left: 0;
+ }
+ .input-large,
+ .input-xlarge,
+ .input-xxlarge,
+ input[class*="span"],
+ select[class*="span"],
+ textarea[class*="span"],
+ .uneditable-input {
+ display: block;
+ width: 100%;
+ min-height: 30px;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ .input-prepend input,
+ .input-append input,
+ .input-prepend input[class*="span"],
+ .input-append input[class*="span"] {
+ display: inline-block;
+ width: auto;
+ }
+ .controls-row [class*="span"] + [class*="span"] {
+ margin-left: 0;
+ }
+ .modal {
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ left: 20px;
+ width: auto;
+ margin: 0;
+ }
+ .modal.fade {
+ top: -100px;
+ }
+ .modal.fade.in {
+ top: 20px;
+ }
+}
+
+@media (max-width: 480px) {
+ .nav-collapse {
+ -webkit-transform: translate3d(0, 0, 0);
+ }
+ .page-header h1 small {
+ display: block;
+ line-height: 20px;
+ }
+ input[type="checkbox"],
+ input[type="radio"] {
+ border: 1px solid #ccc;
+ }
+ .form-horizontal .control-label {
+ float: none;
+ width: auto;
+ padding-top: 0;
+ text-align: left;
+ }
+ .form-horizontal .controls {
+ margin-left: 0;
+ }
+ .form-horizontal .control-list {
+ padding-top: 0;
+ }
+ .form-horizontal .form-actions {
+ padding-right: 10px;
+ padding-left: 10px;
+ }
+ .media .pull-left,
+ .media .pull-right {
+ display: block;
+ float: none;
+ margin-bottom: 10px;
+ }
+ .media-object {
+ margin-right: 0;
+ margin-left: 0;
+ }
+ .modal {
+ top: 10px;
+ right: 10px;
+ left: 10px;
+ }
+ .modal-header .close {
+ padding: 10px;
+ margin: -10px;
+ }
+ .carousel-caption {
+ position: static;
+ }
+}
+
+@media (max-width: 979px) {
+ body {
+ padding-top: 0;
+ }
+ .navbar-fixed-top,
+ .navbar-fixed-bottom {
+ position: static;
+ }
+ .navbar-fixed-top {
+ margin-bottom: 20px;
+ }
+ .navbar-fixed-bottom {
+ margin-top: 20px;
+ }
+ .navbar-fixed-top .navbar-inner,
+ .navbar-fixed-bottom .navbar-inner {
+ padding: 5px;
+ }
+ .navbar .container {
+ width: auto;
+ padding: 0;
+ }
+ .navbar .brand {
+ padding-right: 10px;
+ padding-left: 10px;
+ margin: 0 0 0 -5px;
+ }
+ .nav-collapse {
+ clear: both;
+ }
+ .nav-collapse .nav {
+ float: none;
+ margin: 0 0 10px;
+ }
+ .nav-collapse .nav > li {
+ float: none;
+ }
+ .nav-collapse .nav > li > a {
+ margin-bottom: 2px;
+ }
+ .nav-collapse .nav > .divider-vertical {
+ display: none;
+ }
+ .nav-collapse .nav .nav-header {
+ color: #777777;
+ text-shadow: none;
+ }
+ .nav-collapse .nav > li > a,
+ .nav-collapse .dropdown-menu a {
+ padding: 9px 15px;
+ font-weight: bold;
+ color: #777777;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+ }
+ .nav-collapse .btn {
+ padding: 4px 10px 4px;
+ font-weight: normal;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+ }
+ .nav-collapse .dropdown-menu li + li a {
+ margin-bottom: 2px;
+ }
+ .nav-collapse .nav > li > a:hover,
+ .nav-collapse .nav > li > a:focus,
+ .nav-collapse .dropdown-menu a:hover,
+ .nav-collapse .dropdown-menu a:focus {
+ background-color: #f2f2f2;
+ }
+ .navbar-inverse .nav-collapse .nav > li > a,
+ .navbar-inverse .nav-collapse .dropdown-menu a {
+ color: #999999;
+ }
+ .navbar-inverse .nav-collapse .nav > li > a:hover,
+ .navbar-inverse .nav-collapse .nav > li > a:focus,
+ .navbar-inverse .nav-collapse .dropdown-menu a:hover,
+ .navbar-inverse .nav-collapse .dropdown-menu a:focus {
+ background-color: #111111;
+ }
+ .nav-collapse.in .btn-group {
+ padding: 0;
+ margin-top: 5px;
+ }
+ .nav-collapse .dropdown-menu {
+ position: static;
+ top: auto;
+ left: auto;
+ display: none;
+ float: none;
+ max-width: none;
+ padding: 0;
+ margin: 0 15px;
+ background-color: transparent;
+ border: none;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ border-radius: 0;
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+ }
+ .nav-collapse .open > .dropdown-menu {
+ display: block;
+ }
+ .nav-collapse .dropdown-menu:before,
+ .nav-collapse .dropdown-menu:after {
+ display: none;
+ }
+ .nav-collapse .dropdown-menu .divider {
+ display: none;
+ }
+ .nav-collapse .nav > li > .dropdown-menu:before,
+ .nav-collapse .nav > li > .dropdown-menu:after {
+ display: none;
+ }
+ .nav-collapse .navbar-form,
+ .nav-collapse .navbar-search {
+ float: none;
+ padding: 10px 15px;
+ margin: 10px 0;
+ border-top: 1px solid #f2f2f2;
+ border-bottom: 1px solid #f2f2f2;
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+ -moz-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);
+ }
+ .navbar-inverse .nav-collapse .navbar-form,
+ .navbar-inverse .nav-collapse .navbar-search {
+ border-top-color: #111111;
+ border-bottom-color: #111111;
+ }
+ .navbar .nav-collapse .nav.pull-right {
+ float: none;
+ margin-left: 0;
+ }
+ .nav-collapse,
+ .nav-collapse.collapse {
+ height: 0;
+ overflow: hidden;
+ }
+ .navbar .btn-navbar {
+ display: block;
+ }
+ .navbar-static .navbar-inner {
+ padding-right: 10px;
+ padding-left: 10px;
+ }
+}
+
+@media (min-width: 980px) {
+ .nav-collapse.collapse {
+ height: auto !important;
+ overflow: visible !important;
+ }
+}
\ No newline at end of file
--- /dev/null
+++ b/docs/public/static/css/bootstrap.min.css
@@ -1,0 +1,9 @@
+@import url("http://fonts.googleapis.com/css?family=Lato:400,700,900,400italic");/*!
+ * Bootstrap v2.3.2
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{width:auto\9;height:auto;max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas img,.google-maps img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button,input{*overflow:visible;line-height:normal}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}label,select,button,input[type="button"],input[type="reset"],input[type="submit"],input[type="radio"],input[type="checkbox"]{cursor:pointer}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]: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}@page{margin:.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}body{margin:0;font-family:"Lato","Helvetica Neue",Helvetica,Arial,sans-serif;font-size:15px;line-height:20px;color:#2c3e50;background-color:#fff}a{color:#1abc9c;text-decoration:none}a:hover,a:focus{color:#1dd2af;text-decoration:underline}.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;-webkit-box-sizing:border-box;-moz-box-sizing:bo
\ No newline at end of file
--- /dev/null
+++ b/hugolib/config.go
@@ -1,0 +1,143 @@
+// Copyright © 2013 Steve Francia <[email protected]>.
+//
+// Licensed under the Simple Public 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://opensource.org/licenses/Simple-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 (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path"
+ "path/filepath"
+)
+
+// config file items
+type Config struct {
+ SourceDir, PublishDir, BaseUrl, StaticDir string
+ Path, CacheDir, LayoutDir, DefaultLayout string
+ Indexes map[string]string // singular, plural
+ ProcessFilters map[string][]string
+ BuildDrafts bool
+}
+
+var c Config
+
+// Read cfgfile or setup defaults.
+func SetupConfig(cfgfile *string, path *string) *Config {
+ c.setPath(*path)
+
+ configPath, err := c.findConfigFile(*cfgfile)
+
+ if err != nil {
+ fmt.Printf("%v", err)
+ fmt.Println(" using defaults instead")
+ }
+
+ // set defaults
+
+ c.SourceDir = "content"
+ c.LayoutDir = "layouts"
+ c.PublishDir = "public"
+ c.StaticDir = "static"
+ c.DefaultLayout = "post"
+ c.BuildDrafts = false
+
+ file, err := ioutil.ReadFile(configPath)
+ if err == nil {
+ if err := json.Unmarshal(file, &c); err != nil {
+ fmt.Printf("Error parsing config: %s", err)
+ os.Exit(1)
+ }
+ }
+
+ // set index defaults if none provided
+ if len(c.Indexes) == 0 {
+ c.Indexes = make(map[string]string)
+ c.Indexes["tag"] = "tags"
+ c.Indexes["category"] = "categories"
+ }
+ return &c
+}
+
+func (c *Config) setPath(p string) {
+ if p == "" {
+ path, err := FindPath()
+ if err != nil {
+ fmt.Printf("Error finding path: %s", err)
+ }
+ c.Path = path
+ } else {
+ path, err := filepath.Abs(p)
+ if err != nil {
+ fmt.Printf("Error finding path: %s", err)
+ }
+ c.Path = path
+ }
+}
+
+func (c *Config) GetPath() string {
+ if c.Path == "" {
+ c.setPath("")
+ }
+ return c.Path
+}
+
+func FindPath() (string, error) {
+ serverFile, err := filepath.Abs(os.Args[0])
+
+ if err != nil {
+ return "", fmt.Errorf("Can't get absolute path for executable: %v", err)
+ }
+
+ path := filepath.Dir(serverFile)
+ realFile, err := filepath.EvalSymlinks(serverFile)
+
+ if err != nil {
+ if _, err = os.Stat(serverFile + ".exe"); err == nil {
+ realFile = filepath.Clean(serverFile + ".exe")
+ }
+ }
+
+ if err == nil && realFile != serverFile {
+ path = filepath.Dir(realFile)
+ }
+
+ return path, nil
+}
+
+func (c *Config) GetAbsPath(name string) string {
+ if path.IsAbs(name) {
+ return name
+ }
+
+ p := filepath.Join(c.GetPath(), name)
+ return p
+}
+
+func (c *Config) findConfigFile(configFileName string) (string, error) {
+ // If the full path is given, just use that
+ if path.IsAbs(configFileName) {
+ return configFileName, nil
+ }
+
+ // Else check the local directory
+ t := c.GetAbsPath(configFileName)
+ if b, _ := exists(t); b {
+ return t, nil
+ } else {
+ return "", fmt.Errorf("config file not found at: %s", t)
+ }
+
+ return "", nil // This line won't ever happen.. looking forward to go 1.1 when I don't need it
+}
--- /dev/null
+++ b/hugolib/helpers.go
@@ -1,0 +1,309 @@
+// Copyright © 2013 Steve Francia <[email protected]>.
+//
+// Licensed under the Simple Public 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://opensource.org/licenses/Simple-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"
+ "github.com/kr/pretty"
+ "os"
+ "reflect"
+ "regexp"
+ "strconv"
+ "strings"
+ "time"
+)
+
+var sanitizeRegexp = regexp.MustCompile("[^a-zA-Z0-9/_-]")
+
+// TODO: Make these wrappers private
+// Wrapper around Fprintf taking verbose flag in account.
+func Printvf(format string, a ...interface{}) {
+ //if *verbose {
+ fmt.Fprintf(os.Stderr, format, a...)
+ //}
+}
+
+func Printer(x interface{}) {
+ fmt.Printf("%#v", pretty.Formatter(x))
+ fmt.Println("")
+}
+
+// Wrapper around Fprintln taking verbose flag in account.
+func Printvln(a ...interface{}) {
+ //if *verbose {
+ fmt.Fprintln(os.Stderr, a...)
+ //}
+}
+
+func FatalErr(str string) {
+ fmt.Println(str)
+ os.Exit(1)
+}
+
+func PrintErr(str string, a ...interface{}) {
+ fmt.Fprintln(os.Stderr, str, a)
+}
+
+func Error(str string, a ...interface{}) {
+ fmt.Fprintln(os.Stderr, str, a)
+}
+
+func interfaceToStringToDate(i interface{}) time.Time {
+ s := interfaceToString(i)
+ d, e := time.Parse("02 Jan 06 15:04 MST", s)
+
+ if e != nil {
+ d, e = time.Parse("2006-01-02", s)
+ }
+
+ if e != nil {
+ d, e = time.Parse("02 Jan 06", s)
+ }
+
+ return d
+
+}
+
+func interfaceToBool(i interface{}) bool {
+ switch b := i.(type) {
+ case bool:
+ return b
+ default:
+ Error("Only Boolean values are supported for this JSON key")
+ }
+
+ return false
+
+}
+
+func interfaceArrayToStringArray(i interface{}) []string {
+ var a []string
+
+ switch vv := i.(type) {
+ case []interface{}:
+ for _, u := range vv {
+ a = append(a, interfaceToString(u))
+ }
+ }
+
+ return a
+}
+
+func interfaceToString(i interface{}) string {
+ switch s := i.(type) {
+ case string:
+ return s
+ default:
+ Error("Only Strings are supported for this JSON key")
+ }
+
+ return ""
+}
+
+// Check if Exists && is Directory
+func dirExists(path string) (bool, error) {
+ fi, err := os.Stat(path)
+ if err == nil && fi.IsDir() {
+ return true, nil
+ }
+ if os.IsNotExist(err) {
+ return false, nil
+ }
+ return false, err
+}
+
+// Check if File / Directory Exists
+func exists(path string) (bool, error) {
+ _, err := os.Stat(path)
+ if err == nil {
+ return true, nil
+ }
+ if os.IsNotExist(err) {
+ return false, nil
+ }
+ return false, err
+}
+
+func mkdirIf(path string) {
+ err := os.Mkdir(path, 0777)
+ if err != nil && os.IsNotExist(err) {
+ fmt.Println(err)
+ }
+}
+
+func Urlize(url string) string {
+ return Sanitize(strings.ToLower(strings.Replace(strings.TrimSpace(url), " ", "-", -1)))
+}
+
+func Gt(a interface{}, b interface{}) bool {
+ var left, right int64
+ av := reflect.ValueOf(a)
+
+ switch av.Kind() {
+ case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
+ left = int64(av.Len())
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ left = av.Int()
+ case reflect.String:
+ left, _ = strconv.ParseInt(av.String(), 10, 64)
+ }
+
+ bv := reflect.ValueOf(b)
+
+ switch bv.Kind() {
+ case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
+ right = int64(bv.Len())
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ right = bv.Int()
+ case reflect.String:
+ right, _ = strconv.ParseInt(bv.String(), 10, 64)
+ }
+
+ return left > right
+}
+
+func IsSet(a interface{}, key interface{}) bool {
+ av := reflect.ValueOf(a)
+ kv := reflect.ValueOf(key)
+
+ switch av.Kind() {
+ case reflect.Array, reflect.Chan, reflect.Slice:
+ if int64(av.Len()) > kv.Int() {
+ return true
+ }
+ case reflect.Map:
+ if kv.Type() == av.Type().Key() {
+ return av.MapIndex(kv).IsValid()
+ }
+ }
+
+ return false
+}
+
+func ReturnWhenSet(a interface{}, index int) interface{} {
+ av := reflect.ValueOf(a)
+
+ switch av.Kind() {
+ case reflect.Array, reflect.Slice:
+ if av.Len() > index {
+
+ avv := av.Index(index)
+ switch avv.Kind() {
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ return avv.Int()
+ case reflect.String:
+ return avv.String()
+ }
+ }
+ }
+
+ return ""
+}
+
+func Sanitize(s string) string {
+ return sanitizeRegexp.ReplaceAllString(s, "")
+}
+
+func fileExt(path string) (file, ext string) {
+ if strings.Contains(path, ".") {
+ i := len(path) - 1
+ for path[i] != '.' {
+ i--
+ }
+ return path[:i], path[i+1:]
+ }
+ return path, ""
+}
+
+func replaceExtension(path string, newExt string) string {
+ f, _ := fileExt(path)
+ return f + "." + newExt
+}
+
+func TotalWords(s string) int {
+ return len(strings.Fields(s))
+}
+
+func WordCount(s string) map[string]int {
+ m := make(map[string]int)
+ for _, f := range strings.Fields(s) {
+ m[f] += 1
+ }
+
+ return m
+}
+
+func StripHTML(s string) string {
+ output := ""
+
+ // Shortcut strings with no tags in them
+ if !strings.ContainsAny(s, "<>") {
+ output = s
+ } else {
+ s = strings.Replace(s, "\n", " ", -1)
+ s = strings.Replace(s, "</p>", " \n", -1)
+ s = strings.Replace(s, "<br>", " \n", -1)
+ s = strings.Replace(s, "</br>", " \n", -1)
+
+ // Walk through the string removing all tags
+ b := new(bytes.Buffer)
+ inTag := false
+ for _, r := range s {
+ switch r {
+ case '<':
+ inTag = true
+ case '>':
+ inTag = false
+ default:
+ if !inTag {
+ b.WriteRune(r)
+ }
+ }
+ }
+ output = b.String()
+ }
+ return output
+}
+
+func TruncateWords(s string, max int) string {
+ words := strings.Fields(s)
+ if max > len(words) {
+ return strings.Join(words, " ")
+ }
+
+ return strings.Join(words[:max], " ")
+}
+
+func TruncateWordsToWholeSentence(s string, max int) string {
+ words := strings.Fields(s)
+ if max > len(words) {
+ return strings.Join(words, " ")
+ }
+
+ for counter, word := range words[max:] {
+ if strings.HasSuffix(word, ".") ||
+ strings.HasSuffix(word, "?") ||
+ strings.HasSuffix(word, ".\"") ||
+ strings.HasSuffix(word, "!") {
+ return strings.Join(words[:max+counter+1], " ")
+ }
+ }
+
+ return strings.Join(words[:max], " ")
+}
+
+func MakePermalink(domain string, path string) string {
+ return strings.TrimRight(domain, "/") + "/" + strings.TrimLeft(path, "/")
+}
--- /dev/null
+++ b/hugolib/index.go
@@ -1,0 +1,58 @@
+// Copyright © 2013 Steve Francia <[email protected]>.
+//
+// Licensed under the Simple Public 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://opensource.org/licenses/Simple-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 (
+ "sort"
+)
+
+type Index map[string]Pages
+type IndexList map[string]Index
+
+type OrderedIndex []*Pages
+type OrderedIndexList map[string]OrderedIndex
+
+// KeyPrep... Indexes should be case insensitive. Can make it easily conditional later.
+func kp(in string) string {
+ return Urlize(in)
+}
+
+func (i Index) Get(key string) Pages { return i[kp(key)] }
+func (i Index) Count(key string) int { return len(i[kp(key)]) }
+func (i Index) Add(key string, p *Page) {
+ key = kp(key)
+ i[key] = append(i[key], p)
+}
+
+func (l IndexList) BuildOrderedIndexList() *OrderedIndexList {
+ oil := make(OrderedIndexList, len(l))
+ for idx_name, index := range l {
+ i := 0
+ oi := make(OrderedIndex, len(index))
+ for _, e := range index {
+ oi[i] = &e
+ i++
+ }
+ oi.Sort()
+ oil[idx_name] = oi
+ }
+ return &oil
+}
+
+func (idx OrderedIndex) Len() int { return len(idx) }
+
+func (idx OrderedIndex) Less(i, j int) bool { return len(*idx[i]) < len(*idx[j]) }
+func (idx OrderedIndex) Swap(i, j int) { idx[i], idx[j] = idx[j], idx[i] }
+func (idx OrderedIndex) Sort() { sort.Sort(idx) }
+func (idx OrderedIndex) Limit(n int) OrderedIndex { return idx[0:n] }
--- /dev/null
+++ b/hugolib/node.go
@@ -1,0 +1,43 @@
+// Copyright © 2013 Steve Francia <[email protected]>.
+//
+// Licensed under the Simple Public 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://opensource.org/licenses/Simple-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"
+ "time"
+)
+
+type Node struct {
+ Url string
+ Permalink template.HTML
+ RSSlink template.HTML
+ Site SiteInfo
+ layout string
+ Data map[string]interface{}
+ Section string
+ Slug string
+ Title string
+ Description string
+ Keywords []string
+ Date time.Time
+}
+
+func (n *Node) GetSection() string {
+ s := ""
+ if n.Section != "" {
+ s = n.Section
+ }
+
+ return s
+}
--- /dev/null
+++ b/hugolib/page.go
@@ -1,0 +1,381 @@
+// Copyright © 2013 Steve Francia <[email protected]>.
+//
+// Licensed under the Simple Public 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://opensource.org/licenses/Simple-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"
+ "encoding/json"
+ "fmt"
+ "github.com/theplant/blackfriday"
+ "html/template"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+ "time"
+)
+
+var _ = filepath.Base("")
+
+type Page struct {
+ Status string
+ Images []string
+ Content template.HTML
+ Summary template.HTML
+ RawMarkdown string // TODO should be []byte
+ Params map[string]interface{}
+ RenderedContent *bytes.Buffer
+ contentType string
+ Draft bool
+ Tmpl *template.Template
+ PageMeta
+ File
+ Position
+ Node
+}
+
+const summaryLength = 70
+
+type File struct {
+ FileName, OutFile, Extension string
+}
+
+type PageMeta struct {
+ WordCount int
+ FuzzyWordCount int
+}
+
+type Position struct {
+ Prev *Page
+ Next *Page
+}
+
+type Pages []*Page
+
+func (p Pages) Len() int { return len(p) }
+func (p Pages) Less(i, j int) bool { return p[i].Date.Unix() > p[j].Date.Unix() }
+func (p Pages) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
+
+// TODO eliminate unnecessary things
+func (p Pages) Sort() { sort.Sort(p) }
+func (p Pages) Limit(n int) Pages { return p[0:n] }
+
+func initializePage(filename string) (page Page) {
+ page = Page{}
+ page.Date, _ = time.Parse("20060102", "20080101")
+ page.FileName = filename
+ page.contentType = ""
+ page.Extension = "html"
+ page.Params = make(map[string]interface{})
+ page.Keywords = make([]string, 10, 30)
+ page.setSection()
+
+ return page
+}
+
+func (p *Page) setSection() {
+ x := strings.Split(p.FileName, "/")
+
+ if section := x[len(x)-2]; section != "content" {
+ p.Section = section
+ }
+}
+
+func (page *Page) Type() string {
+ if page.contentType != "" {
+ return page.contentType
+ }
+
+ if x := page.GetSection(); x != "" {
+ return x
+ }
+
+ return "page"
+}
+
+func (page *Page) Layout(l ...string) string {
+ layout := ""
+ if len(l) == 0 {
+ layout = "single"
+ } else {
+ layout = l[0]
+ }
+
+ if x := page.layout; x != "" {
+ return x
+ }
+
+ return strings.ToLower(page.Type()) + "/" + layout + ".html"
+}
+
+// TODO should return errors as well
+// TODO new page should return just a page
+// TODO initalize separately... load from reader (file, or []byte)
+func NewPage(filename string) *Page {
+ p := initializePage(filename)
+ if err := p.buildPageFromFile(); err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+
+ p.analyzePage()
+
+ return &p
+}
+
+func (p *Page) analyzePage() {
+ p.WordCount = TotalWords(p.RawMarkdown)
+ p.FuzzyWordCount = int((p.WordCount+100)/100) * 100
+}
+
+// TODO //rewrite to use byte methods instead
+func (page *Page) parseJsonMetaData(data []byte) ([]string, error) {
+ var err error
+
+ lines := strings.Split(string(data), "\n")
+ datum := lines[0:]
+
+ // go through content parse between "{" and "}"
+ // must be on their own lines (for now)
+ var found = 0
+ for i, line := range lines {
+ line = strings.TrimSpace(line)
+
+ if line == "{" {
+ found += 1
+ }
+
+ if line == "}" {
+ found -= 1
+ }
+
+ if found == 0 {
+ datum = lines[0 : i+1]
+ lines = lines[i+1:]
+ break
+ }
+ }
+
+ err = page.handleJsonMetaData([]byte(strings.Join(datum, "\n")))
+
+ return lines, err
+}
+
+func (p *Page) Permalink() template.HTML {
+ if len(strings.TrimSpace(p.Slug)) > 0 {
+ return template.HTML(MakePermalink(string(p.Site.BaseUrl), strings.TrimSpace(p.Section)+"/"+p.Slug))
+ } else if len(strings.TrimSpace(p.Url)) > 2 {
+ return template.HTML(MakePermalink(string(p.Site.BaseUrl), strings.TrimSpace(p.Url)))
+ } else {
+ _, t := filepath.Split(p.FileName)
+ x := replaceExtension(strings.TrimSpace(t), p.Extension)
+ return template.HTML(MakePermalink(string(p.Site.BaseUrl), strings.TrimSpace(p.Section)+"/"+x))
+ }
+}
+
+func (page *Page) handleJsonMetaData(datum []byte) error {
+ var f interface{}
+ if err := json.Unmarshal(datum, &f); err != nil {
+ return fmt.Errorf("Invalide JSON in $v \nError parsing page meta data: %s", page.FileName, err)
+ }
+
+ m := f.(map[string]interface{})
+
+ for k, v := range m {
+ switch strings.ToLower(k) {
+ case "title":
+ page.Title = interfaceToString(v)
+ case "description":
+ page.Description = interfaceToString(v)
+ case "slug":
+ page.Slug = Urlize(interfaceToString(v))
+ case "url":
+ if url := interfaceToString(v); strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") {
+ return fmt.Errorf("Only relative urls are supported, %v provided", url)
+ }
+ page.Url = Urlize(interfaceToString(v))
+ case "type":
+ page.contentType = interfaceToString(v)
+ case "keywords":
+ page.Keywords = interfaceArrayToStringArray(v)
+ case "date", "pubdate":
+ page.Date = interfaceToStringToDate(v)
+ case "draft":
+ page.Draft = interfaceToBool(v)
+ case "layout":
+ page.layout = interfaceToString(v)
+ case "status":
+ page.Status = interfaceToString(v)
+ default:
+ // If not one of the explicit values, store in Params
+ //fmt.Println(strings.ToLower(k))
+ switch vv := v.(type) {
+ case string: // handle string values
+ page.Params[strings.ToLower(k)] = vv
+ default: // handle array of strings as well
+ switch vvv := vv.(type) {
+ case []interface{}:
+ var a = make([]string, len(vvv))
+ for i, u := range vvv {
+ a[i] = interfaceToString(u)
+ }
+ page.Params[strings.ToLower(k)] = a
+ }
+ }
+ }
+ }
+ //Printer(page.Params)
+ return nil
+}
+
+func (page *Page) GetParam(key string) interface{} {
+ v := page.Params[strings.ToLower(key)]
+
+ if v == nil {
+ return nil
+ }
+
+ switch v.(type) {
+ case string:
+ return interfaceToString(v)
+ case []string:
+ return v
+ }
+ return nil
+}
+
+func (page *Page) parseFileMetaData(data []byte) ([]string, error) {
+ lines := strings.Split(string(data), "\n")
+
+ // go through content parse from --- to ---
+ var found = 0
+ for i, line := range lines {
+ line = strings.TrimSpace(line)
+
+ if found == 1 {
+ // parse line for param
+ colonIndex := strings.Index(line, ":")
+ if colonIndex > 0 {
+ key := strings.TrimSpace(line[:colonIndex])
+ value := strings.TrimSpace(line[colonIndex+1:])
+ value = strings.Trim(value, "\"") //remove quotes
+ switch key {
+ case "title":
+ page.Title = value
+ case "layout":
+ page.layout = value
+ case "extension":
+ page.Extension = "." + value
+ default:
+ page.Params[key] = value
+ }
+ }
+
+ } else if found >= 2 {
+ // params over
+ lines = lines[i:]
+ break
+ }
+
+ if line == "---" {
+ found += 1
+ }
+ }
+
+ return lines, nil
+}
+
+func (page *Page) Err(message string) {
+ fmt.Println(page.FileName + " : " + message)
+}
+
+// TODO return error on last line instead of nil
+func (page *Page) parseFileHeading(data []byte) ([]string, error) {
+ if len(data) == 0 {
+ page.Err("Empty File, skipping")
+ } else {
+ if data[0] == '-' {
+ return page.parseFileMetaData(data)
+ }
+ return page.parseJsonMetaData(data)
+ }
+ return nil, nil
+}
+
+func (p *Page) Render(layout ...string) template.HTML {
+ curLayout := ""
+
+ if len(layout) > 0 {
+ curLayout = layout[0]
+ }
+
+ return template.HTML(string(p.ExecuteTemplate(curLayout).Bytes()))
+}
+
+func (p *Page) ExecuteTemplate(layout string) *bytes.Buffer {
+ l := p.Layout(layout)
+ buffer := new(bytes.Buffer)
+ p.Tmpl.ExecuteTemplate(buffer, l, p)
+ return buffer
+}
+
+func (page *Page) readFile() []byte {
+ var data, err = ioutil.ReadFile(page.FileName)
+ if err != nil {
+ PrintErr("Error Reading: " + page.FileName)
+ return nil
+ }
+ return data
+}
+
+func (page *Page) buildPageFromFile() error {
+ data := page.readFile()
+
+ content, err := page.parseFileHeading(data)
+ if err != nil {
+ return err
+ }
+
+ if err := page.setOutFile(); err != nil {
+ return err
+ }
+
+ page.convertMarkdown(content)
+ return nil
+}
+
+func (p *Page) setOutFile() error {
+ if len(strings.TrimSpace(p.Slug)) > 0 {
+ // Use Slug if provided
+ p.OutFile = strings.TrimSpace(p.Slug + "." + p.Extension)
+ } else if len(strings.TrimSpace(p.Url)) > 2 {
+ // Use Url if provided & Slug missing
+ p.OutFile = strings.TrimSpace(p.Url)
+ } else {
+ // Fall back to filename
+ _, t := filepath.Split(p.FileName)
+ p.OutFile = replaceExtension(strings.TrimSpace(t), p.Extension)
+ }
+
+ return nil
+}
+
+func (page *Page) convertMarkdown(lines []string) {
+
+ page.RawMarkdown = strings.Join(lines, "\n")
+ content := string(blackfriday.MarkdownCommon([]byte(page.RawMarkdown)))
+ page.Content = template.HTML(content)
+ page.Summary = template.HTML(TruncateWordsToWholeSentence(StripHTML(StripShortcodes(content)), summaryLength))
+}
--- /dev/null
+++ b/hugolib/shortcode.go
@@ -1,0 +1,131 @@
+// Copyright © 2013 Steve Francia <[email protected]>.
+//
+// Licensed under the Simple Public 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://opensource.org/licenses/Simple-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"
+ "strings"
+ "unicode"
+)
+
+var _ = fmt.Println
+
+type ShortcodeFunc func([]string) string
+
+type Shortcode struct {
+ Name string
+ Func ShortcodeFunc
+}
+
+type ShortcodeWithPage struct {
+ Params interface{}
+ Page *Page
+}
+
+type Shortcodes map[string]ShortcodeFunc
+
+func ShortcodesHandle(stringToParse string, p *Page, t *template.Template) string {
+ posStart := strings.Index(stringToParse, "{{%")
+ if posStart > 0 {
+ posEnd := strings.Index(stringToParse[posStart:], "%}}") + posStart
+ if posEnd > posStart {
+ name, par := SplitParams(stringToParse[posStart+3 : posEnd])
+ params := Tokenize(par)
+ var data = &ShortcodeWithPage{Params: params, Page: p}
+ newString := stringToParse[:posStart] + ShortcodeRender(name, data, t) + ShortcodesHandle(stringToParse[posEnd+3:], p, t)
+ return newString
+ }
+ }
+ return stringToParse
+}
+
+func StripShortcodes(stringToParse string) string {
+ posStart := strings.Index(stringToParse, "{{%")
+ if posStart > 0 {
+ posEnd := strings.Index(stringToParse[posStart:], "%}}") + posStart
+ if posEnd > posStart {
+ newString := stringToParse[:posStart] + StripShortcodes(stringToParse[posEnd+3:])
+ return newString
+ }
+ }
+ return stringToParse
+}
+
+func Tokenize(in string) interface{} {
+ first := strings.Fields(in)
+ var final = make([]string, 0)
+ var keys = make([]string, 0)
+ inQuote := false
+ start := 0
+
+ for i, v := range first {
+ index := strings.Index(v, "=")
+
+ if !inQuote {
+ if index > 1 {
+ keys = append(keys, v[:index])
+ v = v[index+1:]
+ }
+ }
+
+ if !strings.HasPrefix(v, "“") && !inQuote {
+ final = append(final, v)
+ } else if inQuote && strings.HasSuffix(v, "”") && !strings.HasSuffix(v, "\\\"") {
+ first[i] = v[:len(v)-7]
+ final = append(final, strings.Join(first[start:i+1], " "))
+ inQuote = false
+ } else if strings.HasPrefix(v, "“") && !inQuote {
+ if strings.HasSuffix(v, "”") {
+ final = append(final, v[7:len(v)-7])
+ } else {
+ start = i
+ first[i] = v[7:]
+ inQuote = true
+ }
+ }
+
+ // No closing "... just make remainder the final token
+ if inQuote && i == len(first) {
+ final = append(final, first[start:len(first)]...)
+ }
+ }
+
+ if len(keys) > 0 {
+ var m = make(map[string]string)
+ for i, k := range keys {
+ m[k] = final[i]
+ }
+
+ return m
+ }
+
+ return final
+}
+
+func SplitParams(in string) (name string, par2 string) {
+ i := strings.IndexFunc(strings.TrimSpace(in), unicode.IsSpace)
+ if i < 1 {
+ return strings.TrimSpace(in), ""
+ }
+
+ return strings.TrimSpace(in[:i+1]), strings.TrimSpace(in[i+1:])
+}
+
+func ShortcodeRender(name string, data *ShortcodeWithPage, t *template.Template) string {
+ buffer := new(bytes.Buffer)
+ t.ExecuteTemplate(buffer, "shortcodes/"+name+".html", data)
+ return buffer.String()
+}
--- /dev/null
+++ b/hugolib/site.go
@@ -1,0 +1,362 @@
+// Copyright © 2013 Steve Francia <[email protected]>.
+//
+// Licensed under the Simple Public 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://opensource.org/licenses/Simple-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 (
+ "bitbucket.org/pkg/inflect"
+ "bytes"
+ "fmt"
+ "github.com/spf13/nitro"
+ "html/template"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
+ //"sync"
+)
+
+type Site struct {
+ c Config
+ Pages Pages
+ Tmpl *template.Template
+ Indexes IndexList
+ Files []string
+ Directories []string
+ Sections Index
+ Info SiteInfo
+ Shortcodes map[string]ShortcodeFunc
+ timer *nitro.B
+}
+
+type SiteInfo struct {
+ BaseUrl template.URL
+ Indexes *OrderedIndexList
+ Recent *Pages
+ LastChange time.Time
+}
+
+func (s *Site) getFromIndex(kind string, name string) Pages {
+ return s.Indexes[kind][name]
+}
+
+func NewSite(config *Config) *Site {
+ return &Site{c: *config, timer: nitro.Initalize()}
+}
+
+func (site *Site) Build() {
+ site.Process()
+ site.Render()
+ site.Write()
+}
+
+func (site *Site) Analyze() {
+ site.Process()
+ site.checkDescriptions()
+}
+
+func (site *Site) Process() {
+ site.initialize()
+ site.prepTemplates()
+ site.timer.Step("initialize & template prep")
+ site.CreatePages()
+ site.timer.Step("import pages")
+ site.BuildSiteMeta()
+ site.timer.Step("build indexes")
+}
+
+func (site *Site) Render() {
+ site.RenderIndexes()
+ site.timer.Step("render and write indexes")
+ site.RenderLists()
+ site.timer.Step("render and write lists")
+ site.RenderPages()
+ site.timer.Step("render pages")
+ site.ProcessShortcodes()
+ site.timer.Step("render shortcodes")
+ site.RenderHomePage()
+ site.timer.Step("render and write homepage")
+}
+
+func (site *Site) Write() {
+ site.WritePages()
+ site.timer.Step("write pages")
+}
+
+func (site *Site) checkDescriptions() {
+ for _, p := range site.Pages {
+ if len(p.Description) < 60 {
+ fmt.Print(p.FileName + " ")
+ }
+ }
+}
+
+func (s *Site) prepTemplates() {
+ var templates = template.New("")
+
+ funcMap := template.FuncMap{
+ "urlize": Urlize,
+ "gt": Gt,
+ "isset": IsSet,
+ "echoParam": ReturnWhenSet,
+ }
+
+ templates.Funcs(funcMap)
+
+ walker := func(path string, fi os.FileInfo, err error) error {
+ if err != nil {
+ PrintErr("Walker: ", err)
+ return nil
+ }
+
+ if !fi.IsDir() {
+ filetext, err := ioutil.ReadFile(path)
+ if err != nil {
+ return err
+ }
+ text := string(filetext)
+ name := path[len(s.c.GetAbsPath(s.c.LayoutDir))+1:]
+ t := templates.New(name)
+ template.Must(t.Parse(text))
+ }
+ return nil
+ }
+
+ filepath.Walk(s.c.GetAbsPath(s.c.LayoutDir), walker)
+
+ s.Tmpl = templates
+}
+
+func (s *Site) initialize() {
+ site := s
+
+ s.checkDirectories()
+
+ walker := func(path string, fi os.FileInfo, err error) error {
+ if err != nil {
+ PrintErr("Walker: ", err)
+ return nil
+ }
+
+ if fi.IsDir() {
+ site.Directories = append(site.Directories, path)
+ return nil
+ } else {
+ site.Files = append(site.Files, path)
+ return nil
+ }
+ return nil
+ }
+
+ filepath.Walk(s.c.GetAbsPath(s.c.SourceDir), walker)
+
+ s.Info = SiteInfo{BaseUrl: template.URL(s.c.BaseUrl)}
+
+ s.Shortcodes = make(map[string]ShortcodeFunc)
+}
+
+func (s *Site) checkDirectories() {
+ if b, _ := dirExists(s.c.GetAbsPath(s.c.LayoutDir)); !b {
+ FatalErr("No layout directory found, expecting to find it at " + s.c.GetAbsPath(s.c.LayoutDir))
+ }
+ if b, _ := dirExists(s.c.GetAbsPath(s.c.SourceDir)); !b {
+ FatalErr("No source directory found, expecting to find it at " + s.c.GetAbsPath(s.c.SourceDir))
+ }
+ mkdirIf(s.c.GetAbsPath(s.c.PublishDir))
+}
+
+func (s *Site) ProcessShortcodes() {
+ for i, _ := range s.Pages {
+ var bb bytes.Buffer
+ bb.WriteString(ShortcodesHandle(s.Pages[i].RenderedContent.String(), s.Pages[i], s.Tmpl))
+ s.Pages[i].RenderedContent = &bb
+ }
+}
+
+func (s *Site) CreatePages() {
+ for _, fileName := range s.Files {
+ page := NewPage(fileName)
+ page.Site = s.Info
+ page.Tmpl = s.Tmpl
+ if s.c.BuildDrafts || !page.Draft {
+ s.Pages = append(s.Pages, page)
+ }
+ }
+
+ s.Pages.Sort()
+}
+
+func (s *Site) BuildSiteMeta() {
+ s.Indexes = make(IndexList)
+ s.Sections = make(Index)
+
+ for _, plural := range s.c.Indexes {
+ s.Indexes[plural] = make(Index)
+ for i, p := range s.Pages {
+ vals := p.GetParam(plural)
+
+ if vals != nil {
+ for _, idx := range vals.([]string) {
+ s.Indexes[plural].Add(idx, s.Pages[i])
+ }
+ }
+ }
+ for k, _ := range s.Indexes[plural] {
+ s.Indexes[plural][k].Sort()
+ }
+ }
+
+ for i, p := range s.Pages {
+ sect := p.GetSection()
+ s.Sections.Add(sect, s.Pages[i])
+ }
+
+ for k, _ := range s.Sections {
+ s.Sections[k].Sort()
+ }
+
+ s.Info.Indexes = s.Indexes.BuildOrderedIndexList()
+
+ s.Info.LastChange = s.Pages[0].Date
+}
+
+func (s *Site) RenderPages() {
+ for i, _ := range s.Pages {
+ s.Pages[i].RenderedContent = s.RenderThing(s.Pages[i], s.Pages[i].Layout())
+ }
+}
+
+func (s *Site) WritePages() {
+ for _, p := range s.Pages {
+ s.WritePublic(p.Section, p.OutFile, p.RenderedContent.Bytes())
+ }
+}
+
+func (s *Site) RenderIndexes() {
+ for singular, plural := range s.c.Indexes {
+ for k, o := range s.Indexes[plural] {
+ n := s.NewNode()
+ n.Title = strings.Title(k)
+ url := Urlize(plural + "/" + k)
+ n.Url = url + ".html"
+ n.Permalink = template.HTML(MakePermalink(string(n.Site.BaseUrl), string(n.Url)))
+ n.RSSlink = template.HTML(MakePermalink(string(n.Site.BaseUrl), string(url+".xml")))
+ n.Date = o[0].Date
+ n.Data[singular] = o
+ n.Data["Pages"] = o
+ layout := "indexes/" + singular + ".html"
+
+ x := s.RenderThing(n, layout)
+ s.WritePublic(plural, k+".html", x.Bytes())
+
+ if a := s.Tmpl.Lookup("rss.xml"); a != nil {
+ // XML Feed
+ y := s.NewXMLBuffer()
+ n.Url = Urlize(plural + "/" + k + ".xml")
+ s.Tmpl.ExecuteTemplate(y, "rss.xml", n)
+ s.WritePublic(plural, k+".xml", y.Bytes())
+ }
+ }
+ }
+}
+
+func (s *Site) RenderLists() {
+ for section, data := range s.Sections {
+ n := s.NewNode()
+ n.Title = strings.Title(inflect.Pluralize(section))
+ n.Url = Urlize(section + "/index.html")
+ n.Permalink = template.HTML(MakePermalink(string(n.Site.BaseUrl), string(n.Url)))
+ n.RSSlink = template.HTML(MakePermalink(string(n.Site.BaseUrl), string(section+"/index.xml")))
+ n.Date = data[0].Date
+ n.Data["Pages"] = data
+ layout := "indexes/" + section + ".html"
+
+ x := s.RenderThing(n, layout)
+ s.WritePublic(section, "index.html", x.Bytes())
+
+ if a := s.Tmpl.Lookup("rss.xml"); a != nil {
+ // XML Feed
+ n.Url = Urlize(section + "/index.xml")
+ y := s.NewXMLBuffer()
+ s.Tmpl.ExecuteTemplate(y, "rss.xml", n)
+ s.WritePublic(section, "index.xml", y.Bytes())
+ }
+ }
+}
+
+func (s *Site) RenderHomePage() {
+ n := s.NewNode()
+ n.Title = ""
+ n.Url = Urlize(string(n.Site.BaseUrl))
+ n.RSSlink = template.HTML(MakePermalink(string(n.Site.BaseUrl), string("/index.xml")))
+ n.Permalink = template.HTML(string(n.Site.BaseUrl))
+ n.Date = s.Pages[0].Date
+ if len(s.Pages) < 9 {
+ n.Data["Pages"] = s.Pages
+ } else {
+ n.Data["Pages"] = s.Pages[:9]
+ }
+ x := s.RenderThing(n, "index.html")
+ s.WritePublic("", "index.html", x.Bytes())
+
+ if a := s.Tmpl.Lookup("rss.xml"); a != nil {
+ // XML Feed
+ n.Url = Urlize("index.xml")
+ y := s.NewXMLBuffer()
+ s.Tmpl.ExecuteTemplate(y, "rss.xml", n)
+ s.WritePublic("", "index.xml", y.Bytes())
+ }
+}
+
+func (s *Site) Stats() {
+ fmt.Printf("%d pages created \n", len(s.Pages))
+ for _, pl := range s.c.Indexes {
+ fmt.Printf("%d %s created\n", len(s.Indexes[pl]), pl)
+ }
+}
+
+func (s *Site) NewNode() Node {
+ var y Node
+ y.Data = make(map[string]interface{})
+ y.Site = s.Info
+
+ return y
+}
+
+func (s *Site) RenderThing(d interface{}, layout string) *bytes.Buffer {
+ buffer := new(bytes.Buffer)
+ s.Tmpl.ExecuteTemplate(buffer, layout, d)
+ return buffer
+}
+
+func (s *Site) NewXMLBuffer() *bytes.Buffer {
+ header := "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\" ?>\n"
+ return bytes.NewBufferString(header)
+}
+
+func (s *Site) WritePublic(path string, filename string, content []byte) {
+ AbsPath := ""
+ if path != "" {
+ // TODO double check the following line.. calling GetAbsPath 2x seems wrong
+ mkdirIf(s.c.GetAbsPath(filepath.Join(s.c.GetAbsPath(s.c.PublishDir), path)))
+ AbsPath = filepath.Join(s.c.GetAbsPath(s.c.PublishDir), path, filename)
+ } else {
+ AbsPath = filepath.Join(s.c.GetAbsPath(s.c.PublishDir), filename)
+ }
+
+ file, _ := os.Create(AbsPath)
+ defer file.Close()
+
+ file.Write(content)
+}
--- /dev/null
+++ b/main.go
@@ -1,0 +1,190 @@
+// Copyright © 2013 Steve Francia <[email protected]>.
+//
+// Licensed under the Simple Public 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://opensource.org/licenses/Simple-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 main
+
+import (
+ "flag"
+ "fmt"
+ "github.com/howeyc/fsnotify"
+ "github.com/spf13/hugo/hugolib"
+ "net/http"
+ "os"
+ "path/filepath"
+ "runtime/pprof"
+ "sync"
+)
+
+const (
+ cfgFiledefault = "config.json"
+)
+
+var (
+ baseUrl = flag.String("b", "", "hostname (and path) to the root eg. http://spf13.com/")
+ cfgfile = flag.String("c", cfgFiledefault, "config file (default is path/config.json)")
+ checkMode = flag.Bool("k", false, "analyze content and provide feedback")
+ draft = flag.Bool("d", false, "include content marked as draft")
+ help = flag.Bool("h", false, "show this help")
+ path = flag.String("p", "", "filesystem path to read files relative from")
+ verbose = flag.Bool("v", false, "verbose output")
+ cpuprofile = flag.Int("cpuprofile", 0, "Number of times to create the site and profile it")
+ watchMode = flag.Bool("w", false, "watch filesystem for changes and recreate as needed")
+ server = flag.Bool("s", false, "run a (very) simple web server")
+ port = flag.String("port", "1313", "port to run web server on, default :1313")
+)
+
+func usage() {
+ PrintErr("usage: hugo [flags]", "")
+ flag.PrintDefaults()
+ os.Exit(2)
+}
+
+func main() {
+
+ flag.Usage = usage
+ flag.Parse()
+
+ if *help {
+ usage()
+ }
+
+ config := hugolib.SetupConfig(cfgfile, path)
+ config.BuildDrafts = *draft
+
+ if *baseUrl != "" {
+ config.BaseUrl = *baseUrl
+ }
+
+ if *cpuprofile != 0 {
+ f, err := os.Create("/tmp/hugo-cpuprofile")
+
+ if err != nil {
+ panic(err)
+ }
+
+ pprof.StartCPUProfile(f)
+ defer pprof.StopCPUProfile()
+
+ for i := 0; i < *cpuprofile; i++ {
+ _ = buildSite(config)
+ }
+ }
+
+ if *checkMode {
+ site := hugolib.NewSite(config)
+ site.Analyze()
+ os.Exit(2)
+ }
+
+ if *watchMode {
+ fmt.Println("Watching for changes. Press ctrl+c to stop")
+ _ = buildSite(config)
+ err := NewWatcher(config, *port, *server)
+
+ if err != nil {
+ fmt.Println(err)
+ }
+ }
+
+ _ = buildSite(config)
+
+ if *server {
+ serve(*port, config)
+ }
+
+}
+
+func serve(port string, config *hugolib.Config) {
+ fmt.Println("Web Server is available at http://localhost:" + port)
+ fmt.Println("Press ctrl+c to stop")
+ panic(http.ListenAndServe(":"+port, http.FileServer(http.Dir(config.PublishDir))))
+}
+
+func buildSite(config *hugolib.Config) *hugolib.Site {
+ site := hugolib.NewSite(config)
+ site.Build()
+
+ site.Stats()
+
+ return site
+}
+
+func watchChange(c *hugolib.Config) {
+ fmt.Println("Change detected, rebuilding site\n")
+ buildSite(c)
+}
+
+func NewWatcher(c *hugolib.Config, port string, server bool) error {
+ watcher, err := fsnotify.NewWatcher()
+ var wg sync.WaitGroup
+
+ if err != nil {
+ return err
+ fmt.Println(err)
+ }
+
+ defer watcher.Close()
+
+ wg.Add(1)
+ go func() {
+ for {
+ select {
+ case ev := <-watcher.Event:
+ var _ = ev
+ watchChange(c)
+ // TODO add newly created directories to the watch list
+ case err := <-watcher.Error:
+ if err != nil {
+ fmt.Println("error:", err)
+ }
+ }
+ }
+ }()
+
+ for _, d := range getDirList(c) {
+ if d != "" {
+ _ = watcher.Watch(d)
+ }
+ }
+
+ if server {
+ go serve(port, c)
+ }
+
+ wg.Wait()
+ return nil
+}
+
+func getDirList(c *hugolib.Config) []string {
+ var a []string
+ walker := func(path string, fi os.FileInfo, err error) error {
+ if err != nil {
+ PrintErr("Walker: ", err)
+ return nil
+ }
+
+ if fi.IsDir() {
+ a = append(a, path)
+ }
+ return nil
+ }
+
+ filepath.Walk(c.GetAbsPath(c.SourceDir), walker)
+ filepath.Walk(c.GetAbsPath(c.LayoutDir), walker)
+
+ return a
+}
+
+func PrintErr(str string, a ...interface{}) {
+ fmt.Fprintln(os.Stderr, str, a)
+}