github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/hugolib/paths/paths.go (about)

     1  // Copyright 2018 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 paths
    15  
    16  import (
    17  	"fmt"
    18  	"path/filepath"
    19  	"strings"
    20  
    21  	"github.com/gohugoio/hugo/config"
    22  	"github.com/gohugoio/hugo/langs"
    23  	"github.com/gohugoio/hugo/modules"
    24  	"github.com/pkg/errors"
    25  
    26  	"github.com/gohugoio/hugo/hugofs"
    27  )
    28  
    29  var FilePathSeparator = string(filepath.Separator)
    30  
    31  type Paths struct {
    32  	Fs  *hugofs.Fs
    33  	Cfg config.Provider
    34  
    35  	BaseURL
    36  
    37  	// If the baseURL contains a base path, e.g. https://example.com/docs, then "/docs" will be the BasePath.
    38  	BasePath string
    39  
    40  	// Directories
    41  	// TODO(bep) when we have trimmed down most of the dirs usage outside of this package, make
    42  	// these into an interface.
    43  	ThemesDir  string
    44  	WorkingDir string
    45  
    46  	// Directories to store Resource related artifacts.
    47  	AbsResourcesDir string
    48  
    49  	AbsPublishDir string
    50  
    51  	// pagination path handling
    52  	PaginatePath string
    53  
    54  	PublishDir string
    55  
    56  	// When in multihost mode, this returns a list of base paths below PublishDir
    57  	// for each language.
    58  	MultihostTargetBasePaths []string
    59  
    60  	DisablePathToLower bool
    61  	RemovePathAccents  bool
    62  	UglyURLs           bool
    63  	CanonifyURLs       bool
    64  
    65  	Language              *langs.Language
    66  	Languages             langs.Languages
    67  	LanguagesDefaultFirst langs.Languages
    68  
    69  	// The PathSpec looks up its config settings in both the current language
    70  	// and then in the global Viper config.
    71  	// Some settings, the settings listed below, does not make sense to be set
    72  	// on per-language-basis. We have no good way of protecting against this
    73  	// other than a "white-list". See language.go.
    74  	defaultContentLanguageInSubdir bool
    75  	DefaultContentLanguage         string
    76  	multilingual                   bool
    77  
    78  	AllModules    modules.Modules
    79  	ModulesClient *modules.Client
    80  }
    81  
    82  func New(fs *hugofs.Fs, cfg config.Provider) (*Paths, error) {
    83  	baseURLstr := cfg.GetString("baseURL")
    84  	baseURL, err := newBaseURLFromString(baseURLstr)
    85  	if err != nil {
    86  		return nil, errors.Wrapf(err, "Failed to create baseURL from %q:", baseURLstr)
    87  	}
    88  
    89  	contentDir := filepath.Clean(cfg.GetString("contentDir"))
    90  	workingDir := filepath.Clean(cfg.GetString("workingDir"))
    91  	resourceDir := filepath.Clean(cfg.GetString("resourceDir"))
    92  	publishDir := filepath.Clean(cfg.GetString("publishDir"))
    93  
    94  	if publishDir == "" {
    95  		return nil, fmt.Errorf("publishDir not set")
    96  	}
    97  
    98  	defaultContentLanguage := cfg.GetString("defaultContentLanguage")
    99  
   100  	var (
   101  		language              *langs.Language
   102  		languages             langs.Languages
   103  		languagesDefaultFirst langs.Languages
   104  	)
   105  
   106  	if l, ok := cfg.(*langs.Language); ok {
   107  		language = l
   108  	}
   109  
   110  	if l, ok := cfg.Get("languagesSorted").(langs.Languages); ok {
   111  		languages = l
   112  	}
   113  
   114  	if l, ok := cfg.Get("languagesSortedDefaultFirst").(langs.Languages); ok {
   115  		languagesDefaultFirst = l
   116  	}
   117  
   118  	//
   119  
   120  	if len(languages) == 0 {
   121  		// We have some old tests that does not test the entire chain, hence
   122  		// they have no languages. So create one so we get the proper filesystem.
   123  		languages = langs.Languages{&langs.Language{Lang: "en", Cfg: cfg, ContentDir: contentDir}}
   124  	}
   125  
   126  	absPublishDir := AbsPathify(workingDir, publishDir)
   127  	if !strings.HasSuffix(absPublishDir, FilePathSeparator) {
   128  		absPublishDir += FilePathSeparator
   129  	}
   130  	// If root, remove the second '/'
   131  	if absPublishDir == "//" {
   132  		absPublishDir = FilePathSeparator
   133  	}
   134  	absResourcesDir := AbsPathify(workingDir, resourceDir)
   135  	if !strings.HasSuffix(absResourcesDir, FilePathSeparator) {
   136  		absResourcesDir += FilePathSeparator
   137  	}
   138  	if absResourcesDir == "//" {
   139  		absResourcesDir = FilePathSeparator
   140  	}
   141  
   142  	var multihostTargetBasePaths []string
   143  	if languages.IsMultihost() {
   144  		for _, l := range languages {
   145  			multihostTargetBasePaths = append(multihostTargetBasePaths, l.Lang)
   146  		}
   147  	}
   148  
   149  	p := &Paths{
   150  		Fs:      fs,
   151  		Cfg:     cfg,
   152  		BaseURL: baseURL,
   153  
   154  		DisablePathToLower: cfg.GetBool("disablePathToLower"),
   155  		RemovePathAccents:  cfg.GetBool("removePathAccents"),
   156  		UglyURLs:           cfg.GetBool("uglyURLs"),
   157  		CanonifyURLs:       cfg.GetBool("canonifyURLs"),
   158  
   159  		ThemesDir:  cfg.GetString("themesDir"),
   160  		WorkingDir: workingDir,
   161  
   162  		AbsResourcesDir: absResourcesDir,
   163  		AbsPublishDir:   absPublishDir,
   164  
   165  		multilingual:                   cfg.GetBool("multilingual"),
   166  		defaultContentLanguageInSubdir: cfg.GetBool("defaultContentLanguageInSubdir"),
   167  		DefaultContentLanguage:         defaultContentLanguage,
   168  
   169  		Language:                 language,
   170  		Languages:                languages,
   171  		LanguagesDefaultFirst:    languagesDefaultFirst,
   172  		MultihostTargetBasePaths: multihostTargetBasePaths,
   173  
   174  		PaginatePath: cfg.GetString("paginatePath"),
   175  	}
   176  
   177  	if cfg.IsSet("allModules") {
   178  		p.AllModules = cfg.Get("allModules").(modules.Modules)
   179  	}
   180  
   181  	if cfg.IsSet("modulesClient") {
   182  		p.ModulesClient = cfg.Get("modulesClient").(*modules.Client)
   183  	}
   184  
   185  	// TODO(bep) remove this, eventually
   186  	p.PublishDir = absPublishDir
   187  
   188  	return p, nil
   189  }
   190  
   191  // GetBasePath returns any path element in baseURL if needed.
   192  func (p *Paths) GetBasePath(isRelativeURL bool) string {
   193  	if isRelativeURL && p.CanonifyURLs {
   194  		// The baseURL will be prepended later.
   195  		return ""
   196  	}
   197  	return p.BasePath
   198  }
   199  
   200  func (p *Paths) Lang() string {
   201  	if p == nil || p.Language == nil {
   202  		return ""
   203  	}
   204  	return p.Language.Lang
   205  }
   206  
   207  func (p *Paths) GetTargetLanguageBasePath() string {
   208  	if p.Languages.IsMultihost() {
   209  		// In a multihost configuration all assets will be published below the language code.
   210  		return p.Lang()
   211  	}
   212  	return p.GetLanguagePrefix()
   213  }
   214  
   215  func (p *Paths) GetURLLanguageBasePath() string {
   216  	if p.Languages.IsMultihost() {
   217  		return ""
   218  	}
   219  	return p.GetLanguagePrefix()
   220  }
   221  
   222  func (p *Paths) GetLanguagePrefix() string {
   223  	if !p.multilingual {
   224  		return ""
   225  	}
   226  
   227  	defaultLang := p.DefaultContentLanguage
   228  	defaultInSubDir := p.defaultContentLanguageInSubdir
   229  
   230  	currentLang := p.Language.Lang
   231  	if currentLang == "" || (currentLang == defaultLang && !defaultInSubDir) {
   232  		return ""
   233  	}
   234  	return currentLang
   235  }
   236  
   237  // GetLangSubDir returns the given language's subdir if needed.
   238  func (p *Paths) GetLangSubDir(lang string) string {
   239  	if !p.multilingual {
   240  		return ""
   241  	}
   242  
   243  	if p.Languages.IsMultihost() {
   244  		return ""
   245  	}
   246  
   247  	if lang == "" || (lang == p.DefaultContentLanguage && !p.defaultContentLanguageInSubdir) {
   248  		return ""
   249  	}
   250  
   251  	return lang
   252  }
   253  
   254  // AbsPathify creates an absolute path if given a relative path. If already
   255  // absolute, the path is just cleaned.
   256  func (p *Paths) AbsPathify(inPath string) string {
   257  	return AbsPathify(p.WorkingDir, inPath)
   258  }
   259  
   260  // RelPathify trims any WorkingDir prefix from the given filename. If
   261  // the filename is not considered to be absolute, the path is just cleaned.
   262  func (p *Paths) RelPathify(filename string) string {
   263  	filename = filepath.Clean(filename)
   264  	if !filepath.IsAbs(filename) {
   265  		return filename
   266  	}
   267  
   268  	return strings.TrimPrefix(strings.TrimPrefix(filename, p.WorkingDir), FilePathSeparator)
   269  }
   270  
   271  // AbsPathify creates an absolute path if given a working dir and a relative path.
   272  // If already absolute, the path is just cleaned.
   273  func AbsPathify(workingDir, inPath string) string {
   274  	if filepath.IsAbs(inPath) {
   275  		return filepath.Clean(inPath)
   276  	}
   277  	return filepath.Join(workingDir, inPath)
   278  }