Skip to content

Commit

Permalink
[docs] hyperledger#89: Add scripts, Vue component, parser extension, …
Browse files Browse the repository at this point in the history
…update tutorial

Signed-off-by: 6r1d <vic.6r1d@gmail.com>
  • Loading branch information
6r1d committed Sep 15, 2022
1 parent d8374a8 commit b174cc6
Show file tree
Hide file tree
Showing 23 changed files with 2,188 additions and 5 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ dist
/src/flymd.md
/src/flymd.html
/src/*.temp
/src/snippets
15 changes: 13 additions & 2 deletions .vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { defineConfig, UserConfig, DefaultTheme } from 'vitepress'
import Windi from 'vite-plugin-windicss'
import footnote from 'markdown-it-footnote'
import customHighlight from './plugins/highlight'
import path from 'path'
import { resolve } from 'path'
import { VitePWA } from 'vite-plugin-pwa'
import { snippets_plugin } from './snippet_tabs'

async function themeConfig() {
const cfg: UserConfig = {
Expand Down Expand Up @@ -157,6 +158,15 @@ function getGuideSidebar(): DefaultTheme.SidebarGroup[] {
},
],
},
{
text: 'Documenting Iroha',
items: [
{
text: 'Code snippets',
link: '/documenting/snippets',
},
],
},
]
}

Expand All @@ -172,7 +182,7 @@ export default defineConfig({
lang: 'en-US',
vite: {
plugins: [
Windi({ config: path.resolve(__dirname, '../windi.config.ts') }),
Windi({ config: resolve(__dirname, '../windi.config.ts') }),
VitePWA({
// Based on: https://evilmartians.com/chronicles/how-to-favicon-in-2021-six-files-that-fit-most-needs
manifest: {
Expand Down Expand Up @@ -207,6 +217,7 @@ export default defineConfig({
markdown: {
config(md) {
md.use(footnote)
snippets_plugin(md, {'snippet_root': resolve(__dirname, '../src/snippets/')})
},
},

Expand Down
336 changes: 336 additions & 0 deletions .vitepress/snippet_tabs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,336 @@
"use strict";

import { existsSync, readFileSync } from "fs";
import { resolve, join, basename, dirname } from "path";

const COMP_TAG = "SnippetTabs";

class SnippetAccessError extends Error {
constructor(message: string) {
super(message);
this.name = "SnippetAccessError";
}
}

/**
* A number in {-1, 0, 1} set.
*
* 1 means the tag is opening
* 0 means the tag is self-closing
* -1 means the tag is closing
*/
type TokenNesting = -1 | 0 | 1;

/**
* Redefine the Token type from Markdown-it to avoid importing CJS
* https://markdown-it.github.io/markdown-it/#Token
*
* @typedef {object} Token
*/
type Token = {
// Source map info. Format: [ line_begin, line_end ].
map: number[];
// Used in the renderer to calculate line breaks.
// True for block-level tokens.
// False for inline tokens.
block: boolean;
// '*' or '_' for emphasis, fence string for fence, etc.
markup: string;
// Info string for "fence" tokens
info: string;
// Level change marker
nesting: TokenNesting;
};

/**
// Redefine the StateBlock type from Markdown-it that represents a parser state
// to avoid importing CJS
*
* @typedef {object} StateBlock
*/
type StateBlock = {
line: number;
push(arg0: string, arg1: string, arg2: number): Token;
skipSpaces(pos: number): number;
src: string;
bMarks: number[];
eMarks: number[];
tShift: number[];
sCount: number[];
lineMax: number;
parentType: string;
blkIndent: number;
};

/**
// Redefine a type that (vaguely) represents Markdown-it
*
* @typedef {object} MarkdownIt
*/
type MarkdownIt = {
block: any;
renderer: any;
};

/**
* A function that splits string by new lines
* and trims each of those lines.
*
* @param {string} input - an input string
* @returns {string[]} a list of trimmed lines
*/
function splitAndTrimLines(input: string): Array<string> {
return input.split(/\r?\n/).map((item) => item.trim());
}

/**
* A function that composes a new string out of file paths
* provided to it.
*
* Used by the "snippetLocRender" function to render
* the contents of a Vue component slot.
* This component will then display the snippets.
*
* @param {string[]} paths - a list of file path strings
* @returns {string} a string, composed of file contents
*/
function pathListToStr(paths: string[]): string {
let result = "";
for (let pathId = 0; pathId < paths.length; pathId++) {
const linePath = paths[pathId];
try {
result += readFileSync(linePath);
} catch (err) {
const msg =
`Unable to read a file.\n` +
`Filename: "${basename(linePath)}".\n` +
`Directory path: "${dirname(linePath)}".\n\n` +
`Ensure it exists, its location is correct and its access rights allow to read it.\n` +
`If you did not download the snippets, use the "npm run get_snippets" ` +
`or "pnpm run get_snippets" command.\n` +
`Read more in "Documenting Iroha" → "Code snippets" part of the tutorial.` +
`\n`;
throw new SnippetAccessError(msg);
}
}
return result;
}

/**
* A function that initializes a snippet group Markdown-it plugin.
*/
export function snippets_plugin(md: MarkdownIt, options: Record<string, any>) {
/**
* A function that validates snippet parameters and allows it to be rendered.
* If a path is incorrect, rendering won't happen.
*
* @param {string} params - a parameter string that points out a path to the snippets
* @returns {bool} - whether the snippet directory exists or not
*/
function validateDefault(params: string): boolean {
const snippetPath = resolve(params.trim());
let pathExists = false;
try {
pathExists = existsSync(snippetPath);
} catch (fsLookupError) {
console.error(`Snippet directory lookup error: ${fsLookupError.message}`);
}
return pathExists;
}

/**
* Render a section with a pre-defined wrapper tag
*
* @param {string} tokens - a list of Markdown-It token instances
*/
function snippetRender(tokens: Array<Token>, idx: number): string {
if (tokens[idx].nesting === 1) {
// Render an opening tag
return `<${COMP_TAG}>\n`;
} else {
// Render an closing tag
return `</${COMP_TAG}>\n`;
}
}

/**
* Render slots inside the SnippetTabs Vue component.
*
* Locates the internal path or an updated one,
* outputs the contents of files inside.
*
* @param {Array<Token>} tokens - array of Markdown token instances
* @param {number} idx
* @returns {string} - render results
*/
function snippetLocRender(tokens: Array<Token>, idx: number): string {
const pathStr =
options.snippet_root ||
join(options.snippet_root, tokens[idx - 1].info.trim());
const snippetPath: string = resolve(pathStr);
const paths: string[] = splitAndTrimLines(tokens[idx].info.trim()).map(
(filename) => {
return join(snippetPath, filename);
}
);
return `${pathListToStr(paths)}\n`;
}

options = options || {};

let min_markers: number = 3,
marker_str: string = "+",
marker_char: number = marker_str.charCodeAt(0),
marker_len: number = marker_str.length,
validate: Function = options.validate || validateDefault,
render: Function = snippetRender;

if (
!options.hasOwnProperty("snippet_root") ||
options.snippet_root.constructor.name !== "String"
) {
const errTxt =
"Incorrect configuration. " +
"A correct value for snippet_root is required for snippet_tabs plugin.";
throw new Error(errTxt);
}

function snippet_container(
state: StateBlock,
startLine: number,
endLine: number,
silent: boolean
) {
var pos: number,
nextLine: number,
marker_count: number,
markup: string,
params: string,
token: Token,
old_parent: string,
old_line_max: number,
auto_closed = false,
start: number = state.bMarks[startLine] + state.tShift[startLine],
max: number = state.eMarks[startLine];

// Check out the first character quickly
// to filter out most of non-containers
if (marker_char !== state.src.charCodeAt(start)) {
return false;
}

// Check out the rest of the marker string
for (pos = start + 1; pos <= max; pos++) {
if (marker_str[(pos - start) % marker_len] !== state.src[pos]) {
break;
}
}

marker_count = Math.floor((pos - start) / marker_len);
if (marker_count < min_markers) {
return false;
}
pos -= (pos - start) % marker_len;

markup = state.src.slice(start, pos);
params = state.src.slice(pos, max);
if (!validate(params, markup)) {
return false;
}

// Since start is found, we can report success here in validation mode
if (silent) return true;

// Search for the end of the block
nextLine = startLine;

for (;;) {
nextLine++;
if (nextLine >= endLine) {
// Non-closed block should be autoclosed by end of document.
// Also, block seems to be
// automatically closed by the end of a parent one.
break;
}

start = state.bMarks[nextLine] + state.tShift[nextLine];
max = state.eMarks[nextLine];

if (start < max && state.sCount[nextLine] < state.blkIndent) {
// non-empty line with negative indent should stop the list:
// - ```
// test
break;
}

if (marker_char !== state.src.charCodeAt(start)) {
continue;
}

if (state.sCount[nextLine] - state.blkIndent >= 4) {
// closing fence should be indented less than 4 spaces
continue;
}

for (pos = start + 1; pos <= max; pos++) {
if (marker_str[(pos - start) % marker_len] !== state.src[pos]) {
break;
}
}

// closing code fence must be at least as long as the opening one
if (Math.floor((pos - start) / marker_len) < marker_count) {
continue;
}

// make sure tail has spaces only
pos -= (pos - start) % marker_len;
pos = state.skipSpaces(pos);

if (pos < max) {
continue;
}

// found!
auto_closed = true;
break;
}

old_parent = state.parentType;
old_line_max = state.lineMax;
state.parentType = "snippets";

// Prevent the lazy continuations from ever going past an end marker
state.lineMax = nextLine;

token = state.push("snippets_open", "div", 1);
token.markup = markup;
token.block = true;
token.info = params;
token.map = [startLine, nextLine];

token = state.push("snippet_locations", "div", 1);
token.markup = markup;
token.block = true;
token.info = state.src.slice(
state.bMarks[startLine + 1],
state.bMarks[nextLine]
);
token.map = [startLine, nextLine];

token = state.push("snippets_close", "div", -1);
token.markup = state.src.slice(start, pos);
token.block = true;

state.parentType = old_parent;
state.lineMax = old_line_max;
state.line = nextLine + (auto_closed ? 1 : 0);

return true;
}

md.block.ruler.before("fence", "snippets", snippet_container, {});
md.renderer.rules["snippets_open"] = render;
md.renderer.rules["snippets_close"] = render;
md.renderer.rules["snippet_locations"] = snippetLocRender;
}
Loading

0 comments on commit b174cc6

Please sign in to comment.