graycoreio/daffodil

View on GitHub
tools/dgeni/src/processors/breadcrumb.ts

Summary

Maintainability
B
5 hrs
Test Coverage
import { Document } from 'dgeni';

import {
  DAFF_DOC_KIND_PATH_SEGMENT_MAP,
  DAFF_DOCS_DESIGN_PATH,
  DAFF_DOCS_PATH,
  DaffBreadcrumb,
  DaffDocKind,
} from '@daffodil/docs-utils';

import { KindedDocument } from './add-kind';
import { ParentedDocument } from '../transforms/daffodil-api-package/processors/add-subpackage-exports';
import { FilterableProcessor } from '../utils/filterable-processor.type';

const getStaticBreadcrumb = (segment: string, parent: string): DaffBreadcrumb => {
  switch (segment) {
    case DAFF_DOCS_PATH:
      return {
        label: 'Docs',
        path: `${parent}/${DAFF_DOCS_PATH}`,
      };

    case DAFF_DOCS_DESIGN_PATH:
      return {
        label: 'Design',
        path: `${parent}/${DAFF_DOCS_DESIGN_PATH}`,
      };

    case DAFF_DOC_KIND_PATH_SEGMENT_MAP[DaffDocKind.GUIDE]:
      return {
        label: 'Guides',
        path: `${parent}/${DAFF_DOC_KIND_PATH_SEGMENT_MAP[DaffDocKind.GUIDE]}`,
      };

    case DAFF_DOC_KIND_PATH_SEGMENT_MAP[DaffDocKind.EXPLANATION]:
      return {
        label: 'Explanations',
        path: `${parent}/${DAFF_DOC_KIND_PATH_SEGMENT_MAP[DaffDocKind.EXPLANATION]}`,
      };

    case DAFF_DOC_KIND_PATH_SEGMENT_MAP[DaffDocKind.PACKAGE]:
      return {
        label: 'Packages',
        path: `${parent}/${DAFF_DOC_KIND_PATH_SEGMENT_MAP[DaffDocKind.PACKAGE]}`,
      };

    case DAFF_DOC_KIND_PATH_SEGMENT_MAP[DaffDocKind.EXAMPLE]:
      return {
        label: 'Examples',
        path: `${parent}/${DAFF_DOC_KIND_PATH_SEGMENT_MAP[DaffDocKind.EXAMPLE]}`,
      };

    case DAFF_DOC_KIND_PATH_SEGMENT_MAP[DaffDocKind.API]:
      return {
        label: 'API',
        path: `${parent}/${DAFF_DOC_KIND_PATH_SEGMENT_MAP[DaffDocKind.API]}`,
      };

    case 'components':
      return {
        label: 'Components',
        path: `${parent}/components`,
      };

    default:
      return null;
  }
};

/**
 * A dgeni document which has breadcrumbs added.
 */
export interface BreadcrumbedDocument extends Document {
  breadcrumbs: Array<DaffBreadcrumb>;
}

const getParents = (doc: ParentedDocument): Array<ParentedDocument> =>
  doc.parent
    ? [...getParents(doc.parent), doc.parent]
    : [];

/**
 * Truncates the label of a breadcrumb such that it will only be the info not contained in the parent.
 */
const truncateLabel = (label: string, parent: string): string =>
  label.replace(`${parent}/`, '');

export const BREADCRUMB_PROCESSOR_NAME = 'breadcrumb';

export class BreadcrumbProcessor implements FilterableProcessor {
  readonly name = BREADCRUMB_PROCESSOR_NAME;
  readonly $runAfter = ['paths-absolutified'];
  readonly $runBefore = ['rendering-docs'];

  docTypes = [];

  constructor(
    private aliasMap,
  ) {}

  private getBreadcrumbs(doc: ParentedDocument & KindedDocument): Array<DaffBreadcrumb> {
    const segments = (<string>doc.path).split('/');
    const breadcrumbs = segments
      .map((segment, i) => getStaticBreadcrumb(segment, segments.slice(0, i).join('/')))
      .filter((b, i, ary) => !!b && ary.findIndex((e) => e?.label === b.label) === i);

    // once all static breadcrumbs are generated,
    // create dynamic breadcrumbs for doc kinds that need them
    // TODO: determine actual requirements for this feature
    switch (doc.kind) {
      case DaffDocKind.PACKAGE:
        const parents_ = segments
        // get all the dynamic segments not including the last (which is the current doc)
        // we only want to process dynamic parents here
          .slice(breadcrumbs.length + 1, segments.length - 1)
        // look up parents based on an alias built from segments
          .flatMap((_, i, ids) => this.aliasMap.getDocs(ids.slice(0, i + 1).join('/')));
        breadcrumbs.push(
          // turn the parent docs into breadcrumbs
          ...parents_.map((parent, i) => ({
            label: truncateLabel(parent.title, parents_[i - 1]?.title),
            path: parent.path,
          })),
          {
            label: parents_.length > 0 ? truncateLabel(doc.title, parents_[parents_.length - 1].title) : doc.title,
            path: doc.path,
          },
        );
        break;

      case DaffDocKind.API:
        if (doc.parent) {
          // build a list of parents to this doc and turn them into breadcrumbs
          const parents = [
            ...getParents(doc.parent),
            doc.parent,
          ];
          breadcrumbs.push(
            ...parents.map((parent, i) => ({
              label: truncateLabel(parent.name, parents[i - 1]?.name),
              path: parent.path,
            })),
            {
              label: parents.length > 0 ? truncateLabel(doc.name, parents[parents.length - 1].name) : doc.name,
              path: doc.path,
            },
          );
        } else {
          breadcrumbs.push(
            {
              label: doc.name,
              path: doc.path,
            },
          );
        }
        break;

      case DaffDocKind.GUIDE:
      case DaffDocKind.EXPLANATION:
      case DaffDocKind.EXAMPLE:
      default:
        breadcrumbs.push({
          label: doc.name || doc.title,
          path: doc.path,
        });
        break;
    }

    return breadcrumbs;
  }

  $process(docs: Array<ParentedDocument & KindedDocument>): Array<ParentedDocument & KindedDocument & BreadcrumbedDocument> {
    return docs.map(doc => {
      doc.breadcrumbs = this.docTypes.includes(doc.docType)
        ? this.getBreadcrumbs(doc)
        : [];
      return <ParentedDocument & KindedDocument & BreadcrumbedDocument>doc;
    });
  }
};

export const BREADCRUMB_PROCESSOR_PROVIDER = <const>[
  BREADCRUMB_PROCESSOR_NAME,
  (aliasMap) => new BreadcrumbProcessor(aliasMap),
];