ref: 93bcddebb3030705dcb643dffb65890a270b5182
parent: aae6fa0b6b6319187992231baf773768585820d6
author: spf13 <steve.francia@gmail.com>
date: Tue Apr 8 19:15:57 EDT 2014
Renamed Indexes to Taxonomies. Old template and config parameters still work.
--- a/commands/hugo.go
+++ b/commands/hugo.go
@@ -84,6 +84,8 @@
viper.AddConfigPath(Source)
viper.ReadInConfig()
+ viper.RegisterAlias("taxonomies", "indexes")+
viper.SetDefault("ContentDir", "content") viper.SetDefault("LayoutDir", "layouts") viper.SetDefault("StaticDir", "static")--- a/hugolib/index.go
+++ /dev/null
@@ -1,171 +1,0 @@
-// 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"
-
- "github.com/spf13/hugo/helpers"
-)
-
-/*
- * An index list is a list of all indexes and their values
- * EG. List['tags'] => TagIndex (from above)
- */
-type IndexList map[string]Index
-
-/*
- * An index is a map of keywords to a list of pages.
- * For example
- * TagIndex['technology'] = WeightedPages
- * TagIndex['go'] = WeightedPages2
- */
-type Index map[string]WeightedPages
-
-/*
- * A list of Pages with their corresponding (and relative) weight
- * [{Weight: 30, Page: *1}, {Weight: 40, Page: *2}]- */
-type WeightedPages []WeightedPage
-type WeightedPage struct {- Weight int
- Page *Page
-}
-
-/*
- * This is another representation of an Index using an array rather than a map.
- * Important because you can't order a map.
- */
-type OrderedIndex []OrderedIndexEntry
-
-/*
- * Similar to an element of an Index, but with the key embedded (as name)
- * Eg: {Name: Technology, WeightedPages: Indexedpages}- */
-type OrderedIndexEntry struct {- Name string
- WeightedPages WeightedPages
-}
-
-// KeyPrep... Indexes should be case insensitive. Can make it easily conditional later.
-func kp(in string) string {- return helpers.MakePath(in)
-}
-
-func (i Index) Get(key string) WeightedPages { return i[kp(key)] }-func (i Index) Count(key string) int { return len(i[kp(key)]) }-func (i Index) Add(key string, w WeightedPage) {- key = kp(key)
- i[key] = append(i[key], w)
-}
-
-// Returns an ordered index with a non defined order
-func (i Index) IndexArray() OrderedIndex {- ies := make([]OrderedIndexEntry, len(i))
- count := 0
- for k, v := range i {- ies[count] = OrderedIndexEntry{Name: k, WeightedPages: v}- count++
- }
- return ies
-}
-
-// Returns an ordered index sorted by key name
-func (i Index) Alphabetical() OrderedIndex {- name := func(i1, i2 *OrderedIndexEntry) bool {- return i1.Name < i2.Name
- }
-
- ia := i.IndexArray()
- OIby(name).Sort(ia)
- return ia
-}
-
-// Returns an ordered index sorted by # of pages per key
-func (i Index) ByCount() OrderedIndex {- count := func(i1, i2 *OrderedIndexEntry) bool {- return len(i1.WeightedPages) > len(i2.WeightedPages)
- }
-
- ia := i.IndexArray()
- OIby(count).Sort(ia)
- return ia
-}
-
-// Helper to move the page access up a level
-func (ie OrderedIndexEntry) Pages() []*Page {- return ie.WeightedPages.Pages()
-}
-
-func (ie OrderedIndexEntry) Count() int {- return len(ie.WeightedPages)
-}
-
-/*
- * Implementation of a custom sorter for OrderedIndexes
- */
-
-// A type to implement the sort interface for IndexEntries.
-type orderedIndexSorter struct {- index OrderedIndex
- by OIby
-}
-
-// Closure used in the Sort.Less method.
-type OIby func(i1, i2 *OrderedIndexEntry) bool
-
-func (by OIby) Sort(index OrderedIndex) {- ps := &orderedIndexSorter{- index: index,
- by: by, // The Sort method's receiver is the function (closure) that defines the sort order.
- }
- sort.Sort(ps)
-}
-
-// Len is part of sort.Interface.
-func (s *orderedIndexSorter) Len() int {- return len(s.index)
-}
-
-// Swap is part of sort.Interface.
-func (s *orderedIndexSorter) Swap(i, j int) {- s.index[i], s.index[j] = s.index[j], s.index[i]
-}
-
-// Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter.
-func (s *orderedIndexSorter) Less(i, j int) bool {- return s.by(&s.index[i], &s.index[j])
-}
-
-func (wp WeightedPages) Pages() Pages {- pages := make(Pages, len(wp))
- for i := range wp {- pages[i] = wp[i].Page
- }
- return pages
-}
-
-func (p WeightedPages) Len() int { return len(p) }-func (p WeightedPages) Swap(i, j int) { p[i], p[j] = p[j], p[i] }-func (p WeightedPages) Sort() { sort.Sort(p) }-func (p WeightedPages) Count() int { return len(p) }-func (p WeightedPages) Less(i, j int) bool {- if p[i].Weight == p[j].Weight {- return p[i].Page.Date.Unix() > p[j].Page.Date.Unix()
- } else {- return p[i].Weight < p[j].Weight
- }
-}
-
-// TODO mimic PagesSorter for WeightedPages
--- a/hugolib/indexing_test.go
+++ /dev/null
@@ -1,18 +1,0 @@
-package hugolib
-
-import (
- "strings"
- "testing"
-)
-
-func TestSitePossibleIndexes(t *testing.T) {- site := new(Site)
- page, _ := ReadFrom(strings.NewReader(PAGE_YAML_WITH_INDEXES_A), "path/to/page")
- site.Pages = append(site.Pages, page)
- indexes := site.possibleIndexes()
- if !compareStringSlice(indexes, []string{"tags", "categories"}) {- if !compareStringSlice(indexes, []string{"categories", "tags"}) {- t.Fatalf("possible indexes do not match [tags categories]. Got: %s", indexes)- }
- }
-}
--- a/hugolib/page_index_test.go
+++ /dev/null
@@ -1,79 +1,0 @@
-package hugolib
-
-import (
- "strings"
- "testing"
-)
-
-var PAGE_YAML_WITH_INDEXES_A = `---
-tags: ['a', 'b', 'c']
-categories: 'd'
----
-YAML frontmatter with tags and categories index.`
-
-var PAGE_YAML_WITH_INDEXES_B = `---
-tags:
- - "a"
- - "b"
- - "c"
-categories: 'd'
----
-YAML frontmatter with tags and categories index.`
-
-var PAGE_JSON_WITH_INDEXES = `{- "categories": "d",
- "tags": [
- "a",
- "b",
- "c"
- ]
-}
-JSON Front Matter with tags and categories`
-
-var PAGE_TOML_WITH_INDEXES = `+++
-tags = [ "a", "b", "c" ]
-categories = "d"
-+++
-TOML Front Matter with tags and categories`
-
-func TestParseIndexes(t *testing.T) {- for _, test := range []string{PAGE_TOML_WITH_INDEXES,- PAGE_JSON_WITH_INDEXES,
- PAGE_YAML_WITH_INDEXES_A,
- PAGE_YAML_WITH_INDEXES_B,
- } {- p, err := ReadFrom(strings.NewReader(test), "page/with/index")
- if err != nil {- t.Fatalf("Failed parsing %q: %s", test, err)- }
-
- param := p.GetParam("tags")- params := param.([]string)
-
- expected := []string{"a", "b", "c"}- if !compareStringSlice(params, expected) {- t.Errorf("Expected %s: got: %s", expected, params)- }
-
- param = p.GetParam("categories")- singleparam := param.(string)
-
- if singleparam != "d" {- t.Fatalf("Expected: d, got: %s", singleparam)- }
- }
-}
-
-func compareStringSlice(a, b []string) bool {- if len(a) != len(b) {- return false
- }
-
- for i, v := range a {- if b[i] != v {- return false
- }
- }
-
- return true
-}
--- /dev/null
+++ b/hugolib/page_taxonomy_test.go
@@ -1,0 +1,79 @@
+package hugolib
+
+import (
+ "strings"
+ "testing"
+)
+
+var PAGE_YAML_WITH_TAXONOMIES_A = `---
+tags: ['a', 'b', 'c']
+categories: 'd'
+---
+YAML frontmatter with tags and categories taxonomy.`
+
+var PAGE_YAML_WITH_TAXONOMIES_B = `---
+tags:
+ - "a"
+ - "b"
+ - "c"
+categories: 'd'
+---
+YAML frontmatter with tags and categories taxonomy.`
+
+var PAGE_JSON_WITH_TAXONOMIES = `{+ "categories": "d",
+ "tags": [
+ "a",
+ "b",
+ "c"
+ ]
+}
+JSON Front Matter with tags and categories`
+
+var PAGE_TOML_WITH_TAXONOMIES = `+++
+tags = [ "a", "b", "c" ]
+categories = "d"
++++
+TOML Front Matter with tags and categories`
+
+func TestParseTaxonomies(t *testing.T) {+ for _, test := range []string{PAGE_TOML_WITH_TAXONOMIES,+ PAGE_JSON_WITH_TAXONOMIES,
+ PAGE_YAML_WITH_TAXONOMIES_A,
+ PAGE_YAML_WITH_TAXONOMIES_B,
+ } {+ p, err := ReadFrom(strings.NewReader(test), "page/with/taxonomy")
+ if err != nil {+ t.Fatalf("Failed parsing %q: %s", test, err)+ }
+
+ param := p.GetParam("tags")+ params := param.([]string)
+
+ expected := []string{"a", "b", "c"}+ if !compareStringSlice(params, expected) {+ t.Errorf("Expected %s: got: %s", expected, params)+ }
+
+ param = p.GetParam("categories")+ singleparam := param.(string)
+
+ if singleparam != "d" {+ t.Fatalf("Expected: d, got: %s", singleparam)+ }
+ }
+}
+
+func compareStringSlice(a, b []string) bool {+ if len(a) != len(b) {+ return false
+ }
+
+ for i, v := range a {+ if b[i] != v {+ return false
+ }
+ }
+
+ return true
+}
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -48,7 +48,7 @@
// various targets that will get generated. There will be canonical
// listing. The canonical path can be overruled based on a pattern.
//
-// 3. Indexes are created via configuration and will present some aspect of
+// 3. Taxonomies are created via configuration and will present some aspect of
// the final page and typically a perm url.
//
// 4. All Pages are passed through a template based on their desired
@@ -58,9 +58,9 @@
type Site struct {Pages Pages
Tmpl bundle.Template
- Indexes IndexList
+ Taxonomies TaxonomyList
Source source.Input
- Sections Index
+ Sections Taxonomy
Info SiteInfo
Shortcodes map[string]ShortcodeFunc
timer *nitro.B
@@ -73,7 +73,8 @@
type SiteInfo struct {BaseUrl template.URL
- Indexes IndexList
+ Taxonomies TaxonomyList
+ Indexes *TaxonomyList // legacy, should be identical to Taxonomies
Recent *Pages
LastChange time.Time
Title string
@@ -147,7 +148,7 @@
if err = s.BuildSiteMeta(); err != nil {return
}
- s.timerStep("build indexes")+ s.timerStep("build taxonomies")return
}
@@ -168,13 +169,13 @@
return
}
s.timerStep("render and write aliases")- if err = s.RenderIndexes(); err != nil {+ if err = s.RenderTaxonomiesLists(); err != nil {return
}
- s.timerStep("render and write indexes")- s.RenderIndexesIndexes()
- s.timerStep("render & write index indexes")- if err = s.RenderLists(); err != nil {+ s.timerStep("render and write taxonomies")+ s.RenderListsOfTaxonomyTerms()
+ s.timerStep("render & write taxonomy lists")+ if err = s.RenderSectionLists(); err != nil {return
}
s.timerStep("render and write lists")@@ -299,14 +300,14 @@
}
func (s *Site) BuildSiteMeta() (err error) {- s.Indexes = make(IndexList)
- s.Sections = make(Index)
+ s.Taxonomies = make(TaxonomyList)
+ s.Sections = make(Taxonomy)
- indexes := viper.GetStringMapString("Indexes")- jww.INFO.Printf("found indexes: %#v\n", indexes)+ taxonomies := viper.GetStringMapString("Taxonomies")+ jww.INFO.Printf("found taxonomies: %#v\n", taxonomies)- for _, plural := range indexes {- s.Indexes[plural] = make(Index)
+ for _, plural := range taxonomies {+ s.Taxonomies[plural] = make(Taxonomy)
for _, p := range s.Pages {vals := p.GetParam(plural)
weight := p.GetParam(plural + "_weight")
@@ -320,7 +321,7 @@
for _, idx := range v { x := WeightedPage{weight.(int), p}- s.Indexes[plural].Add(idx, x)
+ s.Taxonomies[plural].Add(idx, x)
}
} else { jww.ERROR.Printf("Invalid %s in %s\n", plural, p.File.FileName)@@ -327,8 +328,8 @@
}
}
}
- for k := range s.Indexes[plural] {- s.Indexes[plural][k].Sort()
+ for k := range s.Taxonomies[plural] {+ s.Taxonomies[plural][k].Sort()
}
}
@@ -340,7 +341,8 @@
s.Sections[k].Sort()
}
- s.Info.Indexes = s.Indexes
+ s.Info.Taxonomies = s.Taxonomies
+ s.Info.Indexes = &s.Taxonomies
if len(s.Pages) == 0 {return
@@ -355,11 +357,11 @@
return
}
-func (s *Site) possibleIndexes() (indexes []string) {+func (s *Site) possibleTaxonomies() (taxonomies []string) { for _, p := range s.Pages { for k := range p.Params {- if !inStringArray(indexes, k) {- indexes = append(indexes, k)
+ if !inStringArray(taxonomies, k) {+ taxonomies = append(taxonomies, k)
}
}
}
@@ -375,6 +377,7 @@
return false
}
+// Render shell pages that simply have a redirect in the header
func (s *Site) RenderAliases() error { for _, p := range s.Pages { for _, a := range p.Aliases {@@ -390,12 +393,13 @@
return nil
}
+// Render pages each corresponding to a markdown file
func (s *Site) RenderPages() (err error) {var wg sync.WaitGroup
for _, page := range s.Pages {wg.Add(1)
go func(p *Page) (err error) {- var layout []string
+ var layouts []string
defer wg.Done()
if !p.IsRenderable() {@@ -404,13 +408,13 @@
if err != nil {return err
}
- layout = append(layout, self)
+ layouts = append(layouts, self)
} else {- layout = append(layout, p.Layout()...)
- layout = append(layout, "_default/single.html")
+ layouts = append(layouts, p.Layout()...)
+ layouts = append(layouts, "_default/single.html")
}
- return s.render(p, p.TargetPath(), layout...)
+ return s.render(p, p.TargetPath(), layouts...)
}(page)
}
wg.Wait()
@@ -421,12 +425,14 @@
return nil
}
-func (s *Site) RenderIndexes() (err error) {+// Render the listing pages based on the meta data
+// each unique term within a taxonomy will have a page created
+func (s *Site) RenderTaxonomiesLists() (err error) {var wg sync.WaitGroup
- indexes := viper.GetStringMapString("Indexes")- for sing, pl := range indexes {- for key, oo := range s.Indexes[pl] {+ taxonomies := viper.GetStringMapString("Taxonomies")+ for sing, pl := range taxonomies {+ for key, oo := range s.Taxonomies[pl] {wg.Add(1)
go func(k string, o WeightedPages, singular string, plural string) (err error) {defer wg.Done()
@@ -437,8 +443,8 @@
n.Date = o[0].Page.Date
n.Data[singular] = o
n.Data["Pages"] = o.Pages()
- layout := "indexes/" + singular + ".html"
- err = s.render(n, base+".html", layout)
+ err = s.render(n, base+".html", "taxonomies/"+singular+".html", "indexes/"+singular+".html")
+ //TODO add , "_default/taxonomy.html", "_default/list.html"
if err != nil {return err
}
@@ -447,6 +453,7 @@
// XML Feed
s.setUrls(n, base+".xml")
err := s.render(n, base+".xml", "rss.xml")
+ // TODO add "taxonomy.xml", "_internal/rss.xml"
if err != nil {return err
}
@@ -459,22 +466,25 @@
return nil
}
-func (s *Site) RenderIndexesIndexes() (err error) {- layout := "indexes/indexes.html"
- if s.Tmpl.Lookup(layout) != nil {-
- indexes := viper.GetStringMapString("Indexes")- for singular, plural := range indexes {+// Render a page per taxonomy that lists the terms for that taxonomy
+func (s *Site) RenderListsOfTaxonomyTerms() (err error) {+ layouts := []string{"taxonomies/termslist.html", "indexes/indexes.html"}+ // TODO add "_default/termsList.html", "_default/termslist.html"
+ // TODO add support for unique taxonomy terms list (`single`terms.html)
+ if s.layoutExists(layouts...) {+ taxonomies := viper.GetStringMapString("Taxonomies")+ for singular, plural := range taxonomies {n := s.NewNode()
n.Title = strings.Title(plural)
s.setUrls(n, plural)
n.Data["Singular"] = singular
n.Data["Plural"] = plural
- n.Data["Index"] = s.Indexes[plural]
+ n.Data["Terms"] = s.Taxonomies[plural]
// keep the following just for legacy reasons
- n.Data["OrderedIndex"] = s.Indexes[plural]
+ n.Data["OrderedIndex"] = n.Data["Terms"]
+ n.Data["Index"] = n.Data["Terms"]
- err := s.render(n, plural+"/index.html", layout)
+ err := s.render(n, plural+"/index.html", layouts...)
if err != nil {return err
}
@@ -483,7 +493,8 @@
return
}
-func (s *Site) RenderLists() error {+// Render a page for each section
+func (s *Site) RenderSectionLists() error { for section, data := range s.Sections {n := s.NewNode()
n.Title = strings.Title(inflect.Pluralize(section))
@@ -490,9 +501,8 @@
s.setUrls(n, section)
n.Date = data[0].Page.Date
n.Data["Pages"] = data.Pages()
- layout := "indexes/" + section + ".html"
- err := s.render(n, section, layout, "_default/indexes.html")
+ err := s.render(n, section, "section/"+section+".html", "indexes/"+section+".html", "_default/section.html", "_default/list.html", "_default/indexes.html")
if err != nil {return err
}
@@ -501,6 +511,8 @@
// XML Feed
s.setUrls(n, section+".xml")
err = s.render(n, section+".xml", "rss.xml")
+ //TODO add section specific rss
+ // TODO add internal rss
if err != nil {return err
}
@@ -533,6 +545,7 @@
n.Date = s.Pages[0].Date
}
err := s.render(n, ".xml", "rss.xml")
+ // TODO add internal RSS
if err != nil {return err
}
@@ -551,10 +564,10 @@
func (s *Site) Stats() { jww.FEEDBACK.Printf("%d pages created \n", len(s.Pages))- indexes := viper.GetStringMapString("Indexes")+ taxonomies := viper.GetStringMapString("Taxonomies")- for _, pl := range indexes {- jww.FEEDBACK.Printf("%d %s index created\n", len(s.Indexes[pl]), pl)+ for _, pl := range taxonomies {+ jww.FEEDBACK.Printf("%d %s created\n", len(s.Taxonomies[pl]), pl)}
}
@@ -587,10 +600,16 @@
}
}
+func (s *Site) layoutExists(layouts ...string) bool {+ _, found := s.findFirstLayout(layouts...)
+
+ return found
+}
+
func (s *Site) render(d interface{}, out string, layouts ...string) (err error) {- layout := s.findFirstLayout(layouts...)
- if layout == "" {+ layout, found := s.findFirstLayout(layouts...)
+ if found == false { jww.WARN.Printf("Unable to locate layout: %s\n", layouts)return
}
@@ -634,13 +653,13 @@
return s.WritePublic(out, outBuffer)
}
-func (s *Site) findFirstLayout(layouts ...string) (layout string) {- for _, layout = range layouts {+func (s *Site) findFirstLayout(layouts ...string) (string, bool) {+ for _, layout := range layouts { if s.Tmpl.Lookup(layout) != nil {- return
+ return layout, true
}
}
- return ""
+ return "", false
}
func (s *Site) renderThing(d interface{}, layout string, w io.Writer) error {--- a/hugolib/site_test.go
+++ b/hugolib/site_test.go
@@ -423,7 +423,7 @@
}
}
-var PAGE_WITH_WEIGHTED_INDEXES_2 = []byte(`+++
+var PAGE_WITH_WEIGHTED_TAXONOMIES_2 = []byte(`+++
tags = [ "a", "b", "c" ]
tags_weight = 22
categories = ["d"]
@@ -432,7 +432,7 @@
+++
Front Matter with weighted tags and categories`)
-var PAGE_WITH_WEIGHTED_INDEXES_1 = []byte(`+++
+var PAGE_WITH_WEIGHTED_TAXONOMIES_1 = []byte(`+++
tags = [ "a" ]
tags_weight = 33
title = "bar"
@@ -443,7 +443,7 @@
+++
Front Matter with weighted tags and categories`)
-var PAGE_WITH_WEIGHTED_INDEXES_3 = []byte(`+++
+var PAGE_WITH_WEIGHTED_TAXONOMIES_3 = []byte(`+++
title = "bza"
categories = [ "e" ]
categories_weight = 11
@@ -452,21 +452,21 @@
+++
Front Matter with weighted tags and categories`)
-func TestWeightedIndexes(t *testing.T) {+func TestWeightedTaxonomies(t *testing.T) {files := make(map[string][]byte)
target := &target.InMemoryTarget{Files: files} sources := []source.ByteSource{- {"sect/doc1.md", PAGE_WITH_WEIGHTED_INDEXES_1, "sect"},- {"sect/doc2.md", PAGE_WITH_WEIGHTED_INDEXES_2, "sect"},- {"sect/doc3.md", PAGE_WITH_WEIGHTED_INDEXES_3, "sect"},+ {"sect/doc1.md", PAGE_WITH_WEIGHTED_TAXONOMIES_1, "sect"},+ {"sect/doc2.md", PAGE_WITH_WEIGHTED_TAXONOMIES_2, "sect"},+ {"sect/doc3.md", PAGE_WITH_WEIGHTED_TAXONOMIES_3, "sect"},}
- indexes := make(map[string]string)
+ taxonomies := make(map[string]string)
- indexes["tag"] = "tags"
- indexes["category"] = "categories"
+ taxonomies["tag"] = "tags"
+ taxonomies["category"] = "categories"
viper.Set("baseurl", "http://auth/bub")- viper.Set("indexes", indexes)+ viper.Set("taxonomies", taxonomies) s := &Site{Target: target,
Source: &source.InMemorySource{ByteSource: sources},@@ -481,15 +481,15 @@
t.Fatalf("Unable to build site metadata: %s", err)}
- if s.Indexes["tags"]["a"][0].Page.Title != "foo" {- t.Errorf("Pages in unexpected order, 'foo' expected first, got '%v'", s.Indexes["tags"]["a"][0].Page.Title)+ if s.Taxonomies["tags"]["a"][0].Page.Title != "foo" {+ t.Errorf("Pages in unexpected order, 'foo' expected first, got '%v'", s.Taxonomies["tags"]["a"][0].Page.Title)}
- if s.Indexes["categories"]["d"][0].Page.Title != "bar" {- t.Errorf("Pages in unexpected order, 'bar' expected first, got '%v'", s.Indexes["categories"]["d"][0].Page.Title)+ if s.Taxonomies["categories"]["d"][0].Page.Title != "bar" {+ t.Errorf("Pages in unexpected order, 'bar' expected first, got '%v'", s.Taxonomies["categories"]["d"][0].Page.Title)}
- if s.Indexes["categories"]["e"][0].Page.Title != "bza" {- t.Errorf("Pages in unexpected order, 'bza' expected first, got '%v'", s.Indexes["categories"]["e"][0].Page.Title)+ if s.Taxonomies["categories"]["e"][0].Page.Title != "bza" {+ t.Errorf("Pages in unexpected order, 'bza' expected first, got '%v'", s.Taxonomies["categories"]["e"][0].Page.Title)}
}
--- a/hugolib/site_url_test.go
+++ b/hugolib/site_url_test.go
@@ -71,8 +71,8 @@
t.Errorf("Unable to build site metadata: %s", err)}
- if err := s.RenderLists(); err != nil {- t.Errorf("Unable to render site lists: %s", err)+ if err := s.RenderSectionLists(); err != nil {+ t.Errorf("Unable to render section lists: %s", err)}
if err := s.RenderAliases(); err != nil {--- /dev/null
+++ b/hugolib/taxonomy.go
@@ -1,0 +1,171 @@
+// 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"
+
+ "github.com/spf13/hugo/helpers"
+)
+
+/*
+ * An taxonomy list is a list of all taxonomies and their values
+ * EG. List['tags'] => TagTaxonomy (from above)
+ */
+type TaxonomyList map[string]Taxonomy
+
+/*
+ * An taxonomy is a map of keywords to a list of pages.
+ * For example
+ * TagTaxonomy['technology'] = WeightedPages
+ * TagTaxonomy['go'] = WeightedPages2
+ */
+type Taxonomy map[string]WeightedPages
+
+/*
+ * A list of Pages with their corresponding (and relative) weight
+ * [{Weight: 30, Page: *1}, {Weight: 40, Page: *2}]+ */
+type WeightedPages []WeightedPage
+type WeightedPage struct {+ Weight int
+ Page *Page
+}
+
+/*
+ * This is another representation of an Taxonomy using an array rather than a map.
+ * Important because you can't order a map.
+ */
+type OrderedTaxonomy []OrderedTaxonomyEntry
+
+/*
+ * Similar to an element of an Taxonomy, but with the key embedded (as name)
+ * Eg: {Name: Technology, WeightedPages: Taxonomyedpages}+ */
+type OrderedTaxonomyEntry struct {+ Name string
+ WeightedPages WeightedPages
+}
+
+// KeyPrep... Taxonomies should be case insensitive. Can make it easily conditional later.
+func kp(in string) string {+ return helpers.MakePath(in)
+}
+
+func (i Taxonomy) Get(key string) WeightedPages { return i[kp(key)] }+func (i Taxonomy) Count(key string) int { return len(i[kp(key)]) }+func (i Taxonomy) Add(key string, w WeightedPage) {+ key = kp(key)
+ i[key] = append(i[key], w)
+}
+
+// Returns an ordered taxonomy with a non defined order
+func (i Taxonomy) TaxonomyArray() OrderedTaxonomy {+ ies := make([]OrderedTaxonomyEntry, len(i))
+ count := 0
+ for k, v := range i {+ ies[count] = OrderedTaxonomyEntry{Name: k, WeightedPages: v}+ count++
+ }
+ return ies
+}
+
+// Returns an ordered taxonomy sorted by key name
+func (i Taxonomy) Alphabetical() OrderedTaxonomy {+ name := func(i1, i2 *OrderedTaxonomyEntry) bool {+ return i1.Name < i2.Name
+ }
+
+ ia := i.TaxonomyArray()
+ OIby(name).Sort(ia)
+ return ia
+}
+
+// Returns an ordered taxonomy sorted by # of pages per key
+func (i Taxonomy) ByCount() OrderedTaxonomy {+ count := func(i1, i2 *OrderedTaxonomyEntry) bool {+ return len(i1.WeightedPages) > len(i2.WeightedPages)
+ }
+
+ ia := i.TaxonomyArray()
+ OIby(count).Sort(ia)
+ return ia
+}
+
+// Helper to move the page access up a level
+func (ie OrderedTaxonomyEntry) Pages() []*Page {+ return ie.WeightedPages.Pages()
+}
+
+func (ie OrderedTaxonomyEntry) Count() int {+ return len(ie.WeightedPages)
+}
+
+/*
+ * Implementation of a custom sorter for OrderedTaxonomies
+ */
+
+// A type to implement the sort interface for TaxonomyEntries.
+type orderedTaxonomySorter struct {+ taxonomy OrderedTaxonomy
+ by OIby
+}
+
+// Closure used in the Sort.Less method.
+type OIby func(i1, i2 *OrderedTaxonomyEntry) bool
+
+func (by OIby) Sort(taxonomy OrderedTaxonomy) {+ ps := &orderedTaxonomySorter{+ taxonomy: taxonomy,
+ by: by, // The Sort method's receiver is the function (closure) that defines the sort order.
+ }
+ sort.Sort(ps)
+}
+
+// Len is part of sort.Interface.
+func (s *orderedTaxonomySorter) Len() int {+ return len(s.taxonomy)
+}
+
+// Swap is part of sort.Interface.
+func (s *orderedTaxonomySorter) Swap(i, j int) {+ s.taxonomy[i], s.taxonomy[j] = s.taxonomy[j], s.taxonomy[i]
+}
+
+// Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter.
+func (s *orderedTaxonomySorter) Less(i, j int) bool {+ return s.by(&s.taxonomy[i], &s.taxonomy[j])
+}
+
+func (wp WeightedPages) Pages() Pages {+ pages := make(Pages, len(wp))
+ for i := range wp {+ pages[i] = wp[i].Page
+ }
+ return pages
+}
+
+func (p WeightedPages) Len() int { return len(p) }+func (p WeightedPages) Swap(i, j int) { p[i], p[j] = p[j], p[i] }+func (p WeightedPages) Sort() { sort.Sort(p) }+func (p WeightedPages) Count() int { return len(p) }+func (p WeightedPages) Less(i, j int) bool {+ if p[i].Weight == p[j].Weight {+ return p[i].Page.Date.Unix() > p[j].Page.Date.Unix()
+ } else {+ return p[i].Weight < p[j].Weight
+ }
+}
+
+// TODO mimic PagesSorter for WeightedPages
--- /dev/null
+++ b/hugolib/taxonomy_test.go
@@ -1,0 +1,18 @@
+package hugolib
+
+import (
+ "strings"
+ "testing"
+)
+
+func TestSitePossibleTaxonomies(t *testing.T) {+ site := new(Site)
+ page, _ := ReadFrom(strings.NewReader(PAGE_YAML_WITH_TAXONOMIES_A), "path/to/page")
+ site.Pages = append(site.Pages, page)
+ taxonomies := site.possibleTaxonomies()
+ if !compareStringSlice(taxonomies, []string{"tags", "categories"}) {+ if !compareStringSlice(taxonomies, []string{"categories", "tags"}) {+ t.Fatalf("possible taxonomies do not match [tags categories]. Got: %s", taxonomies)+ }
+ }
+}
--
⑨