# 機能設計書 37-オプションパーサー

## 概要

本ドキュメントは、CoffeeScriptのOptionParserモジュールの機能設計を記述する。このモジュールは、コマンドライン引数を解析し、オプションフラグと位置引数を構造化されたオブジェクトに変換する機能を提供する。

### 本機能の処理概要

**業務上の目的・背景**：coffeeコマンドの多様なオプションを効率的に解析するため、専用のオプションパーサーが必要である。標準的なUnix形式のショートフラグ（`-c`）とロングフラグ（`--compile`）の両方をサポートし、引数付きオプションやリスト形式オプションにも対応する。また、ヘルプメッセージの自動生成機能も提供する。

**機能の利用シーン**：
- coffeeコマンドの起動時にコマンドライン引数を解析
- 複数のフラグを結合した短縮形式（`-wc`など）の解析
- オプション引数の取得（`--output path`など）
- ヘルプメッセージの生成と表示

**主要な処理内容**：
1. SWITCHESとバナーからOptionParserインスタンスを生成
2. ルール定義（shortFlag、longFlag、hasArgument、isList）の解析
3. コマンドライン引数の正規化（マルチフラグ展開など）
4. オプションと位置引数の分離
5. オプション値のオブジェクトへの格納
6. ヘルプテキストの生成

**関連システム・外部連携**：command.coffeeのrun関数から呼び出される。

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

## 関連画面

本機能はCLI機能のため、画面との直接的な関連はない。

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 19 | Annotated Source - Optparse | 主機能 | オプション解析optparse.coffeeのソースコード解説表示 |
| 34 | Annotated Source - Optparse (v2) | 主機能 | オプション解析optparse.coffeeのソースコード解説表示（v2） |

## 機能種別

引数解析 / ユーティリティ

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| ruleDeclarations | Array | Yes | オプション定義配列。各要素は[shortFlag, longFlag, description]形式 | - |
| banner | String | No | ヘルプメッセージのバナーテキスト | - |
| args | Array | Yes | 解析対象のコマンドライン引数配列 | - |

### ルール定義形式

```coffeescript
SWITCHES = [
  ['-c', '--compile',           'compile to JavaScript']       # 単純フラグ
  ['-o', '--output [PATH]',     'set output path']             # 引数付き
  ['-r', '--require [MODULE*]', 'require module']              # リスト形式（*）
  [      '--ast',               'output AST']                  # 短縮形なし
]
```

### 入力データソース

`process.argv`配列（coffeeコマンドからの呼び出し時）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| options | Object | パース結果のオプションオブジェクト |
| options.arguments | Array | 位置引数の配列 |
| options.doubleDashed | Boolean | `--`が最初の位置引数だった場合true |
| options[name] | Boolean/String/Array | 各オプションの値 |

### 出力例

```coffeescript
# 入力: coffee -wc --output dist src/app.coffee
# 出力:
{
  watch: true
  compile: true
  output: 'dist'
  arguments: ['src/app.coffee']
}
```

### 出力先

呼び出し元への戻り値

## 処理フロー

### 処理シーケンス

```
1. OptionParserインスタンス生成
   └─ buildRules(ruleDeclarations)でルールリストとフラグ辞書を構築
2. parse(args)メソッド呼び出し
   └─ normalizeArguments(args, flagDict)で引数を正規化
3. 正規化処理
   └─ マルチフラグ（-wc）を展開
   └─ 引数付きオプションの引数を次の要素から取得
   └─ 未知のオプションでエラー
4. オプション値の設定
   └─ hasArgument: true → 値を設定
   └─ isList: true → 配列に追加
   └─ それ以外 → trueを設定
5. 位置引数の処理
   └─ 最初の非オプション引数以降をargumentsに格納
6. doubleDashedの処理
   └─ 先頭が'--'の場合、フラグを設定して除去
```

### フローチャート

```mermaid
flowchart TD
    A[new OptionParser SWITCHES, BANNER] --> B[buildRules ruleDeclarations]
    B --> C[ruleList, flagDict 生成]
    C --> D[parser.parse process.argv 2..]
    D --> E[normalizeArguments args, flagDict]
    E --> F{各引数を処理}
    F --> G{マルチフラグ?<br/>例: -wc}
    G -->|Yes| H[個別フラグに展開]
    G -->|No| I{単一フラグ?}
    I -->|Yes| J{hasArgument?}
    J -->|Yes| K[次の引数を値として取得]
    J -->|No| L[rules に追加]
    K --> L
    I -->|No| M[位置引数として処理]
    M --> N[positional に追加]
    H --> F
    L --> F
    N --> O[正規化完了]
    O --> P{各ルールを処理}
    P --> Q{hasArgument?}
    Q -->|Yes| R{isList?}
    R -->|Yes| S[options name に配列追加]
    R -->|No| T[options name = argument]
    Q -->|No| U[options name = true]
    S --> P
    T --> P
    U --> P
    P --> V{positional 0 === '--'?}
    V -->|Yes| W[doubleDashed = true<br/>positional.shift]
    V -->|No| X[終了]
    W --> Y[options.arguments = positional]
    X --> Y
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-37-01 | マルチフラグ展開 | `-wc`のような結合フラグは`-w`、`-c`に展開される | 2文字以上の短縮フラグ |
| BR-37-02 | 最後のみ引数可 | マルチフラグ内で引数を取れるのは最後のフラグのみ | マルチフラグ内 |
| BR-37-03 | 位置引数開始 | 最初の非オプション引数以降はすべて位置引数として扱う | 常時 |
| BR-37-04 | リストオプション | `*`付きの引数は複数回指定可能で配列に蓄積される | `[ARG*]`形式 |
| BR-37-05 | 重複フラグエラー | 同じフラグが複数のスイッチで定義されている場合はエラー | ルール構築時 |
| BR-37-06 | 未知フラグエラー | 定義されていないフラグが指定された場合はエラー | パース時 |

### 計算ロジック

**ルール構築ロジック（buildRule関数）**:
```coffeescript
match     = longFlag.match(OPTIONAL)          # [ARG]または[ARG*]を抽出
shortFlag = shortFlag?.match(SHORT_FLAG)[1]   # -xの形式を取得
longFlag  = longFlag.match(LONG_FLAG)[1]      # --nameの形式を取得
{
  name:         longFlag.replace /^--/, ''     # --を除去してオプション名に
  shortFlag:    shortFlag
  longFlag:     longFlag
  description:  description
  hasArgument:  !!(match and match[1])         # []があれば引数あり
  isList:       !!(match and match[2])         # *があればリスト形式
}
```

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

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

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | Error | 未知のオプションが指定された | オプション名を確認する |
| - | Error | マルチフラグ内で引数付きオプションが最後以外に配置 | フラグの順序を変更する |
| - | Error | 引数が必要なオプションに引数が指定されていない | 引数を指定する |
| - | Error | 同じフラグが複数回定義されている | ルール定義を修正する |

### リトライ仕様

リトライは行わない。エラー発生時はErrorをthrowする。

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

該当なし。

## パフォーマンス要件

特になし。引数数に対して線形時間O(n)で処理。

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

特になし。

## 備考

**正規表現定義**:
```coffeescript
LONG_FLAG  = /^(--\w[\w\-]*)/     # --で始まり、英数字とハイフンが続く
SHORT_FLAG = /^(-\w)$/            # -と1文字
MULTI_FLAG = /^-(\w{2,})/         # -と2文字以上
OPTIONAL   = /\[(\w+(\*?))\]/     # [ARG]または[ARG*]
```

---

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

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

### 推奨読解順序

#### Step 1: OptionParserクラスの構造を理解する

まず、クラスの全体構造とコンストラクタを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | optparse.coffee | `src/optparse.coffee` | OptionParserクラス定義 |

**主要処理フロー**:
- **14行目**: `exports.OptionParser = class OptionParser` - クラス定義
- **21-22行目**: コンストラクタ
  ```coffeescript
  constructor: (ruleDeclarations, @banner) ->
    @rules = buildRules ruleDeclarations
  ```

#### Step 2: ルール構築処理を理解する

buildRulesとbuildRule関数を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | optparse.coffee | `src/optparse.coffee` | buildRules関数 |
| 2-2 | optparse.coffee | `src/optparse.coffee` | buildRule関数 |

**主要処理フロー**:
- **85-98行目**: buildRules関数
  ```coffeescript
  buildRules = (ruleDeclarations) ->
    ruleList = for tuple in ruleDeclarations
      tuple.unshift null if tuple.length < 3  # 短縮形がない場合はnullを追加
      buildRule tuple...
    flagDict = {}
    for rule in ruleList
      for flag in [rule.shortFlag, rule.longFlag] when flag?
        if flagDict[flag]?
          throw new Error "flag #{flag} ... was already declared ..."
        flagDict[flag] = rule
    {ruleList, flagDict}
  ```
- **102-113行目**: buildRule関数
  ```coffeescript
  buildRule = (shortFlag, longFlag, description) ->
    match     = longFlag.match(OPTIONAL)
    shortFlag = shortFlag?.match(SHORT_FLAG)[1]
    longFlag  = longFlag.match(LONG_FLAG)[1]
    {
      name:         longFlag.replace /^--/, ''
      shortFlag:    shortFlag
      longFlag:     longFlag
      description:  description
      hasArgument:  !!(match and match[1])
      isList:       !!(match and match[2])
    }
  ```

**読解のコツ**: `tuple.unshift null`は短縮形がない場合（3要素未満のタプル）の正規化処理。`tuple...`はsplat演算子で配列を引数に展開。

#### Step 3: 正規表現パターンを理解する

オプション認識に使用される正規表現を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | optparse.coffee | `src/optparse.coffee` | 正規表現定義 |

**主要処理フロー**:
- **76-81行目**: 正規表現定義
  ```coffeescript
  LONG_FLAG  = /^(--\w[\w\-]*)/   # --compile, --no-header等
  SHORT_FLAG = /^(-\w)$/          # -c, -w等
  MULTI_FLAG = /^-(\w{2,})/       # -wc, -cmp等
  OPTIONAL   = /\[(\w+(\*?))\]/   # [PATH], [MODULE*]等
  ```

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

メインの解析処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | optparse.coffee | `src/optparse.coffee` | parseメソッド |

**主要処理フロー**:
- **30-58行目**: parse関数
  ```coffeescript
  parse: (args) ->
    {rules, positional} = normalizeArguments args, @rules.flagDict
    options = {}

    for {hasArgument, argument, isList, name} in rules
      if hasArgument
        if isList
          options[name] ?= []
          options[name].push argument
        else
          options[name] = argument
      else
        options[name] = true

    if positional[0] is '--'
      options.doubleDashed = yes
      positional = positional[1..]

    options.arguments = positional
    options
  ```

#### Step 5: normalizeArguments関数を理解する

引数の正規化処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | optparse.coffee | `src/optparse.coffee` | normalizeArguments関数 |

**主要処理フロー**:
- **115-165行目**: normalizeArguments関数
  - マルチフラグの展開
  - 引数付きオプションの処理
  - 位置引数の検出

**読解のコツ**: `needsArgOpt`変数は「次の引数を待っているオプション」を追跡。マルチフラグ内で引数を取れるのは最後のフラグのみ。

#### Step 6: help関数を理解する

ヘルプテキスト生成を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 6-1 | optparse.coffee | `src/optparse.coffee` | helpメソッド |

**主要処理フロー**:
- **62-70行目**: help関数
  ```coffeescript
  help: ->
    lines = []
    lines.unshift "#{@banner}\n" if @banner
    for rule in @rules.ruleList
      spaces  = 15 - rule.longFlag.length
      spaces  = if spaces > 0 then repeat ' ', spaces else ''
      letPart = if rule.shortFlag then rule.shortFlag + ', ' else '    '
      lines.push '  ' + letPart + rule.longFlag + spaces + rule.description
    "\n#{ lines.join('\n') }\n"
  ```

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

```
command.coffee:run
    │
    └─ buildCSOptionParser (command.coffee:66)
           │
           └─ new OptionParser(SWITCHES, BANNER)
                  │
                  └─ buildRules(ruleDeclarations)
                         │
                         ├─ for tuple in ruleDeclarations
                         │      └─ buildRule(shortFlag, longFlag, description)
                         │             ├─ OPTIONAL.match → hasArgument, isList
                         │             ├─ SHORT_FLAG.match → shortFlag
                         │             └─ LONG_FLAG.match → longFlag
                         │
                         └─ flagDict構築（重複チェック付き）
    │
    └─ optionParser.parse(process.argv[2..])
           │
           └─ normalizeArguments(args, flagDict)
                  │
                  ├─ MULTI_FLAG.match → 展開
                  ├─ LONG_FLAG/SHORT_FLAG.match → ルール検索
                  ├─ hasArgument → 次の引数を取得
                  └─ 位置引数 → positional
           │
           └─ オプションオブジェクト構築
                  ├─ hasArgument → options[name] = argument
                  ├─ isList → options[name].push(argument)
                  └─ フラグのみ → options[name] = true
```

### データフロー図

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

SWITCHES                  buildRules                       rules
[                              │                               │
  ['-c', '--compile', ...],    │                               │
  ['-o', '--output [PATH]',..]▶│                               │
]                              ▼                               ▼
                         ruleList + flagDict
                              │
process.argv              normalizeArguments               options
['coffee',                     │                               │
 '-wc',           ────────────▶│                               │
 '--output',                   │                               │
 'dist',                       ▼                               ▼
 'src/app.coffee']        rules + positional
                              │
                              ▼
                         オプションオブジェクト構築
                              │
                              ▼
                         {
                           watch: true,
                           compile: true,
                           output: 'dist',
                           arguments: ['src/app.coffee']
                         }
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| optparse.coffee | `src/optparse.coffee` | ソース | OptionParserクラス、buildRules/buildRule関数 |
| command.coffee | `src/command.coffee` | ソース | SWITCHES定義、OptionParser呼び出し元 |
| helpers.coffee | `src/helpers.coffee` | ソース | repeat関数（ヘルプ生成で使用） |
