Renamed Indexes to Taxonomies. Old template and config parameters still work.
authorspf13 <steve.francia@gmail.com>
Wed, 9 Apr 2014 03:15:57 +0000 (23:15 -0400)
committerspf13 <steve.francia@gmail.com>
Wed, 9 Apr 2014 21:15:04 +0000 (17:15 -0400)
commands/hugo.go
hugolib/index.go [deleted file]
hugolib/indexing_test.go [deleted file]
hugolib/page_index_test.go [deleted file]
hugolib/page_taxonomy_test.go [new file with mode: 0644]
hugolib/site.go
hugolib/site_test.go
hugolib/site_url_test.go
hugolib/taxonomy.go [new file with mode: 0644]
hugolib/taxonomy_test.go [new file with mode: 0644]

index b172a08aaf51d00e4e769df149f66dafecc0fe91..e08337859ae46cd78c8742c4e3b1103fe6928e42 100644 (file)
@@ -84,6 +84,8 @@ func InitializeConfig() {
        viper.AddConfigPath(Source)
        viper.ReadInConfig()
 
+       viper.RegisterAlias("taxonomies", "indexes")
+
        viper.SetDefault("ContentDir", "content")
        viper.SetDefault("LayoutDir", "layouts")
        viper.SetDefault("StaticDir", "static")
diff --git a/hugolib/index.go b/hugolib/index.go
deleted file mode 100644 (file)
index b683f2b..0000000
+++ /dev/null
@@ -1,171 +0,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
diff --git a/hugolib/indexing_test.go b/hugolib/indexing_test.go
deleted file mode 100644 (file)
index ad828de..0000000
+++ /dev/null
@@ -1,18 +0,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)
-               }
-       }
-}
diff --git a/hugolib/page_index_test.go b/hugolib/page_index_test.go
deleted file mode 100644 (file)
index 0edb048..0000000
+++ /dev/null
@@ -1,79 +0,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
-}
diff --git a/hugolib/page_taxonomy_test.go b/hugolib/page_taxonomy_test.go
new file mode 100644 (file)
index 0000000..36924f9
--- /dev/null
@@ -0,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
+}
index e6103a6fad7a7df9432c64e0295c7478b0cb3964..7d165393260fb0d10c8c2d223535f470cb063173 100644 (file)
@@ -48,7 +48,7 @@ var DefaultTimer *nitro.B
 //    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 @@ var DefaultTimer *nitro.B
 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 Site struct {
 
 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 @@ func (s *Site) Process() (err error) {
        if err = s.BuildSiteMeta(); err != nil {
                return
        }
-       s.timerStep("build indexes")
+       s.timerStep("build taxonomies")
        return
 }
 
@@ -168,13 +169,13 @@ func (s *Site) Render() (err error) {
                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) CreatePages() (err error) {
 }
 
 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,15 +321,15 @@ func (s *Site) BuildSiteMeta() (err error) {
                                        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)
                                }
                        }
                }
-               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 @@ func (s *Site) BuildSiteMeta() (err error) {
                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 @@ func (s *Site) BuildSiteMeta() (err error) {
        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 @@ func inStringArray(arr []string, el string) bool {
        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 @@ func (s *Site) RenderAliases() error {
        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 @@ func (s *Site) RenderPages() (err error) {
                                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 @@ func (s *Site) RenderPages() (err error) {
        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 @@ func (s *Site) RenderIndexes() (err error) {
                                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 @@ func (s *Site) RenderIndexes() (err error) {
                                        // 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 @@ func (s *Site) RenderIndexes() (err error) {
        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,16 +493,16 @@ func (s *Site) RenderIndexesIndexes() (err error) {
        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))
                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 @@ func (s *Site) RenderLists() error {
                        // 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 @@ func (s *Site) RenderHomePage() error {
                        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) RenderHomePage() error {
 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) NewNode() *Node {
        }
 }
 
+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 @@ func (s *Site) render(d interface{}, out string, layouts ...string) (err error)
        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 {
index b5409b104c000622229de8406cb390c887668f52..c90df96f84be2c0ac4c6514d22789061db7198bf 100644 (file)
@@ -423,7 +423,7 @@ func TestOrderedPages(t *testing.T) {
        }
 }
 
-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 @@ categories_weight = 44
 +++
 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 @@ date = 1979-05-27T07:32:00Z
 +++
 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 @@ date = 2010-05-27T07:32:00Z
 +++
 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 @@ func TestWeightedIndexes(t *testing.T) {
                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)
        }
 }
index f248d6df184bcbee6489f9fcdd61427603b58185..63b737c316e59150114e951040c25674932d62ff 100644 (file)
@@ -71,8 +71,8 @@ func TestPageCount(t *testing.T) {
                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 {
diff --git a/hugolib/taxonomy.go b/hugolib/taxonomy.go
new file mode 100644 (file)
index 0000000..5bb69eb
--- /dev/null
@@ -0,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
diff --git a/hugolib/taxonomy_test.go b/hugolib/taxonomy_test.go
new file mode 100644 (file)
index 0000000..8deaa2c
--- /dev/null
@@ -0,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)
+               }
+       }
+}