add article vue2md

This commit is contained in:
Jordan Blasenhauer 2024-05-19 18:35:50 +02:00
parent c5ffbde7c7
commit 506023afc0
7 changed files with 378 additions and 0 deletions

View file

@ -0,0 +1,37 @@
<script setup>
/**
@name Form/Input.vue
@description This component is custom input to be used with forms.
@example
{
name : "my subtitle"
value : "P@ssw0rd"
type : "password"
}
@param {string} name
@param {string} [value=""]
@param {string} [type="text"]
*/
const props = defineProps({
// id && value && method
name: {
type: String,
required: true,
},
value: {
type: String,
required: false,
default: "",
},
type: {
type: String,
required: false,
default: "type",
},
});
</script>
<template>
<input :type="props.type" :name="props.name" :value="props.value" />
</template>

View file

@ -0,0 +1,33 @@
<script setup>
/**
@name Main.vue
@description This component is my main layout for my app.
@example
{
title : "My app",
subtitle : "my subtitle"
}
@param {string} title
@param {string} [subtitle=""] - More information if needed
*/
const props = defineProps({
// id && value && method
title: {
type: String,
required: true,
},
subtitle: {
type: String,
required: false,
default: "",
},
});
</script>
<template>
<div>
<h1>{{ props.title }}</h1>
<p v-if="props.subtitle">{{ props.subtitle }}</p>
</div>
</template>

View file

@ -0,0 +1,40 @@
## Form
### Input.vue
This component is custom input to be used with forms.
#### Parameters
* `name` **[string][4]**&#x20;
* `value` **[string][4]** (optional, default `""`)
* `type` **[string][4]** (optional, default `"text"`)
#### Examples
```javascript
{
name : "my subtitle"
value : "P@ssw0rd"
type : "password"
}
```
## Main.vue
This component is my main layout for my app.
### Parameters
* `title` **[string][4]**&#x20;
* `subtitle` **[string][4]** More information if needed (optional, default `""`)
### Examples
```javascript
{
title : "My app",
subtitle : "my subtitle"
}
```

Binary file not shown.

20
vue2md/output/Input.md Normal file
View file

@ -0,0 +1,20 @@
## Form/Input.vue
This component is custom input to be used with forms.
### Parameters
* `name` **[string][4]**&#x20;
* `value` **[string][4]** (optional, default `""`)
* `type` **[string][4]** (optional, default `"text"`)
### Examples
```javascript
{
name : "my subtitle"
value : "P@ssw0rd"
type : "password"
}
```

18
vue2md/output/Main.md Normal file
View file

@ -0,0 +1,18 @@
## Main.vue
This component is my main layout for my app.
### Parameters
* `title` **[string][4]**&#x20;
* `subtitle` **[string][4]** More information if needed (optional, default `""`)
### Examples
```javascript
{
title : "My app",
subtitle : "my subtitle"
}
```

230
vue2md/vue2md.js Normal file
View file

@ -0,0 +1,230 @@
const fs = require("fs");
const path = require("path");
// Merge all components md on this file name
const finalFile = "COMPONENTS.md";
// Where we have all SFC components
const inputFolder = path.join(__dirname, "components");
// Where we want to output md components file
const ouputFolder = path.join(__dirname, "output");
// Check that input folder exists
if (!fs.existsSync(inputFolder)) {
console.error("Input folder does not exist");
process.exit(1);
}
// Create the output folder if it doesn't exist
if (!fs.existsSync(ouputFolder)) {
fs.mkdirSync(ouputFolder);
}
// Remove previous content of the output folder
fs.readdirSync(ouputFolder).forEach((file) => {
fs.unlinkSync(path.join(ouputFolder, file));
});
// Allow to get all subfolders from inputFolder
function flatten(lists) {
return lists.reduce((a, b) => a.concat(b), []);
}
function getDirectories(srcpath) {
return fs
.readdirSync(srcpath)
.map((file) => path.join(srcpath, file))
.filter((path) => fs.statSync(path).isDirectory());
}
function getDirectoriesRecursive(srcpath) {
return [
srcpath,
...flatten(getDirectories(srcpath).map(getDirectoriesRecursive)),
];
}
// Get the script part of a Vue file and create a JS file
function vue2js() {
const folders = getDirectoriesRecursive(inputFolder);
// Read every subfolders from the input folder and get all files
folders.forEach((folder) => {
const files = fs.readdirSync(path.join(folder), {
withFileTypes: true,
});
files.forEach((file) => {
// Get only .vue file
if (file.isFile() && file.name.endsWith(".vue")) {
const src = path.join(folder, file.name);
const data = fs.readFileSync(src, "utf8");
// Get only the content between <script setup> and </script> tag
const script = data.match(/<script setup>([\s\S]*?)<\/script>/g);
// I want to remove the <script setup> and </script> tags
script[0] = script[0]
.replace("<script setup>", "")
.replace("</script>", "");
// Create a file on the output folder with the same name but with .js extension
const fileName = file.name.replace(".vue", ".js");
const dest = path.join(ouputFolder, fileName);
fs.writeFileSync(dest, script[0], "utf8");
}
});
});
}
// Run a command to render markdown from JS files
function js2md() {
// Get all files from the output folder
const files = fs.readdirSync(ouputFolder, { withFileTypes: true });
// Create a markdown file for each JS file
files.forEach((file) => {
// Run a process `documentation build <filename> -f md > <filename>.md
const command = `documentation build ${path.join(
ouputFolder,
file.name
)} -f md > ${path.join(ouputFolder, file.name.replace(".js", ".md"))}`;
// Run the command
const { execSync } = require("child_process");
execSync(command, (error, stdout, stderr) => {
if (error) {
// console.error(`exec error: ${error}`);
return;
}
// console.log(`stdout: ${stdout}`);
// console.error(`stderr: ${stderr}`);
});
});
// Remove all JS files when all processes are done
files.forEach((file) => {
fs.unlinkSync(path.join(ouputFolder, file.name));
});
}
// Format each md file to remove specific content
function mergeMd() {
// Get all files from the output folder
const files = fs.readdirSync(ouputFolder, { withFileTypes: true });
files.forEach((file, id) => {
let data = fs.readFileSync(path.join(ouputFolder, file.name), "utf8");
// In case we have "[1]:", remove everything after
data = data.replace(/\[\d+\]:[\s\S]*?$/g, "");
// Remove ### Table of contents
data = data.replace("### Table of Contents", "");
// Remove everything after the first ## tag
const index = data.indexOf("## ");
data = data.substring(index);
fs.writeFileSync(path.join(ouputFolder, file.name), data, "utf8");
});
// Create order using the tag title path of each file
// For example using Form/Input.vue
const order = [];
files.forEach((file, id) => {
// Get the title from first line
const data = fs.readFileSync(path.join(ouputFolder, file.name), "utf8");
const filePath = data.split("\n")[0].replace("## ", "");
order.push({ path: filePath, file: file });
});
// Sort by path
order.sort((a, b) => {
return a.path.localeCompare(b.path);
});
// Create the md file to merge
const merge = path.join(ouputFolder, finalFile);
fs.writeFileSync(merge, "", "utf8");
// Append each file in order
order.forEach((item) => {
let data = fs.readFileSync(path.join(ouputFolder, item.file.name), "utf8");
fs.appendFileSync(merge, data, "utf8");
});
}
// Format merge file
function formatMd() {
// Create a md file to merge
const merge = path.join(ouputFolder, finalFile);
// Get data from merge
let data = fs.readFileSync(merge, "utf8");
let isLevel = true;
let currAttemps = 0;
const maxAttemps = 6;
while (isLevel && currAttemps < maxAttemps) {
currAttemps++;
const titles = [];
let tag = "#";
for (let i = 0; i < currAttemps; i++) {
tag += "#";
}
tag += " ";
// Each time, get the first level title and add it to the titles array
data.split("\n").forEach((line) => {
if (line.startsWith(tag) && line.includes("/")) {
const firstLevel = line.split("/")[0];
if (!titles.includes(firstLevel.replace(tag, "").trim()))
titles.push(firstLevel.replace(tag, ""));
}
});
// Create a top title at the first occurrence
// And remove from component the first level string
titles.forEach((title) => {
let isTitleSet = false;
data.split("\n").forEach((line) => {
// For title
if (line.startsWith(tag) && line.includes("/")) {
// Add a top title before the current line
if (!isTitleSet && line.includes(`${title}/`)) {
data = data.replace(
line,
`${tag} ${title}\n\n${line
.replace(tag, "#" + tag)
.replace(`${title}/`, "")}`
);
isTitleSet = true;
return;
}
if (line.includes(`${title}/`)) {
data = data.replace(
line,
line.replace(tag, "#" + tag).replace(`${title}/`, "")
);
}
return;
}
});
});
}
// Update the child of .vue component title to keep title levels consistency
let componentTag = "";
let dataSplit = data.split("\n");
data.split("\n").forEach((line, id) => {
if (line.startsWith("#") && line.includes(".vue")) {
componentTag = line.split(" ")[0];
return;
}
if (
(line.startsWith("#") && line.includes("Parameters")) ||
(line.startsWith("#") && line.includes("Examples"))
) {
const elTag = line.split(" ")[0];
// get line per id
const updateLine = line.replace(elTag, `${componentTag}#`);
dataSplit[id] = updateLine;
}
});
// Update the data with split
data = dataSplit.join("\n");
fs.writeFileSync(merge, data, "utf8");
}
vue2js();
js2md();
mergeMd();
formatMd();