github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/resources/page/taxonomy.go (about)

     1  // Copyright 2023 The Hugo Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package page
    15  
    16  import (
    17  	"fmt"
    18  	"sort"
    19  
    20  	"github.com/gohugoio/hugo/compare"
    21  	"github.com/gohugoio/hugo/langs"
    22  )
    23  
    24  // The TaxonomyList is a list of all taxonomies and their values
    25  // e.g. List['tags'] => TagTaxonomy (from above)
    26  type TaxonomyList map[string]Taxonomy
    27  
    28  func (tl TaxonomyList) String() string {
    29  	return fmt.Sprintf("TaxonomyList(%d)", len(tl))
    30  }
    31  
    32  // A Taxonomy is a map of keywords to a list of pages.
    33  // For example
    34  //
    35  //	TagTaxonomy['technology'] = WeightedPages
    36  //	TagTaxonomy['go']  =  WeightedPages
    37  type Taxonomy map[string]WeightedPages
    38  
    39  // OrderedTaxonomy is another representation of an Taxonomy using an array rather than a map.
    40  // Important because you can't order a map.
    41  type OrderedTaxonomy []OrderedTaxonomyEntry
    42  
    43  // getOneOPage returns one page in the taxonomy,
    44  // nil if there is none.
    45  func (t OrderedTaxonomy) getOneOPage() Page {
    46  	if len(t) == 0 {
    47  		return nil
    48  	}
    49  	return t[0].Pages()[0]
    50  }
    51  
    52  // OrderedTaxonomyEntry is similar to an element of a Taxonomy, but with the key embedded (as name)
    53  // e.g:  {Name: Technology, WeightedPages: TaxonomyPages}
    54  type OrderedTaxonomyEntry struct {
    55  	Name string
    56  	WeightedPages
    57  }
    58  
    59  // Get the weighted pages for the given key.
    60  func (i Taxonomy) Get(key string) WeightedPages {
    61  	return i[key]
    62  }
    63  
    64  // Count the weighted pages for the given key.
    65  func (i Taxonomy) Count(key string) int { return len(i[key]) }
    66  
    67  // TaxonomyArray returns an ordered taxonomy with a non defined order.
    68  func (i Taxonomy) TaxonomyArray() OrderedTaxonomy {
    69  	ies := make([]OrderedTaxonomyEntry, len(i))
    70  	count := 0
    71  	for k, v := range i {
    72  		ies[count] = OrderedTaxonomyEntry{Name: k, WeightedPages: v}
    73  		count++
    74  	}
    75  	return ies
    76  }
    77  
    78  // Alphabetical returns an ordered taxonomy sorted by key name.
    79  func (i Taxonomy) Alphabetical() OrderedTaxonomy {
    80  	ia := i.TaxonomyArray()
    81  	p := ia.getOneOPage()
    82  	if p == nil {
    83  		return ia
    84  	}
    85  	currentSite := p.Site().Current()
    86  	coll := langs.GetCollator(currentSite.Language())
    87  	coll.Lock()
    88  	defer coll.Unlock()
    89  	name := func(i1, i2 *OrderedTaxonomyEntry) bool {
    90  		return coll.CompareStrings(i1.Name, i2.Name) < 0
    91  	}
    92  	oiBy(name).Sort(ia)
    93  	return ia
    94  }
    95  
    96  // ByCount returns an ordered taxonomy sorted by # of pages per key.
    97  // If taxonomies have the same # of pages, sort them alphabetical
    98  func (i Taxonomy) ByCount() OrderedTaxonomy {
    99  	count := func(i1, i2 *OrderedTaxonomyEntry) bool {
   100  		li1 := len(i1.WeightedPages)
   101  		li2 := len(i2.WeightedPages)
   102  
   103  		if li1 == li2 {
   104  			return compare.LessStrings(i1.Name, i2.Name)
   105  		}
   106  		return li1 > li2
   107  	}
   108  
   109  	ia := i.TaxonomyArray()
   110  	oiBy(count).Sort(ia)
   111  	return ia
   112  }
   113  
   114  // Pages returns the Pages for this taxonomy.
   115  func (ie OrderedTaxonomyEntry) Pages() Pages {
   116  	return ie.WeightedPages.Pages()
   117  }
   118  
   119  // Count returns the count the pages in this taxonomy.
   120  func (ie OrderedTaxonomyEntry) Count() int {
   121  	return len(ie.WeightedPages)
   122  }
   123  
   124  // Term returns the name given to this taxonomy.
   125  func (ie OrderedTaxonomyEntry) Term() string {
   126  	return ie.Name
   127  }
   128  
   129  // Reverse reverses the order of the entries in this taxonomy.
   130  func (t OrderedTaxonomy) Reverse() OrderedTaxonomy {
   131  	for i, j := 0, len(t)-1; i < j; i, j = i+1, j-1 {
   132  		t[i], t[j] = t[j], t[i]
   133  	}
   134  
   135  	return t
   136  }
   137  
   138  // A type to implement the sort interface for TaxonomyEntries.
   139  type orderedTaxonomySorter struct {
   140  	taxonomy OrderedTaxonomy
   141  	by       oiBy
   142  }
   143  
   144  // Closure used in the Sort.Less method.
   145  type oiBy func(i1, i2 *OrderedTaxonomyEntry) bool
   146  
   147  func (by oiBy) Sort(taxonomy OrderedTaxonomy) {
   148  	ps := &orderedTaxonomySorter{
   149  		taxonomy: taxonomy,
   150  		by:       by, // The Sort method's receiver is the function (closure) that defines the sort order.
   151  	}
   152  	sort.Stable(ps)
   153  }
   154  
   155  // Len is part of sort.Interface.
   156  func (s *orderedTaxonomySorter) Len() int {
   157  	return len(s.taxonomy)
   158  }
   159  
   160  // Swap is part of sort.Interface.
   161  func (s *orderedTaxonomySorter) Swap(i, j int) {
   162  	s.taxonomy[i], s.taxonomy[j] = s.taxonomy[j], s.taxonomy[i]
   163  }
   164  
   165  // Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter.
   166  func (s *orderedTaxonomySorter) Less(i, j int) bool {
   167  	return s.by(&s.taxonomy[i], &s.taxonomy[j])
   168  }