github.com/rezahousseini/hugo@v0.32.3/source/dirs.go (about) 1 // Copyright 2017 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 source 15 16 import ( 17 "errors" 18 "os" 19 "path/filepath" 20 "strings" 21 22 "github.com/spf13/afero" 23 24 "github.com/gohugoio/hugo/config" 25 "github.com/gohugoio/hugo/helpers" 26 "github.com/gohugoio/hugo/hugofs" 27 jww "github.com/spf13/jwalterweatherman" 28 ) 29 30 // Dirs holds the source directories for a given build. 31 // In case where there are more than one of a kind, the order matters: 32 // It will be used to construct a union filesystem, so the right-most directory 33 // will "win" on duplicates. Typically, the theme version will be the first. 34 type Dirs struct { 35 logger *jww.Notepad 36 pathSpec *helpers.PathSpec 37 38 staticDirs []string 39 AbsStaticDirs []string 40 41 Language *helpers.Language 42 } 43 44 // NewDirs creates a new dirs with the given configuration and filesystem. 45 func NewDirs(fs *hugofs.Fs, cfg config.Provider, logger *jww.Notepad) (*Dirs, error) { 46 ps, err := helpers.NewPathSpec(fs, cfg) 47 if err != nil { 48 return nil, err 49 } 50 51 var l *helpers.Language 52 if language, ok := cfg.(*helpers.Language); ok { 53 l = language 54 } 55 56 d := &Dirs{Language: l, pathSpec: ps, logger: logger} 57 58 return d, d.init(cfg) 59 60 } 61 62 func (d *Dirs) init(cfg config.Provider) error { 63 64 var ( 65 statics []string 66 ) 67 68 if d.pathSpec.Theme() != "" { 69 statics = append(statics, filepath.Join(d.pathSpec.ThemesDir(), d.pathSpec.Theme(), "static")) 70 } 71 72 _, isLanguage := cfg.(*helpers.Language) 73 languages, hasLanguages := cfg.Get("languagesSorted").(helpers.Languages) 74 75 if !isLanguage && !hasLanguages { 76 return errors.New("missing languagesSorted in config") 77 } 78 79 if !isLanguage { 80 // Merge all the static dirs. 81 for _, l := range languages { 82 addend, err := d.staticDirsFor(l) 83 if err != nil { 84 return err 85 } 86 87 statics = append(statics, addend...) 88 } 89 } else { 90 addend, err := d.staticDirsFor(cfg) 91 if err != nil { 92 return err 93 } 94 95 statics = append(statics, addend...) 96 } 97 98 d.staticDirs = removeDuplicatesKeepRight(statics) 99 d.AbsStaticDirs = make([]string, len(d.staticDirs)) 100 for i, di := range d.staticDirs { 101 d.AbsStaticDirs[i] = d.pathSpec.AbsPathify(di) + helpers.FilePathSeparator 102 } 103 104 return nil 105 } 106 107 func (d *Dirs) staticDirsFor(cfg config.Provider) ([]string, error) { 108 var statics []string 109 ps, err := helpers.NewPathSpec(d.pathSpec.Fs, cfg) 110 if err != nil { 111 return statics, err 112 } 113 114 statics = append(statics, ps.StaticDirs()...) 115 116 return statics, nil 117 } 118 119 // CreateStaticFs will create a union filesystem with the static paths configured. 120 // Any missing directories will be logged as warnings. 121 func (d *Dirs) CreateStaticFs() (afero.Fs, error) { 122 var ( 123 source = d.pathSpec.Fs.Source 124 absPaths []string 125 ) 126 127 for _, staticDir := range d.AbsStaticDirs { 128 if _, err := source.Stat(staticDir); os.IsNotExist(err) { 129 d.logger.WARN.Printf("Unable to find Static Directory: %s", staticDir) 130 } else { 131 absPaths = append(absPaths, staticDir) 132 } 133 134 } 135 136 if len(absPaths) == 0 { 137 return nil, nil 138 } 139 140 return d.createOverlayFs(absPaths), nil 141 142 } 143 144 // IsStatic returns whether the given filename is located in one of the static 145 // source dirs. 146 func (d *Dirs) IsStatic(filename string) bool { 147 for _, absPath := range d.AbsStaticDirs { 148 if strings.HasPrefix(filename, absPath) { 149 return true 150 } 151 } 152 return false 153 } 154 155 // MakeStaticPathRelative creates a relative path from the given filename. 156 // It will return an empty string if the filename is not a member of dirs. 157 func (d *Dirs) MakeStaticPathRelative(filename string) string { 158 for _, currentPath := range d.AbsStaticDirs { 159 if strings.HasPrefix(filename, currentPath) { 160 return strings.TrimPrefix(filename, currentPath) 161 } 162 } 163 164 return "" 165 166 } 167 168 func (d *Dirs) createOverlayFs(absPaths []string) afero.Fs { 169 source := d.pathSpec.Fs.Source 170 171 if len(absPaths) == 1 { 172 return afero.NewReadOnlyFs(afero.NewBasePathFs(source, absPaths[0])) 173 } 174 175 base := afero.NewReadOnlyFs(afero.NewBasePathFs(source, absPaths[0])) 176 overlay := d.createOverlayFs(absPaths[1:]) 177 178 return afero.NewCopyOnWriteFs(base, overlay) 179 } 180 181 func removeDuplicatesKeepRight(in []string) []string { 182 seen := make(map[string]bool) 183 var out []string 184 for i := len(in) - 1; i >= 0; i-- { 185 v := in[i] 186 if seen[v] { 187 continue 188 } 189 out = append([]string{v}, out...) 190 seen[v] = true 191 } 192 193 return out 194 }