# 機能設計書 34-literateオプション

## 概要

本ドキュメントは、CoffeeScript CLIの`--literate`（`-l`）オプションの機能設計を記述する。このオプションは、入力をLiterate CoffeeScriptとして扱い、Markdownスタイルのドキュメント内からコードブロックを抽出してコンパイルする機能を提供する。

### 本機能の処理概要

**業務上の目的・背景**：Literate Programming（文学的プログラミング）のスタイルをサポートし、ドキュメントとコードを一体化させたファイル形式を可能にする。技術文書、チュートリアル、学習教材において、説明文とコードが自然に混在するファイルを作成できるようにする。これにより、コードの可読性と保守性を向上させる。

**機能の利用シーン**：
- `.coffee`拡張子のファイルをLiterate形式として扱いたい場合
- `--stdio`オプションでパイプ入力をLiterate形式として処理したい場合
- 拡張子で自動判定されない場合に明示的にLiterate形式を指定したい場合
- Markdownドキュメント内にCoffeeScriptコードを埋め込んでコンパイルしたい場合

**主要な処理内容**：
1. `--literate`オプションが指定されているかを判定
2. コンパイルオプションにliterate: trueを設定
3. 拡張子自動判定（`.litcoffee`、`.coffee.md`）との組み合わせ処理
4. helpers.invertLiterate関数でMarkdownからコードブロックを抽出
5. 抽出されたコードをCoffeeScriptとしてコンパイル

**関連システム・外部連携**：特になし。

**権限による制御**：特になし。

## 関連画面

本機能はCLI機能のため、画面との直接的な関連はない。ただし、ブラウザ環境では`text/x-literate-coffeescript`タイプのscriptタグとの関連がある。

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 3 | テストスイート | 補助機能 | text/x-literate-coffeescriptタイプのテストコードに対応 |

## 機能種別

入力形式指定 / 前処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| --literate / -l | Boolean | No | 入力をLiterate CoffeeScriptとして扱う | - |
| input | String | Yes | Literate CoffeeScript形式のソースコード | Markdown形式であること |

### 入力データソース

- ソースファイル（`.litcoffee`、`.coffee.md`、または任意のファイル）
- 標準入力（`--stdio`との併用時）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| extractedCode | String | Markdownから抽出されたCoffeeScriptコード |
| compiledJS | String | 最終的にコンパイルされたJavaScript |

### 出力先

通常のコンパイル出力先（ファイル、標準出力等）

## 処理フロー

### 処理シーケンス

```
1. オプション解析
   └─ opts.literateがtrueに設定される
2. compileOptions関数でのliterate判定
   └─ opts.literate または helpers.isLiterate(filename) で判定
3. コンパイル時のliterate処理
   └─ lexer.tokenize内でinvertLiterateが呼び出される
4. invertLiterate関数の実行
   └─ Markdownからコードブロックを抽出
5. 抽出されたコードのトークン化・パース・コンパイル
```

### フローチャート

```mermaid
flowchart TD
    A[coffee --literate file.coffee] --> B[parseOptions]
    B --> C[opts.literate = true]
    C --> D[compileOptions file, base]
    D --> E{opts.literate?}
    E -->|Yes| F[options.literate = true]
    E -->|No| G{helpers.isLiterate filename?}
    G -->|Yes| F
    G -->|No| H[options.literate = false]
    F --> I[CoffeeScript.compile input, options]
    H --> I
    I --> J{options.literate?}
    J -->|Yes| K[lexer.tokenize で invertLiterate 呼び出し]
    J -->|No| L[通常のトークン化]
    K --> M[Markdownからコード抽出]
    M --> N[抽出コードをトークン化]
    N --> O[パース・コンパイル]
    L --> O
    O --> P[JavaScript 出力]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-34-01 | 拡張子自動判定 | `.litcoffee`または`.coffee.md`拡張子のファイルは自動的にLiterateとして扱う | ファイル名に該当拡張子がある場合 |
| BR-34-02 | オプション優先 | `--literate`オプションが明示的に指定された場合、拡張子に関わらずLiterateとして扱う | --literate指定時 |
| BR-34-03 | インデント規則 | タブまたはスペースでインデントされた行はコードとして扱う | Literate形式処理時 |
| BR-34-04 | 非コード行コメント化 | インデントされていない行（Markdown説明文等）はCoffeeScriptコメント（`#`）として出力 | Literate形式処理時 |
| BR-34-05 | 空行保持 | 空行はそのまま出力してソースマップの行対応を維持 | Literate形式処理時 |

### 計算ロジック

**invertLiterate関数のコード抽出ロジック**:
```
for each line in code:
  if 空行:
    insideComment = no
    そのまま出力
  else if コメント内 or リストアイテム開始:
    insideComment = yes
    "# " + line を出力
  else if コメント外 and インデントあり:
    そのまま出力（コード行）
  else:
    insideComment = yes
    "# " + line を出力
```

## データベース操作仕様

本機能はデータベース操作を行わない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| SYNTAX_ERROR | SyntaxError | 抽出されたコードに構文エラーがある | コードブロックの内容を確認する |
| - | 論理エラー | インデントが不正でコードが正しく抽出されない | インデント（タブまたはスペース）を確認する |

### リトライ仕様

リトライは行わない。

## トランザクション仕様

該当なし。

## パフォーマンス要件

特になし。invertLiterate関数は入力を行単位で1回走査するのみで、O(n)の計算量。

## セキュリティ考慮事項

特になし。

## 備考

**Literate CoffeeScript形式の例**:
```markdown
# Calculator Module

This module provides basic arithmetic operations.

    add = (a, b) -> a + b
    subtract = (a, b) -> a - b

## Usage

You can use these functions like this:

    result = add 1, 2
    console.log result  # 3
```

上記は以下のCoffeeScriptとして処理される：
```coffeescript
# # Calculator Module
#
# This module provides basic arithmetic operations.

add = (a, b) -> a + b
subtract = (a, b) -> a - b

# ## Usage
#
# You can use these functions like this:

result = add 1, 2
console.log result  # 3
```

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: オプション定義を理解する

まず、--literateオプションがどのように定義されているかを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | command.coffee | `src/command.coffee` | SWITCHESでの-l/--literateオプション定義 |

**主要処理フロー**:
- **42行目**: `-l`/`--literate`オプションの定義（`'treat stdio as literate style coffeescript'`）

#### Step 2: コンパイルオプションでのliterate判定を理解する

compileOptions関数でliterateフラグがどのように設定されるかを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | command.coffee | `src/command.coffee` | compileOptions関数でのliterate設定 |

**主要処理フロー**:
- **501-509行目**: compileOptions関数のanswer生成部分
  ```coffeescript
  answer =
    filename: filename
    literate: opts.literate or helpers.isLiterate(filename)
    bare: opts.bare
    ...
  ```
- **503行目**: `opts.literate or helpers.isLiterate(filename)`でリテレート判定

**読解のコツ**: `or`演算子により、明示的な`--literate`オプションと拡張子による自動判定の両方をサポートしている。

#### Step 3: isLiterate関数を理解する

拡張子による自動判定の実装を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | helpers.coffee | `src/helpers.coffee` | isLiterate関数の実装 |

**主要処理フロー**:
- **207行目**: `exports.isLiterate = (file) -> /\.(litcoffee|coffee\.md)$/.test file`
- 正規表現で`.litcoffee`または`.coffee.md`拡張子を判定

#### Step 4: invertLiterate関数を理解する

Markdownからコードを抽出する核心部分を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | helpers.coffee | `src/helpers.coffee` | invertLiterate関数の実装 |

**主要処理フロー**:
- **67-92行目**: invertLiterate関数全体
- **68行目**: `out = []` - 出力行を格納する配列
- **69行目**: `blankLine = /^\s*$/` - 空行判定用正規表現
- **70行目**: `indented = /^[\t ]/` - インデント判定用正規表現
- **71-78行目**: `listItemStart` - Markdownリストアイテム判定用正規表現
  ```coffeescript
  listItemStart = /// ^
    (?:\t?|\ {0,3})   # Up to one tab, or up to three spaces, or neither;
    (?:
      [\*\-\+] |      # followed by `*`, `-` or `+`;
      [0-9]{1,9}\.    # or by an integer up to 9 digits long, followed by a period;
    )
    [\ \t]            # followed by a space or a tab.
  ///
  ```
- **79行目**: `insideComment = no` - コメント（非コード部分）内かどうかのフラグ
- **80-91行目**: 各行を処理するループ
  ```coffeescript
  for line in code.split('\n')
    if blankLine.test(line)
      insideComment = no
      out.push line
    else if insideComment or listItemStart.test(line)
      insideComment = yes
      out.push "# #{line}"
    else if not insideComment and indented.test(line)
      out.push line
    else
      insideComment = yes
      out.push "# #{line}"
  ```
- **92行目**: `out.join '\n'` - 行を改行で結合して返す

**読解のコツ**: `insideComment`フラグはMarkdownの段落継続を追跡している。空行で段落が終わり、次のインデント行はコードとして扱われる。

#### Step 5: lexer内でのinvertLiterate呼び出しを理解する

Lexerでどのようにliterate処理が呼び出されるかを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | lexer.coffee | `src/lexer.coffee` | tokenize関数内でのliterate処理 |

**主要処理フロー**:
- lexer.coffee tokenize関数内で、options.literateがtrueの場合にhelpers.invertLiterateを呼び出す

### プログラム呼び出し階層図

```
exports.run (command.coffee:72)
    │
    ├─ parseOptions (command.coffee:445)
    │      └─ opts.literate = true
    │
    └─ compileScript (command.coffee:202)
           │
           ├─ compileOptions(file, base) (command.coffee:452)
           │      └─ literate: opts.literate or helpers.isLiterate(filename)
           │
           └─ CoffeeScript.compile(input, options)
                  │
                  └─ lexer.tokenize(code, options)
                         │
                         ├─ options.literate? チェック
                         │
                         └─ helpers.invertLiterate(code)
                                │
                                ├─ blankLine判定
                                ├─ indented判定
                                ├─ listItemStart判定
                                └─ コード行/コメント行の振り分け
```

### データフロー図

```
[入力]                        [処理]                              [出力]

Literate ファイル         invertLiterate                   抽出されたコード
   │                           │                               │
   │ # Title                   │ "# " + "# Title"               │ # # Title
   │                           │                               │
   │ Description text          │ "# " + "Description..."       │ # Description text
   │                           │                               │
   │     code = 1              │ そのまま出力                    │     code = 1
   │                           │                               │
   │ More text                 │ "# " + "More text"            │ # More text
   │                           │                               │
   ▼                           ▼                               ▼
                        out.join('\n')
                               │
                               ▼
                        通常のCoffeeScriptとして
                        トークン化・パース・コンパイル
                               │
                               ▼
                        JavaScript出力
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| command.coffee | `src/command.coffee` | ソース | CLI本体、compileOptions関数でliterateフラグ設定 |
| helpers.coffee | `src/helpers.coffee` | ソース | isLiterate関数、invertLiterate関数 |
| lexer.coffee | `src/lexer.coffee` | ソース | tokenize関数内でinvertLiterate呼び出し |
| coffeescript.coffee | `src/coffeescript.coffee` | ソース | compile関数（lexer.tokenize呼び出し） |
