From bc2f6242b2ee5cd6ae56b14be3e78b44edfdd1a1 Mon Sep 17 00:00:00 2001 From: Niccolo Borgioli Date: Thu, 16 Nov 2023 13:29:00 +0100 Subject: [PATCH] initial push --- .gitignore | 176 ++++++++++++++++++++++++++++++++++++++++++++++++++ .nvmrc | 1 + README.md | 23 +++++++ bun.lockb | Bin 0 -> 1696 bytes package.json | 47 ++++++++++++++ src/index.ts | 69 ++++++++++++++++++++ tsconfig.json | 18 ++++++ 7 files changed, 334 insertions(+) create mode 100644 .gitignore create mode 100644 .nvmrc create mode 100644 README.md create mode 100755 bun.lockb create mode 100644 package.json create mode 100644 src/index.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ab5afb2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,176 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +\*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +\*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +\*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +\*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.cache +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +.cache/ + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp +.cache + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.\* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store + diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..805b5a4 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v20.9.0 diff --git a/README.md b/README.md new file mode 100644 index 0000000..044f19f --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# Markdown import plugin + +This is a `markdown-it` plugin to include/import raw files from your filesystem. + +## Features + +- No dependencies +- Recursive import +- Import whatever file +- Customizable RegEx + +## Installation + +```bash +npm install @nicco.io/markdown-it-import +``` + +## Similar works + +There are two very similar plugins, which this one is def. inspired by, however while the one can only import `.md` files, the other cannot select single lines. + +- https://github.com/camelaissani/markdown-it-include +- https://github.com/h-hg/markdown-it-import diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..648e70c905cb2832c418a225d87be1e79f169e2b GIT binary patch literal 1696 zcmY#Z)GsYA(of3F(@)JSQ%EY!;{sycoc!eMw9K4T-L(9o+{6;yG6OCq1_p+yvwlB# z8q@dl?5U~ek801{`OGw7l5yRU`+T}H4@a!sJXe4Xs0av{Aru1#8r=Zpm%|i*`9cf~ z4KhF$Cy)lR8MuISb=DsBXHs#mtKX!b?GSpcG+(trN4qsOi4kf7xfIA3AfO7w)e&h8YBi2Cu{<$dSrdb<{-rX@&AK4k{H8@RKv&sF&Eirme>U! z`U+AfUOBf#kfE@`dCr$Ek&^`?>wn%}^QBvD(b^{F)=55RC!0F@F~2QI66l?@WQka{ z>F26G!*1eMfbIJ~c0t zpZHPueR$Uvbuvnn+|O-I2*Au%U2 zJug2Em`C8?-+u@I34p^L>Uj<*&1F++Y-9)2i^CLo3D&%uUEQk)Dn+>FN=14" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..e091e1d --- /dev/null +++ b/src/index.ts @@ -0,0 +1,69 @@ +import fs from 'node:fs' +import path from 'node:path' +import process from 'node:process' + +const defaultOptions = { + matcher: /@import\((?.+)\)(\s*?\[(?\d+-\d+)\])?/g, + root: process.cwd(), +} + +export type Options = Partial + +export function importPlugin(md: any, options: Options = {}) { + // Options + const o = Object.assign(defaultOptions, options) + + // Parser + function parse(code: string, alreadyVisited: string[] = []) { + if (!o.matcher.global) throw new Error('RegExp must be global') + o.matcher.lastIndex = 0 // Reset Regexp + const newFiles = [] + + while (true) { + const match = o.matcher.exec(code) + if (!match) break + + // Get groups + const file = match.groups?.['file']?.trim() + if (!file) throw new Error('Regexp must expose a named group "file"') + const range = match.groups?.['range'] + ?.trim() + .split('-') + .map((n) => parseInt(n)) + + // Load content + const filename = path.resolve(o.root, file) + if (alreadyVisited.includes(filename)) throw new Error(`cycles are not allowed, already parsed "${filename}"`) + newFiles.push(filename) + const exists = fs.existsSync(filename) + if (!exists) throw new Error(`cannot locate file "${filename}"`) + let contents = fs.readFileSync(filename, 'utf-8') + + // Apply line range + if (range) { + const lines = contents.split('\n') + const maxLines = lines.length + const start = range[0] + const end = range[1] + if (start === undefined || end === undefined) throw new Error(`invalid range "${match.groups?.['range']}"`) + if (end < start) throw new Error(`end position "${end}" cannot be smaller than start "${start}"`) + if (start < 1) throw new Error(`start position "${start}" needs to be at least 1`) + if (end > maxLines) throw new Error(`end position "${end}" is higher than the file contents "${maxLines}"`) + contents = lines.slice(start - 1, end).join('\n') + } + + // Recursion + contents = parse(contents, [...alreadyVisited, ...newFiles]) + + // Replace + code = code.slice(0, match.index) + contents + code.slice(match.index + match[0].length, code.length) + } + + return code + } + + // Mount hook + md.core.ruler.before('normalize', 'include', (state: any) => { + state.src = parse(state.src) + }) +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..0771619 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "lib": ["ESNext"], + "module": "esnext", + "target": "esnext", + "moduleResolution": "bundler", + "moduleDetection": "force", + "allowImportingTsExtensions": true, + "emitDeclarationOnly": true, + "outDir": "./dist", + "composite": true, + "strict": true, + "types": [ + "bun-types" // add Bun global + ] + }, + "include": ["./src"] +}