1// Copyright 2017-present 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
14package source
15
16import (
17	"os"
18	"path/filepath"
19	"regexp"
20	"runtime"
21
22	"github.com/gohugoio/hugo/hugofs/glob"
23
24	"github.com/gohugoio/hugo/langs"
25	"github.com/spf13/afero"
26
27	"github.com/gohugoio/hugo/helpers"
28	"github.com/spf13/cast"
29)
30
31// SourceSpec abstracts language-specific file creation.
32// TODO(bep) rename to Spec
33type SourceSpec struct {
34	*helpers.PathSpec
35
36	SourceFs afero.Fs
37
38	shouldInclude func(filename string) bool
39
40	Languages              map[string]interface{}
41	DefaultContentLanguage string
42	DisabledLanguages      map[string]bool
43}
44
45// NewSourceSpec initializes SourceSpec using languages the given filesystem and PathSpec.
46func NewSourceSpec(ps *helpers.PathSpec, inclusionFilter *glob.FilenameFilter, fs afero.Fs) *SourceSpec {
47	cfg := ps.Cfg
48	defaultLang := cfg.GetString("defaultContentLanguage")
49	languages := cfg.GetStringMap("languages")
50
51	disabledLangsSet := make(map[string]bool)
52
53	for _, disabledLang := range cfg.GetStringSlice("disableLanguages") {
54		disabledLangsSet[disabledLang] = true
55	}
56
57	if len(languages) == 0 {
58		l := langs.NewDefaultLanguage(cfg)
59		languages[l.Lang] = l
60		defaultLang = l.Lang
61	}
62
63	ignoreFiles := cast.ToStringSlice(cfg.Get("ignoreFiles"))
64	var regexps []*regexp.Regexp
65	if len(ignoreFiles) > 0 {
66		for _, ignorePattern := range ignoreFiles {
67			re, err := regexp.Compile(ignorePattern)
68			if err != nil {
69				helpers.DistinctErrorLog.Printf("Invalid regexp %q in ignoreFiles: %s", ignorePattern, err)
70			} else {
71				regexps = append(regexps, re)
72			}
73
74		}
75	}
76	shouldInclude := func(filename string) bool {
77		if !inclusionFilter.Match(filename, false) {
78			return false
79		}
80		for _, r := range regexps {
81			if r.MatchString(filename) {
82				return false
83			}
84		}
85		return true
86	}
87
88	return &SourceSpec{shouldInclude: shouldInclude, PathSpec: ps, SourceFs: fs, Languages: languages, DefaultContentLanguage: defaultLang, DisabledLanguages: disabledLangsSet}
89}
90
91// IgnoreFile returns whether a given file should be ignored.
92func (s *SourceSpec) IgnoreFile(filename string) bool {
93	if filename == "" {
94		if _, ok := s.SourceFs.(*afero.OsFs); ok {
95			return true
96		}
97		return false
98	}
99
100	base := filepath.Base(filename)
101
102	if len(base) > 0 {
103		first := base[0]
104		last := base[len(base)-1]
105		if first == '.' ||
106			first == '#' ||
107			last == '~' {
108			return true
109		}
110	}
111
112	if !s.shouldInclude(filename) {
113		return true
114	}
115
116	if runtime.GOOS == "windows" {
117		// Also check the forward slash variant if different.
118		unixFilename := filepath.ToSlash(filename)
119		if unixFilename != filename {
120			if !s.shouldInclude(unixFilename) {
121				return true
122			}
123		}
124	}
125
126	return false
127}
128
129// IsRegularSourceFile returns whether filename represents a regular file in the
130// source filesystem.
131func (s *SourceSpec) IsRegularSourceFile(filename string) (bool, error) {
132	fi, err := helpers.LstatIfPossible(s.SourceFs, filename)
133	if err != nil {
134		return false, err
135	}
136
137	if fi.IsDir() {
138		return false, nil
139	}
140
141	if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
142		link, err := filepath.EvalSymlinks(filename)
143		if err != nil {
144			return false, err
145		}
146
147		fi, err = helpers.LstatIfPossible(s.SourceFs, link)
148		if err != nil {
149			return false, err
150		}
151
152		if fi.IsDir() {
153			return false, nil
154		}
155	}
156
157	return true, nil
158}
159