https://github.com/Microsoft/TypeScript
Tip revision: e630ce18a89fd90995dbf4a5ea2b40999a930a00 authored by Sheetal Nandi on 11 February 2020, 23:27:16 UTC
Instead of creating object for search result use false to avoid having to construct another object
Instead of creating object for search result use false to avoid having to construct another object
Tip revision: e630ce1
authors.ts
import fs = require("fs");
import path = require("path");
import childProcess = require("child_process");
interface Author {
displayNames: string[];
preferredName?: string;
emails: string[];
}
interface AuthorMap {
[s: string]: Author
}
interface Command {
(...arg: string[]): void;
description?: string;
}
const mailMapPath = path.resolve(__dirname, "../.mailmap");
const authorsPath = path.resolve(__dirname, "../AUTHORS.md");
function getKnownAuthors(): Author[] {
const segmentRegExp = /\s?([^<]+)\s+<([^>]+)>/g;
const preferredNameRegeExp = /\s?#\s?([^#]+)$/;
const knownAuthors: Author[] = [];
if (!fs.existsSync(mailMapPath)) {
throw new Error(`Could not load known users form .mailmap file at: ${mailMapPath}`);
}
const mailMap = fs.readFileSync(mailMapPath).toString();
for (const line of mailMap.split("\r\n")) {
const author: Author = { displayNames: [], emails: [] };
let match: RegExpMatchArray | null;
while (match = segmentRegExp.exec(line)) {
author.displayNames.push(match[1]);
author.emails.push(match[2]);
}
if (match = preferredNameRegeExp.exec(line)) {
author.preferredName = match[1];
}
if (!author.emails) continue;
knownAuthors.push(author);
if (line.indexOf("#") > 0 && !author.preferredName) {
throw new Error("Could not match preferred name for: " + line);
}
// console.log("===> line: " + line);
// console.log(JSON.stringify(author, undefined, 2));
}
return knownAuthors;
}
function getAuthorName(author: Author) {
return author.preferredName || author.displayNames[0];
}
function getKnownAuthorMaps() {
const knownAuthors = getKnownAuthors();
const authorsByName: AuthorMap = {};
const authorsByEmail: AuthorMap = {};
knownAuthors.forEach(author => {
author.displayNames.forEach(n => authorsByName[n] = author);
author.emails.forEach(e => authorsByEmail[e.toLocaleLowerCase()] = author);
});
return {
knownAuthors,
authorsByName,
authorsByEmail
};
}
function deduplicate<T>(array: T[]): T[] {
const result: T[] = [];
if (array) {
for (const item of array) {
if (result.indexOf(item) < 0) {
result.push(item);
}
}
}
return result;
}
function log(s: string) {
console.log(` ${s}`);
}
function sortAuthors(a: string, b: string) {
if (a.charAt(0) === "@") a = a.substr(1);
if (b.charAt(0) === "@") b = b.substr(1);
if (a.toLocaleLowerCase() < b.toLocaleLowerCase()) {
return -1;
}
else {
return 1;
}
}
namespace Commands {
export const writeAuthors: Command = () => {
const output = deduplicate(getKnownAuthors().map(getAuthorName).filter(a => !!a)).sort(sortAuthors).join("\r\n* ");
fs.writeFileSync(authorsPath, "TypeScript is authored by:\r\n* " + output);
};
writeAuthors.description = "Write known authors to AUTHORS.md file.";
export const listKnownAuthors: Command = () => {
deduplicate(getKnownAuthors().map(getAuthorName)).filter(a => !!a).sort(sortAuthors).forEach(log);
};
listKnownAuthors.description = "List known authors as listed in .mailmap file.";
export const listAuthors: Command = (...specs: string[]) => {
const cmd = "git shortlog -se " + specs.join(" ");
console.log(cmd);
const outputRegExp = /\d+\s+([^<]+)<([^>]+)>/;
const authors: { name: string, email: string, knownAuthor?: Author }[] = [];
const {output: [error, stdout, stderr]} = childProcess.spawnSync(`git`, ["shortlog", "-se", ...specs], { cwd: path.resolve(__dirname, "../") });
if (error) {
console.log(stderr.toString());
}
else {
const output = stdout.toString();
const lines = output.split("\n");
lines.forEach(line => {
if (line) {
let match: RegExpExecArray | null;
if (match = outputRegExp.exec(line)) {
authors.push({ name: match[1], email: match[2] });
}
else {
throw new Error("Could not parse output: " + line);
}
}
});
const maps = getKnownAuthorMaps();
const lookupAuthor = ({name, email}: { name: string, email: string }) => {
return maps.authorsByEmail[email.toLocaleLowerCase()] || maps.authorsByName[name];
};
const knownAuthors = authors
.map(lookupAuthor)
.filter(a => !!a)
.map(getAuthorName);
const unknownAuthors = authors
.filter(a => !lookupAuthor(a))
.map(a => `${a.name} <${a.email}>`);
if (knownAuthors.length) {
console.log("\r\n");
console.log("Found known authors: ");
console.log("=====================");
deduplicate(knownAuthors).sort(sortAuthors).forEach(log);
}
if (unknownAuthors.length) {
console.log("\r\n");
console.log("Found unknown authors: ");
console.log("=====================");
deduplicate(unknownAuthors).sort(sortAuthors).forEach(log);
}
const allAuthors = deduplicate([...knownAuthors, ...unknownAuthors].map(a => a.split("<")[0].trim())).sort(sortAuthors);
if (allAuthors.length) {
console.log("\r\n");
console.log("Revised Authors.md: ");
console.log("=====================");
allAuthors.forEach(name => console.log(" - " + name));
}
}
};
listAuthors.description = "List known and unknown authors for a given spec, e.g. 'node authors.js listAuthors origin/release-2.6..origin/release-2.7'";
}
const args = process.argv.slice(2);
if (args.length < 1) {
console.log("Usage: node authors.js [command]");
console.log("List of commands: ");
Object.keys(Commands).forEach(k => console.log(` ${k}: ${(Commands as any)[k].description}`));
}
else {
const cmd: Function = (Commands as any)[args[0]];
if (cmd === undefined) {
console.log("Unknown command " + args[1]);
}
else {
cmd.apply(undefined, args.slice(1));
}
}