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 }