forked from parse-community/parse-server
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathUtils.js
More file actions
205 lines (187 loc) · 6.24 KB
/
Copy pathUtils.js
File metadata and controls
205 lines (187 loc) · 6.24 KB
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
204
205
/**
* utils.js
* @file General purpose utilities
* @description General purpose utilities.
*/
const path = require('path');
const fs = require('fs').promises;
/**
* The general purpose utilities.
*/
class Utils {
/**
* @function getLocalizedPath
* @description Returns a localized file path accoring to the locale.
*
* Localized files are searched in subfolders of a given path, e.g.
*
* root/
* ├── base/ // base path to files
* │ ├── example.html // default file
* │ └── de/ // de language folder
* │ │ └── example.html // de localized file
* │ └── de-AT/ // de-AT locale folder
* │ │ └── example.html // de-AT localized file
*
* Files are matched with the locale in the following order:
* 1. Locale match, e.g. locale `de-AT` matches file in folder `de-AT`.
* 2. Language match, e.g. locale `de-AT` matches file in folder `de`.
* 3. Default; file in base folder is returned.
*
* @param {String} defaultPath The absolute file path, which is also
* the default path returned if localization is not available.
* @param {String} locale The locale.
* @returns {Promise<Object>} The object contains:
* - `path`: The path to the localized file, or the original path if
* localization is not available.
* - `subdir`: The subdirectory of the localized file, or undefined if
* there is no matching localized file.
*/
static async getLocalizedPath(defaultPath, locale) {
// Get file name and paths
const file = path.basename(defaultPath);
const basePath = path.dirname(defaultPath);
// If locale is not set return default file
if (!locale) {
return { path: defaultPath };
}
// Check file for locale exists
const localePath = path.join(basePath, locale, file);
const localeFileExists = await Utils.fileExists(localePath);
// If file for locale exists return file
if (localeFileExists) {
return { path: localePath, subdir: locale };
}
// Check file for language exists
const language = locale.split('-')[0];
const languagePath = path.join(basePath, language, file);
const languageFileExists = await Utils.fileExists(languagePath);
// If file for language exists return file
if (languageFileExists) {
return { path: languagePath, subdir: language };
}
// Return default file
return { path: defaultPath };
}
/**
* @function fileExists
* @description Checks whether a file exists.
* @param {String} path The file path.
* @returns {Promise<Boolean>} Is true if the file can be accessed, false otherwise.
*/
static async fileExists(path) {
try {
await fs.access(path);
return true;
} catch (e) {
return false;
}
}
/**
* @function isPath
* @description Evaluates whether a string is a file path (as opposed to a URL for example).
* @param {String} s The string to evaluate.
* @returns {Boolean} Returns true if the evaluated string is a path.
*/
static isPath(s) {
return /(^\/)|(^\.\/)|(^\.\.\/)/.test(s);
}
/**
* Flattens an object and crates new keys with custom delimiters.
* @param {Object} obj The object to flatten.
* @param {String} [delimiter='.'] The delimiter of the newly generated keys.
* @param {Object} result
* @returns {Object} The flattened object.
**/
static flattenObject(obj, parentKey, delimiter = '.', result = {}) {
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
const newKey = parentKey ? parentKey + delimiter + key : key;
if (typeof obj[key] === 'object' && obj[key] !== null) {
this.flattenObject(obj[key], newKey, delimiter, result);
} else {
result[newKey] = obj[key];
}
}
}
return result;
}
/**
* Determines whether an object is a Promise.
* @param {any} object The object to validate.
* @returns {Boolean} Returns true if the object is a promise.
*/
static isPromise(object) {
return object instanceof Promise;
}
/**
* Creates an object with all permutations of the original keys.
* For example, this definition:
* ```
* {
* a: [true, false],
* b: [1, 2],
* c: ['x']
* }
* ```
* permutates to:
* ```
* [
* { a: true, b: 1, c: 'x' },
* { a: true, b: 2, c: 'x' },
* { a: false, b: 1, c: 'x' },
* { a: false, b: 2, c: 'x' }
* ]
* ```
* @param {Object} object The object to permutate.
* @param {Integer} [index=0] The current key index.
* @param {Object} [current={}] The current result entry being composed.
* @param {Array} [results=[]] The resulting array of permutations.
*/
static getObjectKeyPermutations(object, index = 0, current = {}, results = []) {
const keys = Object.keys(object);
const key = keys[index];
const values = object[key];
for (const value of values) {
current[key] = value;
const nextIndex = index + 1;
if (nextIndex < keys.length) {
Utils.getObjectKeyPermutations(object, nextIndex, current, results);
} else {
const result = Object.assign({}, current);
results.push(result);
}
}
return results;
}
/**
* Validates parameters and throws if a parameter is invalid.
* Example parameter types syntax:
* ```
* {
* parameterName: {
* t: 'boolean',
* v: isBoolean,
* o: true
* },
* ...
* }
* ```
* @param {Object} params The parameters to validate.
* @param {Array<Object>} types The parameter types used for validation.
* @param {Object} types.t The parameter type; used for error message, not for validation.
* @param {Object} types.v The function to validate the parameter value.
* @param {Boolean} [types.o=false] Is true if the parameter is optional.
*/
static validateParams(params, types) {
for (const key of Object.keys(params)) {
const type = types[key];
const isOptional = !!type.o;
const param = params[key];
if (!(isOptional && param == null) && !type.v(param)) {
throw `Invalid parameter ${key} must be of type ${type.t} but is ${typeof param}`;
}
}
}
}
module.exports = Utils;