adding hugo

This commit is contained in:
spf13 2013-07-04 11:32:55 -04:00
parent 50a1d6f3f1
commit 6e16449e5f
34 changed files with 3458 additions and 0 deletions

67
LICENSE.md Normal file
View file

@ -0,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").

4
docs/config.json Normal file
View file

@ -0,0 +1,4 @@
{
"Indexes" : {"tag": "tags"},
"BaseUrl" : "http://localhost"
}

View file

@ -0,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/"
}

View file

@ -0,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

View file

@ -0,0 +1,9 @@
{
"title": "Contributors",
"Pubdate": "2013-07-01"
}
Hugo was built with love and golang by:
* [spf13](https://github.com/spf13)

View file

@ -0,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.

View file

@ -0,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.*

View file

@ -0,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.

View file

@ -0,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").

View file

@ -0,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

View file

@ -0,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

View file

@ -0,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

View file

@ -0,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 '{{&#37;' 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 }}

View file

@ -0,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.

View file

@ -0,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.

52
docs/content/doc/usage.md Normal file
View file

@ -0,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

View file

@ -0,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>

View file

@ -0,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>

View file

@ -0,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">

View file

@ -0,0 +1,2 @@
<link rel="stylesheet" href="/static/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/css/bootstrap-responsive.css">

View file

@ -0,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>

View file

@ -0,0 +1,4 @@
{{ template "chrome/header.html" . }}
<h1>{{ .Title }}</h1>
{{ .Content }}
{{ template "chrome/footer.html" . }}

46
docs/layouts/index.html Normal file
View file

@ -0,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" }}

BIN
docs/public/static/.DS_Store vendored Normal file

Binary file not shown.

1109
docs/public/static/css/bootstrap-responsive.css vendored Executable file

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

143
hugolib/config.go Normal file
View file

@ -0,0 +1,143 @@
// Copyright © 2013 Steve Francia <spf@spf13.com>.
//
// 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
}

309
hugolib/helpers.go Normal file
View file

@ -0,0 +1,309 @@
// Copyright © 2013 Steve Francia <spf@spf13.com>.
//
// 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, "/")
}

58
hugolib/index.go Normal file
View file

@ -0,0 +1,58 @@
// Copyright © 2013 Steve Francia <spf@spf13.com>.
//
// 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] }

43
hugolib/node.go Normal file
View file

@ -0,0 +1,43 @@
// Copyright © 2013 Steve Francia <spf@spf13.com>.
//
// 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
}

381
hugolib/page.go Normal file
View file

@ -0,0 +1,381 @@
// Copyright © 2013 Steve Francia <spf@spf13.com>.
//
// 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))
}

131
hugolib/shortcode.go Normal file
View file

@ -0,0 +1,131 @@
// Copyright © 2013 Steve Francia <spf@spf13.com>.
//
// 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, "&ldquo;") && !inQuote {
final = append(final, v)
} else if inQuote && strings.HasSuffix(v, "&rdquo;") && !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, "&ldquo;") && !inQuote {
if strings.HasSuffix(v, "&rdquo;") {
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()
}

362
hugolib/site.go Normal file
View file

@ -0,0 +1,362 @@
// Copyright © 2013 Steve Francia <spf@spf13.com>.
//
// 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)
}

190
main.go Normal file
View file

@ -0,0 +1,190 @@
// Copyright © 2013 Steve Francia <spf@spf13.com>.
//
// 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)
}