30-seconds/30-seconds-of-code

View on GitHub
src/blocks/extractor/extractor.js

Summary

Maintainability
A
2 hrs
Test Coverage
/* eslint-disable no-unused-vars */
import pathSettings from '#settings/paths';
import { Logger } from '#blocks/utilities/logger';
import { TextParser } from '#blocks/extractor/textParser';
import { MarkdownParser } from '#blocks/extractor/markdownParser/markdownParser';
import { JSONHandler } from '#blocks/utilities/jsonHandler';
import { YAMLHandler } from '#blocks/utilities/yamlHandler';
import { stripMarkdownFormat } from '#utils';

const mdCodeFence = '```';

const { rawContentPath: contentDir } = pathSettings;

export class Extractor {
  static data = {
    collections: [],
    snippets: [],
    languages: [],
    collectionsHub: {},
  };
  static languageData = new Map();
  static grammars = {};

  static prepared = false;
  static quiet = false;

  static prepare = async () => {
    Extractor.extractLanguageData();
    Extractor.extractGrammars();
    MarkdownParser.setupProcessors({
      languageData: Extractor.languageData,
      grammars: Extractor.grammars,
    });
    Extractor.extractCollectionConfigs();
    await Extractor.extractSnippets();
    Extractor.extractCollectionsHubConfig();
    Extractor.prepared = true;
  };

  static extract = async ({ force = false, quiet = false } = {}) => {
    Extractor.quiet = quiet;
    if (!Extractor.prepared || force) await Extractor.prepare();

    await Extractor.writeData();
    return Extractor.data;
  };

  static extractCollectionConfigs = () => {
    const logger = new Logger('Extractor.extractCollectionConfigs', {
      muted: Extractor.quiet,
    });
    logger.log('Extracting collection configurations');
    const configs = YAMLHandler.fromGlob(
      `${contentDir}/collections/**/*.yaml`,
      { withNames: true }
    ).map(([path, config]) => {
      const {
        snippetIds = [],
        slug: id,
        name,
        shortName = name,
        miniName = shortName,
        shortDescription,
        topLevel = false,
        allowUnlisted = false,
        ...rest
      } = config;
      const slug = `/${id}`;
      const seoDescription = stripMarkdownFormat(shortDescription);

      if (seoDescription.length > 140) {
        logger.warn(`Collection ${id} has a long SEO description.`);
      }

      return {
        id,
        name,
        slug,
        shortName,
        miniName,
        topLevel,
        shortDescription,
        seoDescription,
        allowUnlisted,
        ...rest,
        snippetIds,
      };
    });
    logger.success('Finished extracting collection configurations');
    Extractor.data.collections = configs;
  };

  static extractLanguageData = () => {
    const logger = new Logger('Extractor.extractLanguageData', {
      muted: Extractor.quiet,
    });
    logger.log('Extracting language data');
    const languageData = YAMLHandler.fromGlob(
      `${contentDir}/languages/*.yaml`
    ).reduce((acc, language) => {
      const {
        short,
        long,
        name,
        references = {},
        additionalReferences = [],
      } = language;
      acc.set(long, {
        id: long,
        long,
        short,
        name,
        references,
        allLanguageReferences: [long, ...additionalReferences],
      });
      return acc;
    }, new Map());
    logger.success('Finished extracting language data');
    Extractor.languageData = languageData;
    Extractor.data.languages = [...languageData].map(([id, data]) => {
      const { references, allLanguageReferences, ...restData } = data;
      return { ...restData };
    });
  };

  static extractSnippets = async () => {
    const logger = new Logger('Extractor.extractSnippets', {
      muted: Extractor.quiet,
    });
    logger.log('Extracting snippets');

    const snippetsGlob = `${contentDir}/snippets/**/s/*.md`;
    let snippets = [];

    await TextParser.fromGlob(snippetsGlob).then(snippetData => {
      const parsedData = snippetData.map(snippet =>
        Extractor.parseSnippet(snippet)
      );

      snippets = parsedData;
    });
    logger.success('Finished extracting snippets');
    Extractor.data.snippets = snippets;
  };

  static extractSnippet = async snippetPath => {
    const logger = new Logger('Extractor.extractSnippet', {
      muted: Extractor.quiet,
    });
    logger.log(`Extracting snippet ${snippetPath}`);

    let snippet = {};

    await TextParser.fromPath(snippetPath).then(snippetData => {
      snippet = Extractor.parseSnippet(snippetData);
    });

    Extractor.updateSnippetData(snippet.id, snippet);
    logger.success(`Finished extracting snippet ${snippetPath}`);

    return [snippet.id, snippet];
  };

  static updateSnippetData = (id, snippetData) => {
    const logger = new Logger('Extractor.updateSnippetData', {
      muted: Extractor.quiet,
    });
    logger.log(`Updating data for snippet ${id}`);

    const index = Extractor.data.snippets.findIndex(
      snippet => snippet.id === id
    );

    if (index === -1) {
      Extractor.data.snippets.push(snippetData);
      logger.success(`Finished creating snippet ${id}`);
    } else {
      Extractor.data.snippets[index] = snippetData;
      logger.success(`Finished updating snippet ${id}`);
    }
  };

  static unlinkSnippetData = id => {
    const logger = new Logger('Extractor.unlinkSnippetData');
    logger.log(`Unlinking data for snippet ${id}`);

    const index = Extractor.data.snippets.findIndex(
      snippet => snippet.id === id
    );

    if (index === -1) {
      logger.warn(`Snippet ${id} not found in data`);
    } else {
      Extractor.data.snippets.splice(index, 1);
      logger.success(`Finished unlinking snippet ${id}`);
    }
  };

  static parseSnippet = snippet => {
    const logger = new Logger('Extractor.parseSnippet', {
      muted: Extractor.quiet,
    });

    const {
      filePath,
      fileName,
      title,
      shortTitle = title,
      tags: rawTags,
      type = 'snippet',
      language: languageKey,
      excerpt,
      cover,
      dateModified,
      body,
      unlisted,
    } = snippet;

    const language = Extractor.languageData.get(languageKey) || undefined;
    const id = filePath.replace(`${contentDir}/snippets/`, '').slice(0, -3);
    const tags = rawTags.map(tag => tag.toLowerCase());

    const bodyText = body
      .slice(0, body.indexOf(mdCodeFence))
      .replace(/\r\n/g, '\n');
    const shortText =
      excerpt && excerpt.trim()
        ? excerpt
        : bodyText.slice(0, bodyText.indexOf('\n\n'));

    const fullText = body;
    const seoDescription = stripMarkdownFormat(shortText);

    if (seoDescription.length > 140) {
      logger.warn(`Snippet ${id} has a long SEO description.`);
    }

    const html = MarkdownParser.parseSegments(
      {
        fullDescription: fullText,
        description: shortText,
      },
      language ? language.short : null
    );

    return {
      id,
      fileName,
      title,
      shortTitle,
      tags,
      dateModified,
      listed: unlisted === true ? false : true,
      type,
      shortText,
      fullText,
      ...html,
      cover,
      seoDescription,
      language: languageKey,
    };
  };

  static extractCollectionsHubConfig = () => {
    const logger = new Logger('Extractor.extractCollectionsHubConfig', {
      muted: Extractor.quiet,
    });
    logger.log('Extracting hub pages configuration');
    const hubConfig = YAMLHandler.fromFile(`${contentDir}/hub.yaml`);
    logger.log('Finished extracting hub pages configuration');
    Extractor.data.collectionsHub = hubConfig;
  };

  static extractGrammars = () => {
    const logger = new Logger('Extractor.extractGrammars', {
      muted: Extractor.quiet,
    });
    logger.log('Extracting grammars');
    const grammars = YAMLHandler.fromFile(`${contentDir}/grammars.yaml`);
    logger.log('Finished extracting grammars');
    Extractor.grammars = grammars;
  };

  static writeData = () => {
    const logger = new Logger('Extractor.writeData', {
      muted: Extractor.quiet,
    });
    logger.log('Writing data to disk');
    return JSONHandler.toFile(
      `${pathSettings.contentPath}/content.json`,
      Extractor.data
    ).then(() => logger.success('Finished writing data'));
  };
}