mirror of
https://gitee.com/vant-contrib/vant.git
synced 2025-04-06 03:57:59 +08:00
chore: add vant-markdown-vetur package
This commit is contained in:
parent
3a65004c6f
commit
c5514f73b8
24
packages/vant-markdown-vetur/README.md
Normal file
24
packages/vant-markdown-vetur/README.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Vant Markdown Vetur
|
||||||
|
|
||||||
|
将 .md 文件转换成能描述 vue 组件的 .json 文件,供 vscode 插件 *vetur* 读取,从而可以在 vue 模版语法中拥有自动补全的功能。
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
#### parseAndWrite
|
||||||
|
|
||||||
|
解析目录下所有匹配的文件,并输出为 tags.json 和 attributes.json
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface Options {
|
||||||
|
// 需要解析的文件夹路径
|
||||||
|
path: PathLike;
|
||||||
|
// 文件匹配正则
|
||||||
|
test: RegExp;
|
||||||
|
// 输出目录
|
||||||
|
outputDir: string;
|
||||||
|
// 递归的目录最大深度
|
||||||
|
maxDeep?: number;
|
||||||
|
// 解析出来的组件名前缀
|
||||||
|
tagPrefix?: string;
|
||||||
|
}
|
||||||
|
```
|
16
packages/vant-markdown-vetur/package.json
Normal file
16
packages/vant-markdown-vetur/package.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "@vant/markdown-vetur",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "simple parse markdown to vue component description for vetur auto-completion",
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": "https://github.com/youzan/vant/tree/dev/packages/vant-markdown-vetur",
|
||||||
|
"author": "zhangshuai",
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"release": "npm run build && npm publish"
|
||||||
|
}
|
||||||
|
}
|
75
packages/vant-markdown-vetur/src/codegen.ts
Normal file
75
packages/vant-markdown-vetur/src/codegen.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
/* eslint-disable no-continue */
|
||||||
|
import { Artical } from './md-parser';
|
||||||
|
|
||||||
|
const FLAG_REG = /(.*?)\s*(Props|Event)/i;
|
||||||
|
|
||||||
|
export type Tag = {
|
||||||
|
attributes: Record<string, Attribute>;
|
||||||
|
description: string;
|
||||||
|
defaults?: Array<string>;
|
||||||
|
subtags?: Array<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Attribute = {
|
||||||
|
description: string;
|
||||||
|
type?: string;
|
||||||
|
options?: Array<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function camelCaseToKebabCase(input: string): string {
|
||||||
|
return input.replace(
|
||||||
|
/[A-Z]/g,
|
||||||
|
(val, index) => (index === 0 ? '' : '-') + val.toLowerCase()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function codegen(artical: Artical) {
|
||||||
|
const tags: Record<string, Tag> = {};
|
||||||
|
let tagDescription = '';
|
||||||
|
|
||||||
|
for (let i = 0, len = artical.length; i < len; i++) {
|
||||||
|
const item = artical[i];
|
||||||
|
if (item.type === 'title' && item.level === 2) {
|
||||||
|
if (item.content) {
|
||||||
|
tagDescription = item.content;
|
||||||
|
}
|
||||||
|
} else if (item.type === 'table') {
|
||||||
|
const before = artical[i - 1];
|
||||||
|
if (!before || !before.content) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { table } = item;
|
||||||
|
const match = FLAG_REG.exec(before.content);
|
||||||
|
|
||||||
|
if (!match || !table) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = camelCaseToKebabCase(match[1] || 'default');
|
||||||
|
const tag: Tag = tags[key] || {
|
||||||
|
description: tagDescription,
|
||||||
|
attributes: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
tags[key] = tag;
|
||||||
|
|
||||||
|
const isProp = /Props/i.test(match[2]);
|
||||||
|
|
||||||
|
table.body.forEach(td => {
|
||||||
|
const attrName = td[0];
|
||||||
|
|
||||||
|
const attr: Attribute = {
|
||||||
|
description: `${td[1]}, ${
|
||||||
|
isProp ? 'default: ' + td[3].replace(/`/g, '') : 'params: ' + td[2]
|
||||||
|
}`,
|
||||||
|
type: isProp ? td[2].replace(/`/g, '').toLowerCase() : 'event'
|
||||||
|
};
|
||||||
|
|
||||||
|
tag.attributes[attrName] = attr;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tags;
|
||||||
|
}
|
137
packages/vant-markdown-vetur/src/index.ts
Normal file
137
packages/vant-markdown-vetur/src/index.ts
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
import { join } from 'path';
|
||||||
|
import { mdParser } from './md-parser';
|
||||||
|
import { codegen, Tag, Attribute } from './codegen';
|
||||||
|
import {
|
||||||
|
PathLike,
|
||||||
|
statSync,
|
||||||
|
mkdirSync,
|
||||||
|
existsSync,
|
||||||
|
readdirSync,
|
||||||
|
readFileSync,
|
||||||
|
writeFileSync
|
||||||
|
} from 'fs';
|
||||||
|
|
||||||
|
export function parseText(input: string) {
|
||||||
|
const ast = mdParser(input);
|
||||||
|
|
||||||
|
return codegen(ast);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Options = {
|
||||||
|
// 需要解析的文件夹路径
|
||||||
|
path: PathLike;
|
||||||
|
// 文件匹配正则
|
||||||
|
test: RegExp;
|
||||||
|
// 输出目录
|
||||||
|
outputDir?: string;
|
||||||
|
// 递归的目录最大深度
|
||||||
|
maxDeep?: number;
|
||||||
|
// 解析出来的组件名前缀
|
||||||
|
tagPrefix?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ParseResult = {
|
||||||
|
tags: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
description: string;
|
||||||
|
attributes: string[];
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
attributes: Record<string, Attribute>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultOptions = {
|
||||||
|
maxDeep: Infinity,
|
||||||
|
tagPrefix: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
export function parse(options: Options) {
|
||||||
|
options = {
|
||||||
|
...defaultOptions,
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
|
||||||
|
const result: ParseResult = {
|
||||||
|
tags: {},
|
||||||
|
attributes: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
function putResult(componentName: string, component: Tag) {
|
||||||
|
componentName = options.tagPrefix + componentName;
|
||||||
|
const attributes = Object.keys(component.attributes);
|
||||||
|
const tag = {
|
||||||
|
description: component.description,
|
||||||
|
attributes
|
||||||
|
};
|
||||||
|
|
||||||
|
result.tags[componentName] = tag;
|
||||||
|
attributes.forEach(key => {
|
||||||
|
result.attributes[`${componentName}/${key}`] = component.attributes[key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function recursiveParse(options: Options, deep: number) {
|
||||||
|
if (options.maxDeep && deep > options.maxDeep) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
deep++;
|
||||||
|
const files = readdirSync(options.path);
|
||||||
|
files.forEach(item => {
|
||||||
|
const currentPath = join(options.path.toString(), item);
|
||||||
|
const stats = statSync(currentPath);
|
||||||
|
if (stats.isDirectory()) {
|
||||||
|
recursiveParse(
|
||||||
|
{
|
||||||
|
...options,
|
||||||
|
path: currentPath
|
||||||
|
},
|
||||||
|
deep
|
||||||
|
);
|
||||||
|
} else if (stats.isFile() && options.test.test(item)) {
|
||||||
|
const file = readFileSync(currentPath);
|
||||||
|
|
||||||
|
const tags = parseText(file.toString());
|
||||||
|
|
||||||
|
if (tags.default) {
|
||||||
|
// one tag
|
||||||
|
putResult(currentPath.split('/').slice(-2)[0], tags.default);
|
||||||
|
} else {
|
||||||
|
Object.keys(tags).forEach(key => {
|
||||||
|
putResult(key, tags[key]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
recursiveParse(options, 0);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseAndWrite(options: Options) {
|
||||||
|
const { tags, attributes } = parse(options);
|
||||||
|
|
||||||
|
if (!options.outputDir) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isExist = existsSync(options.outputDir);
|
||||||
|
if (!isExist) {
|
||||||
|
mkdirSync(options.outputDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeFileSync(join(options.outputDir, 'tags.json'), JSON.stringify(tags, null, 2));
|
||||||
|
writeFileSync(
|
||||||
|
join(options.outputDir, 'attributes.json'),
|
||||||
|
JSON.stringify(attributes, null, 2)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
parse,
|
||||||
|
parseText,
|
||||||
|
parseAndWrite
|
||||||
|
};
|
109
packages/vant-markdown-vetur/src/md-parser.ts
Normal file
109
packages/vant-markdown-vetur/src/md-parser.ts
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
/* eslint-disable no-cond-assign */
|
||||||
|
const TITLE_REG = /^(#+)\s+([^\n]*)/;
|
||||||
|
const TABLE_REG = /^\|.+\n\|\s*-+/;
|
||||||
|
const TD_REG = /\s*`[^`]+`\s*|([^|`]+)/g;
|
||||||
|
const TABLE_SPLIT_LINE_REG = /^\|\s*-/;
|
||||||
|
|
||||||
|
interface TableContent {
|
||||||
|
head: Array<string>;
|
||||||
|
body: Array<Array<string>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SimpleMdAst {
|
||||||
|
type: string;
|
||||||
|
content?: string;
|
||||||
|
table?: TableContent;
|
||||||
|
level?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Artical extends Array<SimpleMdAst> {}
|
||||||
|
|
||||||
|
function readLine(input: string) {
|
||||||
|
const end = input.indexOf('\n');
|
||||||
|
|
||||||
|
return input.substr(0, end !== -1 ? end : input.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
function tableParse(input: string) {
|
||||||
|
let start = 0;
|
||||||
|
let isHead = true;
|
||||||
|
|
||||||
|
const end = input.length;
|
||||||
|
const table: TableContent = {
|
||||||
|
head: [],
|
||||||
|
body: []
|
||||||
|
};
|
||||||
|
|
||||||
|
while (start < end) {
|
||||||
|
const target = input.substr(start);
|
||||||
|
const line = readLine(target);
|
||||||
|
|
||||||
|
if (!/^\|/.test(target)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TABLE_SPLIT_LINE_REG.test(target)) {
|
||||||
|
isHead = false;
|
||||||
|
} else if (isHead) {
|
||||||
|
// temp do nothing
|
||||||
|
} else {
|
||||||
|
const matched = line.trim().match(TD_REG);
|
||||||
|
|
||||||
|
if (matched) {
|
||||||
|
table.body.push(
|
||||||
|
matched.map(i => {
|
||||||
|
if (i.indexOf('|') !== 0) {
|
||||||
|
return i
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
.split('|')
|
||||||
|
.map(s => s.trim())
|
||||||
|
.join('|');
|
||||||
|
}
|
||||||
|
return i.trim();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
start += line.length + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
table,
|
||||||
|
usedLength: start
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mdParser(input: string): Array<SimpleMdAst> {
|
||||||
|
const artical = [];
|
||||||
|
let start = 0;
|
||||||
|
const end = input.length;
|
||||||
|
|
||||||
|
while (start < end) {
|
||||||
|
const target = input.substr(start);
|
||||||
|
|
||||||
|
let match;
|
||||||
|
if ((match = TITLE_REG.exec(target))) {
|
||||||
|
artical.push({
|
||||||
|
type: 'title',
|
||||||
|
content: match[2],
|
||||||
|
level: match[1].length
|
||||||
|
});
|
||||||
|
|
||||||
|
start += match.index + match[0].length;
|
||||||
|
} else if ((match = TABLE_REG.exec(target))) {
|
||||||
|
const { table, usedLength } = tableParse(target.substr(match.index));
|
||||||
|
artical.push({
|
||||||
|
type: 'table',
|
||||||
|
table
|
||||||
|
});
|
||||||
|
|
||||||
|
start += match.index + usedLength;
|
||||||
|
} else {
|
||||||
|
start += readLine(target).length + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return artical;
|
||||||
|
}
|
9
packages/vant-markdown-vetur/tsconfig.json
Normal file
9
packages/vant-markdown-vetur/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig",
|
||||||
|
"include": ["src/*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"outDir": "lib",
|
||||||
|
"noEmit": false
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user