-
-
Notifications
You must be signed in to change notification settings - Fork 60
/
file-utils.ts
203 lines (171 loc) · 5.73 KB
/
file-utils.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
import { fs, path, readFileSync } from "./file-methods.ts";
var _BOM = /^\uFEFF/;
// express is set like: app.engine('html', require('eta').renderFile)
import EtaErr from "./err.ts";
/* TYPES */
import type { EtaConfig } from "./config.ts";
/* END TYPES */
/**
* Get the path to the included file from the parent file path and the
* specified path.
*
* If `name` does not have an extension, it will default to `.eta`
*
* @param name specified path
* @param parentfile parent file path
* @param isDirectory whether parentfile is a directory
* @return absolute path to template
*/
function getWholeFilePath(
name: string,
parentfile: string,
isDirectory?: boolean,
): string {
var includePath = path.resolve(
isDirectory ? parentfile : path.dirname(parentfile), // returns directory the parent file is in
name, // file
);
var ext = path.extname(name);
if (!ext) {
includePath += ".eta";
}
return includePath;
}
/**
* Get the absolute path to an included template
*
* If this is called with an absolute path (for example, starting with '/' or 'C:\')
* then Eta will attempt to resolve the absolute path within options.views. If it cannot,
* Eta will fallback to options.root or '/'
*
* If this is called with a relative path, Eta will:
* - Look relative to the current template (if the current template has the `filename` property)
* - Look inside each directory in options.views
*
* Note: if Eta is unable to find a template using path and options, it will throw an error.
*
* @param path specified path
* @param options compilation options
* @return absolute path to template
*/
function getPath(path: string, options: EtaConfig) {
var includePath: string | false = false;
var views = options.views;
var searchedPaths: Array<string> = [];
// If these four values are the same,
// getPath() will return the same result every time.
// We can cache the result to avoid expensive
// file operations.
var pathOptions = JSON.stringify({
filename: options.filename, // filename of the template which called includeFile()
path: path,
root: options.root,
views: options.views,
});
if (
options.cache && options.filepathCache && options.filepathCache[pathOptions]
) {
// Use the cached filepath
return options.filepathCache[pathOptions];
}
/** Add a filepath to the list of paths we've checked for a template */
function addPathToSearched(pathSearched: string) {
if (!searchedPaths.includes(pathSearched)) {
searchedPaths.push(pathSearched);
}
}
/**
* Take a filepath (like 'partials/mypartial.eta'). Attempt to find the template file inside `views`;
* return the resulting template file path, or `false` to indicate that the template was not found.
*
* @param views the filepath that holds templates, or an array of filepaths that hold templates
* @param path the path to the template
*/
function searchViews(
views: Array<string> | string | undefined,
path: string,
): string | false {
var filePath;
// If views is an array, then loop through each directory
// And attempt to find the template
if (
Array.isArray(views) &&
views.some(function (v) {
filePath = getWholeFilePath(path, v, true);
addPathToSearched(filePath);
return fs.existsSync(filePath);
})
) {
// If the above returned true, we know that the filePath was just set to a path
// That exists (Array.some() returns as soon as it finds a valid element)
return (filePath as unknown) as string;
} else if (typeof views === "string") {
// Search for the file if views is a single directory
filePath = getWholeFilePath(path, views, true);
addPathToSearched(filePath);
if (fs.existsSync(filePath)) {
return filePath;
}
}
// Unable to find a file
return false;
}
// Path starts with '/', 'C:\', etc.
var match = /^[A-Za-z]+:\\|^\//.exec(path);
// Absolute path, like /partials/partial.eta
if (match && match.length) {
// We have to trim the beginning '/' off the path, or else
// path.resolve(dir, path) will always resolve to just path
var formattedPath = path.replace(/^\/*/, "");
// First, try to resolve the path within options.views
includePath = searchViews(views, formattedPath);
if (!includePath) {
// If that fails, searchViews will return false. Try to find the path
// inside options.root (by default '/', the base of the filesystem)
var pathFromRoot = getWholeFilePath(
formattedPath,
options.root || "/",
true,
);
addPathToSearched(pathFromRoot);
includePath = pathFromRoot;
}
} else {
// Relative paths
// Look relative to a passed filename first
if (options.filename) {
var filePath = getWholeFilePath(path, options.filename);
addPathToSearched(filePath);
if (fs.existsSync(filePath)) {
includePath = filePath;
}
}
// Then look for the template in options.views
if (!includePath) {
includePath = searchViews(views, path);
}
if (!includePath) {
throw EtaErr(
'Could not find the template "' + path + '". Paths tried: ' +
searchedPaths,
);
}
}
// If caching and filepathCache are enabled,
// cache the input & output of this function.
if (options.cache && options.filepathCache) {
options.filepathCache[pathOptions] = includePath;
}
return includePath;
}
/**
* Reads a file synchronously
*/
function readFile(filePath: string) {
try {
return readFileSync(filePath).toString().replace(_BOM, ""); // TODO: is replacing BOM's necessary?
} catch {
throw EtaErr("Failed to read template at '" + filePath + "'");
}
}
export { getPath, readFile };