github.com/SuCicada/su-hugo@v1.0.0/hugofs/files/classifier.go (about)

     1  // Copyright 2019 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 files
    15  
    16  import (
    17  	"bufio"
    18  	"fmt"
    19  	"io"
    20  	"os"
    21  	"path/filepath"
    22  	"sort"
    23  	"strings"
    24  	"unicode"
    25  
    26  	"github.com/spf13/afero"
    27  )
    28  
    29  const (
    30  	// The NPM package.json "template" file.
    31  	FilenamePackageHugoJSON = "package.hugo.json"
    32  	// The NPM package file.
    33  	FilenamePackageJSON = "package.json"
    34  )
    35  
    36  var (
    37  	// This should be the only list of valid extensions for content files.
    38  	contentFileExtensions = []string{
    39  		"html", "htm",
    40  		"mdown", "markdown", "md",
    41  		"asciidoc", "adoc", "ad",
    42  		"rest", "rst",
    43  		"org",
    44  		"pandoc", "pdc",
    45  	}
    46  
    47  	contentFileExtensionsSet map[string]bool
    48  
    49  	htmlFileExtensions = []string{
    50  		"html", "htm",
    51  	}
    52  
    53  	htmlFileExtensionsSet map[string]bool
    54  )
    55  
    56  func init() {
    57  	contentFileExtensionsSet = make(map[string]bool)
    58  	for _, ext := range contentFileExtensions {
    59  		contentFileExtensionsSet[ext] = true
    60  	}
    61  	htmlFileExtensionsSet = make(map[string]bool)
    62  	for _, ext := range htmlFileExtensions {
    63  		htmlFileExtensionsSet[ext] = true
    64  	}
    65  }
    66  
    67  func IsContentFile(filename string) bool {
    68  	return contentFileExtensionsSet[strings.TrimPrefix(filepath.Ext(filename), ".")]
    69  }
    70  
    71  func IsIndexContentFile(filename string) bool {
    72  	if !IsContentFile(filename) {
    73  		return false
    74  	}
    75  
    76  	base := filepath.Base(filename)
    77  
    78  	return strings.HasPrefix(base, "index.") || strings.HasPrefix(base, "_index.")
    79  }
    80  
    81  func IsHTMLFile(filename string) bool {
    82  	return htmlFileExtensionsSet[strings.TrimPrefix(filepath.Ext(filename), ".")]
    83  }
    84  
    85  func IsContentExt(ext string) bool {
    86  	return contentFileExtensionsSet[ext]
    87  }
    88  
    89  type ContentClass string
    90  
    91  const (
    92  	ContentClassLeaf    ContentClass = "leaf"
    93  	ContentClassBranch  ContentClass = "branch"
    94  	ContentClassFile    ContentClass = "zfile" // Sort below
    95  	ContentClassContent ContentClass = "zcontent"
    96  )
    97  
    98  func (c ContentClass) IsBundle() bool {
    99  	return c == ContentClassLeaf || c == ContentClassBranch
   100  }
   101  
   102  func ClassifyContentFile(filename string, open func() (afero.File, error)) ContentClass {
   103  	if !IsContentFile(filename) {
   104  		return ContentClassFile
   105  	}
   106  
   107  	if IsHTMLFile(filename) {
   108  		// We need to look inside the file. If the first non-whitespace
   109  		// character is a "<", then we treat it as a regular file.
   110  		// Eearlier we created pages for these files, but that had all sorts
   111  		// of troubles, and isn't what it says in the documentation.
   112  		// See https://github.com/gohugoio/hugo/issues/7030
   113  		if open == nil {
   114  			panic(fmt.Sprintf("no file opener provided for %q", filename))
   115  		}
   116  
   117  		f, err := open()
   118  		if err != nil {
   119  			return ContentClassFile
   120  		}
   121  		ishtml := isHTMLContent(f)
   122  		f.Close()
   123  		if ishtml {
   124  			return ContentClassFile
   125  		}
   126  
   127  	}
   128  
   129  	if strings.HasPrefix(filename, "_index.") {
   130  		return ContentClassBranch
   131  	}
   132  
   133  	if strings.HasPrefix(filename, "index.") {
   134  		return ContentClassLeaf
   135  	}
   136  
   137  	return ContentClassContent
   138  }
   139  
   140  var htmlComment = []rune{'<', '!', '-', '-'}
   141  
   142  func isHTMLContent(r io.Reader) bool {
   143  	br := bufio.NewReader(r)
   144  	i := 0
   145  	for {
   146  		c, _, err := br.ReadRune()
   147  		if err != nil {
   148  			break
   149  		}
   150  
   151  		if i > 0 {
   152  			if i >= len(htmlComment) {
   153  				return false
   154  			}
   155  
   156  			if c != htmlComment[i] {
   157  				return true
   158  			}
   159  
   160  			i++
   161  			continue
   162  		}
   163  
   164  		if !unicode.IsSpace(c) {
   165  			if i == 0 && c != '<' {
   166  				return false
   167  			}
   168  			i++
   169  		}
   170  	}
   171  	return true
   172  }
   173  
   174  const (
   175  	ComponentFolderArchetypes = "archetypes"
   176  	ComponentFolderStatic     = "static"
   177  	ComponentFolderLayouts    = "layouts"
   178  	ComponentFolderContent    = "content"
   179  	ComponentFolderData       = "data"
   180  	ComponentFolderAssets     = "assets"
   181  	ComponentFolderI18n       = "i18n"
   182  
   183  	FolderResources = "resources"
   184  	FolderJSConfig  = "_jsconfig" // Mounted below /assets with postcss.config.js etc.
   185  )
   186  
   187  var (
   188  	JsConfigFolderMountPrefix = filepath.Join(ComponentFolderAssets, FolderJSConfig)
   189  
   190  	ComponentFolders = []string{
   191  		ComponentFolderArchetypes,
   192  		ComponentFolderStatic,
   193  		ComponentFolderLayouts,
   194  		ComponentFolderContent,
   195  		ComponentFolderData,
   196  		ComponentFolderAssets,
   197  		ComponentFolderI18n,
   198  	}
   199  
   200  	componentFoldersSet = make(map[string]bool)
   201  )
   202  
   203  func init() {
   204  	sort.Strings(ComponentFolders)
   205  	for _, f := range ComponentFolders {
   206  		componentFoldersSet[f] = true
   207  	}
   208  }
   209  
   210  // ResolveComponentFolder returns "content" from "content/blog/foo.md" etc.
   211  func ResolveComponentFolder(filename string) string {
   212  	filename = strings.TrimPrefix(filename, string(os.PathSeparator))
   213  	for _, cf := range ComponentFolders {
   214  		if strings.HasPrefix(filename, cf) {
   215  			return cf
   216  		}
   217  	}
   218  
   219  	return ""
   220  }
   221  
   222  func IsComponentFolder(name string) bool {
   223  	return componentFoldersSet[name]
   224  }