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 }