1 | import * as path from 'path';
|
2 | import * as out from 'out';
|
3 | import { ContentGenerator } from './content';
|
4 | import { BUILDERS, BadgeBuilder, isBadgeUrl } from './badges';
|
5 | import { Package, PackageData } from './package';
|
6 | import { readFileContent, isFilePresent } from './file-tools';
|
7 | import { insertLicense } from './license-generator';
|
8 |
|
9 | const debug = require('debug')('embellish');
|
10 | const { parse } = require('marked-ast');
|
11 | const { toMarkdown } = require('marked-ast-markdown');
|
12 |
|
13 | interface BaseOptions<T> {
|
14 | type: T;
|
15 | }
|
16 |
|
17 | interface FileOptions extends BaseOptions<'file'> {
|
18 | filename: string;
|
19 | }
|
20 |
|
21 | interface ContentOptions extends BaseOptions<'content'> {
|
22 | content: string;
|
23 | basePath: string;
|
24 | packageData: PackageData;
|
25 | }
|
26 |
|
27 | type AstSegmenter = number | (() => boolean);
|
28 |
|
29 | async 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 |
|
48 | async 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 |
|
64 | function 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 |
|
83 | async 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 |
|
124 | module.exports = {
|
125 | embellish,
|
126 | };
|
127 |
|
\ | No newline at end of file |