Skip to content

Commit

Permalink
Add a script for generating credits for scratch-gui (#1216)
Browse files Browse the repository at this point in the history
Will be used for updating https://turbowarp.org/credits.html
  • Loading branch information
GarboMuffin authored Dec 29, 2023
1 parent 28f8f4e commit 1bf1d05
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 46 deletions.
47 changes: 1 addition & 46 deletions development/builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const AdmZip = require("adm-zip");
const pathUtil = require("path");
const compatibilityAliases = require("./compatibility-aliases");
const parseMetadata = require("./parse-extension-metadata");
const { mkdirp, recursiveReadDirectory } = require("./fs-utils");

/**
* @typedef {'development'|'production'|'desktop'} Mode
Expand All @@ -14,52 +15,6 @@ const parseMetadata = require("./parse-extension-metadata");
* @property {string} developer_comment Helper text to help translators
*/

/**
* Recursively read a directory.
* @param {string} directory
* @returns {Array<[string, string]>} List of tuples [name, absolutePath].
* The return result includes files in subdirectories, but not the subdirectories themselves.
*/
const recursiveReadDirectory = (directory) => {
const result = [];
for (const name of fs.readdirSync(directory)) {
if (name.startsWith(".")) {
// Ignore .eslintrc.js, .DS_Store, etc.
continue;
}
const absolutePath = pathUtil.join(directory, name);
const stat = fs.statSync(absolutePath);
if (stat.isDirectory()) {
for (const [
relativeToChildName,
childAbsolutePath,
] of recursiveReadDirectory(absolutePath)) {
// This always needs to use / on all systems
result.push([`${name}/${relativeToChildName}`, childAbsolutePath]);
}
} else {
result.push([name, absolutePath]);
}
}
return result;
};

/**
* Synchronous create a directory and any parents. Does nothing if the folder already exists.
* @param {string} directory
*/
const mkdirp = (directory) => {
try {
fs.mkdirSync(directory, {
recursive: true,
});
} catch (e) {
if (e.code !== "ENOENT") {
throw e;
}
}
};

/**
* @param {Record<string, Record<string, string>>} allTranslations
* @param {string} idPrefix
Expand Down
53 changes: 53 additions & 0 deletions development/fs-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const fs = require("fs");
const pathUtil = require("path");

/**
* Recursively read a directory.
* @param {string} directory
* @returns {Array<[string, string]>} List of tuples [name, absolutePath].
* The return result includes files in subdirectories, but not the subdirectories themselves.
*/
const recursiveReadDirectory = (directory) => {
const result = [];
for (const name of fs.readdirSync(directory)) {
if (name.startsWith(".")) {
// Ignore .eslintrc.js, .DS_Store, etc.
continue;
}
const absolutePath = pathUtil.join(directory, name);
const stat = fs.statSync(absolutePath);
if (stat.isDirectory()) {
for (const [
relativeToChildName,
childAbsolutePath,
] of recursiveReadDirectory(absolutePath)) {
// This always needs to use / on all systems
result.push([`${name}/${relativeToChildName}`, childAbsolutePath]);
}
} else {
result.push([name, absolutePath]);
}
}
return result;
};

/**
* Synchronous create a directory and any parents. Does nothing if the folder already exists.
* @param {string} directory
*/
const mkdirp = (directory) => {
try {
fs.mkdirSync(directory, {
recursive: true,
});
} catch (e) {
if (e.code !== "ENOENT") {
throw e;
}
}
};

module.exports = {
recursiveReadDirectory,
mkdirp,
};
118 changes: 118 additions & 0 deletions development/get-credits-for-gui.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
const fs = require("fs");
const path = require("path");
const https = require("https");
const fsUtils = require("./fs-utils");
const parseMetadata = require("./parse-extension-metadata");

class AggregatePersonInfo {
/** @param {Person} person */
constructor(person) {
this.name = person.name;

/** @type {Set<string>} */
this.links = new Set();
}

/** @param {string} link */
addLink(link) {
this.links.add(link);
}
}

/**
* @param {string} username
* @returns {Promise<string>}
*/
const getUserID = (username) =>
new Promise((resolve, reject) => {
process.stdout.write(`Getting user ID for ${username}... `);
const request = https.get(`https://api.scratch.mit.edu/users/${username}`);

request.on("response", (response) => {
const data = [];
response.on("data", (newData) => {
data.push(newData);
});

response.on("end", () => {
const allData = Buffer.concat(data);
const json = JSON.parse(allData.toString("utf-8"));
const userID = String(json.id);
process.stdout.write(`${userID}\n`);
resolve(userID);
});

response.on("error", (error) => {
process.stdout.write("error\n");
reject(error);
});
});

request.on("error", (error) => {
process.stdout.write("error\n");
reject(error);
});

request.end();
});

const run = async () => {
/**
* @type {Map<string, AggregatePersonInfo>}
*/
const aggregate = new Map();

const extensionRoot = path.resolve(__dirname, "../extensions/");
for (const [name, absolutePath] of fsUtils.recursiveReadDirectory(
extensionRoot
)) {
if (!name.endsWith(".js")) {
continue;
}

const code = fs.readFileSync(absolutePath, "utf-8");
const metadata = parseMetadata(code);

for (const person of [...metadata.by, ...metadata.original]) {
const personID = person.name.toLowerCase();
if (!aggregate.has(personID)) {
aggregate.set(personID, new AggregatePersonInfo(person));
}

if (person.link) {
aggregate.get(personID).addLink(person.link);
}
}
}

const result = [];

for (const id of [...aggregate.keys()].sort()) {
const info = aggregate.get(id);

if (info.links.size > 0) {
const link = [...info.links.values()].sort()[0];
const username = link.match(/users\/(.+?)\/?$/)[1];
const userID = await getUserID(username);
result.push({
userID,
username,
});
} else {
result.push({
username: info.name,
});
}
}

return result;
};

run()
.then((result) => {
console.log(JSON.stringify(result, null, 4));
})
.catch((error) => {
console.error(error);
process.exit(1);
});

0 comments on commit 1bf1d05

Please sign in to comment.