github.com/rezahousseini/hugo@v0.32.3/source/dirs.go (about)

     1  // Copyright 2017 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 source
    15  
    16  import (
    17  	"errors"
    18  	"os"
    19  	"path/filepath"
    20  	"strings"
    21  
    22  	"github.com/spf13/afero"
    23  
    24  	"github.com/gohugoio/hugo/config"
    25  	"github.com/gohugoio/hugo/helpers"
    26  	"github.com/gohugoio/hugo/hugofs"
    27  	jww "github.com/spf13/jwalterweatherman"
    28  )
    29  
    30  // Dirs holds the source directories for a given build.
    31  // In case where there are more than one of a kind, the order matters:
    32  // It will be used to construct a union filesystem, so the right-most directory
    33  // will "win" on duplicates. Typically, the theme version will be the first.
    34  type Dirs struct {
    35  	logger   *jww.Notepad
    36  	pathSpec *helpers.PathSpec
    37  
    38  	staticDirs    []string
    39  	AbsStaticDirs []string
    40  
    41  	Language *helpers.Language
    42  }
    43  
    44  // NewDirs creates a new dirs with the given configuration and filesystem.
    45  func NewDirs(fs *hugofs.Fs, cfg config.Provider, logger *jww.Notepad) (*Dirs, error) {
    46  	ps, err := helpers.NewPathSpec(fs, cfg)
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  
    51  	var l *helpers.Language
    52  	if language, ok := cfg.(*helpers.Language); ok {
    53  		l = language
    54  	}
    55  
    56  	d := &Dirs{Language: l, pathSpec: ps, logger: logger}
    57  
    58  	return d, d.init(cfg)
    59  
    60  }
    61  
    62  func (d *Dirs) init(cfg config.Provider) error {
    63  
    64  	var (
    65  		statics []string
    66  	)
    67  
    68  	if d.pathSpec.Theme() != "" {
    69  		statics = append(statics, filepath.Join(d.pathSpec.ThemesDir(), d.pathSpec.Theme(), "static"))
    70  	}
    71  
    72  	_, isLanguage := cfg.(*helpers.Language)
    73  	languages, hasLanguages := cfg.Get("languagesSorted").(helpers.Languages)
    74  
    75  	if !isLanguage && !hasLanguages {
    76  		return errors.New("missing languagesSorted in config")
    77  	}
    78  
    79  	if !isLanguage {
    80  		// Merge all the static dirs.
    81  		for _, l := range languages {
    82  			addend, err := d.staticDirsFor(l)
    83  			if err != nil {
    84  				return err
    85  			}
    86  
    87  			statics = append(statics, addend...)
    88  		}
    89  	} else {
    90  		addend, err := d.staticDirsFor(cfg)
    91  		if err != nil {
    92  			return err
    93  		}
    94  
    95  		statics = append(statics, addend...)
    96  	}
    97  
    98  	d.staticDirs = removeDuplicatesKeepRight(statics)
    99  	d.AbsStaticDirs = make([]string, len(d.staticDirs))
   100  	for i, di := range d.staticDirs {
   101  		d.AbsStaticDirs[i] = d.pathSpec.AbsPathify(di) + helpers.FilePathSeparator
   102  	}
   103  
   104  	return nil
   105  }
   106  
   107  func (d *Dirs) staticDirsFor(cfg config.Provider) ([]string, error) {
   108  	var statics []string
   109  	ps, err := helpers.NewPathSpec(d.pathSpec.Fs, cfg)
   110  	if err != nil {
   111  		return statics, err
   112  	}
   113  
   114  	statics = append(statics, ps.StaticDirs()...)
   115  
   116  	return statics, nil
   117  }
   118  
   119  // CreateStaticFs will create a union filesystem with the static paths configured.
   120  // Any missing directories will be logged as warnings.
   121  func (d *Dirs) CreateStaticFs() (afero.Fs, error) {
   122  	var (
   123  		source   = d.pathSpec.Fs.Source
   124  		absPaths []string
   125  	)
   126  
   127  	for _, staticDir := range d.AbsStaticDirs {
   128  		if _, err := source.Stat(staticDir); os.IsNotExist(err) {
   129  			d.logger.WARN.Printf("Unable to find Static Directory: %s", staticDir)
   130  		} else {
   131  			absPaths = append(absPaths, staticDir)
   132  		}
   133  
   134  	}
   135  
   136  	if len(absPaths) == 0 {
   137  		return nil, nil
   138  	}
   139  
   140  	return d.createOverlayFs(absPaths), nil
   141  
   142  }
   143  
   144  // IsStatic returns whether the given filename is located in one of the static
   145  // source dirs.
   146  func (d *Dirs) IsStatic(filename string) bool {
   147  	for _, absPath := range d.AbsStaticDirs {
   148  		if strings.HasPrefix(filename, absPath) {
   149  			return true
   150  		}
   151  	}
   152  	return false
   153  }
   154  
   155  // MakeStaticPathRelative creates a relative path from the given filename.
   156  // It will return an empty string if the filename is not a member of dirs.
   157  func (d *Dirs) MakeStaticPathRelative(filename string) string {
   158  	for _, currentPath := range d.AbsStaticDirs {
   159  		if strings.HasPrefix(filename, currentPath) {
   160  			return strings.TrimPrefix(filename, currentPath)
   161  		}
   162  	}
   163  
   164  	return ""
   165  
   166  }
   167  
   168  func (d *Dirs) createOverlayFs(absPaths []string) afero.Fs {
   169  	source := d.pathSpec.Fs.Source
   170  
   171  	if len(absPaths) == 1 {
   172  		return afero.NewReadOnlyFs(afero.NewBasePathFs(source, absPaths[0]))
   173  	}
   174  
   175  	base := afero.NewReadOnlyFs(afero.NewBasePathFs(source, absPaths[0]))
   176  	overlay := d.createOverlayFs(absPaths[1:])
   177  
   178  	return afero.NewCopyOnWriteFs(base, overlay)
   179  }
   180  
   181  func removeDuplicatesKeepRight(in []string) []string {
   182  	seen := make(map[string]bool)
   183  	var out []string
   184  	for i := len(in) - 1; i >= 0; i-- {
   185  		v := in[i]
   186  		if seen[v] {
   187  			continue
   188  		}
   189  		out = append([]string{v}, out...)
   190  		seen[v] = true
   191  	}
   192  
   193  	return out
   194  }