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