import {promises as fsp} from 'fs'; import {basename, join} from 'path'; import fecha from 'fecha'; // @ts-ignore import htmlclean from 'htmlclean'; import marked from 'marked'; import nunjucks from 'nunjucks'; import wordWrap from 'wordwrap'; import {generateFeeds} from './feeds'; import {OfficialTopic, getTopicsFromMonth} from './official-topics'; import {Statistics} from './statistics'; // Regular expressions taken and slightly adapted from Tildes' Markdown code: // https://gitlab.com/tildes/tildes/-/blob/89c7c13be2a7fc11d429ee1e340bf2079862eb98/tildes/tildes/lib/markdown.py#L285-296 const groupRegex = /\s(? { nunjucks.configure(join(__dirname, '../pages/templates/'), { lstripBlocks: true, throwOnUndefined: true, trimBlocks: true }); const dataDirectory: string = join(__dirname, '../pages/data/'); const statistics: Statistics[] = JSON.parse( await fsp.readFile(join(dataDirectory, 'statistics.json'), 'utf8') ); const officialTopics: OfficialTopic[] = JSON.parse( await fsp.readFile(join(dataDirectory, 'official-topics.json'), 'utf8') ); const allPosts: PostList[] = []; // Get a list of all the years we have Markdown files for. const yearDirectories: string[] = await fsp.readdir( join(__dirname, '../pages/posts/') ); for (const year of yearDirectories) { const postList: PostList = { year: Number(year), posts: [] }; // Create the output directory if it doesn't already exist. await fsp.mkdir(join(__dirname, `../../public/${year}/`), { recursive: true }); // Get a list of all the Markdown files. const markdownFiles: string[] = await fsp.readdir( join(__dirname, `../pages/posts/${year}/`) ); for (const file of markdownFiles) { // Get the filename without the extension. const fileBasename: string = basename(file, '.md'); // Find the month index in our months array. const monthNumber: number = months.indexOf(fileBasename); // Read the actual Markdown. const markdown: string = await fsp.readFile( join(__dirname, `../pages/posts/${year}/${file}`), 'utf8' ); // Add the post to the list. postList.posts.push({ markdown, month: monthNumber, rawMarkdown: markdown, url: `${year}/${fileBasename}.html` }); // Get the statistics for that month. const statistic: Statistics = statistics.find( val => val.year === Number(year) && val.month === monthNumber )!; const topics: OfficialTopic[] = await getTopicsFromMonth( officialTopics, Number(year), monthNumber ); let renderedHTML: string = marked(markdown); // Replace ~group with a link to that group. renderedHTML = renderedHTML.replace( groupRegex, ' ~$1' ); // Replace @person with a link to that person. renderedHTML = renderedHTML.replace( mentionRegex, ' @$1' ); // Render the post template. await renderTemplate( 'post.html', join(__dirname, `../../public/${year}/${fileBasename}.html`), { fecha, months, officialTopics: topics, post: { month: monthNumber, renderedHTML, year: Number(year) }, pluralize, statistic } ); } // Sort the posts to be ascending by month. postList.posts.sort((a, b) => a.month - b.month); allPosts.push(postList); } // Sort the post list to be descending by year. allPosts.sort((a, b) => b.year - a.year); await renderTemplate( 'posts.html', join(__dirname, '../../public/posts.html'), { months, posts: allPosts } ); // Sort the posts of the most recent year descending by month to select the most recent post. allPosts[0].posts.sort((a, b) => b.month - a.month); await renderTemplate( 'index.html', join(__dirname, '../../public/index.html'), { months, newestPost: { year: allPosts[0].year, ...allPosts[0].posts[0] } } ); await renderTemplate( 'attributions.html', join(__dirname, '../../public/attributions.html') ); await writeEmail(allPosts[0].posts[0].rawMarkdown); await generateFeeds(allPosts); } export async function renderTemplate( template: string, location: string, context: object = {} ): Promise { const html: string = htmlclean(nunjucks.render(template, context)); await fsp.writeFile(location, html); } export async function writeEmail(input: string): Promise { await fsp.mkdir(join(__dirname, '../../temp/'), {recursive: true}); await fsp.writeFile( join(__dirname, '../../temp/email.md'), wordWrap(72)('# Tildes Issue Log\n\n' + input) ); } export function pluralize( singular: string, plural: string, count: number ): string { if (count === 1) { return singular; } return plural; } if (require.main === module) { entry(); }