// Build tool for generating README.md
const { EOL } = require('os');
const path = require('path');
const fs = require('fs-extra');
const moment = require('moment');
const YAML = require('yaml');
const BUILD_SECTION = {
header: () => readFile('md/_header.md').replace('{{DATE}}', moment().format('MMMM Do YYYY').replace(/ /g, '%20')),
index: () => readFile('md/_index.md'),
contributing: () => readFile('md/_contributing.md'),
browserExtensions: () => generateBrowserExtensions(),
disclaimer: () => readFile('md/_disclaimer.md'),
webBasedProducts: () => generateCategorySection('Web-based products', readYaml()['web based products']),
operatingSystems: () => generateCategorySection('Operating systems', readYaml()['operating systems']),
desktopApps: () => generateCategorySection('Desktop apps', readYaml()['desktop applications']),
mobileApps: () => generateCategorySection('Mobile apps', readYaml()['mobile applications']),
hardware: () => generateCategorySection('Hardware', readYaml()['hardware']),
useful: () => '# Useful links, tools, and advice',
resources: () => readFile('md/_resources.md'),
books: () => generatePublications('Books', 'books'),
blogs: () => generatePublications('Blog posts', 'blogs'),
news: () => generatePublications('News articles', 'news'),
lighterSide: () => readFile('md/_lighterSide.md'),
closingRemarks: () => readFile('md/_closingRemarks.md')
};
// Button that brings the user to the top of the page
const BACK_TO_TOP = '[![Back to top](https://img.shields.io/badge/Back%20to%20top-lightgrey?style=flat-square)](#index)';
/**
* Synchronously reads a file using fs-extra and path.join()
* @param {String} filename The file to read
*/
function readFile(filename) {
return fs.readFileSync(path.join(__dirname, filename)).toString();
}
/**
* Reads degoogle.yml
*/
function readYaml() {
return YAML.parse(fs.readFileSync(path.join(__dirname, 'yaml/degoogle.yml')).toString());
}
/**
* Generates a major section or "category" such as Mobile Apps
* @param {String} header Title for section
* @param {Object} data Object of data to populate README.md with
*/
function generateCategorySection(header, data) {
if (!data) return '';
return `## ${header}${EOL}${BACK_TO_TOP}${EOL}${EOL}`.concat(Object.values(data).map((value) => `${generateServiceSection(value)}${EOL}${EOL}`).join(''))
}
/**
* Generates a service (such as Gmail) section to be placed under a category section
* @param {Array} data
*/
function generateServiceSection(data) {
// Start the section with an
header
let serviceSection = `#### ${data[0].title + EOL + EOL}`;
// Prep section notes
let notes = EOL + '';
// If there is data to be displayed, add the start of a Markdown table
const tableHeader = (data.filter((d) => 'name' in d).length === 0)
? `No known alternatives.${EOL}`
: `| Name | Eyes | Description |${EOL}| ---- | ---- | ----------- |${EOL}`;
// Add the header to the section body
serviceSection = serviceSection.concat(tableHeader);
// Iterate over each alternative service and add it to the table
data.forEach((item) => {
// If the object has length one, it's either title or note
if (Object.keys(item).length == 1) {
if (!item.notes) return;
else item.notes.forEach((note) => notes = notes.concat(`- *${note.trim()}*${EOL}`));
} else {
// Build the cells for the table
let name = `[${item.name}](${item.url})`;
let eyes = item.eyes ? `**${item.eyes}-eyes**` : '';
let text = item.text.trim();
// Append the F-Droid badge to the name
if (item.fdroid) name = name.concat('
', fdroidLink(item.fdroid));
// Append the Repo badge to the name
if (item.repo) name = name.concat('
', repoLink(item.repo));
// Append the Star badge to the name
if (item.repo) name = name.concat('
', starBadge(item.repo));
// Build the row
const tableItem = `| ${name} | ${eyes} | ${text} |${EOL}`;
// Add the row to the table
serviceSection = serviceSection.concat(tableItem);
}
});
return `${serviceSection}${notes}`;
}
/**
* Returns a badge acting as a link to an F-Droid page for an app.
* @param {String} appId The package identifier on F-Droid
*/
function fdroidLink(appId) {
return `[![F-Droid](https://img.shields.io/f-droid/v/${appId}?style=flat-square&logo=f-droid)](https://f-droid.org/en/packages/${appId}/)`;
}
/**
* Returns a badge acting as a link to a source repository for an app.
* @param {String} repo The repository url
*/
function repoLink(repo) {
const repoURL = new URL(repo);
let repoHost = path.basename(repoURL.hostname, path.extname(repoURL.hostname));
if (repoHost.includes(".")) repoHost = path.extname(repoHost).replace(".", "");
return `[![Repo](https://img.shields.io/badge/open-source-3DA639?style=flat-square&logo=${repoHost})](${repo})`;
}
/**
* Returns a badge displaying the number of GitHub Stars for a repository.
* @param {String} repo The repository url
*/
function starBadge(repo) {
if (repo.startsWith('https://github.com/')) {
const [user, repoName] = repo.split('github.com/')[1].split('/');
if (!repoName || repoName === '') return '';
return `![GitHub Repo stars](https://img.shields.io/github/stars/${user}/${repoName}?logo=github&style=flat-square)`;
} else return '';
}
/**
* Returns a badge displaying user for a Firefox addon/extension
* @param {String} link URL to extension WITHOUT trailing slash
*/
function addonLink(link) {
return (link.includes('addons.mozilla.org')) ? `![Mozilla Add-on](https://img.shields.io/amo/users/${link.split('/').pop()}?style=flat-square)` : '';
}
/**
* Returns a badge with the date of publication
* @param {String|Number} date Date of publication
*/
function dateBadge(date) {
return `![Published](https://img.shields.io/badge/${date.toString().replace(/\-/g, '--')}-informational?style=flat-square)`;
}
/**
* Generates a table with browser extensions and their descriptions
*/
function generateBrowserExtensions() {
return `# Browser extensions${EOL + EOL}| Name | Description |${EOL}| ---- | ----------- |${EOL}`
.concat(YAML.parse(fs.readFileSync(path.join(__dirname, 'yaml/browserExtensions.yml')).toString())
.map(({ name, text, url }) => `| [${name}](${url}) ${addonLink(url)} | ${text.trim()} |${EOL}`).join(''));
}
/**
* Generates sections for Books, Blogs, and News
* @param {String} pubTitle
* @param {String} filename
*/
function generatePublications(pubTitle, filename) {
return `## ${pubTitle} ${EOL + BACK_TO_TOP + EOL + EOL}| Title | Published | Author |${EOL}| ----- | --------- | ------ |${EOL}`
.concat(YAML.parse(fs.readFileSync(path.join(__dirname, `yaml/${filename}.yml`)).toString())
.map(({ title, url, date, author }) => `| [${title}](${url}) | ${dateBadge(date)} | ${author.trim()} |${EOL}`).join(''));
}
// ! Generate README.md
fs.writeFileSync(path.join(__dirname, 'README.md'), Object.values(BUILD_SECTION).map((section) => section()).join(`${EOL}${EOL}`));
console.log('Done!');