mirror of
https://github.com/Xaymar/obs-StreamFX
synced 2024-12-29 11:01:23 +00:00
tools: Add tool to generate and update copyright headers
This tool aids in the proper upkeep of copyright headers for changes contained within this repository. It will generate a new header, or replace the original one if one exists. As this task has often been forgotten by both developers and contributors, having a tool manage this will hopefully improve the situation. The choice of Node.JS for this tool was deliberate, as many developers and CI solutions already have Node.JS in a reasonably up to date version installed. Additionally the versatility of Node.JS eliminates the need to create custom or platform specific solutions for tasks that are relatively simple. While the performance is not ideal, it still completes its task relatively quickly.
This commit is contained in:
parent
c80a19ae3c
commit
d1e3b6d0d1
1 changed files with 346 additions and 0 deletions
346
tools/copyright.js
Normal file
346
tools/copyright.js
Normal file
|
@ -0,0 +1,346 @@
|
||||||
|
/* AUTOGENERATED COPYRIGHT HEADER START
|
||||||
|
* Copyright (C) 2023 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
|
||||||
|
* AUTOGENERATED COPYRIGHT HEADER END */
|
||||||
|
const CHILD_PROCESS = require("node:child_process");
|
||||||
|
const PROCESS = require("node:process");
|
||||||
|
const PATH = require("node:path");
|
||||||
|
const FS = require("node:fs");
|
||||||
|
const FSPROMISES = require("node:fs/promises");
|
||||||
|
const OS = require("os");
|
||||||
|
|
||||||
|
async function git_isIgnored(path) {
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
let proc = CHILD_PROCESS.spawn("git", [
|
||||||
|
"check-ignore",
|
||||||
|
path
|
||||||
|
], {
|
||||||
|
"cwd": PROCESS.cwd(),
|
||||||
|
"encoding": "utf8",
|
||||||
|
});
|
||||||
|
proc.stdout.on('data', (data) => {
|
||||||
|
})
|
||||||
|
proc.on('close', (code) => {
|
||||||
|
resolve(code == 0);
|
||||||
|
});
|
||||||
|
proc.on('exit', (code) => {
|
||||||
|
resolve(code == 0);
|
||||||
|
});
|
||||||
|
} catch (ex) {
|
||||||
|
reject(ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
/* Sync alternative
|
||||||
|
try {
|
||||||
|
return CHILD_PROCESS.spawnSync("git", [
|
||||||
|
"check-ignore",
|
||||||
|
path
|
||||||
|
], {
|
||||||
|
"cwd": PROCESS.cwd(),
|
||||||
|
"encoding": "utf8"
|
||||||
|
}).status == 0;
|
||||||
|
} catch (ex) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
async function git_retrieveAuthors(file) {
|
||||||
|
// git --no-pager log --date-order --reverse "--format=format:%aI|%aN <%aE>" -- file
|
||||||
|
let lines = await new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
let lines = "";
|
||||||
|
let proc = CHILD_PROCESS.spawn("git", [
|
||||||
|
"--no-pager",
|
||||||
|
"log",
|
||||||
|
"--date-order",
|
||||||
|
"--reverse",
|
||||||
|
"--format=format:%aI|%aN <%aE>",
|
||||||
|
"--",
|
||||||
|
file
|
||||||
|
], {
|
||||||
|
"cwd": PROCESS.cwd(),
|
||||||
|
"encoding": "utf8",
|
||||||
|
});
|
||||||
|
proc.stdout.on('data', (data) => {
|
||||||
|
lines += data.toString();
|
||||||
|
})
|
||||||
|
proc.on('close', (code) => {
|
||||||
|
resolve(lines);
|
||||||
|
});
|
||||||
|
proc.on('exit', (code) => {
|
||||||
|
resolve(lines);
|
||||||
|
});
|
||||||
|
} catch (ex) {
|
||||||
|
reject(ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
lines = lines.split(lines.indexOf("\r\n") >= 0 ? "\r\n" : "\n");
|
||||||
|
let authors = new Map();
|
||||||
|
for (let line of lines) {
|
||||||
|
let [date, name] = line.split("|");
|
||||||
|
|
||||||
|
let author = authors.get(name);
|
||||||
|
if (author) {
|
||||||
|
author.to = new Date(date)
|
||||||
|
} else {
|
||||||
|
authors.set(name, {
|
||||||
|
from: new Date(date),
|
||||||
|
to: new Date(date),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return authors;
|
||||||
|
|
||||||
|
/* Sync Variant
|
||||||
|
try {
|
||||||
|
let data = await CHILD_PROCESS
|
||||||
|
let lines = data.stdout.toString().split("\n");
|
||||||
|
let authors = new Map();
|
||||||
|
for (let line of lines) {
|
||||||
|
let [date, name] = line.split("|");
|
||||||
|
|
||||||
|
let author = authors.get(name);
|
||||||
|
if (author) {
|
||||||
|
author.to = new Date(date)
|
||||||
|
} else {
|
||||||
|
authors.set(name, {
|
||||||
|
from: new Date(date),
|
||||||
|
to: new Date(date),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return authors;
|
||||||
|
} catch (ex) {
|
||||||
|
console.error(ex);
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateCopyright(file) {
|
||||||
|
let authors = await git_retrieveAuthors(file)
|
||||||
|
let lines = [];
|
||||||
|
for (let entry of authors) {
|
||||||
|
let from = entry[1].from.getUTCFullYear();
|
||||||
|
let to = entry[1].to.getUTCFullYear();
|
||||||
|
lines.push(`Copyright (C) ${from != to ? `${from}-${to}` : to} ${entry[0]}`);
|
||||||
|
}
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeHeader(file, copyright) {
|
||||||
|
let file_name = PATH.basename(file).toLocaleLowerCase();
|
||||||
|
let file_exts = file_name.substring(file_name.indexOf("."));
|
||||||
|
|
||||||
|
let styles = {
|
||||||
|
"#": {
|
||||||
|
files: [
|
||||||
|
"cmakelists.txt"
|
||||||
|
], exts: [
|
||||||
|
".clang-tidy",
|
||||||
|
".clang-format",
|
||||||
|
".cmake",
|
||||||
|
".editorconfig",
|
||||||
|
".gitignore",
|
||||||
|
".gitmodules",
|
||||||
|
".yml",
|
||||||
|
],
|
||||||
|
prepend: [
|
||||||
|
"#\u0020AUTOGENERATED COPYRIGHT HEADER START",
|
||||||
|
],
|
||||||
|
append: [
|
||||||
|
"#\u0020AUTOGENERATED COPYRIGHT HEADER END",
|
||||||
|
],
|
||||||
|
prefix: "# ",
|
||||||
|
suffix: "",
|
||||||
|
},
|
||||||
|
";": {
|
||||||
|
files: [
|
||||||
|
""
|
||||||
|
], exts: [
|
||||||
|
".iss",
|
||||||
|
".iss.in",
|
||||||
|
],
|
||||||
|
prepend: [
|
||||||
|
";\u0020AUTOGENERATED COPYRIGHT HEADER START",
|
||||||
|
],
|
||||||
|
append: [
|
||||||
|
";\u0020AUTOGENERATED COPYRIGHT HEADER END",
|
||||||
|
],
|
||||||
|
prefix: "; ",
|
||||||
|
suffix: "",
|
||||||
|
},
|
||||||
|
"/**/": {
|
||||||
|
files: [
|
||||||
|
], exts: [
|
||||||
|
".c",
|
||||||
|
".c.in",
|
||||||
|
".cpp",
|
||||||
|
".cpp.in",
|
||||||
|
".h",
|
||||||
|
".h.in",
|
||||||
|
".hpp",
|
||||||
|
".hpp.in",
|
||||||
|
".js",
|
||||||
|
".rc",
|
||||||
|
".rc.in",
|
||||||
|
".effect"
|
||||||
|
],
|
||||||
|
prepend: [
|
||||||
|
"/*\u0020AUTOGENERATED COPYRIGHT HEADER START",
|
||||||
|
],
|
||||||
|
append: [
|
||||||
|
" *\u0020AUTOGENERATED COPYRIGHT HEADER END */",
|
||||||
|
],
|
||||||
|
prefix: " * ",
|
||||||
|
suffix: "",
|
||||||
|
},
|
||||||
|
"<!---->": {
|
||||||
|
files: [
|
||||||
|
], exts: [
|
||||||
|
".htm",
|
||||||
|
".htm.in",
|
||||||
|
".html",
|
||||||
|
".html.in",
|
||||||
|
".xml",
|
||||||
|
".xml.in",
|
||||||
|
".plist",
|
||||||
|
".plist.in",
|
||||||
|
".pkgproj",
|
||||||
|
".pkgproj.in",
|
||||||
|
],
|
||||||
|
prepend: [
|
||||||
|
"<!--\u0020AUTOGENERATED COPYRIGHT HEADER START",
|
||||||
|
],
|
||||||
|
append: [
|
||||||
|
" --\u0020AUTOGENERATED COPYRIGHT HEADER END -->",
|
||||||
|
],
|
||||||
|
prefix: " --",
|
||||||
|
suffix: "",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let key in styles) {
|
||||||
|
let style = [key, styles[key]];
|
||||||
|
if (style[1].files.includes(file_name)
|
||||||
|
|| style[1].files.includes(file)
|
||||||
|
|| style[1].exts.includes(file_exts)) {
|
||||||
|
let header = [];
|
||||||
|
header.push(...style[1].prepend);
|
||||||
|
for (let line of copyright) {
|
||||||
|
header.push(`${style[1].prefix}${line}${style[1].suffix}`);
|
||||||
|
}
|
||||||
|
header.push(...style[1].append);
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Unrecognized file format.")
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addCopyright(file) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Async/Promises
|
||||||
|
let content = await FSPROMISES.readFile(file);
|
||||||
|
let eol = (content.indexOf("\r\n") != -1 ? OS.EOL : "\n");
|
||||||
|
|
||||||
|
let copyright = await generateCopyright(file);
|
||||||
|
let header = makeHeader(file, copyright);
|
||||||
|
let insert = Buffer.from(header.join(eol) + eol);
|
||||||
|
|
||||||
|
let startHeader = content.indexOf(header[0]);
|
||||||
|
let endHeader = content.indexOf(header[header.length - 1], startHeader + 1);
|
||||||
|
endHeader += header[header.length - 1].length + eol.length;
|
||||||
|
|
||||||
|
let fd = await FSPROMISES.open(file, "w+");
|
||||||
|
let fp = [];
|
||||||
|
if ((startHeader >= 0) && (endHeader >= 0)) {
|
||||||
|
let pos = 0;
|
||||||
|
if (startHeader > 0) {
|
||||||
|
fd.write(content, 0, startHeader, 0);
|
||||||
|
pos += startHeader;
|
||||||
|
}
|
||||||
|
fd.write(insert, 0, undefined, pos);
|
||||||
|
pos += insert.byteLength;
|
||||||
|
fd.write(content, endHeader, undefined, pos);
|
||||||
|
} else {
|
||||||
|
fd.write(insert, 0, undefined, 0);
|
||||||
|
fd.write(content, 0, undefined, insert.byteLength);
|
||||||
|
}
|
||||||
|
await fd.close();
|
||||||
|
|
||||||
|
/* Sync variant (slow!)
|
||||||
|
let content = FS.readFileSync(file);
|
||||||
|
let eol = (content.indexOf("\r\n") != -1 ? OS.EOL : "\n");
|
||||||
|
|
||||||
|
let copyright = await generateCopyright(file);
|
||||||
|
let header = makeHeader(file, copyright);
|
||||||
|
let insert = Buffer.from(header.join(eol) + eol);
|
||||||
|
|
||||||
|
let startHeader = content.indexOf(header[0]);
|
||||||
|
let endHeader = content.indexOf(header[header.length - 1], startHeader + 1);
|
||||||
|
endHeader += header[header.length - 1].length + eol.length;
|
||||||
|
|
||||||
|
let fd = FS.openSync(file, "w+");
|
||||||
|
if ((startHeader >= 0) && (endHeader >= 0)) {
|
||||||
|
let pos = 0;
|
||||||
|
if (startHeader > 0) {
|
||||||
|
FS.writeSync(fd, content, 0, startHeader, 0);
|
||||||
|
pos += startHeader;
|
||||||
|
}
|
||||||
|
FS.writeSync(fd, insert, 0, undefined, pos);
|
||||||
|
pos += insert.byteLength;
|
||||||
|
FS.writeSync(fd, content, endHeader, undefined, pos);
|
||||||
|
} else {
|
||||||
|
FS.writeSync(fd, insert, 0, undefined, 0);
|
||||||
|
FS.writeSync(fd, content, 0, undefined, insert.byteLength);
|
||||||
|
}
|
||||||
|
FS.close(fd, (err) => {
|
||||||
|
if (err)
|
||||||
|
throw err;
|
||||||
|
})*/
|
||||||
|
} catch (ex) {
|
||||||
|
console.error(`Error processing '${file}'!: ${ex}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addCopyrights(path) {
|
||||||
|
if (await git_isIgnored(path)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let promises = [];
|
||||||
|
|
||||||
|
let files = await FSPROMISES.readdir(path, { "withFileTypes": true });
|
||||||
|
for (let file of files) {
|
||||||
|
let fullname = PATH.join(path, file.name);
|
||||||
|
if (await git_isIgnored(fullname)) {
|
||||||
|
console.log(`Ignoring path '${fullname}'...`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
console.log(`Scanning path '${fullname}'...`);
|
||||||
|
promises.push(addCopyrights(fullname));
|
||||||
|
} else {
|
||||||
|
console.log(`Updating file '${fullname}'...`);
|
||||||
|
promises.push(addCopyright(fullname));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
(async function () {
|
||||||
|
let file = PROCESS.argv[2];
|
||||||
|
let pathStat = await FSPROMISES.stat(file);
|
||||||
|
if (pathStat.isDirectory()) {
|
||||||
|
await addCopyrights(PATH.resolve(file));
|
||||||
|
} else {
|
||||||
|
await addCopyright(PATH.resolve(file));
|
||||||
|
}
|
||||||
|
console.log("Done");
|
||||||
|
})();
|
Loading…
Reference in a new issue