/
utils.ts
258 lines (219 loc) · 6.42 KB
/
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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
import * as fs from 'fs';
import * as fsPath from 'path';
import {Locale} from './locale';
import {Pod} from './pod';
export interface FrontMatterResult {
frontMatter?: string;
body?: string;
}
export class DataType {
static isArray(value: any) {
if (Array.isArray) {
return Array.isArray(value);
}
return value && typeof value === 'object' && value.constructor === Array;
}
static isBoolean(value: any) {
return typeof value === 'boolean';
}
static isInstance(value: any) {
return (
value &&
typeof value === 'object' &&
!DataType.isObject(value) &&
!DataType.isArray(value) &&
!DataType.isRegExp(value)
);
}
static isCollection(value: any) {
return (
value?.constructor?.name === 'Collection' && value.podPath !== undefined
);
}
static isDate(value: any) {
return value instanceof Date;
}
static isDocument(value: any) {
return (
value?.constructor?.name === 'Document' && value.podPath !== undefined
);
}
static isFunction(value: any) {
return typeof value === 'function';
}
static isNumber(value: any) {
return typeof value === 'number' && isFinite(value);
}
static isNull(value: any) {
return value === null;
}
static isObject(value: any) {
return value && typeof value === 'object' && value.constructor === Object;
}
static isRegExp(value: any) {
return value && typeof value === 'object' && value.constructor === RegExp;
}
static isStaticFile(value: any) {
return (
value?.constructor?.name === 'StaticFile' && value.podPath !== undefined
);
}
static isString(value: any) {
return typeof value === 'string' || value instanceof String;
}
static isSymbol(value: any) {
return typeof value === 'symbol';
}
static isUndefined(value: any) {
return typeof value === 'undefined';
}
}
export function basename(path: string) {
return path.split('/').reverse()[0];
}
export function formatBytes(bytes: number) {
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if (bytes === 0) {
return '0 Bytes';
}
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return (bytes / Math.pow(1024, i)).toFixed(2) + ' ' + sizes[i];
}
/**
* Applies string interpolation to a given string. For example, if a given
* string is `Hello ${name}` and the params are `{string: 'World'}`, the result
* of calling the interpolation is "Hello World".
*
* Typically, this would be used with the `pod` object to provide the ability
* to refer to pod content: `Hello ${pod.meta.title}`, and then providing params
* such as: `{pod: <Pod instance>}`. The value of `pod.meta` would be specified
* within `amagaki.ts`.
* @param pod The pod.
* @param string A string to interpolate.
* @param params The parameters to use for interpolation.
* @returns The interpolated string.
*/
export function interpolate(pod: Pod, string: string, params: any) {
// Cache created functions to avoid memory leak.
const names = Object.keys(params);
const vals = Object.values(params);
const key = `${string}:${names}`;
if (pod.cache.interpolations[key]) {
return pod.cache.interpolations[key](...vals);
}
const func = new Function(...names, `return \`${string}\`;`);
pod.cache.interpolations[key] = func;
return func(...vals);
}
export function localizeData(data: any, locale: Locale): any {
if (!data) {
return data;
}
if (DataType.isObject(data)) {
const newData: any = {};
for (const key of Object.keys(data)) {
newData[key] = localizeData(data[key], locale);
}
return newData;
}
if (DataType.isArray(data)) {
const newData = [];
for (const item of data) {
newData.push(localizeData(item, locale));
}
return newData;
}
if (DataType.isInstance(data) && DataType.isFunction(data.localize)) {
return data.localize(locale);
}
return data;
}
export function splitFrontMatter(content: string | null): FrontMatterResult {
if (!content) {
return {};
}
const openingPart = '---\n';
const closingPart = '\n---';
// No opening part present, assume the whole file is not front matter.
if (!content.startsWith(openingPart)) {
return {
body: content,
};
}
// Strip the opening part.
content = content.slice(openingPart.length);
const closingIndex = content.indexOf(closingPart);
// No closing part present, assume the whole file is front matter.
if (closingIndex === -1) {
return {
frontMatter: content,
};
}
return {
frontMatter: content.slice(0, closingIndex).trim(),
body: content.slice(closingIndex + closingPart.length).trim(),
};
}
export function walk(path: string, newFiles?: string[], removePrefix?: string) {
let files = newFiles || [];
fs.readdirSync(path).forEach(file => {
const absPath = fsPath.join(path, file);
if (fs.statSync(absPath).isDirectory()) {
files = walk(absPath, files, removePrefix);
} else {
files.push(removePrefix ? absPath.replace(removePrefix, '') : absPath);
}
});
return files;
}
/**
* Returns data queried from a YAML file. Use by specifying a query, such as
* 'foo.bar'. This will return the value nested under the keys: `{foo: {bar:
* 'value'}}`.
* @param parts A string containing dots, indicating the query.
* @param data An object mapping keys to values.
* @returns The YAML content at the specified query.
*/
export function queryObject(query: string | undefined, data: any) {
if (!query) {
return data;
}
let result = data;
const queryParts = query.split('.');
while (queryParts.length > 0) {
const key = queryParts.shift() as string;
result = result[key];
if (result === undefined) {
break;
}
}
return result;
}
/** Debounces a function so that it's only called once within a window. Adopted
* from https://github.com/hayes/just-debounce. */
export function debounce(fn: Function, delay: number, immediate: boolean = false) {
let timeout: NodeJS.Timeout;
let args: any;
let self: any;
return function debounced() {
self = this;
args = Array.prototype.slice.call(arguments);
if (timeout && immediate) {
return;
} else if (!immediate) {
clear();
timeout = setTimeout(run, delay);
return timeout;
}
timeout = setTimeout(clear, delay);
fn.apply(self, args);
function run() {
clear();
fn.apply(self, args);
}
function clear() {
clearTimeout(timeout);
timeout = null;
}
};
}