Skip to content

Commit

Permalink
chore: better documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
Gum-Joe committed Jul 31, 2024
1 parent ba67011 commit ae15958
Show file tree
Hide file tree
Showing 20 changed files with 327 additions and 185 deletions.

This file was deleted.

This file was deleted.

20 changes: 9 additions & 11 deletions packages/email/docsoc-mail-merge/src/cli-testing.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import Mailer from "./mailer/mailer";
import createLogger from "./util/logger";

import { parse } from "csv-parse";
import "dotenv/config";
import { promises as fs } from "fs";
import nunjucks from "nunjucks";
import markdownit from "markdown-it";

import "dotenv/config"; // load .env
import nunjucks from "nunjucks";
// load .env
import { join } from "path";
import { renderMarkdownTemplate, renderMarkdownToHtml } from "./markdown/template";
import { parse } from "csv-parse";

import { defaultMailer, getDefaultMailer } from "./mailer/defaultMailer";
import Mailer from "./mailer/mailer";
import { renderMarkdownToHtml } from "./markdown/toHtml";
import createLogger from "./util/logger";

const logger = createLogger("docsoc");

Expand All @@ -32,9 +32,7 @@ async function main() {

const csvData = parse(csv, { columns: true });
for await (const record of csvData) {
const expanded = renderMarkdownTemplate(templateCompiled, {
name: record["name"],
});
const expanded = "";
const html = renderMarkdownToHtml(expanded);

// wrap the html in the wrapper
Expand Down
20 changes: 11 additions & 9 deletions packages/email/docsoc-mail-merge/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import { ENGINES_MAP } from "./engines";
import { TemplatePreview } from "./engines/types";
import { getFileNameSchemeInteractively } from "./interactivity/getFileNameSchemeInteractively";
import getRunNameInteractively from "./interactivity/getRunNameInteractively";
import mapInteractive from "./interactivity/mapInteractive";
import mapCSVFieldsInteractive from "./interactivity/mapCSVFieldsInteractive";
import { stopIfCriticalFsError } from "./util/files";
import createLogger from "./util/logger";
import { CliOptions } from "./util/types";
import { CliOptions, CSVRecord } from "./util/types";

const logger = createLogger("docsoc");

Expand All @@ -40,7 +40,7 @@ async function main(opts: CliOptions) {
const csvRaw = await stopIfCriticalFsError(fs.readFile(join(__dirname, "../data/names.csv"), "utf-8"));
logger.debug("Parsing & loading CSV...");
const csvParsed = parse(csvRaw, { columns: true });
const records = [];
const records: CSVRecord[] = [];
for await (const record of csvParsed) {
records.push(record);
}
Expand All @@ -54,7 +54,7 @@ async function main(opts: CliOptions) {
const headers = Object.keys(records[0]);
logger.info(`Fields: ${headers.join(", ")}`);

// 4: Map to template
// 4: Load template via template engine
logger.info("Loading template...");
const EngineClass = ENGINES_MAP[opts.templateEngine];
if (!EngineClass) {
Expand All @@ -71,17 +71,17 @@ async function main(opts: CliOptions) {

// 6: Map CSV fields to template interactively
logger.info("Mapping CSV fields to template interactively");
const fieldsMapCSVtoTemplate = await mapInteractive(
const fieldsMapCSVtoTemplate = await mapCSVFieldsInteractive(
new Set([...Array.from(templateFields), ...ADDITIONAL_FIELDS]),
headers,
);

// 7: Ask what to name files using
const fileNamer = await getFileNameSchemeInteractively(headers, records);

// 7: Render intermediate results
// 8: Render intermediate results
logger.info("Rendering template previews/intermediates...");
const previews: [TemplatePreview, Record<string, any>][] = await Promise.all(
const previews: [TemplatePreview, CSVRecord][] = await Promise.all(
records.map(async (csvRecord) => {
const preparedRecord = Object.fromEntries(
Object.entries(csvRecord).map(([key, value]) => {
Expand All @@ -91,8 +91,9 @@ async function main(opts: CliOptions) {
return [await engine.renderPreview(preparedRecord), csvRecord];
}),
);
// 7: Write to file
logger.info("Writing previews files...");

// 9: Write to file
logger.info("Writing preview files...");
const previewsRoot = join(opts.output, "previews", runName);
logger.warn(`Writing previews to ${previewsRoot}...`);
logger.debug("Creating directories...");
Expand All @@ -102,6 +103,7 @@ async function main(opts: CliOptions) {
previews.flatMap(async ([previews, record]) =>
previews.map(async (preview) => {
const fileName = fileNamer(record);
logger.debug(`Writing ${fileName}__${opts.templateEngine}__${preview.name}`);
await fs.writeFile(
join(previewsRoot, `${fileName}__${opts.templateEngine}__${preview.name}`),
preview.content,
Expand Down
5 changes: 3 additions & 2 deletions packages/email/docsoc-mail-merge/src/engines/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
* Contains the different templating engines mail merge supports
* @module engines
*/
import nunjucksEngine from "./nunjucks";
import { TemplateEngine, TemplateEngineConstructor } from "./types";
import nunjucksEngine from "./nunjucks-md";
import { TemplateEngineConstructor } from "./types";

/** Map of engine names (provided on the CLI) to constructors for those engines */
export const ENGINES_MAP: Record<string, TemplateEngineConstructor> = {
nunjucks: nunjucksEngine,
};
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
/**
* Given a nunjucks template, returns a set of all the fields used in the template via regexp
* @param template String version of the template
* @returns Set of fields used in the template
*
* @example
* const template = "This is a template with one field: {{ field }}.";
* const result = getTemplateFields(template);
* console.log(result); // Set(["field"])
*/
const getTemplateFields = (template: string): Set<string> => {
const regex = /{{\s*(?<field>[a-zA-Z0-9_]+)(\s*\|\s*([a-zA-Z0-9_]+))?\s*}}/g;
const fields = new Set<string>();
Expand Down
88 changes: 88 additions & 0 deletions packages/email/docsoc-mail-merge/src/engines/nunjucks-md/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { promises as fs } from "fs";
import nunjucks from "nunjucks";

import { renderMarkdownToHtml } from "../../markdown/toHtml";
import { CSVRecord } from "../../util/types";
import { TemplateEngineOptions } from "../types";
import { TemplateEngine } from "../types";
import getTemplateFields from "./getFields";
import { assertIsNunjucksTemplateOptions, NunjucksMarkdownTemplateOptions } from "./types";

/**
* A Nunjucks Markdown template engine
*
* Given two templates:
* 1. A Markdown template that contains Nunjucks variables
* 2. An HTML template that will be used to wrap the rendered Markdown
*
* We produce two previews:
* 1. A Markdown preview, where the Nunjucks variables are expanded in the Markdown template
* 2. An HTML preview, where the Markdown preview is rendered to HTML and embedded in the HTML template.
*
* The HTML preview __must__ contain a `{{ content }}` variable that will be replaced with the rendered Markdown
*
* The HTML preview is what will be sent as the final email, however we provide
* the Markdown preview for editing purposes - you can edit it, trigger a re-render,
* and see the changes in the HTML preview that will be sent
*/
export default class NunjucksMarkdownEngine extends TemplateEngine {
private loadedTemplate?: string;
private templateOptions: NunjucksMarkdownTemplateOptions;

constructor(templateOptions: TemplateEngineOptions) {
super();
assertIsNunjucksTemplateOptions(templateOptions);
this.templateOptions = templateOptions;
}

async loadTemplate() {
this.loadedTemplate = await fs.readFile(this.templateOptions.templatePath, "utf-8");
}

extractFields() {
if (!this.loadedTemplate) {
throw new Error("Template not loaded");
}

return getTemplateFields(this.loadedTemplate);
}

async renderPreview(record: CSVRecord) {
if (!this.loadedTemplate) {
throw new Error("Template not loaded");
}

const templateCompiled = nunjucks.compile(
this.loadedTemplate,
nunjucks.configure({
throwOnUndefined: true,
}),
);
const htmlWrapper = await fs.readFile(this.templateOptions.rootHtmlTemplate, "utf-8");
const htmlWrapperCompiled = nunjucks.compile(htmlWrapper, nunjucks.configure({ autoescape: false }));

// Render the Markdown template with the record, so that we have something to preview
const expanded = templateCompiled.render({
name: record["name"],
});
// Render the MD to HTML
const html = renderMarkdownToHtml(expanded);

// Wrap the rendered markdown html in the wrapper
const wrapped = htmlWrapperCompiled.render({ content: html });

// Return both
return [
{
name: "Preview-Markdown.md",
content: expanded, // you can edit this and re-render
metadata: {},
},
{
name: "Preview-HTML.html",
content: wrapped, // this is what will be sent - do not edit it, re-rendering will overwrite it
metadata: {},
},
];
}
}
26 changes: 26 additions & 0 deletions packages/email/docsoc-mail-merge/src/engines/nunjucks-md/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { TemplateEngineOptions } from "../types";

/**
* Nunjucks template options
*/
export interface NunjucksMarkdownTemplateOptions {
/** Path to the Markdown template to use to produce __editable__ previews of emails */
templatePath: string;
/** Path to the root HTML template to use to produce __final__ emails - this is what the final rendered markdown from {@link NunjucksMarkdownTemplateOptions.templatePath} will be embedded into */
rootHtmlTemplate: string;
[key: string]: string;
}

/**
* Asserts that the given options are valid Nunjucks template options & throws an error if they are not
*/
export function assertIsNunjucksTemplateOptions(
options: TemplateEngineOptions,
): asserts options is NunjucksMarkdownTemplateOptions {
if (!options["templatePath"] || typeof options["templatePath"] !== "string") {
throw new Error("Invalid template option");
}
if (!options["rootHtmlTemplate"] || typeof options["rootHtmlTemplate"] !== "string") {
throw new Error("Invalid rootHtmlTemplate option");
}
}
67 changes: 0 additions & 67 deletions packages/email/docsoc-mail-merge/src/engines/nunjucks/index.ts

This file was deleted.

16 changes: 0 additions & 16 deletions packages/email/docsoc-mail-merge/src/engines/nunjucks/types.ts

This file was deleted.

Loading

0 comments on commit ae15958

Please sign in to comment.