398 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			398 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 
								 | 
							
								const unified = require('unified');
							 | 
						||
| 
								 | 
							
								const markdown = require('remark-parse');
							 | 
						||
| 
								 | 
							
								const remark2rehype = require('remark-rehype');
							 | 
						||
| 
								 | 
							
								const stringify = require('remark-stringify');
							 | 
						||
| 
								 | 
							
								const frontmatter = require('remark-frontmatter');
							 | 
						||
| 
								 | 
							
								const raw = require('rehype-raw');
							 | 
						||
| 
								 | 
							
								const visit = require('unist-util-visit');
							 | 
						||
| 
								 | 
							
								const vfile = require('to-vfile');
							 | 
						||
| 
								 | 
							
								const path = require('path');
							 | 
						||
| 
								 | 
							
								const { Transform } = require('stream');
							 | 
						||
| 
								 | 
							
								const { Translate } = require('@google-cloud/translate');
							 | 
						||
| 
								 | 
							
								const YAML = require('js-yaml');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const solutionsToData = require('./solution-to-data');
							 | 
						||
| 
								 | 
							
								const challengeSeedToData = require('./challengeSeed-to-data');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const transformChallenge = new Transform({
							 | 
						||
| 
								 | 
							
								  transform(chunk, encoding, callback) {
							 | 
						||
| 
								 | 
							
								    const fileName = chunk.toString().trim();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    rebuildChallengeFile(fileName)
							 | 
						||
| 
								 | 
							
								      .then(file => callback(null, String(file.contents)))
							 | 
						||
| 
								 | 
							
								      .catch(err => console.error(err));
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								});
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								process.stdin.pipe(transformChallenge).pipe(process.stdout);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const processor = unified()
							 | 
						||
| 
								 | 
							
								  .use(markdown)
							 | 
						||
| 
								 | 
							
								  .use(frontmatter, ['yaml'])
							 | 
						||
| 
								 | 
							
								  .use(frontmatterToData)
							 | 
						||
| 
								 | 
							
								  .use(testsToData)
							 | 
						||
| 
								 | 
							
								  .use(textToData, ['description', 'instructions'])
							 | 
						||
| 
								 | 
							
								  .use(remark2rehype, { allowDangerousHTML: true })
							 | 
						||
| 
								 | 
							
								  .use(raw)
							 | 
						||
| 
								 | 
							
								  .use(solutionsToData)
							 | 
						||
| 
								 | 
							
								  .use(challengeSeedToData)
							 | 
						||
| 
								 | 
							
								  .use(replaceWithReferenceData)
							 | 
						||
| 
								 | 
							
								  .use(output);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								exports.rebuildChallengeFile = rebuildChallengeFile;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								async function rebuildChallengeFile(fileName) {
							 | 
						||
| 
								 | 
							
								  const filePath = path.resolve(fileName);
							 | 
						||
| 
								 | 
							
								  const lang = detectLang(filePath);
							 | 
						||
| 
								 | 
							
								  let referenceChallenge;
							 | 
						||
| 
								 | 
							
								  let translateText;
							 | 
						||
| 
								 | 
							
								  if (lang !== 'english') {
							 | 
						||
| 
								 | 
							
								    referenceChallenge = await getReferenceChallengeData(filePath);
							 | 
						||
| 
								 | 
							
								    translateText = createTranslateText(lang);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const file = await vfile.read(filePath);
							 | 
						||
| 
								 | 
							
								  file.data = {
							 | 
						||
| 
								 | 
							
								    ...file.data,
							 | 
						||
| 
								 | 
							
								    lang,
							 | 
						||
| 
								 | 
							
								    referenceChallenge,
							 | 
						||
| 
								 | 
							
								    translateText
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								  return await processor.process(file);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								async function getReferenceChallengeData(filePath) {
							 | 
						||
| 
								 | 
							
								  const parts = filePath.split(path.sep);
							 | 
						||
| 
								 | 
							
								  parts.push(parts.pop().replace(/\.[^.]+\.md$/, '.english.md'));
							 | 
						||
| 
								 | 
							
								  parts[parts.length - 4] = 'english';
							 | 
						||
| 
								 | 
							
								  const filePathEnglishChallenge = parts.join(path.sep);
							 | 
						||
| 
								 | 
							
								  try {
							 | 
						||
| 
								 | 
							
								    const fileData = await vfile.read(filePathEnglishChallenge);
							 | 
						||
| 
								 | 
							
								    fileData.data = { ...fileData.data, refData: true };
							 | 
						||
| 
								 | 
							
								    return (await processor.process(fileData)).data;
							 | 
						||
| 
								 | 
							
								  } catch (err) {
							 | 
						||
| 
								 | 
							
								    console.error(err);
							 | 
						||
| 
								 | 
							
								    return null;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function detectLang(filePath) {
							 | 
						||
| 
								 | 
							
								  const match = /\.([^.]+)\.md$/.exec(filePath);
							 | 
						||
| 
								 | 
							
								  if (!match) {
							 | 
						||
| 
								 | 
							
								    throw new Error(`Incorrect file path ${filePath}`);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return match[1];
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function frontmatterToData() {
							 | 
						||
| 
								 | 
							
								  return transformer;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  function transformer(tree, file) {
							 | 
						||
| 
								 | 
							
								    visit(tree, 'yaml', visitor);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    function visitor(node) {
							 | 
						||
| 
								 | 
							
								      const frontmatter = node.value;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      file.data = { ...file.data, frontmatter };
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function testsToData() {
							 | 
						||
| 
								 | 
							
								  return (tree, file) => {
							 | 
						||
| 
								 | 
							
								    visit(tree, 'code', visitor);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    function visitor(node) {
							 | 
						||
| 
								 | 
							
								      const { lang, value } = node;
							 | 
						||
| 
								 | 
							
								      if (lang === 'yml') {
							 | 
						||
| 
								 | 
							
								        file.data = {
							 | 
						||
| 
								 | 
							
								          ...file.data,
							 | 
						||
| 
								 | 
							
								          tests: value
							 | 
						||
| 
								 | 
							
								        };
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function textToData(sectionIds) {
							 | 
						||
| 
								 | 
							
								  return (tree, file) => {
							 | 
						||
| 
								 | 
							
								    let indexId = 0;
							 | 
						||
| 
								 | 
							
								    let currentSection = sectionIds[indexId];
							 | 
						||
| 
								 | 
							
								    let inSection = false;
							 | 
						||
| 
								 | 
							
								    let nodes = [];
							 | 
						||
| 
								 | 
							
								    let findSection;
							 | 
						||
| 
								 | 
							
								    const visitor = (node, index, parent) => {
							 | 
						||
| 
								 | 
							
								      if (!parent) {
							 | 
						||
| 
								 | 
							
								        return visit.CONTINUE;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (node.type === 'heading') {
							 | 
						||
| 
								 | 
							
								        if (inSection) {
							 | 
						||
| 
								 | 
							
								          findSection = new RegExp(`^<section id=('|")${currentSection}\\1>`);
							 | 
						||
| 
								 | 
							
								          file.data = {
							 | 
						||
| 
								 | 
							
								            ...file.data,
							 | 
						||
| 
								 | 
							
								            [currentSection]: new stringify.Compiler(
							 | 
						||
| 
								 | 
							
								              { type: 'root', children: nodes },
							 | 
						||
| 
								 | 
							
								              file
							 | 
						||
| 
								 | 
							
								            )
							 | 
						||
| 
								 | 
							
								              .compile()
							 | 
						||
| 
								 | 
							
								              .trim()
							 | 
						||
| 
								 | 
							
								              .replace(findSection, '')
							 | 
						||
| 
								 | 
							
								              .replace(/<\/section>$/, '')
							 | 
						||
| 
								 | 
							
								              .trim()
							 | 
						||
| 
								 | 
							
								          };
							 | 
						||
| 
								 | 
							
								          nodes = [];
							 | 
						||
| 
								 | 
							
								          indexId++;
							 | 
						||
| 
								 | 
							
								          if (indexId < sectionIds.length) {
							 | 
						||
| 
								 | 
							
								            currentSection = sectionIds[indexId];
							 | 
						||
| 
								 | 
							
								          } else {
							 | 
						||
| 
								 | 
							
								            return visit.EXIT;
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        inSection = true;
							 | 
						||
| 
								 | 
							
								      } else if (inSection) {
							 | 
						||
| 
								 | 
							
								        nodes.push(node);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      return visit.SKIP;
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								    visit(tree, visitor);
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function createTranslateText(target, source = 'english') {
							 | 
						||
| 
								 | 
							
								  const projectId = process.env.GOOGLE_CLOUD_PROJECT_ID;
							 | 
						||
| 
								 | 
							
								  if (!projectId) {
							 | 
						||
| 
								 | 
							
								    return async text => text;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  const languageCodes = {
							 | 
						||
| 
								 | 
							
								    arabic: 'ar',
							 | 
						||
| 
								 | 
							
								    chinese: 'zh',
							 | 
						||
| 
								 | 
							
								    english: 'en',
							 | 
						||
| 
								 | 
							
								    portuguese: 'pt',
							 | 
						||
| 
								 | 
							
								    russian: 'ru',
							 | 
						||
| 
								 | 
							
								    spanish: 'es'
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								  const from = languageCodes[source];
							 | 
						||
| 
								 | 
							
								  const to = languageCodes[target];
							 | 
						||
| 
								 | 
							
								  return async text => {
							 | 
						||
| 
								 | 
							
								    if (!text) {
							 | 
						||
| 
								 | 
							
								      return text;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    try {
							 | 
						||
| 
								 | 
							
								      const translate = new Translate({ projectId });
							 | 
						||
| 
								 | 
							
								      const result = await translate.translate(text, { from, to });
							 | 
						||
| 
								 | 
							
								      const translations = result[0];
							 | 
						||
| 
								 | 
							
								      return translations;
							 | 
						||
| 
								 | 
							
								    } catch (err) {
							 | 
						||
| 
								 | 
							
								      // console.error(err);
							 | 
						||
| 
								 | 
							
								      return text;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								async function processTests(tests, referenceTests, translateText) {
							 | 
						||
| 
								 | 
							
								  const testsObject = YAML.load(referenceTests);
							 | 
						||
| 
								 | 
							
								  if (
							 | 
						||
| 
								 | 
							
								    !testsObject.tests ||
							 | 
						||
| 
								 | 
							
								    testsObject.tests.length === 0 ||
							 | 
						||
| 
								 | 
							
								    !testsObject.tests[0].text
							 | 
						||
| 
								 | 
							
								  ) {
							 | 
						||
| 
								 | 
							
								    return referenceTests;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  const newTests = await Promise.all(
							 | 
						||
| 
								 | 
							
								    testsObject.tests.map(async test => {
							 | 
						||
| 
								 | 
							
								      const text = await translateText(test.text);
							 | 
						||
| 
								 | 
							
								      return { ...test, text };
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								  );
							 | 
						||
| 
								 | 
							
								  const testStrings = newTests
							 | 
						||
| 
								 | 
							
								    .map(
							 | 
						||
| 
								 | 
							
								      ({ text, testString }) =>
							 | 
						||
| 
								 | 
							
								        `  - text: ${dumpToYamlString(text)}    testString: ${dumpToYamlString(
							 | 
						||
| 
								 | 
							
								          testString
							 | 
						||
| 
								 | 
							
								        )}`
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								    .join('');
							 | 
						||
| 
								 | 
							
								  return `tests:${testStrings ? '\n' + testStrings : ' []\n'}`;
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function dumpToYamlString(text) {
							 | 
						||
| 
								 | 
							
								  let fromYaml;
							 | 
						||
| 
								 | 
							
								  try {
							 | 
						||
| 
								 | 
							
								    fromYaml = YAML.load(text);
							 | 
						||
| 
								 | 
							
								  } catch {
							 | 
						||
| 
								 | 
							
								    // console.error(`YAML load: ${text}`);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  if (text === fromYaml) {
							 | 
						||
| 
								 | 
							
								    return text + '\n';
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  return YAML.dump(text, { lineWidth: 10000 });
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								async function processFrontmatter(fileData) {
							 | 
						||
| 
								 | 
							
								  const { referenceChallenge, lang, translateText } = fileData;
							 | 
						||
| 
								 | 
							
								  const challengeData = YAML.load(fileData.frontmatter);
							 | 
						||
| 
								 | 
							
								  let data;
							 | 
						||
| 
								 | 
							
								  if (referenceChallenge) {
							 | 
						||
| 
								 | 
							
								    data = YAML.load(referenceChallenge.frontmatter);
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    data = challengeData;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (lang && lang !== 'english') {
							 | 
						||
| 
								 | 
							
								    if (challengeData.localeTitle) {
							 | 
						||
| 
								 | 
							
								      data.localeTitle = challengeData.localeTitle;
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      data.localeTitle = await translateText(data.title);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  fileData.frontmatter = Object.entries(data)
							 | 
						||
| 
								 | 
							
								    .map(([name, value]) => {
							 | 
						||
| 
								 | 
							
								      if (typeof value === 'object') {
							 | 
						||
| 
								 | 
							
								        return `${name}:
							 | 
						||
| 
								 | 
							
								  ${dumpToYamlString(value)
							 | 
						||
| 
								 | 
							
								    .replace(/\n/, '\n  ')
							 | 
						||
| 
								 | 
							
								    .trimRight()}
							 | 
						||
| 
								 | 
							
								`;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      return `${name}: ${dumpToYamlString(value)}`;
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								    .join('')
							 | 
						||
| 
								 | 
							
								    .trimRight();
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function replaceWithReferenceData() {
							 | 
						||
| 
								 | 
							
								  return async (tree, file) => {
							 | 
						||
| 
								 | 
							
								    if (file.data.refData) {
							 | 
						||
| 
								 | 
							
								      return;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    const { referenceChallenge, translateText } = file.data;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    await processFrontmatter(file.data);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (referenceChallenge) {
							 | 
						||
| 
								 | 
							
								      const { description, instructions } = file.data;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if (!description || description === 'undefined') {
							 | 
						||
| 
								 | 
							
								        file.data.description = await translateText(
							 | 
						||
| 
								 | 
							
								          referenceChallenge.description
							 | 
						||
| 
								 | 
							
								        );
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      if (!instructions || instructions === 'undefined') {
							 | 
						||
| 
								 | 
							
								        file.data.instructions = await translateText(
							 | 
						||
| 
								 | 
							
								          referenceChallenge.instructions
							 | 
						||
| 
								 | 
							
								        );
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      file.data.tests = await processTests(
							 | 
						||
| 
								 | 
							
								        file.data.tests,
							 | 
						||
| 
								 | 
							
								        referenceChallenge.tests,
							 | 
						||
| 
								 | 
							
								        translateText
							 | 
						||
| 
								 | 
							
								      );
							 | 
						||
| 
								 | 
							
								      file.data.files = referenceChallenge.files;
							 | 
						||
| 
								 | 
							
								      file.data.solutions = referenceChallenge.solutions;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function output() {
							 | 
						||
| 
								 | 
							
								  this.Compiler = function(tree, file) {
							 | 
						||
| 
								 | 
							
								    let {
							 | 
						||
| 
								 | 
							
								      frontmatter,
							 | 
						||
| 
								 | 
							
								      description,
							 | 
						||
| 
								 | 
							
								      instructions,
							 | 
						||
| 
								 | 
							
								      tests,
							 | 
						||
| 
								 | 
							
								      files: [challengeFile = {}]
							 | 
						||
| 
								 | 
							
								    } = file.data;
							 | 
						||
| 
								 | 
							
								    const { ext, contents, head, tail } = challengeFile;
							 | 
						||
| 
								 | 
							
								    let { solutions = [] } = file.data;
							 | 
						||
| 
								 | 
							
								    solutions = solutions
							 | 
						||
| 
								 | 
							
								      .map(s => s.trim())
							 | 
						||
| 
								 | 
							
								      .map(s =>
							 | 
						||
| 
								 | 
							
								        !s.includes('\n') && /^\/\//.test(s) ? '// solution required' : s
							 | 
						||
| 
								 | 
							
								      );
							 | 
						||
| 
								 | 
							
								    return `---
							 | 
						||
| 
								 | 
							
								${frontmatter}
							 | 
						||
| 
								 | 
							
								---
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								## Description
							 | 
						||
| 
								 | 
							
								<section id='description'>
							 | 
						||
| 
								 | 
							
								${description}
							 | 
						||
| 
								 | 
							
								</section>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								## Instructions
							 | 
						||
| 
								 | 
							
								<section id='instructions'>
							 | 
						||
| 
								 | 
							
								${instructions}
							 | 
						||
| 
								 | 
							
								</section>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								## Tests
							 | 
						||
| 
								 | 
							
								<section id='tests'>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								\`\`\`yml
							 | 
						||
| 
								 | 
							
								${tests}
							 | 
						||
| 
								 | 
							
								\`\`\`
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								</section>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								${
							 | 
						||
| 
								 | 
							
								  ext
							 | 
						||
| 
								 | 
							
								    ? `## Challenge Seed
							 | 
						||
| 
								 | 
							
								<section id='challengeSeed'>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								<div id='${ext}-seed'>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								\`\`\`${ext}
							 | 
						||
| 
								 | 
							
								${contents}
							 | 
						||
| 
								 | 
							
								\`\`\`
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								</div>${
							 | 
						||
| 
								 | 
							
								        head
							 | 
						||
| 
								 | 
							
								          ? `
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### Before Tests
							 | 
						||
| 
								 | 
							
								<div id='${ext}-setup'>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								\`\`\`${ext}
							 | 
						||
| 
								 | 
							
								${head}
							 | 
						||
| 
								 | 
							
								\`\`\`
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								</div>`
							 | 
						||
| 
								 | 
							
								          : ''
							 | 
						||
| 
								 | 
							
								      }${
							 | 
						||
| 
								 | 
							
								        tail
							 | 
						||
| 
								 | 
							
								          ? `
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								### After Tests
							 | 
						||
| 
								 | 
							
								<div id='${ext}-teardown'>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								\`\`\`${ext}
							 | 
						||
| 
								 | 
							
								${tail}
							 | 
						||
| 
								 | 
							
								\`\`\`
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								</div>`
							 | 
						||
| 
								 | 
							
								          : ''
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								</section>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								## Solution
							 | 
						||
| 
								 | 
							
								${solutions.reduce(
							 | 
						||
| 
								 | 
							
								  (result, solution) =>
							 | 
						||
| 
								 | 
							
								    result +
							 | 
						||
| 
								 | 
							
								    `<section id='solution'>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								\`\`\`${ext}
							 | 
						||
| 
								 | 
							
								${solution.trim()}
							 | 
						||
| 
								 | 
							
								\`\`\`
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								</section>`,
							 | 
						||
| 
								 | 
							
								  ''
							 | 
						||
| 
								 | 
							
								)}
							 | 
						||
| 
								 | 
							
								`
							 | 
						||
| 
								 | 
							
								    : ''
							 | 
						||
| 
								 | 
							
								}`;
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								}
							 |