UNPKG

3.67 kBPlain TextView Raw
1import * as path from 'path';
2import * as out from 'out';
3import { ContentGenerator } from './content';
4import { BUILDERS, BadgeBuilder, isBadgeUrl } from './badges';
5import { Package, PackageData } from './package';
6import { readFileContent, isFilePresent } from './file-tools';
7import { insertLicense } from './license-generator';
8
9const debug = require('debug')('embellish');
10const { parse } = require('marked-ast');
11const { toMarkdown } = require('marked-ast-markdown');
12
13interface BaseOptions<T> {
14 type: T;
15}
16
17interface FileOptions extends BaseOptions<'file'> {
18 filename: string;
19}
20
21interface ContentOptions extends BaseOptions<'content'> {
22 content: string;
23 basePath: string;
24 packageData: PackageData;
25}
26
27type AstSegmenter = number | (() => boolean);
28
29async function embellish(options: FileOptions | ContentOptions): Promise<string> {
30 if (options.type === 'file') {
31 const packageFile = path.resolve(path.dirname(options.filename), 'package.json');
32
33 return embellish({
34 type: 'content',
35 content: await readFileContent(options.filename),
36 basePath: path.dirname(packageFile),
37 packageData: await Package.readFromFile(packageFile),
38 });
39 }
40
41 const { content, basePath, packageData } = options;
42 const ast = parse(content);
43 await insertLicense(ast, packageData, basePath);
44 await insertBadges(ast, packageData, basePath);
45 return Promise.resolve(toMarkdown(ast));
46}
47
48async function insertBadges(ast: any, data: PackageData, basePath: string) {
49 const firstNonPrimaryHeadingIndex = ast.findIndex((item: any) => {
50 return item.type === 'heading' && item.level > 1;
51 });
52
53 const badges = await generateBadges(data, basePath);
54 if (firstNonPrimaryHeadingIndex === -1) {
55 badges.forEach(badge => ast.push(badge));
56 } else {
57 badges.forEach(badge => ast.splice(firstNonPrimaryHeadingIndex, 0, badge));
58
59 // remove any of the previously generated (or manually created badges)
60 removeNodeIfBadges(ast, firstNonPrimaryHeadingIndex - 1);
61 }
62}
63
64function removeNodeIfBadges(ast: any, index: number) {
65 const node = ast[index];
66 if (node.type !== 'paragraph') {
67 return;
68 }
69
70 const nonEmptyNodes = node.text.filter((content: any) => content !== ' ');
71 const badgeUrls = nonEmptyNodes
72 .map((node: any) => node.type === 'link' && node.href)
73 .filter((href: string) => isBadgeUrl(href));
74
75 // if we only have badge urls in the paragraph then remove the line and recurse to the
76 // ast node above (same index after we remove this one)
77 if (badgeUrls.length === nonEmptyNodes.length) {
78 ast.splice(index, 1);
79 removeNodeIfBadges(ast, index - 1);
80 }
81}
82
83async function generateBadges(data: PackageData, basePath: string) {
84 const badgeLoaders: (BadgeBuilder | string)[] = [
85 BUILDERS.nodeico,
86 '---',
87 BUILDERS.stability,
88 BUILDERS.travis,
89 BUILDERS.codeClimateMaintainability,
90 ];
91
92 out('!{bold}generating badges');
93 const promises = badgeLoaders.map(loader => {
94 if (typeof loader == 'string') {
95 return loader;
96 }
97
98 return loader(data, basePath);
99 });
100
101 // build the blocks
102 const blocks = await Promise.all(promises).then(badges => badges.filter(Boolean));
103
104 // create the paragraphs
105 const paragraphs = blocks.reduce<string[][]>(
106 (memo, block) => {
107 if (block === '---') {
108 memo.push([]);
109 } else if (block) {
110 memo[memo.length - 1].push(block);
111 }
112
113 return memo;
114 },
115 [[]],
116 );
117
118 return paragraphs
119 .reverse() // TODO: remove this when we are generating paragraphs in the expected order
120 .map(para => para.join(' '))
121 .map(ContentGenerator.paragraph);
122}
123
124module.exports = {
125 embellish,
126};
127
\No newline at end of file