dsifford/academic-bloggers-toolkit

View on GitHub
scripts/update-styles.ts

Summary

Maintainability
A
35 mins
Test Coverage
import { spawnSync } from 'child_process';
import fs from 'fs';
import path from 'path';
import { promisify } from 'util';
 
import { transform } from 'camaro';
import rimraf from 'rimraf';
 
import oldStyles from '../src/citation-styles.json';
import { Style, StyleJSON } from '../src/js/stores/data';
import { StyleKind } from '../src/js/stores/data/constants';
 
const mkdir = promisify(fs.mkdir);
const readfile = promisify(fs.readFile);
const readdir = promisify(fs.readdir);
const writeFile = promisify(fs.writeFile);
 
const CACHE_DIR = path.join(
__dirname,
'..',
'node_modules',
'.cache',
'academic-bloggers-toolkit',
);
const TEMP_DIR = path.join(CACHE_DIR, 'tmp');
const OUTPUT_PATH = path.join(__dirname, '..', 'src');
 
(async () => {
await mkdir(TEMP_DIR, { recursive: true });
spawnSync(
'git',
[
'clone',
'https://github.com/citation-style-language/styles.git',
TEMP_DIR,
],
{
stdio: 'inherit',
},
);
 
// eslint-disable-next-line @typescript-eslint/no-var-requires
const renamedStyles = require(path.join(TEMP_DIR, 'renamed-styles.json'));
const independent = await getFiles(TEMP_DIR, { extension: '.csl' });
const dependent = await getFiles(TEMP_DIR, {
relpath: 'dependent',
extension: '.csl',
});
 
const INCLUDED_STYLES: Set<string> = new Set();
let styles: Style[] = [];
 
for (const style of independent) {
const styleData = await parseCSL(path.join(TEMP_DIR, style), true);
styleData.forEach(({ value }) => INCLUDED_STYLES.add(value));
styles = [...styles, ...styleData];
}
 
for (const style of dependent) {
const styleData = (await parseCSL(
path.join(TEMP_DIR, style),
false,
)).filter(({ value }) => INCLUDED_STYLES.has(value));
styles = [...styles, ...styleData];
}
 
styles = [...styles].sort((a, b) => {
const prev = a.label.toLowerCase();
const curr = b.label.toLowerCase();
return prev < curr ? -1 : 1;
});
 
const renamed = Object.entries<string>(renamedStyles).reduce(
(obj, [key, val]) => {
if (INCLUDED_STYLES.has(val)) {
return { ...obj, [key]: val };
}
return obj;
},
{},
);
 
const newStyles: StyleJSON = {
renamed,
styles,
};
 
logNewStyles(oldStyles as StyleJSON, newStyles);
 
await writeFile(
path.join(OUTPUT_PATH, 'citation-styles.json'),
JSON.stringify(newStyles, null, 4),
);
 
rimraf.sync(TEMP_DIR);
})();
 
interface GetFilesOptions {
relpath?: string;
extension?: string;
}
 
async function getFiles(
rootpath: string,
{ relpath = '', extension = '' }: GetFilesOptions = {},
) {
const files = await readdir(path.join(rootpath, relpath), {
withFileTypes: true,
});
return files
.filter(
file =>
file.isFile() &&
!file.isDirectory() &&
file.name.endsWith(extension),
)
.map(file => path.join(relpath, file.name));
}
 
interface StyleQuery {
kind: 'in-text' | 'note';
hasBibliography: boolean;
parent: string;
title: string;
shortTitle: string;
updated: string;
}
 
Function `parseCSL` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.
async function parseCSL(
filepath: string,
isIndependent: boolean,
): Promise<Style[]> {
const xml = await readfile(filepath, { encoding: 'utf-8' });
const data: StyleQuery = await transform(xml, {
kind: '/style/@class',
hasBibliography: 'boolean(/style/bibliography)',
parent: '/style/info/link[@rel="independent-parent"]/@href',
title: '/style/info/title',
shortTitle: '/style/info/title-short',
updated: '/style/info/updated',
});
return data.kind === 'note' || (isIndependent && !data.hasBibliography)
? []
: [
{
kind: StyleKind.PREDEFINED,
value: path.basename(
isIndependent ? filepath : data.parent,
'.csl',
),
label: data.title,
...(data.shortTitle ? { shortTitle: data.shortTitle } : {}),
},
];
}
 
function logNewStyles(before: StyleJSON, after: StyleJSON) {
const BEFORE_LABELS: Set<string> = new Set(before.styles.map(s => s.label));
console.log('============== STYLES ADDED ==============');
for (const { label } of after.styles) {
if (BEFORE_LABELS.has(label)) {
continue;
}
console.log(label);
}
}