2
0
Fork 0
mirror of https://github.com/remoteintech/remote-jobs synced 2025-01-22 17:15:28 +00:00

Fix formatting changes previously made

This commit is contained in:
Sandy McFadden 2022-02-04 21:24:39 -04:00
parent 745db9c291
commit cce5c06fae

View file

@ -1,164 +1,169 @@
#!/usr/bin/env node #!/usr/bin/env node
const fs = require("fs"); const fs = require( 'fs' );
const path = require("path"); const path = require( 'path' );
const util = require("util"); const util = require( 'util' );
const cheerio = require( 'cheerio' );
const lunr = require( 'lunr' );
const marked = require( 'marked' );
const cheerio = require("cheerio");
const lunr = require("lunr");
const marked = require("marked");
/** /**
* Constants * Constants
*/ */
const headingsRequired = ["Company blurb"]; const headingsRequired = [
const headingsOptional = [ 'Company blurb',
"Company size",
"Remote status",
"Region",
"Company technologies",
"Office locations",
"How to apply",
]; ];
const headingsAll = headingsRequired.concat(headingsOptional); const headingsOptional = [
'Company size',
'Remote status',
'Region',
'Company technologies',
'Office locations',
'How to apply',
];
const headingsAll = headingsRequired.concat( headingsOptional );
/** /**
* Utility functions * Utility functions
*/ */
function companyNameToProfileFilename(companyName) { function companyNameToProfileFilename( companyName ) {
return companyName return companyName.toLowerCase()
.toLowerCase() .replace( /&/g, ' and ' )
.replace(/&/g, " and ") .replace( /'/g, '' )
.replace(/'/g, "") .replace( /[^a-z0-9]+/gi, '-' )
.replace(/[^a-z0-9]+/gi, "-") .replace( /^-|-$/g, '' );
.replace(/^-|-$/g, "");
} }
exports.companyNameToProfileFilename = companyNameToProfileFilename; exports.companyNameToProfileFilename = companyNameToProfileFilename;
// adapted from https://gist.github.com/RandomEtc/2657669 // adapted from https://gist.github.com/RandomEtc/2657669
function jsonStringifyUnicodeEscaped(obj) { function jsonStringifyUnicodeEscaped( obj ) {
return JSON.stringify(obj).replace(/[\u007f-\uffff]/g, (c) => { return JSON.stringify( obj ).replace( /[\u007f-\uffff]/g, c => {
return "\\u" + ("0000" + c.charCodeAt(0).toString(16)).slice(-4); return '\\u' + ( '0000' + c.charCodeAt( 0 ).toString( 16 ) ).slice( -4 );
}); } );
} }
exports.jsonStringifyUnicodeEscaped = jsonStringifyUnicodeEscaped; exports.jsonStringifyUnicodeEscaped = jsonStringifyUnicodeEscaped;
function toIdentifierCase(text) { function toIdentifierCase( text ) {
return text return text
.replace(/'/g, "") .replace( /'/g, '' )
.replace(/[^a-z0-9]+/gi, " ") .replace( /[^a-z0-9]+/gi, ' ' )
.trim() .trim()
.split(/\s+/) .split( /\s+/ )
.map((word, i) => { .map( ( word, i ) => {
if (i === 0) { if ( i === 0 ) {
return word.toLowerCase(); return word.toLowerCase();
} }
return word.substr(0, 1).toUpperCase() + word.substr(1).toLowerCase(); return (
}) word.substr( 0, 1 ).toUpperCase()
.join(""); + word.substr( 1 ).toLowerCase()
);
} )
.join( '' );
} }
exports.toIdentifierCase = toIdentifierCase; exports.toIdentifierCase = toIdentifierCase;
function stripExtraChars(text) { function stripExtraChars( text ) {
return text.replace(/\ufe0f/g, ""); return text.replace( /\ufe0f/g, '' );
} }
exports.stripExtraChars = stripExtraChars; exports.stripExtraChars = stripExtraChars;
/** /**
* Other exports * Other exports
*/ */
function getHeadingPropertyNames() { function getHeadingPropertyNames() {
return headingsAll.reduce((acc, val) => { return headingsAll.reduce( ( acc, val ) => {
acc[toIdentifierCase(val)] = val; acc[ toIdentifierCase( val ) ] = val;
return acc; return acc;
}, {}); }, {} );
} }
exports.headingPropertyNames = getHeadingPropertyNames(); exports.headingPropertyNames = getHeadingPropertyNames();
/** /**
* The main exported function * The main exported function
* *
* Start with a directory including a README.md and company-profiles/*.md * Start with a directory including a README.md and company-profiles/*.md
* files, and validate and parse the content of the Markdown files. * files, and validate and parse the content of the Markdown files.
*/ */
exports.parseFromDirectory = (contentPath) => { exports.parseFromDirectory = contentPath => {
const companyNamesSeen = {}; const companyNamesSeen = {};
let errors = []; let errors = [];
function error(filename, msg, ...params) { function error( filename, msg, ...params ) {
errors.push({ errors.push( {
filename, filename,
message: util.format(msg, ...params), message: util.format( msg, ...params ),
}); } );
} }
// Build list of Markdown files containing company profiles. // Build list of Markdown files containing company profiles.
const profilesPath = path.join(contentPath, "company-profiles"); const profilesPath = path.join( contentPath, 'company-profiles' );
const profileFilenames = fs.readdirSync(profilesPath); const profileFilenames = fs.readdirSync( profilesPath );
// Scan the company table in the readme. // Scan the company table in the readme.
const readmeCompanies = []; const readmeCompanies = [];
const readmeMarkdown = stripExtraChars( const readmeMarkdown = stripExtraChars( fs.readFileSync(
fs.readFileSync(path.join(contentPath, "README.md"), "utf8") path.join( contentPath, 'README.md' ),
); 'utf8'
) );
let inTable = false; let inTable = false;
readmeMarkdown.split("\n").forEach((line) => { readmeMarkdown.split( '\n' ).forEach( line => {
if (/^\s*-+\s*\|\s*-+\s*\|\s*-+\s*$/.test(line)) { if ( /^\s*-+\s*\|\s*-+\s*\|\s*-+\s*$/.test( line ) ) {
inTable = true; inTable = true;
} else if (/^\s*$/.test(line)) { } else if ( /^\s*$/.test( line ) ) {
inTable = false; inTable = false;
} else if (inTable) { } else if ( inTable ) {
const fields = line.split("|"); const fields = line.split( '|' );
if (fields.length !== 3) { if ( fields.length !== 3 ) {
readmeError( readmeError(
"Expected 3 table cells but found %d: %s", 'Expected 3 table cells but found %d: %s',
fields.length, fields.length,
line line
); );
} }
} }
}); } );
const $ = cheerio.load(marked.parse(readmeMarkdown)); const $ = cheerio.load( marked.parse( readmeMarkdown ) );
function readmeError(msg, ...params) { function readmeError( msg, ...params ) {
error("README.md", msg, ...params); error( 'README.md', msg, ...params );
} }
const mainUrl = "remoteintech.company"; const mainUrl = 'remoteintech.company'
function addTargetBlankAndExternalLinkIcons(el) { function addTargetBlankAndExternalLinkIcons (el) {
if (el.type === "tag") { if (el.type === 'tag') {
const anchorTagElements = el.children.filter( const anchorTagElements = el.children.filter(element => element.name === 'a')
(element) => element.name === "a"
);
if (anchorTagElements.length > 0) { if (anchorTagElements.length > 0) {
anchorTagElements.forEach((element) => { anchorTagElements.forEach(element => {
const url = element.attribs.href; const url = element.attribs.href
const urlInfo = getUrlInfo(url); const urlInfo = getUrlInfo(url)
if (urlInfo.is_email || urlInfo.is_internal) { if (urlInfo.is_email || urlInfo.is_internal) {
return; return
} }
element.attribs.target = "_blank"; element.attribs.target = '_blank'
$element = $(element); $element = $( element )
$element.append( $element.append('<span style="vertical-align: text-top;"> <img src="/assets/external-link.svg" /> </span>')
'<span style="vertical-align: text-top;"> <img src="/assets/external-link.svg" /> </span>' })
);
});
} }
if (el.children && el.children.length) { if (el.children && el.children.length) {
el.children.forEach((element) => { el.children.forEach(element => {
addTargetBlankAndExternalLinkIcons(element); addTargetBlankAndExternalLinkIcons(element)
}); })
} }
} }
} }
@ -167,110 +172,109 @@ exports.parseFromDirectory = (contentPath) => {
* Getting info about the url. It includes checking isEmail of isInternal * Getting info about the url. It includes checking isEmail of isInternal
* @param {*} url * @param {*} url
*/ */
function getUrlInfo(url) { function getUrlInfo (url) {
const data = {}; const data = {}
if (url.match(/^mailto:/)) { if (url.match(/^mailto:/)) { // checking url email or not
// checking url email or not data.is_email = true
data.is_email = true; return data
return data;
} }
const mainDomainFromGivenUrl = extractMainDomainFromUrl(url); const mainDomainFromGivenUrl = extractMainDomainFromUrl(url)
// checking url is email or not // checking url is email or not
if (mainDomainFromGivenUrl !== mainUrl) { if (mainDomainFromGivenUrl !== mainUrl) {
data.is_internal = false; data.is_internal = false
return data; return data
} else { } else {
data.is_internal = true; data.is_internal = true
} }
return data; return data
} }
/** /**
* Extracting main domain from the url * Extracting main domain from the url
* @param {*} url * @param {*} url
*/ */
function extractMainDomainFromUrl(url) { function extractMainDomainFromUrl (url) {
const domainRe = /(https?:\/\/){0,1}((?:[\w\d-]+\.)+[\w\d]{2,})/i; // taken example from https://stackoverflow.com/questions/6238351/fastest-way-to-detect-external-urls const domainRe = /(https?:\/\/){0,1}((?:[\w\d-]+\.)+[\w\d]{2,})/i; // taken example from https://stackoverflow.com/questions/6238351/fastest-way-to-detect-external-urls
const data = domainRe.exec(url); const data = domainRe.exec(url)
const splittedDomain = data[2].split("."); const splittedDomain = data[2].split('.')
if (splittedDomain.length === 2) { if (splittedDomain.length === 2) { // check extra subdomain is present or not
// check extra subdomain is present or not return data[2]
return data[2];
} }
return ( return splittedDomain[splittedDomain.length - 2] + '.' + splittedDomain[splittedDomain.length - 1] // return only main domain address
splittedDomain[splittedDomain.length - 2] +
"." +
splittedDomain[splittedDomain.length - 1]
); // return only main domain address
} }
let lastCompanyName = null; let lastCompanyName = null;
$("tr").each((i, tr) => { $( 'tr' ).each( ( i, tr ) => {
const $tr = $(tr); const $tr = $( tr );
if (i === 0) { if ( i === 0 ) {
// Assign an ID to the table. // Assign an ID to the table.
$tr.closest("table").attr("id", "companies-table"); $tr.closest( 'table' ).attr( 'id', 'companies-table' );
// Skip the table header row. // Skip the table header row.
return; return;
} }
const $td = $tr.children("td"); const $td = $tr.children( 'td' );
const websiteUrl = $td.eq(1).text(); const websiteUrl = $td.eq( 1 ).text();
const websiteText = websiteUrl const websiteText = websiteUrl
.replace(/^https?:\/\//, "") .replace( /^https?:\/\//, '' )
.replace(/^www\./, "") .replace( /^www\./, '' )
.replace(/\/$/, ""); .replace( /\/$/, '' );
const readmeEntry = { const readmeEntry = {
// Strip out warning emoji indicating that this profile is incomplete // Strip out warning emoji indicating that this profile is incomplete
name: $td name: $td.eq( 0 ).text().replace( /\u26a0/, '' ).trim(),
.eq(0)
.text()
.replace(/\u26a0/, "")
.trim(),
// Detect warning emoji next to company name // Detect warning emoji next to company name
isIncomplete: /\u26a0/.test($td.eq(0).text()), isIncomplete: /\u26a0/.test( $td.eq( 0 ).text() ),
websiteUrl, websiteUrl,
websiteText, websiteText,
shortRegion: $td.eq(2).text().trim(), shortRegion: $td.eq( 2 ).text().trim(),
}; };
if (!websiteText) { if ( ! websiteText ) {
readmeError("Missing website for company: %s", readmeEntry.name);
}
if (readmeEntry.name) {
if (companyNamesSeen[readmeEntry.name.toLowerCase()]) {
readmeError("Duplicate company: %s", readmeEntry.name);
}
companyNamesSeen[readmeEntry.name.toLowerCase()] = true;
} else {
readmeError("Missing company name: %s", $tr.html().replace(/\n/g, ""));
}
if (
$td.eq(1).children().length !== 1 ||
!$td.eq(1).children().eq(0).is("a")
) {
readmeError( readmeError(
"Invalid content in Website column: %s", 'Missing website for company: %s',
$tr.html().replace(/\n/g, "") readmeEntry.name
); );
} }
if ($td.eq(2).children().length > 0) { if ( readmeEntry.name ) {
if ( companyNamesSeen[ readmeEntry.name.toLowerCase() ] ) {
readmeError( readmeError(
"Extra content in Region column: %s", 'Duplicate company: %s',
$tr.html().replace(/\n/g, "") readmeEntry.name
);
}
companyNamesSeen[ readmeEntry.name.toLowerCase() ] = true;
} else {
readmeError(
'Missing company name: %s',
$tr.html().replace( /\n/g, '' )
);
}
if (
$td.eq( 1 ).children().length !== 1 ||
! $td.eq( 1 ).children().eq( 0 ).is( 'a' )
) {
readmeError(
'Invalid content in Website column: %s',
$tr.html().replace( /\n/g, '' )
);
}
if ( $td.eq( 2 ).children().length > 0 ) {
readmeError(
'Extra content in Region column: %s',
$tr.html().replace( /\n/g, '' )
); );
} }
@ -286,119 +290,123 @@ exports.parseFromDirectory = (contentPath) => {
} }
lastCompanyName = readmeEntry.name; lastCompanyName = readmeEntry.name;
const $profileLink = $td.eq(0).find("a"); const $profileLink = $td.eq( 0 ).find( 'a' );
if ($profileLink.length === 1) { if ( $profileLink.length === 1 ) {
const match = $profileLink const match = $profileLink.attr( 'href' ).match( /^\/company-profiles\/(.*\.md)$/ );
.attr("href")
.match(/^\/company-profiles\/(.*\.md)$/);
if (match) { if ( match ) {
readmeEntry.linkedFilename = match[1]; readmeEntry.linkedFilename = match[ 1 ];
if (profileFilenames.indexOf(readmeEntry.linkedFilename) === -1) { if ( profileFilenames.indexOf( readmeEntry.linkedFilename ) === -1 ) {
readmeError( readmeError(
'Missing company profile for "%s", or broken link: "%s"', 'Missing company profile for "%s", or broken link: "%s"',
readmeEntry.name, readmeEntry.name,
$profileLink.attr("href") $profileLink.attr( 'href' )
); );
} }
const nameCheck = $profileLink.text().trim(); const nameCheck = $profileLink.text().trim();
if (nameCheck !== readmeEntry.name) { if ( nameCheck !== readmeEntry.name ) {
readmeError( readmeError(
"Extra text in company name: %s, %s", 'Extra text in company name: %s, %s',
jsonStringifyUnicodeEscaped(nameCheck), jsonStringifyUnicodeEscaped( nameCheck ),
jsonStringifyUnicodeEscaped(readmeEntry.name) jsonStringifyUnicodeEscaped( readmeEntry.name )
); );
} }
} else { } else {
readmeError( readmeError(
'Invalid link to company profile for "%s": "%s"', 'Invalid link to company profile for "%s": "%s"',
readmeEntry.name, readmeEntry.name,
$profileLink.attr("href") $profileLink.attr( 'href' )
); );
} }
} else { } else {
readmeError( readmeError(
'Company "%s" has no linked Markdown profile ("/company-profiles/%s.md")', 'Company "%s" has no linked Markdown profile ("/company-profiles/%s.md")',
readmeEntry.name, readmeEntry.name,
companyNameToProfileFilename(readmeEntry.name) companyNameToProfileFilename( readmeEntry.name )
); );
} }
// Set identifying attributes on table elements // Set identifying attributes on table elements
$tr.attr("class", "company-row").attr("id", "company-row-" + (i - 1)); $tr
$td.eq(0).attr("class", "company-name"); .attr( 'class', 'company-row' )
$td.eq(1).attr("class", "company-website"); .attr( 'id', 'company-row-' + ( i - 1 ) );
$td.eq(2).attr("class", "company-region"); $td.eq( 0 ).attr( 'class', 'company-name' );
$td.eq( 1 ).attr( 'class', 'company-website' );
$td.eq( 2 ).attr( 'class', 'company-region' );
// Rewrite company profile link to the correct URL for the static site // Rewrite company profile link to the correct URL for the static site
if ($profileLink.length) { if ( $profileLink.length ) {
$profileLink.attr( $profileLink.attr(
"href", 'href',
$profileLink $profileLink.attr( 'href' )
.attr("href") .replace( /^\/company-profiles\//, '/' )
.replace(/^\/company-profiles\//, "/") .replace( /\.md$/, '/' )
.replace(/\.md$/, "/")
); );
} }
// Rewrite external website link (target="_blank" etc, shorter text) // Rewrite external website link (target="_blank" etc, shorter text)
const $websiteLink = $td.eq(1).children().eq(0); const $websiteLink = $td.eq( 1 ).children().eq( 0 );
$websiteLink $websiteLink
.attr("target", "_blank") .attr( 'target', '_blank' )
.attr("rel", "noopener noreferrer") .attr( 'rel', 'noopener noreferrer' )
.text(websiteText); .text( websiteText );
readmeCompanies.push(readmeEntry); readmeCompanies.push( readmeEntry );
}); } );
const readmeContent = $("body").html(); const readmeContent = $( 'body' ).html();
// Scan the individual Markdown files containing the company profiles. // Scan the individual Markdown files containing the company profiles.
const allProfileHeadings = {}; const allProfileHeadings = {};
profileFilenames.forEach((filename) => { profileFilenames.forEach( filename => {
function profileError(msg, ...params) { function profileError( msg, ...params ) {
error(filename, msg, ...params); error( filename, msg, ...params );
} }
const profileMarkdown = stripExtraChars( const profileMarkdown = stripExtraChars( fs.readFileSync(
fs.readFileSync(path.join(profilesPath, filename), "utf8") path.join( profilesPath, filename ),
); 'utf8'
const $ = cheerio.load(marked.parse(profileMarkdown)); ) );
const $ = cheerio.load( marked.parse( profileMarkdown ) );
let hasTitleError = false; let hasTitleError = false;
if ($("h1").length !== 1) { if ( $( 'h1' ).length !== 1 ) {
profileError( profileError(
"Expected 1 first-level heading but found %d", 'Expected 1 first-level heading but found %d',
$("h1").length $( 'h1' ).length
); );
hasTitleError = true; hasTitleError = true;
} }
if (!$("h1").parent().is("body")) { if ( ! $( 'h1' ).parent().is( 'body' ) ) {
profileError("The main title is wrapped inside of another element."); profileError(
'The main title is wrapped inside of another element.'
);
} }
const companyName = $("h1").text(); const companyName = $( 'h1' ).text();
if (!/[a-z]/i.test(companyName)) { if ( ! /[a-z]/i.test( companyName ) ) {
profileError('Company name looks wrong: "%s"', companyName); profileError(
'Company name looks wrong: "%s"',
companyName
);
hasTitleError = true; hasTitleError = true;
} }
const filenameBase = filename.replace(/\.md$/, ""); const filenameBase = filename.replace( /\.md$/, '' );
const filenameExpected = companyNameToProfileFilename(companyName); const filenameExpected = companyNameToProfileFilename( companyName );
if ( if (
!hasTitleError && ! hasTitleError &&
filenameBase !== filenameExpected && filenameBase !== filenameExpected &&
// Some profile files just have shorter names than the company name, // Some profile files just have shorter names than the company name,
// which is fine. // which is fine.
filenameExpected.substring(0, filenameBase.length + 1) !== filenameExpected.substring( 0, filenameBase.length + 1 ) !== filenameBase + '-'
filenameBase + "-"
) { ) {
profileError( profileError(
'Company title "%s" doesn\'t match filename (expected ~ "%s.md")', 'Company title "%s" doesn\'t match filename (expected ~ "%s.md")',
@ -408,177 +416,177 @@ exports.parseFromDirectory = (contentPath) => {
} }
const readmeEntry = readmeCompanies.find( const readmeEntry = readmeCompanies.find(
(readmeEntry) => readmeEntry.linkedFilename === filename readmeEntry => readmeEntry.linkedFilename === filename
); );
if (filename !== "example.md" && !readmeEntry) { if ( filename !== 'example.md' && ! readmeEntry ) {
profileError("No link to company profile from readme"); profileError( 'No link to company profile from readme' );
} }
// Build and validate list of headings contained in this Markdown profile. // Build and validate list of headings contained in this Markdown profile.
const profileHeadings = []; const profileHeadings = [];
$("h2").each((i, el) => { $( 'h2' ).each( ( i, el ) => {
const headingName = $(el).html(); const headingName = $( el ).html();
if (!$(el).parent().is("body")) { if ( ! $( el ).parent().is( 'body' ) ) {
profileError( profileError(
'The section heading for "%s" is wrapped inside of another element.', 'The section heading for "%s" is wrapped inside of another element.',
headingName headingName
); );
} }
if (profileHeadings.indexOf(headingName) >= 0) { if ( profileHeadings.indexOf( headingName ) >= 0 ) {
profileError('Duplicate section: "%s".', headingName); profileError(
'Duplicate section: "%s".',
headingName
);
} else { } else {
// Track headings for this profile // Track headings for this profile
profileHeadings.push(headingName); profileHeadings.push( headingName );
// Track heading counts across all profiles // Track heading counts across all profiles
if (!allProfileHeadings[headingName]) { if ( ! allProfileHeadings[ headingName ] ) {
allProfileHeadings[headingName] = []; allProfileHeadings[ headingName ] = [];
} }
allProfileHeadings[headingName].push(filename); allProfileHeadings[ headingName ].push( filename );
} }
if (headingsAll.indexOf(headingName) === -1) { if ( headingsAll.indexOf( headingName ) === -1 ) {
profileError( profileError(
'Invalid section: "%s". Expected one of: %s', 'Invalid section: "%s". Expected one of: %s',
headingName, headingName,
JSON.stringify(headingsAll) JSON.stringify( headingsAll )
); );
} }
}); } );
headingsRequired.forEach((headingName) => { headingsRequired.forEach( headingName => {
if (profileHeadings.indexOf(headingName) === -1) { if ( profileHeadings.indexOf( headingName ) === -1 ) {
profileError('Required section "%s" not found.', headingName); profileError(
'Required section "%s" not found.',
headingName
);
} }
}); } );
// Build and validate the content of each section in this profile. // Build and validate the content of each section in this profile.
const profileContent = {}; const profileContent = {};
if (readmeEntry) { if ( readmeEntry ) {
readmeEntry.profileContent = profileContent; readmeEntry.profileContent = profileContent;
} }
let currentHeading = null; let currentHeading = null;
$("body") $( 'body' ).children().each( ( i, el ) => {
.children() const $el = $( el );
.each((i, el) => {
const $el = $(el);
if ($el.is("h1")) { if ( $el.is( 'h1' ) ) {
return; return;
} }
if ($el.is("h2")) { if ( $el.is( 'h2' ) ) {
currentHeading = $el.html(); currentHeading = $el.html();
profileContent[currentHeading] = ""; profileContent[ currentHeading ] = '';
} else if (currentHeading) { } else if ( currentHeading ) {
// Note: This assumes that the only possible children of the // Note: This assumes that the only possible children of the
// 'body' are block-level elements. I think this is correct, // 'body' are block-level elements. I think this is correct,
// because from what I've seen, any inline content is wrapped // because from what I've seen, any inline content is wrapped
// in a <p>. // in a <p>.
addTargetBlankAndExternalLinkIcons(el); addTargetBlankAndExternalLinkIcons(el)
profileContent[currentHeading] = ( profileContent[ currentHeading ] = (
profileContent[currentHeading] + profileContent[ currentHeading ]
"\n\n" + + '\n\n' + $.html( el )
$.html(el)
).trim(); ).trim();
} else { } else {
profileError( profileError(
"Content is not part of any section: %s", 'Content is not part of any section: %s',
$.html(el).replace(/\n/g, "") $.html( el ).replace( /\n/g, '' )
); );
} }
}); } );
Object.keys(profileContent).forEach((heading) => { Object.keys( profileContent ).forEach( heading => {
const sectionText = profileContent[heading] const sectionText = profileContent[ heading ]
.replace(/<[^>]+>/g, "") .replace( /<[^>]+>/g, '' )
.trim(); .trim();
if (!sectionText) { if ( ! sectionText ) {
profileError( profileError(
'Empty section: "%s". Fill it in or leave it out instead.', 'Empty section: "%s". Fill it in or leave it out instead.',
heading heading
); );
} }
}); } );
// Rewrite profile content to use more code-friendly heading names. // Rewrite profile content to use more code-friendly heading names.
Object.keys(profileContent).forEach((headingName) => { Object.keys( profileContent ).forEach( headingName => {
const headingIdentifier = toIdentifierCase(headingName); const headingIdentifier = toIdentifierCase( headingName );
profileContent[headingIdentifier] = profileContent[headingName]; profileContent[ headingIdentifier ] = profileContent[ headingName ];
delete profileContent[headingName]; delete profileContent[ headingName ];
}); } );
if (readmeEntry && profileContent.companyBlurb) { if ( readmeEntry && profileContent.companyBlurb ) {
// Check for company profiles that were filled in, but the "incomplete" // Check for company profiles that were filled in, but the "incomplete"
// mark was left in the readme, or vice versa. // mark was left in the readme, or vice versa.
const isIncomplete = { const isIncomplete = {
readme: readmeEntry.isIncomplete, readme: readmeEntry.isIncomplete,
sections: sections: (
profileHeadings.length === 1 && profileHeadings.length === 1 &&
profileHeadings[0] === "Company blurb", profileHeadings[ 0 ] === 'Company blurb'
content: /&#x26A0;/.test(profileContent.companyBlurb), ),
content: /&#x26A0;/.test( profileContent.companyBlurb ),
}; };
const incompleteCount = Object.values(isIncomplete).reduce( const incompleteCount = Object.values( isIncomplete )
(sum, v) => sum + (v ? 1 : 0), .reduce( ( sum, v ) => sum + ( v ? 1 : 0 ), 0 );
0
);
// incompleteCount === 0: Profile is incomplete; all 3 indicators are consistent // incompleteCount === 0: Profile is incomplete; all 3 indicators are consistent
// incompleteCount === 3: Profile is "complete"; all 3 indicators are consistent // incompleteCount === 3: Profile is "complete"; all 3 indicators are consistent
if (incompleteCount === 1) { if ( incompleteCount === 1 ) {
if (isIncomplete.readme) { if ( isIncomplete.readme ) {
profileError( profileError(
"Profile looks complete, but the main readme contains a warning emoji." 'Profile looks complete, but the main readme contains a warning emoji.'
); );
} else if (isIncomplete.sections) { } else if ( isIncomplete.sections ) {
profileError( profileError(
'Profile is marked as complete, but it only contains a "Company blurb" heading.' 'Profile is marked as complete, but it only contains a "Company blurb" heading.'
); )
} else { } else { // isIncomplete.content
// isIncomplete.content
profileError( profileError(
'Profile looks complete, but the "Company blurb" contains a warning emoji.' 'Profile looks complete, but the "Company blurb" contains a warning emoji.'
); );
} }
} else if (incompleteCount === 2) { } else if ( incompleteCount === 2 ) {
if (!isIncomplete.readme) { if ( ! isIncomplete.readme ) {
profileError( profileError(
"Profile looks incomplete, but the main readme does not contain a warning emoji." 'Profile looks incomplete, but the main readme does not contain a warning emoji.'
); );
} else if (!isIncomplete.sections) { } else if ( ! isIncomplete.sections ) {
profileError( profileError(
"Profile is marked as incomplete, but it contains multiple sections." + 'Profile is marked as incomplete, but it contains multiple sections.'
'\nPlease remove the warning emoji from the "Company blurb" section and the main readme.' + '\nPlease remove the warning emoji from the "Company blurb" section and the main readme.'
); )
} else { } else { // ! isIncomplete.content
// ! isIncomplete.content
profileError( profileError(
'Profile looks incomplete, but the "Company blurb" does not contain a warning emoji.' 'Profile looks incomplete, but the "Company blurb" does not contain a warning emoji.'
); );
} }
} }
} }
}); } );
const profileHeadingCounts = {}; const profileHeadingCounts = {};
Object.keys(allProfileHeadings).forEach((heading) => { Object.keys( allProfileHeadings ).forEach( heading => {
profileHeadingCounts[heading] = allProfileHeadings[heading].length; profileHeadingCounts[ heading ] = allProfileHeadings[ heading ].length;
}); } );
if (errors.length > 0) { if ( errors.length > 0 ) {
return { return {
ok: false, ok: false,
errors, errors,
profileFilenames, profileFilenames,
profileHeadingCounts, profileHeadingCounts,
}; }
} }
return { return {
@ -593,63 +601,61 @@ exports.parseFromDirectory = (contentPath) => {
/** /**
* Build search index data from the result of parseFromDirectory(). * Build search index data from the result of parseFromDirectory().
*/ */
exports.buildSearchData = (data) => { exports.buildSearchData = data => {
const textData = []; const textData = [];
data.companies.forEach((company, i) => { data.companies.forEach( ( company, i ) => {
const thisTextData = { const thisTextData = {
id: String(i), id: String( i ),
nameText: company.name, nameText: company.name,
websiteText: company.websiteText, websiteText: company.websiteText,
}; };
if (company.shortRegion) { if ( company.shortRegion ) {
thisTextData.shortRegion = company.shortRegion; thisTextData.shortRegion = company.shortRegion;
} }
Object.keys(exports.headingPropertyNames).forEach((h) => { Object.keys( exports.headingPropertyNames ).forEach( h => {
if (company.profileContent[h]) { if ( company.profileContent[ h ] ) {
const text = cheerio const text = cheerio.load( company.profileContent[ h ] ).text()
.load(company.profileContent[h])
.text()
// Replace warning emoji with a searchable token // Replace warning emoji with a searchable token
.replace(/\u26a0/, "(_incomplete)"); .replace( /\u26a0/, '(_incomplete)' );
thisTextData[h] = text; thisTextData[ h ] = text;
} }
}); } );
textData.push(thisTextData); textData.push( thisTextData );
}); } );
const index = lunr(function () { const index = lunr( function() {
this.field("nameText"); this.field( 'nameText' );
this.field("websiteText"); this.field( 'websiteText' );
this.field("shortRegion"); this.field( 'shortRegion' );
Object.keys(exports.headingPropertyNames).forEach((h) => { Object.keys( exports.headingPropertyNames ).forEach( h => {
this.field(h); this.field( h );
}); } );
// https://github.com/olivernn/lunr.js/issues/25#issuecomment-623267494 // https://github.com/olivernn/lunr.js/issues/25#issuecomment-623267494
this.metadataWhitelist = ["position"]; this.metadataWhitelist = ['position'];
// https://github.com/olivernn/lunr.js/issues/192#issuecomment-172915226 // https://github.com/olivernn/lunr.js/issues/192#issuecomment-172915226
// https://gist.github.com/olivernn/7cd496f8654a0246c53c // https://gist.github.com/olivernn/7cd496f8654a0246c53c
function contractionTrimmer(token) { function contractionTrimmer( token ) {
return token.update((str) => { return token.update( str => {
return str.replace(/('m|'ve|n't|'d|'ll|'ve|'s|'re)$/, ""); return str.replace( /('m|'ve|n't|'d|'ll|'ve|'s|'re)$/, '' );
}); } );
} }
lunr.Pipeline.registerFunction(contractionTrimmer, "contractionTrimmer"); lunr.Pipeline.registerFunction( contractionTrimmer, 'contractionTrimmer' );
this.pipeline.after(lunr.trimmer, contractionTrimmer); this.pipeline.after( lunr.trimmer, contractionTrimmer );
Object.keys(textData).forEach((c) => this.add(textData[c])); Object.keys( textData ).forEach( c => this.add( textData[ c ] ) );
}); } );
const headings = getHeadingPropertyNames(); const headings = getHeadingPropertyNames();
headings.nameText = "Company name"; headings.nameText = 'Company name';
headings.websiteText = "Website"; headings.websiteText = 'Website';
headings.shortRegion = "Region"; headings.shortRegion = 'Region';
return { index, textData, headings }; return { index, textData, headings };
}; };