// Build tool for generating README.md
const os = 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)';
/**
* Main method
*/
function __main__() {
// dgSectionData will be join at the end and represents the full contents of README.md
const dgSectionData = [];
// Add all the sections
dgSectionData.push(BUILD_SECTION.header());
dgSectionData.push(BUILD_SECTION.index());
dgSectionData.push(BUILD_SECTION.contributing());
dgSectionData.push(BUILD_SECTION.browserExtensions());
dgSectionData.push(BUILD_SECTION.disclaimer());
dgSectionData.push(BUILD_SECTION.webBasedProducts());
dgSectionData.push(BUILD_SECTION.operatingSystems());
dgSectionData.push(BUILD_SECTION.desktopApps());
dgSectionData.push(BUILD_SECTION.mobileApps());
dgSectionData.push(BUILD_SECTION.hardware());
dgSectionData.push(BUILD_SECTION.useful());
dgSectionData.push(BUILD_SECTION.resources());
dgSectionData.push(BUILD_SECTION.books());
dgSectionData.push(BUILD_SECTION.blogs());
dgSectionData.push(BUILD_SECTION.news());
dgSectionData.push(BUILD_SECTION.lighterSide());
dgSectionData.push(BUILD_SECTION.closingRemarks());
// Write to the README file
fs.writeFileSync(path.join(__dirname, 'README.md'), dgSectionData.join(`${os.EOL}${os.EOL}`));
console.log('Done!');
}
/**
* 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 '';
// Set the header to HTML
let categorySection = `## ${header}${os.EOL}${BACK_TO_TOP}${os.EOL}${os.EOL}`;
// Generate service sections for this category
Object.keys(data).forEach((key) => categorySection = categorySection.concat(`${generateServiceSection(data[key])}${os.EOL}${os.EOL}`));
return categorySection;
}
/**
* 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 + os.EOL + os.EOL}`;
// Prep section notes
let notes = os.EOL + '';
// If there is data to be displayed, add the start of a Markdown table
let tableHeader = `| Name | Eyes | Description |${os.EOL}| ---- | ---- | ----------- |${os.EOL}`;
if (data.filter((d) => 'name' in d).length === 0) tableHeader = `No known alternatives.${os.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()}*${os.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));
// Build the row
let tableItem = `| ${name} | ${eyes} | ${text} |${os.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) {
let 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 user for a Firefox addon/extension
* @param {String} link URL to extension WITHOUT trailing slash
*/
function addonLink(link) {
if (!link.includes('addons.mozilla.org')) return '';
let addonId = link.split('/')[link.split('/').length - 1];
return `![Mozilla Add-on](https://img.shields.io/amo/users/${addonId}?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() {
let extensions = `# Browser extensions${os.EOL + os.EOL}| Name | Description |${os.EOL}| ---- | ----------- |${os.EOL}`;
let data = YAML.parse(fs.readFileSync(path.join(__dirname, 'yaml/browserExtensions.yml')).toString());
data.forEach((item) => {
let name = `[${item.name}](${item.url})`;
let text = item.text.trim();
let badge = addonLink(item.url);
let tableItem = `| ${name + ' ' + badge} | ${text} |${os.EOL}`;
extensions = extensions.concat(tableItem);
});
return extensions;
}
/**
* Generates sections for Books, Blogs, and News
* @param {String} title
* @param {String} filename
*/
function generatePublications(title, filename) {
let publications = `## ${title} ${os.EOL + BACK_TO_TOP + os.EOL + os.EOL}| Title | Published | Author |${os.EOL}| ----- | --------- | ------ |${os.EOL}`;
let data = YAML.parse(fs.readFileSync(path.join(__dirname, `yaml/${filename}.yml`)).toString());
data.forEach((item) => {
let name = `[${item.title}](${item.url})`;
let author = item.author.trim();
let tableItem = `| ${name} | ${dateBadge(item.date)} | ${author} |${os.EOL}`;
publications = publications.concat(tableItem);
});
return publications;
}
__main__();