path.go (5445B)
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 path provides template functions for manipulating paths.
15 package path
16
17 import (
18 "fmt"
19 _path "path"
20 "path/filepath"
21 "strings"
22
23 "github.com/gohugoio/hugo/deps"
24 "github.com/spf13/cast"
25 )
26
27 // New returns a new instance of the path-namespaced template functions.
28 func New(deps *deps.Deps) *Namespace {
29 return &Namespace{
30 deps: deps,
31 }
32 }
33
34 // Namespace provides template functions for the "os" namespace.
35 type Namespace struct {
36 deps *deps.Deps
37 }
38
39 // DirFile holds the result from path.Split.
40 type DirFile struct {
41 Dir string
42 File string
43 }
44
45 // Used in test.
46 func (df DirFile) String() string {
47 return fmt.Sprintf("%s|%s", df.Dir, df.File)
48 }
49
50 // Ext returns the file name extension used by path.
51 // The extension is the suffix beginning at the final dot
52 // in the final slash-separated element of path;
53 // it is empty if there is no dot.
54 // The input path is passed into filepath.ToSlash converting any Windows slashes
55 // to forward slashes.
56 func (ns *Namespace) Ext(path any) (string, error) {
57 spath, err := cast.ToStringE(path)
58 if err != nil {
59 return "", err
60 }
61 spath = filepath.ToSlash(spath)
62 return _path.Ext(spath), nil
63 }
64
65 // Dir returns all but the last element of path, typically the path's directory.
66 // After dropping the final element using Split, the path is Cleaned and trailing
67 // slashes are removed.
68 // If the path is empty, Dir returns ".".
69 // If the path consists entirely of slashes followed by non-slash bytes, Dir
70 // returns a single slash. In any other case, the returned path does not end in a
71 // slash.
72 // The input path is passed into filepath.ToSlash converting any Windows slashes
73 // to forward slashes.
74 func (ns *Namespace) Dir(path any) (string, error) {
75 spath, err := cast.ToStringE(path)
76 if err != nil {
77 return "", err
78 }
79 spath = filepath.ToSlash(spath)
80 return _path.Dir(spath), nil
81 }
82
83 // Base returns the last element of path.
84 // Trailing slashes are removed before extracting the last element.
85 // If the path is empty, Base returns ".".
86 // If the path consists entirely of slashes, Base returns "/".
87 // The input path is passed into filepath.ToSlash converting any Windows slashes
88 // to forward slashes.
89 func (ns *Namespace) Base(path any) (string, error) {
90 spath, err := cast.ToStringE(path)
91 if err != nil {
92 return "", err
93 }
94 spath = filepath.ToSlash(spath)
95 return _path.Base(spath), nil
96 }
97
98 // BaseName returns the last element of path, removing the extension if present.
99 // Trailing slashes are removed before extracting the last element.
100 // If the path is empty, Base returns ".".
101 // If the path consists entirely of slashes, Base returns "/".
102 // The input path is passed into filepath.ToSlash converting any Windows slashes
103 // to forward slashes.
104 func (ns *Namespace) BaseName(path any) (string, error) {
105 spath, err := cast.ToStringE(path)
106 if err != nil {
107 return "", err
108 }
109 spath = filepath.ToSlash(spath)
110 return strings.TrimSuffix(_path.Base(spath), _path.Ext(spath)), nil
111 }
112
113 // Split splits path immediately following the final slash,
114 // separating it into a directory and file name component.
115 // If there is no slash in path, Split returns an empty dir and
116 // file set to path.
117 // The input path is passed into filepath.ToSlash converting any Windows slashes
118 // to forward slashes.
119 // The returned values have the property that path = dir+file.
120 func (ns *Namespace) Split(path any) (DirFile, error) {
121 spath, err := cast.ToStringE(path)
122 if err != nil {
123 return DirFile{}, err
124 }
125 spath = filepath.ToSlash(spath)
126 dir, file := _path.Split(spath)
127
128 return DirFile{Dir: dir, File: file}, nil
129 }
130
131 // Join joins any number of path elements into a single path, adding a
132 // separating slash if necessary. All the input
133 // path elements are passed into filepath.ToSlash converting any Windows slashes
134 // to forward slashes.
135 // The result is Cleaned; in particular,
136 // all empty strings are ignored.
137 func (ns *Namespace) Join(elements ...any) (string, error) {
138 var pathElements []string
139 for _, elem := range elements {
140 switch v := elem.(type) {
141 case []string:
142 for _, e := range v {
143 pathElements = append(pathElements, filepath.ToSlash(e))
144 }
145 case []any:
146 for _, e := range v {
147 elemStr, err := cast.ToStringE(e)
148 if err != nil {
149 return "", err
150 }
151 pathElements = append(pathElements, filepath.ToSlash(elemStr))
152 }
153 default:
154 elemStr, err := cast.ToStringE(elem)
155 if err != nil {
156 return "", err
157 }
158 pathElements = append(pathElements, filepath.ToSlash(elemStr))
159 }
160 }
161 return _path.Join(pathElements...), nil
162 }
163
164 // Clean replaces the separators used with standard slashes and then
165 // extraneous slashes are removed.
166 func (ns *Namespace) Clean(path any) (string, error) {
167 spath, err := cast.ToStringE(path)
168
169 if err != nil {
170 return "", err
171 }
172 spath = filepath.ToSlash(spath)
173 return _path.Clean(spath), nil
174 }