From c5514f73b85a5345cfcc74c0f5701dd4cf55fe8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=98=89=E6=B6=B5?= Date: Thu, 22 Aug 2019 11:37:13 +0800 Subject: [PATCH] chore: add vant-markdown-vetur package --- packages/vant-markdown-vetur/README.md | 24 +++ packages/vant-markdown-vetur/package.json | 16 ++ packages/vant-markdown-vetur/src/codegen.ts | 75 ++++++++++ packages/vant-markdown-vetur/src/index.ts | 137 ++++++++++++++++++ packages/vant-markdown-vetur/src/md-parser.ts | 109 ++++++++++++++ packages/vant-markdown-vetur/tsconfig.json | 9 ++ 6 files changed, 370 insertions(+) create mode 100644 packages/vant-markdown-vetur/README.md create mode 100644 packages/vant-markdown-vetur/package.json create mode 100644 packages/vant-markdown-vetur/src/codegen.ts create mode 100644 packages/vant-markdown-vetur/src/index.ts create mode 100644 packages/vant-markdown-vetur/src/md-parser.ts create mode 100644 packages/vant-markdown-vetur/tsconfig.json diff --git a/packages/vant-markdown-vetur/README.md b/packages/vant-markdown-vetur/README.md new file mode 100644 index 000000000..b48663a47 --- /dev/null +++ b/packages/vant-markdown-vetur/README.md @@ -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; +} +``` diff --git a/packages/vant-markdown-vetur/package.json b/packages/vant-markdown-vetur/package.json new file mode 100644 index 000000000..2564e6009 --- /dev/null +++ b/packages/vant-markdown-vetur/package.json @@ -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" + } +} diff --git a/packages/vant-markdown-vetur/src/codegen.ts b/packages/vant-markdown-vetur/src/codegen.ts new file mode 100644 index 000000000..111ddb9ac --- /dev/null +++ b/packages/vant-markdown-vetur/src/codegen.ts @@ -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; + description: string; + defaults?: Array; + subtags?: Array; +} + +export type Attribute = { + description: string; + type?: string; + options?: Array; +} + +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 = {}; + 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; +} diff --git a/packages/vant-markdown-vetur/src/index.ts b/packages/vant-markdown-vetur/src/index.ts new file mode 100644 index 000000000..8cec37615 --- /dev/null +++ b/packages/vant-markdown-vetur/src/index.ts @@ -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; +}; + +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 +}; diff --git a/packages/vant-markdown-vetur/src/md-parser.ts b/packages/vant-markdown-vetur/src/md-parser.ts new file mode 100644 index 000000000..4f6be3ba4 --- /dev/null +++ b/packages/vant-markdown-vetur/src/md-parser.ts @@ -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; + body: Array>; +} + +interface SimpleMdAst { + type: string; + content?: string; + table?: TableContent; + level?: number; +} + +export interface Artical extends Array {} + +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 { + 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; +} diff --git a/packages/vant-markdown-vetur/tsconfig.json b/packages/vant-markdown-vetur/tsconfig.json new file mode 100644 index 000000000..dc75009d2 --- /dev/null +++ b/packages/vant-markdown-vetur/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig", + "include": ["src/*"], + "compilerOptions": { + "module": "commonjs", + "outDir": "lib", + "noEmit": false + } +}