github.com/fighterlyt/hugo@v0.47.1/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  
    24  	"github.com/gohugoio/hugo/hugofs"
    25  )
    26  
    27  var FilePathSeparator = string(filepath.Separator)
    28  
    29  type Paths struct {
    30  	Fs  *hugofs.Fs
    31  	Cfg config.Provider
    32  
    33  	BaseURL
    34  
    35  	// If the baseURL contains a base path, e.g. https://example.com/docs, then "/docs" will be the BasePath.
    36  	// This will not be set if canonifyURLs is enabled.
    37  	BasePath string
    38  
    39  	// Directories
    40  	// TODO(bep) when we have trimmed down mos of the dirs usage outside of this package, make
    41  	// these into an interface.
    42  	ContentDir string
    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  
    68  	// The PathSpec looks up its config settings in both the current language
    69  	// and then in the global Viper config.
    70  	// Some settings, the settings listed below, does not make sense to be set
    71  	// on per-language-basis. We have no good way of protecting against this
    72  	// other than a "white-list". See language.go.
    73  	defaultContentLanguageInSubdir bool
    74  	DefaultContentLanguage         string
    75  	multilingual                   bool
    76  
    77  	themes    []string
    78  	AllThemes []ThemeConfig
    79  }
    80  
    81  func New(fs *hugofs.Fs, cfg config.Provider) (*Paths, error) {
    82  	baseURLstr := cfg.GetString("baseURL")
    83  	baseURL, err := newBaseURLFromString(baseURLstr)
    84  
    85  	if err != nil {
    86  		return nil, fmt.Errorf("Failed to create baseURL from %q: %s", baseURLstr, err)
    87  	}
    88  
    89  	contentDir := cfg.GetString("contentDir")
    90  	workingDir := cfg.GetString("workingDir")
    91  	resourceDir := cfg.GetString("resourceDir")
    92  	publishDir := cfg.GetString("publishDir")
    93  
    94  	if contentDir == "" {
    95  		return nil, fmt.Errorf("contentDir not set")
    96  	}
    97  	if resourceDir == "" {
    98  		return nil, fmt.Errorf("resourceDir not set")
    99  	}
   100  	if publishDir == "" {
   101  		return nil, fmt.Errorf("publishDir not set")
   102  	}
   103  
   104  	defaultContentLanguage := cfg.GetString("defaultContentLanguage")
   105  
   106  	var (
   107  		language  *langs.Language
   108  		languages langs.Languages
   109  	)
   110  
   111  	if l, ok := cfg.(*langs.Language); ok {
   112  		language = l
   113  
   114  	}
   115  
   116  	if l, ok := cfg.Get("languagesSorted").(langs.Languages); ok {
   117  		languages = l
   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  		ContentDir: contentDir,
   160  		ThemesDir:  cfg.GetString("themesDir"),
   161  		WorkingDir: workingDir,
   162  
   163  		AbsResourcesDir: absResourcesDir,
   164  		AbsPublishDir:   absPublishDir,
   165  
   166  		themes: config.GetStringSlicePreserveString(cfg, "theme"),
   167  
   168  		multilingual:                   cfg.GetBool("multilingual"),
   169  		defaultContentLanguageInSubdir: cfg.GetBool("defaultContentLanguageInSubdir"),
   170  		DefaultContentLanguage:         defaultContentLanguage,
   171  
   172  		Language:                 language,
   173  		Languages:                languages,
   174  		MultihostTargetBasePaths: multihostTargetBasePaths,
   175  
   176  		PaginatePath: cfg.GetString("paginatePath"),
   177  	}
   178  
   179  	if cfg.IsSet("allThemes") {
   180  		p.AllThemes = cfg.Get("allThemes").([]ThemeConfig)
   181  	} else {
   182  		p.AllThemes, err = collectThemeNames(p)
   183  		if err != nil {
   184  			return nil, err
   185  		}
   186  	}
   187  
   188  	// TODO(bep) remove this, eventually
   189  	p.PublishDir = absPublishDir
   190  
   191  	return p, nil
   192  }
   193  
   194  func (p *Paths) Lang() string {
   195  	if p == nil || p.Language == nil {
   196  		return ""
   197  	}
   198  	return p.Language.Lang
   199  }
   200  
   201  // ThemeSet checks whether a theme is in use or not.
   202  func (p *Paths) ThemeSet() bool {
   203  	return len(p.themes) > 0
   204  }
   205  
   206  func (p *Paths) Themes() []string {
   207  	return p.themes
   208  }
   209  
   210  func (p *Paths) GetTargetLanguageBasePath() string {
   211  	if p.Languages.IsMultihost() {
   212  		// In a multihost configuration all assets will be published below the language code.
   213  		return p.Lang()
   214  	}
   215  	return p.GetLanguagePrefix()
   216  }
   217  
   218  func (p *Paths) GetURLLanguageBasePath() string {
   219  	if p.Languages.IsMultihost() {
   220  		return ""
   221  	}
   222  	return p.GetLanguagePrefix()
   223  }
   224  
   225  func (p *Paths) GetLanguagePrefix() string {
   226  	if !p.multilingual {
   227  		return ""
   228  	}
   229  
   230  	defaultLang := p.DefaultContentLanguage
   231  	defaultInSubDir := p.defaultContentLanguageInSubdir
   232  
   233  	currentLang := p.Language.Lang
   234  	if currentLang == "" || (currentLang == defaultLang && !defaultInSubDir) {
   235  		return ""
   236  	}
   237  	return currentLang
   238  }
   239  
   240  // GetLangSubDir returns the given language's subdir if needed.
   241  func (p *Paths) GetLangSubDir(lang string) string {
   242  	if !p.multilingual {
   243  		return ""
   244  	}
   245  
   246  	if p.Languages.IsMultihost() {
   247  		return ""
   248  	}
   249  
   250  	if lang == "" || (lang == p.DefaultContentLanguage && !p.defaultContentLanguageInSubdir) {
   251  		return ""
   252  	}
   253  
   254  	return lang
   255  }
   256  
   257  // AbsPathify creates an absolute path if given a relative path. If already
   258  // absolute, the path is just cleaned.
   259  func (p *Paths) AbsPathify(inPath string) string {
   260  	return AbsPathify(p.WorkingDir, inPath)
   261  }
   262  
   263  // AbsPathify creates an absolute path if given a working dir and arelative path.
   264  // If already absolute, the path is just cleaned.
   265  func AbsPathify(workingDir, inPath string) string {
   266  	if filepath.IsAbs(inPath) {
   267  		return filepath.Clean(inPath)
   268  	}
   269  	return filepath.Join(workingDir, inPath)
   270  }