diff --git a/README.md b/README.md index 8c557a1..06d1f92 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,17 @@ # codegrid-markdown -CodeGrid-specified markdown processor. + +[CodeGrid](https://www.codegrid.net/)の記事で使われているMarkdown拡張記法を含んだ、[markded](https://marked.js.org/)ベースのMarkdownライブラリーです。 ## 使い方 -### Install + +### パッケージのインストール ```sh npm i codegrid-markdown ``` -### Node +### Node.jsスクリプトでの実行 + ```javascript import fs from 'node:fs'; import CodeGridMarkdown from 'codegrid-markdown'; @@ -21,7 +24,8 @@ const str = fs.readFileSync(import.meta.dirname + '/cg.md', 'utf-8'); const htmlStr = CGMDRenderer.render(str); ``` -### CLI +### CLIでの実行 + ```sh cgmd ./path/to/your.md @@ -32,16 +36,18 @@ cgmd ./path/to/your.md -o ./path/to/your.html cgmd '# foo' ``` -## 記法 +## CGMD記法 + +以下の2パターンの拡張があります。 -- cgmdとしての拡張記法 -- mdの拡張記法 +- CodeGridで使うコラムや注釈を実現するCGMD拡張ブロック +- Markdownのコードブロックにタイトルを付与する拡張 -この2パターンの拡張があります。 +CGMD拡張ブロックは、互いにネストできません。 -cgmdパターンは、通常のMarkdownの中に混ぜて書くことができ、`[foo]通常のMarkdownテキスト[/foo]`の形式で記述します。 +### note -### cgmd#note +本文を`.Note`で包み、内部の見出しをタイトルとして表示する注釈ボックスを作成します。 ``` [note] @@ -60,7 +66,9 @@ cgmdパターンは、通常のMarkdownの中に混ぜて書くことができ ``` -### cgmd#column +### column + +`.Column`で囲んでコラム風の補足ブロックに仕立てます。 ``` [column] @@ -79,7 +87,9 @@ cgmdパターンは、通常のMarkdownの中に混ぜて書くことができ ``` -### cgmd#demo +### demo + +`.Demo`コンテナを生成し、インラインフレームでのデモ表示を行います。ソースリンクの設置や遅延iframeにも対応します。 ``` [demo] @@ -134,7 +144,9 @@ cgmdパターンは、通常のMarkdownの中に混ぜて書くことができ [/demo] ``` -### cgmd#imgbox +### imgbox + +`.ImgBox`の`
`に変換し、タイトル、画像とキャプションをひとまとまりに表示します。 ``` [imgbox] @@ -155,7 +167,9 @@ cgmdパターンは、通常のMarkdownの中に混ぜて書くことができ ``` -### cgmd#tree +### tree + +Markdownのリストを`
`付きのインタラクティブなファイルツリーに組み替えます。 ``` [tree] @@ -206,34 +220,9 @@ cgmdパターンは、通常のMarkdownの中に混ぜて書くことができ ``` -### cgmd#jade - -``` -[jade] -ul - li jadeが - li そのまま書けます - -p またの名をpugとも言う -[/jade] -``` - -↓ - -```html - - -

またの名をpugとも言う

-``` - -これらの記法は、互いにネストすることはできません。 +### code(タイトルつき) -次に、mdパターン。 - -### md#code +Markdownのコードブロックにタイトルを付与する拡張です。
 ```html#素敵なdiv
@@ -259,5 +248,34 @@ GFMのコードブロックで、Syntaxに続けて`#コードのタイトル`
 コードのタイトル指定がない場合、通常のMarkdownのコードブロックとして処理されます。
 
 
+## コードの修正・機能追加
+
+メンテナンスモードに入っていますが、バグ修正や軽微な機能追加を行いたい場合は以下を参考にしてください。
+
+### ランタイムの全体像
+
+`lib/index.js`がトークナイザ・レンダラ・トランスフォーマを束ねてMarkdownをHTMLに変換します。新しい機能はここから呼び出される既存の拡張点を利用してください。処理の流れを追うには`lib/tokenizer/`→`lib/renderer/`→`lib/transformer/`の順に読むと把握しやすいです。
+
+### 拡張記法
+
+cgmdのレンダラは`lib/renderer/cgmd/`に追加し、`lib/renderer/cgmd.js`に登録します。DOMレベルの整形は`lib/transformer/`に置きます。小さな単位のレンダラを作り、必要に応じて`lib/renderer/md/`の既存実装を参考にしてください。
+
+### CLIとサンプル
+
+`bin/cgmd.js`が`cgmd`コマンドを提供します。ローカルでHTML出力を試すには`example/main.js`を使うか`npm run example`を実行して挙動を確認できます。CLIの引数追加や既定値の変更は`bin/cgmd.js`を編集してください。
+
+### テスト
+
+テストは`test/`以下に配置し、Node組み込みテストで実行します。全体の実行は`npm test`、反復実行は`npm run test:watch`を使います。レンダラ関連は`test/cgmd/renderer`、トークナイザは`test/cgmd/tokenizer`、トランスフォーマは`test/cgmd/transformer`を参照・追加します。
+
+### 開発の流れ
+
+- 依存を整える:`npm ci`
+- 実装を加える:拡張は`lib/renderer/cgmd/`、DOM整形は`lib/transformer/`
+- 動作確認:サンプルの`npm run example`または最小入力での単体実行
+- テスト:失敗→修正→`npm run test:watch`で反復
+- ドキュメント:新しい記法やオプションは`README.md`に追記
+
 ## LICENSE
-MIT
+
+[MIT License](./LICENSE)
diff --git a/lib/cgmd_syntaxes.js b/lib/cgmd_syntaxes.js
new file mode 100644
index 0000000..dd9493e
--- /dev/null
+++ b/lib/cgmd_syntaxes.js
@@ -0,0 +1,9 @@
+const CGMD_SYNTAXES = [
+  'note',
+  'column',
+  'imgbox',
+  'demo',
+  'tree'
+];
+
+export default CGMD_SYNTAXES;
diff --git a/lib/renderer/cgmd.js b/lib/renderer/cgmd.js
index c1269e3..5fa33cb 100644
--- a/lib/renderer/cgmd.js
+++ b/lib/renderer/cgmd.js
@@ -3,7 +3,7 @@ import note from './cgmd/note.js';
 import column from './cgmd/column.js';
 import imgbox from './cgmd/imgbox.js';
 import demo from './cgmd/demo.js';
-import jade from './cgmd/jade.js';
+import CGMD_SYNTAXES from '../cgmd_syntaxes.js';
 
 // mdレベルでの独自記法が増えたらココを増やす
 const renderFunc = {
@@ -11,8 +11,7 @@ const renderFunc = {
   note,
   column,
   imgbox,
-  demo,
-  jade
+  demo
 };
 
 /**
@@ -38,11 +37,16 @@ class CGMDRenderer {
    */
   renderToken(token) {
     const syntax = token.getCGSyntax();
-    if (syntax in renderFunc === false) {
+    if (CGMD_SYNTAXES.includes(syntax) === false) {
       throw new Error('Undefined syntax: ' + syntax);
     }
 
-    return renderFunc[syntax](token.getBody(), this.MDRenderer);
+    const renderer = renderFunc[syntax];
+    if (typeof renderer !== 'function') {
+      throw new Error('Renderer not implemented for syntax: ' + syntax);
+    }
+
+    return renderer(token.getBody(), this.MDRenderer);
   }
 }
 
diff --git a/lib/renderer/cgmd/jade.js b/lib/renderer/cgmd/jade.js
deleted file mode 100644
index 01d23ef..0000000
--- a/lib/renderer/cgmd/jade.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import pug from 'pug';
-
-/**
- * cg:jade をレンダリングする
- *
- * @param {string} str
- *   Jade文字列
- * @param {object} renderer
- *   Markdownのレンダラ
- *   jadeなので使ってない
- *
- */
-export default function (str) {
-  return pug.render(str);
-}
diff --git a/lib/tokenizer.js b/lib/tokenizer.js
index 4a3bd8a..8d8c0db 100644
--- a/lib/tokenizer.js
+++ b/lib/tokenizer.js
@@ -1,10 +1,12 @@
 import MD_Token from './tokenizer/token/md.js';
 import CGMD_Token from './tokenizer/token/cgmd.js';
+import CGMD_SYNTAXES from './cgmd_syntaxes.js';
 
 // mdレベルでの独自記法が増えたらココを増やす
-const CGMD_START_RE = /^\[(note|column|imgbox|demo|tree)\]$/;   // [hoge]
-const CGMD_END_RE   = /^\[\/(note|column|imgbox|demo|tree)\]$/; // [/hoge]
-const LB_RE         = /[\n\r]/;
+const CGMD_PATTERN = CGMD_SYNTAXES.join('|');
+const CGMD_START_RE = new RegExp(`^\\[(${CGMD_PATTERN})\\]$`);   // [hoge]
+const CGMD_END_RE   = new RegExp(`^\\[\/(${CGMD_PATTERN})\\]$`); // [/hoge]
+const NEWLINE_RE    = /\r\n|\n|\r/;
 
 
 /**
@@ -27,7 +29,7 @@ const Tokenizer = {
  *   トークンの配列
  */
 function tokenize(str) {
-  const lines = str.split(LB_RE);
+  const lines = str.split(NEWLINE_RE);
   let line;
   const tokens = [];
 
diff --git a/package-lock.json b/package-lock.json
index b231202..7323572 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,8 +10,7 @@
       "license": "MIT",
       "dependencies": {
         "cheerio": "^1.1.2",
-        "marked": "^7.0.5",
-        "pug": "^3.0.3"
+        "marked": "^7.0.5"
       },
       "bin": {
         "cgmd": "bin/cgmd.js"
@@ -644,139 +643,6 @@
         "asap": "~2.0.3"
       }
     },
-    "node_modules/pug": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.3.tgz",
-      "integrity": "sha512-uBi6kmc9f3SZ3PXxqcHiUZLmIXgfgWooKWXcwSGwQd2Zi5Rb0bT14+8CJjJgI8AB+nndLaNgHGrcc6bPIB665g==",
-      "license": "MIT",
-      "dependencies": {
-        "pug-code-gen": "^3.0.3",
-        "pug-filters": "^4.0.0",
-        "pug-lexer": "^5.0.1",
-        "pug-linker": "^4.0.0",
-        "pug-load": "^3.0.0",
-        "pug-parser": "^6.0.0",
-        "pug-runtime": "^3.0.1",
-        "pug-strip-comments": "^2.0.0"
-      }
-    },
-    "node_modules/pug-attrs": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz",
-      "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==",
-      "license": "MIT",
-      "dependencies": {
-        "constantinople": "^4.0.1",
-        "js-stringify": "^1.0.2",
-        "pug-runtime": "^3.0.0"
-      }
-    },
-    "node_modules/pug-code-gen": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.3.tgz",
-      "integrity": "sha512-cYQg0JW0w32Ux+XTeZnBEeuWrAY7/HNE6TWnhiHGnnRYlCgyAUPoyh9KzCMa9WhcJlJ1AtQqpEYHc+vbCzA+Aw==",
-      "license": "MIT",
-      "dependencies": {
-        "constantinople": "^4.0.1",
-        "doctypes": "^1.1.0",
-        "js-stringify": "^1.0.2",
-        "pug-attrs": "^3.0.0",
-        "pug-error": "^2.1.0",
-        "pug-runtime": "^3.0.1",
-        "void-elements": "^3.1.0",
-        "with": "^7.0.0"
-      }
-    },
-    "node_modules/pug-error": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.1.0.tgz",
-      "integrity": "sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==",
-      "license": "MIT"
-    },
-    "node_modules/pug-filters": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz",
-      "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==",
-      "license": "MIT",
-      "dependencies": {
-        "constantinople": "^4.0.1",
-        "jstransformer": "1.0.0",
-        "pug-error": "^2.0.0",
-        "pug-walk": "^2.0.0",
-        "resolve": "^1.15.1"
-      }
-    },
-    "node_modules/pug-lexer": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.1.tgz",
-      "integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==",
-      "license": "MIT",
-      "dependencies": {
-        "character-parser": "^2.2.0",
-        "is-expression": "^4.0.0",
-        "pug-error": "^2.0.0"
-      }
-    },
-    "node_modules/pug-linker": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz",
-      "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==",
-      "license": "MIT",
-      "dependencies": {
-        "pug-error": "^2.0.0",
-        "pug-walk": "^2.0.0"
-      }
-    },
-    "node_modules/pug-load": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz",
-      "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==",
-      "license": "MIT",
-      "dependencies": {
-        "object-assign": "^4.1.1",
-        "pug-walk": "^2.0.0"
-      }
-    },
-    "node_modules/pug-load/node_modules/object-assign": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
-      "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/pug-parser": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz",
-      "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==",
-      "license": "MIT",
-      "dependencies": {
-        "pug-error": "^2.0.0",
-        "token-stream": "1.0.0"
-      }
-    },
-    "node_modules/pug-runtime": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.1.tgz",
-      "integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==",
-      "license": "MIT"
-    },
-    "node_modules/pug-strip-comments": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz",
-      "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==",
-      "license": "MIT",
-      "dependencies": {
-        "pug-error": "^2.0.0"
-      }
-    },
-    "node_modules/pug-walk": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz",
-      "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==",
-      "license": "MIT"
-    },
     "node_modules/resolve": {
       "version": "1.22.10",
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
diff --git a/package.json b/package.json
index fa0b2d6..79d17c2 100644
--- a/package.json
+++ b/package.json
@@ -10,8 +10,7 @@
   },
   "dependencies": {
     "cheerio": "^1.1.2",
-    "marked": "^7.0.5",
-    "pug": "^3.0.3"
+    "marked": "^7.0.5"
   },
   "devDependencies": {
     "@power-assert/node": "^0.6.0"
diff --git a/test/cgmd/renderer/cgmd/jade.js b/test/cgmd/renderer/cgmd/jade.js
deleted file mode 100644
index 8c9fbb8..0000000
--- a/test/cgmd/renderer/cgmd/jade.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import assert from 'node:assert/strict';
-import { describe, it } from 'node:test';
-import renderJade from '../../../../lib/renderer/cgmd/jade.js';
-
-describe('CodeGridMarkdown - Renderer - cgmd', function() {
-
-
-describe('#jade', function() {
-  it('レンダリングできること', function() {
-    const res = renderJade('.foo foo');
-    const expect = '
foo
'; - assert.equal(res, expect); - }); -}); -}); diff --git a/test/cgmd/tokenizer.js b/test/cgmd/tokenizer.js index 3459e48..ae01ba5 100644 --- a/test/cgmd/tokenizer.js +++ b/test/cgmd/tokenizer.js @@ -80,5 +80,12 @@ describe('#tokenize', function() { assert.equal(tokens.length, 1); assert(tokens[0].isTypeMD()); }); + + it('CRLF改行でも余分な空行を作らない', function() { + const tokens = Tokenizer.tokenize('[note]\r\n# Title\r\n[/note]'); + assert.equal(tokens.length, 1); + assert(tokens[0].isTypeCGMD()); + assert.equal(tokens[0].getBody(), '# Title'); + }); }); });