-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
godot-learning-app-exporter.js
executable file
·211 lines (190 loc) · 5.72 KB
/
godot-learning-app-exporter.js
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
#!/usr/bin/env node
//@ts-check
/**
* Godot Learning App Exporter
*
* ### Usage
*
* <this file name> path/to/godot/project
*
* `path/to/godot/project` is the directory where a `project.godot` file can be
* found.
*
* This command will output JSON with the proper configuration to STDOUT.
* Pipe it/redirect it to the tool of your choice
*
* If you don't like redirection, just use --output:
*
* <this file name> path/to/godot/project -o config.json
*
* ### FLAGS:
*
* -h --help this help text
* -o --output [file] save to file
*/
const {
readdirSync,
existsSync,
readFileSync,
statSync,
writeFileSync,
} = require("fs");
const { extname, join, resolve, basename, dirname } = require("path");
const cwd = process.cwd();
const glob = (dirPath = cwd, extension = ".gd") =>
readdirSync(dirPath)
.map((file) => join(dirPath, file))
.reduce(
(arr, path) =>
statSync(path).isDirectory()
? arr.concat(glob(path))
: extname(path).toLowerCase() === extension
? (arr.push(path), arr)
: arr,
[]
);
const tagRegExp = /^(\s*)#\s(EXPORT)(?:(?:\s+)(.*?))?(?:\s|$)/;
const sanitizeFileContents = (fileContents = "") =>
fileContents
.replace(/\r\n|\r/g, "\n")
.replace(/^( +)/g, (subs) => "\t".repeat(subs.length));
const getProjectSlices = (projectPath = cwd) =>
glob(projectPath, ".gd")
.reduce((validFiles, path) => {
const filePath = path.replace(projectPath, "");
const contents = sanitizeFileContents(readFileSync(path, "utf-8"));
const lines = contents.split(`\n`);
const validSlices = lines.reduce((validLines, line, lineNb) => {
const [, indentStr, keyword, name] = line.match(tagRegExp) || [];
if (!keyword) {
return validLines;
}
const indent = indentStr.length;
const slice = {
all: true,
name: "",
before: "",
after: "",
contents,
indent,
start: lineNb,
end: lines.length,
};
if (!name || name === "*") {
slice.name = "*";
slice.contents = (
indent ? lines.map((line) => line.slice(indent)) : lines.slice()
).join("\n");
validLines.push(slice);
return validLines;
}
const start = lineNb + 1;
const endRegExp = RegExp(
"^" + indentStr + "# \\/" + keyword + " " + name + "\\s*$"
);
const _end = lines
.slice(start)
.findIndex((line) => line.match(endRegExp));
if (_end === -1) {
throw new Error(
`${filePath}: The slice "${name}" does not have a closing tag`
);
}
const end = start + _end;
const original = lines.slice(start, end);
slice.all = false;
slice.name = name;
slice.end = end;
slice.start = start + 1;
slice.before = lines.slice(0, start + 1).join("\n");
slice.after = lines.slice(end + 1).join("\n");
slice.original = original.join("\n");
slice.contents = (
indent ? original.map((line) => line.slice(indent)) : original.slice()
).join("\n");
validLines.push(slice);
return validLines;
}, []);
if (!validSlices.length) {
return validFiles;
}
const file_name = basename(filePath, extname(filePath));
const dir_name = dirname(filePath);
const godot_path = `res://${filePath}`;
const { slices, slices_names } = validSlices.reduce(
(obj, slice) => {
obj.slices[slice.name] = slice;
obj.slices_names.push(slice.name);
return obj;
},
{ slices: {}, slices_names: [] }
);
validFiles.push({
file_path: filePath,
dir_name,
file_name,
godot_path,
slices_names,
slices,
});
return validFiles;
}, [])
.reduce(
(obj, fileConfig) => {
obj.files[fileConfig.file_path] = fileConfig;
obj.files_paths.push(fileConfig.file_path);
return obj;
},
{ files_paths: [], files: {} }
);
if (require.main === module) {
const error = (message = "") => {
console.error(message);
process.exit(1);
};
const getProjectPath = (path = cwd) => {
const projectPath = resolve(cwd, path) + "/";
if (!existsSync(projectPath)) {
error(
`"${projectPath}" doesn't exist or isn't accessible. Please check your path`
);
}
const projectFilePath = join(projectPath, "project.godot");
if (!existsSync(projectFilePath)) {
error(
`No "project.godot" file found in "${projectPath}". Are you sure this is a Godot repository?`
);
}
return projectPath;
};
const [, , arg, save, providedSavePath] = process.argv;
if (arg === "-h" || arg === "--help") {
const text = (readFileSync(__filename, "utf-8").match(
/\n\/\*\*\s+\*([\s\S]+?)\*\/\n/
) || [, ""])[1]
.replace(/^ +\*/gm, "")
.replace(/<this file name>/g, basename(__filename, ".js"));
console.log(text);
process.exit(0);
}
const doSave = save === "-o" || save === "--output";
if (doSave) {
if (!providedSavePath) {
error(
`You provided the output flag, but no output file. Please specify the output file`
);
}
}
const savePath = doSave && resolve(cwd, providedSavePath);
const projectPath = getProjectPath(arg);
doSave && console.log(`will write generated config to "${savePath}"`);
const config = getProjectSlices(projectPath);
const jsonConfig = JSON.stringify(config, null, 2);
if (doSave) {
writeFileSync(savePath, jsonConfig, "utf-8");
} else {
console.log(jsonConfig);
}
} else {
module.exports = getProjectSlices;
}