hierarchy.js 3.8 KB
Newer Older
1 2 3 4
import express from 'express';
import fs from 'fs';
import path from 'path';
import { NotFoundError } from '/common/error';
J
Jason Park 已提交
5 6
import { exec } from 'child_process';
import { repo } from '/common/github';
7 8 9

const router = express.Router();

10 11 12
const getPath = (...args) => path.resolve(__dirname, '..', 'public', 'algorithms', ...args);
const createKey = name => name.toLowerCase().replace(/ /g, '-');
const list = dirPath => fs.readdirSync(dirPath).filter(filename => !filename.startsWith('.'));
13

14
const cacheHierarchy = () => {
J
Jason Park 已提交
15 16
  const allFiles = [];
  const cacheCategory = categoryName => {
17 18
    const categoryKey = createKey(categoryName);
    const categoryPath = getPath(categoryName);
J
Jason Park 已提交
19
    const algorithms = list(categoryPath).map(algorithmName => cacheAlgorithm(categoryName, algorithmName));
20 21 22 23 24 25
    return {
      key: categoryKey,
      name: categoryName,
      algorithms,
    };
  };
J
Jason Park 已提交
26
  const cacheAlgorithm = (categoryName, algorithmName) => {
27 28
    const algorithmKey = createKey(algorithmName);
    const algorithmPath = getPath(categoryName, algorithmName);
J
Jason Park 已提交
29 30
    const files = list(algorithmPath).map(fileName => cacheFile(categoryName, algorithmName, fileName));
    allFiles.push(...files);
31 32 33 34
    return {
      key: algorithmKey,
      name: algorithmName,
      files,
35
    };
36
  };
J
Jason Park 已提交
37 38 39 40 41 42 43 44 45 46 47 48 49
  const cacheFile = (categoryName, algorithmName, fileName) => {
    const filePath = getPath(categoryName, algorithmName, fileName);
    const content = fs.readFileSync(filePath, 'utf-8');
    return {
      name: fileName,
      path: filePath,
      content,
      contributors: [],
      toJSON: () => fileName,
    };
  };

  const hierarchy = list(getPath()).map(cacheCategory);
50

J
Jason Park 已提交
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
  const commitAuthors = {};
  const cacheCommitAuthors = (page, done) => {
    repo.listCommits({ per_page: 100, page }).then(({ data }) => {
      if (data.length) {
        data.forEach(({ sha, commit, author }) => {
          if (!author) return;
          const { login, avatar_url } = author;
          commitAuthors[sha] = { login, avatar_url };
        });
        cacheCommitAuthors(page + 1, done);
      } else done && done();
    }).catch(console.error);
  };
  const cacheContributors = (fileIndex, done) => {
    const file = allFiles[fileIndex];
    if (file) {
      const cwd = getPath();
68
      exec(`git --no-pager log --follow --no-merges --format="%H" "${file.path}"`, { cwd }, (error, stdout, stderr) => {
J
Jason Park 已提交
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
        if (!error && !stderr) {
          const output = stdout.toString().replace(/\n$/, '');
          const shas = output.split('\n').reverse();
          const contributors = [];
          for (const sha of shas) {
            const author = commitAuthors[sha];
            if (author && !contributors.find(contributor => contributor.login === author.login)) {
              contributors.push(author);
            }
          }
          file.contributors = contributors;
        }
        cacheContributors(fileIndex + 1, done);
      });
    } else done && done();
  };
  cacheCommitAuthors(1, () => cacheContributors(0));

  return hierarchy;
};
const cachedHierarchy = cacheHierarchy(); // TODO: cache again when webhooked
90

91
const getHierarchy = (req, res, next) => {
J
Jason Park 已提交
92
  res.json({ hierarchy: cachedHierarchy });
93 94
};

95 96
const getAlgorithm = (req, res, next) => {
  const { categoryKey, algorithmKey } = req.params;
97

J
Jason Park 已提交
98
  const category = cachedHierarchy.find(category => category.key === categoryKey);
99 100 101 102
  if (!category) return next(new NotFoundError());
  const algorithm = category.algorithms.find(algorithm => algorithm.key === algorithmKey);
  if (!algorithm) return next(new NotFoundError());

103 104
  const files = algorithm.files.map(({ name, content, contributors }) => ({ name, content, contributors }));
  res.json({ algorithm: { ...algorithm, files } });
105 106 107
};

router.route('/')
108
  .get(getHierarchy);
109

110 111
router.route('/:categoryKey/:algorithmKey')
  .get(getAlgorithm);
112 113

export default router;