This is a list of Free Software network services and web applications which can be hosted locally. Selfhosting is the process of locally hosting and managing applications instead of renting from SaaS providers.
https://reddit.com/r/selfhosted
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
251 lines
7.3 KiB
251 lines
7.3 KiB
// USAGE:
|
|
// node test.js -r README.md (Checks whole file)
|
|
// node test.js -r README.md -d temp.md (Checks just the diff)
|
|
|
|
const fs = require('fs');
|
|
const chalk = require('chalk');
|
|
let licenses = new Set();
|
|
let pr = false;
|
|
let readme;
|
|
let diff;
|
|
|
|
//Parse the command options and set the pr var
|
|
function parseArgs(args) {
|
|
if ( args.indexOf('-r', 2) > 0 ) {
|
|
readme = fs.readFileSync(args[args.indexOf('-r', 2)+1], 'utf8')
|
|
}
|
|
if (args.indexOf('-d', 2) > 0) {
|
|
pr = true;
|
|
diff = fs.readFileSync(args[args.indexOf('-d', 2)+1], 'utf8');
|
|
}
|
|
if ( pr === true) {
|
|
console.log(chalk.blue(`Running on PR. README.md: ${args[args.indexOf('-r', 2)+1]} diff: ${args[args.indexOf('-d', 2)+1]}`))
|
|
}
|
|
}
|
|
|
|
// Function to find lines with entries
|
|
function entryFilter(md) {
|
|
const linepatt = /^\s{0,2}-\s\[.*`/;
|
|
return linepatt.test(md);
|
|
}
|
|
|
|
// Function to find lines with licenses
|
|
function licenseFilter(md) {
|
|
const linepatt = /^- `.*` - .*/;
|
|
return linepatt.test(md)
|
|
}
|
|
|
|
// Function to split lines into array
|
|
function split(text) {
|
|
return text.split(/\r?\n/);
|
|
}
|
|
|
|
// All entries should match this pattern. If matches pattern returns true.
|
|
function findPattern(text) {
|
|
const patt = /^\s{0,2}-\s\[.*?\]\(.*?\) (`⚠` )?- .{0,249}?\.( \(\[(Demo|Source Code|Clients)\]\([^)\]]*\)(, \[(Source Code|Clients)\]\([^)\]]*\))?(, \[(Source Code|Clients)\]\([^)\]]*\))*\))? \`.*?\` \`.*?\`$/;
|
|
if (patt.test(text) === true) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Parses SPDX identifiers from list of licenses
|
|
function parseLicense(md) {
|
|
const patt = /^- `(.*)` - .*/
|
|
return patt.exec(md)[1]
|
|
}
|
|
|
|
//Test '- [Name](http://homepage/)'
|
|
function testMainLink(text) {
|
|
let testA = /(^ {0,2}- \[.*?\]\([^)]*\.[^)]*?\))(?=\ ?\-?\ ?\w)/ // /(^ {0,2}- \[.*?\]\(.*\))(?=.?-? ?\w)/;
|
|
const testA1 = /(- \W?\w*\W{0,2}.*?\)?)( .*$)/;
|
|
if (!testA.test(text)) {
|
|
let a1 = testA1.exec(text)[2];
|
|
return chalk.red(text.replace(a1, ''))
|
|
}
|
|
return chalk.green(testA.exec(text)[1])
|
|
}
|
|
|
|
//Test '`⚠` - Short description, less than 250 characters.'
|
|
function testDescription(text) {
|
|
const testB = /( - .*\. )(?:(\(?\[?|\`))/;
|
|
const testA1 = /(- \W?\w*\W{0,2}.*?\)?)( .*$)/;
|
|
const testB2 = /((\(\[|\`).*$)/;
|
|
if (!testB.test(text)) {
|
|
let b1 = testA1.exec(text)[1];
|
|
let b2 = testB2.exec(text)[1];
|
|
return chalk.red(text.replace(b1, '').replace(b2, ''))
|
|
}
|
|
return chalk.green(testB.exec(text)[1])
|
|
}
|
|
|
|
//If present, tests '([Demo](http://url.to/demo), [Source Code](http://url.of/source/code), [Clients](https://url.to/list/of/related/clients-or-apps))'
|
|
function testSrcDemCli(text) {
|
|
let testC = text.search(/\.\ \(|\.\ \[|\ \(\[[sSdDcC]/); // /\(\[|\)\,|\)\)/);
|
|
let testD = /(?<=\w. )(\(\[(Demo|Source Code|Clients)\]\([^)\]]*\)(, \[(Source Code|Clients)\]\([^)\]]*\))?(, \[(Source Code|Clients)\]\([^)\]]*\))*\))(?= \`?)/;
|
|
const testD1 = /(^- \W[a-zA-Z0-9-_ .]*\W{0,2}http[^\[]*)(?<= )/;
|
|
const testD2 = /(\`.*\` \`.*\`$)/;
|
|
if ((testC > -1) && (!testD.test(text))) {
|
|
let d1 = testD1.exec(text)[1];
|
|
let d2 = testD2.exec(text)[1];
|
|
return chalk.red(text.replace(d1, '').replace(d2, ''))
|
|
} else if (testC > -1) {
|
|
return chalk.green(testD.exec(text)[1])
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// Tests '`License` `Language`'
|
|
function testLangLic(text) {
|
|
const testD2 = /(\`.*\` \`.*\`$)/;
|
|
let testE = testD2.test(text);
|
|
const testE1 = /(^[^`]*)/;
|
|
if (!testE) {
|
|
let e1 = testE1.exec(text)[1];
|
|
return chalk.red(text.replace(e1, ''))
|
|
}
|
|
return chalk.green(testD2.exec(text)[1])
|
|
}
|
|
|
|
//Runs all the syntax tests...
|
|
function findError(text) {
|
|
let res
|
|
res = testMainLink(text)
|
|
res += testDescription(text)
|
|
res += testSrcDemCli(text)
|
|
res += testLangLic(text)
|
|
return res + `\n`
|
|
}
|
|
|
|
//Check if license is in the list of licenses.
|
|
function testLicense(md) {
|
|
let pass = true;
|
|
let lFailed = []
|
|
let lPassed = []
|
|
const regex = /.*\`(.*)\` .*$/;
|
|
try {
|
|
for (l of regex.exec(md)[1].split("/")) {
|
|
if (!licenses.has(l)) {
|
|
pass = false;
|
|
lPassed.push(l)
|
|
}
|
|
lFailed.push(l)
|
|
}
|
|
}
|
|
catch(err) {
|
|
console.log(chalk.yellow("Error in License syntax, license not checked against list."))
|
|
return [false, "", ""]
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return [pass, lFailed, lPassed]
|
|
}
|
|
|
|
|
|
//Parses name from entry
|
|
function parseName(md) {
|
|
const regex = /^\W*(.*?)\W/
|
|
return regex.exec(md)[1]
|
|
}
|
|
|
|
function entryErrorCheck() {
|
|
const lines = split(readme); // Inserts each line into the entries array
|
|
let totalFail = 0;
|
|
let totalPass = 0;
|
|
let total = 0;
|
|
let entries = [];
|
|
let diffEntries = [];
|
|
|
|
if (lines[0] === "") {
|
|
console.log(chalk.red("0 Entries Found, check your commandline arguments"))
|
|
process.exit(0)
|
|
}
|
|
for (let i = 0; i < lines.length; i ++) { // Loop through array of lines
|
|
if (entryFilter(lines[i]) === true) { // filter out lines that don't start with * [)
|
|
e = {};
|
|
e.raw = lines[i];
|
|
e.line = i + 1
|
|
entries.push(e);
|
|
} else if (licenseFilter(lines[i]) === true) {
|
|
licenses.add(parseLicense(lines[i]))
|
|
}
|
|
}
|
|
|
|
if (pr === true) {
|
|
console.log(chalk.cyan("Only testing the diff from the PR.\n"))
|
|
const diffLines = split(diff); // Inserts each line of diff into an array
|
|
for (let l of diffLines) {
|
|
if (entryFilter(l) === true) { // filter out lines that don't start with * [)
|
|
e = {};
|
|
e.raw = l;
|
|
diffEntries.push(e);
|
|
} else if (licenseFilter(l) === true) {
|
|
licenses.add(parseLicense(l))
|
|
}
|
|
}
|
|
if (diffEntries.length === 0) {
|
|
console.log("No entries changed in README.md, Exiting...")
|
|
process.exit(0)
|
|
}
|
|
total = diffEntries.length
|
|
for (let e of diffEntries) {
|
|
e.pass = true
|
|
e.name = parseName(e.raw)
|
|
if (!findPattern(e.raw)) {
|
|
e.highlight = findError(e.raw);
|
|
e.pass = false;
|
|
console.log(e.highlight)
|
|
}
|
|
e.licenseTest = testLicense(e.raw);
|
|
if (!e.licenseTest) {
|
|
e.pass = false;
|
|
console.log(chalk.red(`${e.name}'s license is not on License list.`))
|
|
}
|
|
if (e.pass) {
|
|
totalPass++
|
|
} else {
|
|
totalFail++
|
|
}
|
|
}
|
|
} else {
|
|
console.log(chalk.cyan("Testing entire README.md\n"))
|
|
total = entries.length
|
|
for (let e of entries) {
|
|
e.pass = true
|
|
e.name = parseName(e.raw)
|
|
if (!findPattern(e.raw)) {
|
|
e.highlight = findError(e.raw);
|
|
e.pass = false;
|
|
console.log(`${chalk.yellow(e.line + ": ")}${e.highlight}`);
|
|
syntax = e.highlight;
|
|
}
|
|
e.licenseTest = testLicense(e.raw);
|
|
if (!e.licenseTest[0]) {
|
|
e.pass = false;
|
|
console.log(chalk.yellow(e.line + ": ") + `${e.name}'s license ${chalk.red(`'${e.licenseTest[1]}'`)} is not on the License list.\n`)
|
|
}
|
|
if (e.pass) {
|
|
totalPass++
|
|
} else {
|
|
totalFail++
|
|
}
|
|
}
|
|
}
|
|
if (totalFail > 0) {
|
|
console.log(chalk.blue(`\n-----------------------------\n`))
|
|
console.log(chalk.red(`${totalFail} Failed, `) + chalk.green(`${totalPass} Passed, `) + chalk.blue(`of ${total}`))
|
|
console.log(chalk.blue(`\n-----------------------------\n`))
|
|
process.exit(1);
|
|
} else {
|
|
console.log(chalk.blue(`\n-----------------------------\n`))
|
|
console.log(chalk.green(`${totalPass} Passed of ${total}`))
|
|
console.log(chalk.blue(`\n-----------------------------\n`))
|
|
process.exit(0)
|
|
}
|
|
}
|
|
|
|
parseArgs(process.argv)
|
|
entryErrorCheck();
|
|
|