# ☄️ docomet - 彗星の速さでドキュメントを更新する。

[![npm version](https://badge.fury.io/js/docomet.svg)](https://badge.fury.io/js/docomet)
[![npm downloads](https://img.shields.io/npm/dt/docomet)](https://www.npmjs.com/package/docomet)
[![types included](https://img.shields.io/npm/types/docomet)](https://www.npmjs.com/package/docomet)
[![ESM](https://img.shields.io/badge/ESM-supported-green?style=flat-square)](https://www.npmjs.com/package/docomet)
[![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![API Docs](https://img.shields.io/badge/API-Docs-ff69b4)](https://mskz-3110.github.io/docomet)

| 機能名 | 開発動機 | 解決策 |
| :--- | :--- | :--- |
| [Inspectable](#quick-start-inspectable) | REPLで `console.log()` にクラスを指定して静的プロパティやメソッド名を確認したい | 継承するだけで内部構造を整理して出力する専用クラスを提供。 |
| [MtimeCheck](#quick-start-mtime-check) | 翻訳元ファイルが更新されたのに翻訳先が古いままという「更新漏れ」を防ぎたい | 2つのファイルの更新日時を比較して更新が必要か判定する専用メソッド／クラス／コマンドを提供。 |
| [Replace](#quick-start-replace) | ファイル内のコマンドの実行結果を手軽に最新状態に更新したい | コメントタグ内のコマンドの実行結果で置換する専用メソッド／クラス／コマンドを提供。 |
| [ClassMap](#quick-start-class-map) | クラスの関係性を手軽に `mermaid` で可視化したい | TSDocから `mermaid` 形式のテキストを生成する専用メソッド／クラス／コマンドを提供。 |

<!-- docomet:ignore -->

## 🚀 インストール

```bash
pnpm install docomet
```

## 💖 クイックスタート

`docomet` は、開発者にとっての「使いやすさ」と「実装の気楽さ」を重視しています。

### Import

```javascript
// REPL対応(動的インポート)
const {docomet} = await import('docomet');
```

<div id="quick-start-inspectable"></div>

### Inspectable

<img src="docs/assets/add_an_inspect_method.gif" alt="inspectメソッドを追加" width="640">

<details>
  <summary>sample.js</summary>

```javascript
const {DocometInspectableObjectClass} = await import('docomet/docomet-inspectable');

class Sample extends DocometInspectableObjectClass {
  static staticPublicProperty = '';
  static #staticPrivateProperty = '';
  static get staticPrivateProperty() {return Sample.#staticPrivateProperty;}
  static set staticPrivateProperty(value) {Sample.#staticPrivateProperty = value;}
  static staticMethod() {}

  toJSON() {
    return {
      name: this.constructor.name,
    };
  }
}
```
</details>

| クラスの出力結果(Default) | クラスの出力結果(DocometInspectableObjectClass) |
| :--- | :--- |
| <pre>$ console.log(Sample);<br>[class Sample] { staticPublicProperty: '' }</pre> | <pre>$ console.log(Sample);<br>[class Sample] {<br>  propertyNames: [<br>    'staticPublicProperty'<br>  ],<br>  accessorNames: [<br>    'staticPrivateProperty'<br>  ],<br>  methodNames: [<br>    'staticMethod'<br>  ]<br>}</pre> |

| インスタンスの出力結果(Default) | インスタンスの出力結果(DocometInspectableObjectClass) |
| :--- | :--- |
| <pre>$ console.log(new Sample());<br>Sample {}</pre> | <pre>$ console.log(new Sample());<br>{<br>  name: 'Sample'<br>}</pre> |

##### Object以外のクラス(ArrayやMapなど)をベースとしたMixin

```javascript
const {DocometInspectable} = await import('docomet/docomet-inspectable');
const InspectableArrayClass = DocometInspectable(Array);
const InspectableMapClass = DocometInspectable(Map);

class SampleArray extends InspectableArrayClass {
  ...
}

class SampleMap extends InspectableMapClass {
  ...
}
```

<div id="quick-start-mtime-check"></div>

### MtimeCheck

<img src="docs/assets/mtime_check.gif" alt="最終更新日時をチェック" width="640">

`abc.log` より `xyz.log` が新しいか確認するコマンド例です。

```bash
npx docomet-mtime-check -o abc.log -n xyz.log
```

| 出力種別 | 出力内容 | 説明 |
| :--- | :--- | :--- |
| エラー | `Outdated: {path}` | ファイルが古くなっています。 |
| エラー | `NotFoundOlderPath: {path}` | 比較元の古いファイルパスが存在しません。 |
| エラー | `NotFoundNewerPath: {path}` | 比較先の新しいファイルパスが存在しません。 |

<div id="quick-start-replace"></div>

### Replace

<img src="docs/assets/replace_node_version.gif" alt="nodeバージョンの置換" width="640">

```html
<!-- docomet:bash node -v -->
<!-- docomet:/bash -->
```

`node` のバージョン表示コマンドを実行して置換するコマンド例です。

```bash
npx docomet-replace < <(cat replace_node_version.md)
```

```html
<!-- docomet:bash node -v -->
vX.Y.Z
<!-- docomet:/bash -->
```

| 出力種別 | 出力内容 | 説明 |
| :--- | :--- | :--- |
| ログ | `Skipped: {path}` | ファイルの内容に変更がないので、スキップしました。 |
| ログ | `Replaced: {path}` | ファイルの内容に差分があるので、新しい内容で置換しました。 |
| エラー | `Unexpected: {tag}` | タグが一致していません。 |
| エラー | `Unclosed: {tag}` | 閉じタグがありません。 |

#### 基本構文

独自形式のHTMLコメントタグを解析して実行します。

また、`#` や `//` などのコメント内のタグも認識します。

```html
<!-- docomet:TYPE[(LANGUAGE)] [ARGS...] -->
<!-- docomet:/TYPE -->

# <!-- docomet:TYPE[(LANGUAGE)] [ARGS...] -->
# <!-- docomet:/TYPE -->

// <!-- docomet:TYPE[(LANGUAGE)] [ARGS...] -->
// <!-- docomet:/TYPE -->
```

| キーワード | 説明 |
| :--- | :--- |
| [**TYPE**](#quick-start-replace-type-list) | 実行する操作の種類です。 |
| **`(LANGUAGE)`** | コードブロックの言語識別子です。 |
| **`ARGS...`** | `TYPE` に必要な引数です。<br>指定可能な値は `TYPE` と併せて説明します。 |

`LANGUAGE` を省略した場合、`TYPE` の実行結果がそのまま出力されます。

```html
<!-- docomet:bash node -v -->
<!-- docomet:/bash -->
```
↓
````html
<!-- docomet:bash node -v -->
vX.Y.Z
<!-- docomet:/bash -->
````

`LANGUAGE` を指定した場合、指定した文字列がコードブロックの言語識別子として出力されます。

```html
<!-- docomet:bash(text) node -v -->
<!-- docomet:/bash -->
```
↓
````html
<!-- docomet:bash(text) node -v -->
```text
vX.Y.Z
```
<!-- docomet:/bash -->
````

ただし、`none` を指定した場合は何も出力されません。

結果を出力せずにコマンドだけを実行したい場合に使用してください。

```html
<!-- docomet:bash(none) node -v -->
<!-- docomet:/bash -->
```
↓
```html
<!-- docomet:bash(none) node -v -->
<!-- docomet:/bash -->
```

<div id="quick-start-replace-type-list"></div>

#### TYPEリスト

##### bash

`ARGS` で指定されたコマンドの実行結果を出力します。

```html
<!-- docomet:bash node -v -->
<!-- docomet:/bash -->
```
↓
```html
<!-- docomet:bash node -v -->
vX.Y.Z
<!-- docomet:/bash -->
```

##### file

`ARGS` で指定されたファイルの内容を出力します。

```html
<!-- docomet:file(javascript) sample.js -->
<!-- docomet:/file -->
```
↓
````html
<!-- docomet:file(javascript) sample.js -->
```javascript
const {DocometInspectableObjectClass} = await import('docomet/docomet-inspectable');

class Sample extends DocometInspectableObjectClass {
  static staticPublicProperty = '';
  static #staticPrivateProperty = '';
  static get staticPrivateProperty() {return Sample.#staticPrivateProperty;}
  static set staticPrivateProperty(value) {Sample.#staticPrivateProperty = value;}
  static staticMethod() {}

  toJSON() {
    return {
      name: this.constructor.name,
    };
  }
}
```
<!-- docomet:/file -->
````

##### ignore

`ARGS` は不要で `ignore` で囲まれたタグは無視されます。

```html
<!-- docomet:ignore -->
  <!-- docomet:bash node -v -->
  <!-- docomet:/bash -->

  <!-- docomet:file(javascript) sample.js -->
  <!-- docomet:/file -->
<!-- docomet:/ignore -->
```

<div id="quick-start-class-map"></div>

### ClassMap

`src` ディレクトリのクラスマップを生成するコマンド例です。

```
npx docomet-class-map src
```

| 出力種別 | 出力内容 | 説明 |
| :--- | :--- | :--- |
| エラー | `NotFoundProject: {"entryPoints":[""],"rootPath":""}` | 指定されたプロジェクトが存在しません。 |

<!-- docomet:/ignore -->

## 🗺️ クラスマップ

<!-- docomet:bash(mermaid) npx docomet-class-map src/*.ts src/base/*.ts src/cli/docomet-commander.ts src/system/*.ts -->
```mermaid
classDiagram
  namespace base {
    class DocometBuffer {
    }
    class DocometCount {
      static * down(count:number,start?:number) Generator&lt;number&gt;$
      static * up(count:number,start:number) Generator&lt;number&gt;$
    }
    class DocometEncoding {
      static readonly utf8 : string$
      static decode(buffer:DocometBuffer) string$
      static detectEncoding(buffer:DocometBuffer) string$
    }
    class DocometError {
      DocometError(...args:[message?:string,options?:ErrorOptions]) DocometError
      toJSON() string
      static new(...args:[message?:string,options?:ErrorOptions]) DocometError$
      static normalize(error:unknown) Error$
      static stringify(error:unknown) string$
    }
    class DocometStream {
      static async readAsync(stream:Readable) Promise&lt;string&gt;$
      static readLineAsync(stream:Readable,encoding:string) AsyncGenerator&lt;string&gt;$
      static async readLinesAsync(stream:Readable,encoding:string) Promise&lt;string[]&gt;$
      static async toBufferAsync(stream:Readable) Promise&lt;DocometBuffer&gt;$
    }
    class DocometString {
      static async detectEncodingAsync(string:string) Promise&lt;string&gt;$
      static escapeHtml(html:string) string$
      static joinLines(lines:string[],separator:string) string$
      static replace(string:string,map:DocometStringReplaceMap) string$
      static async splitLinesAsync(string:string,encoding:string) Promise&lt;string[]&gt;$
    }
  }
  namespace cli {
    class DocometCommander {
      baseCommandName() string &lt;&lt;getter&gt;&gt;
      command() Command &lt;&lt;getter&gt;&gt;
      static packageJson() DocometCommanderPackageJson &lt;&lt;getter&gt;&gt;$
      DocometCommander(baseCommandName:string) DocometCommander
      addHelpText(text:string) DocometCommander
      addOption(flags:string,description:string,required:boolean,defaultValue?:string,choices:string[]) DocometCommander
      toJSON() object
      toString() string
      static new(...args:[baseCommandName:string]) DocometCommander$
      static async runAsync(callback:DocometCommanderRunAsyncCallback) Promise&lt;void&gt;$
    }
  }
  namespace system {
    class DocometColor {
      static coloring(colorName:string,message:string) string$
      static names() string[]$
      static set(colorName:string,newFormatter:DocometColorFormatter) DocometColorFormatter$
    }
    class DocometDir {
      static async changeAsync(path:string,callback:DocometDirChangeCallback) Promise&lt;void&gt;$
    }
    class DocometFile {
      absolutePath() string &lt;&lt;getter&gt;&gt;
      path() DocometPath &lt;&lt;getter&gt;&gt;
      DocometFile(path:string) DocometFile
      read() string
      async readAsync() Promise&lt;string&gt;
      readLineAsync(size?:number) AsyncGenerator&lt;string&gt;
      async readLinesAsync(size?:number) Promise&lt;string[]&gt;
      toJSON() string
      toString() string
      async writeAsync(data:string|Uint8Array&lt;ArrayBufferLike&gt;|Readable) Promise&lt;void&gt;
      static async detectEncodingAsync(path:string) Promise&lt;string&gt;$
      static new(...args:[path:string]) DocometFile$
    }
    class DocometPath {
      base() string &lt;&lt;getter&gt;&gt;
      dir() string &lt;&lt;getter&gt;&gt;
      ext() string &lt;&lt;getter&gt;&gt;
      name() string &lt;&lt;getter&gt;&gt;
      static rootPath() string &lt;&lt;getter&gt;&gt;$
      DocometPath(dir:string,name?:string,ext?:string) DocometPath
      toJSON() string
      toPosixString() string
      toString() string
      with(updates:Partial&lt;#123;dir:string,ext:string,name:string#125;&gt;) DocometPath
      static absolute(path:string) string$
      static cwd() string$
      static doesNotExist(path:string) boolean$
      static exists(path:string) boolean$
      static findUp(name:string,startPath:string) string|undefined$
      static glob(patterns:string|string[],options?:Options) string[]|Entry[]$
      static async globAsync(patterns:string|string[],options?:Options) Promise&lt;string[]|Entry[]&gt;$
      static join(...paths:string[]) string$
      static joinPosix(...paths:string[]) string$
      static new(...args:[dir:string,name?:string,ext?:string]) DocometPath$
      static normalize(path:string) string$
      static parse(path:string) DocometPath$
      static rebuild(path:string,split:DocometPathSplit,join:DocometPathJoin) string$
      static relative(toPath:string,fromPath:string) string$
      static split(path:string) string[]$
      static splitPosix(path:string) string[]$
    }
    class DocometProcess {
      static logging : DocometLogging$
      static abort(message:string) void$
      static exit(code:number,message?:string) void$
      static getStdin(isTTY:boolean) Readable|undefined$
      static setupErrorHandlers() void$
    }
    class DocometStat {
      static checkMtime(olderPath:string,newerPath:string) void$
      static getMtime(path:string) Date|undefined$
      static needsUpdate(olderPath:string,newerPath:string) boolean$
    }
  }
  DocometBuffer -- DocometCount
  DocometStream --> DocometBuffer
  DocometStream --> DocometEncoding
  DocometString --> DocometEncoding
  DocometString --> DocometStream
  DocometCommander --> DocometError
  DocometCommander --> DocometFile
  DocometCommander --> DocometPath
  DocometCommander --> DocometProcess
  DocometDir --> DocometError
  DocometDir --> DocometPath
  DocometFile --> DocometEncoding
  DocometFile --> DocometPath
  DocometFile --> DocometStream
  DocometProcess --> DocometColor
  DocometProcess --> DocometError
  DocometProcess --> DocometStream
  DocometStat --> DocometError
  DocometStat --> DocometPath
```
<!-- docomet:/bash -->

## 📋 クラスサマリー

<!-- docomet:bash(javascript) node -e "const {docomet} = await import('docomet');console.log(docomet);" -->
```javascript
{
  base: {
    buffer: [class DocometBuffer] {
      propertyNames: [],
      accessorNames: [],
      methodNames: []
    },
    count: [class DocometCount] {
      propertyNames: [],
      accessorNames: [],
      methodNames: [
        'up',
        'down'
      ]
    },
    encoding: [class DocometEncoding] {
      propertyNames: [
        'utf8'
      ],
      accessorNames: [],
      methodNames: [
        'detectEncoding',
        'decode'
      ]
    },
    error: [class DocometError] {
      propertyNames: [],
      accessorNames: [],
      methodNames: [
        'stringify',
        'normalize',
        'new'
      ]
    },
    stream: [class DocometStream] {
      propertyNames: [],
      accessorNames: [],
      methodNames: [
        'toBufferAsync',
        'readLineAsync',
        'readLinesAsync',
        'readAsync'
      ]
    },
    string: [class DocometString] {
      propertyNames: [],
      accessorNames: [],
      methodNames: [
        'detectEncodingAsync',
        'replace',
        'escapeHtml',
        'splitLinesAsync',
        'joinLines'
      ]
    }
  },
  cli: {
    commander: [class DocometCommander] {
      propertyNames: [],
      accessorNames: [
        'packageJson'
      ],
      methodNames: [
        'runAsync',
        'new'
      ]
    }
  },
  system: {
    color: [class DocometColor] {
      propertyNames: [],
      accessorNames: [],
      methodNames: [
        'names',
        'set',
        'coloring'
      ]
    },
    dir: [class DocometDir] {
      propertyNames: [],
      accessorNames: [],
      methodNames: [
        'changeAsync'
      ]
    },
    file: [class DocometFile] {
      propertyNames: [],
      accessorNames: [],
      methodNames: [
        'detectEncodingAsync',
        'new'
      ]
    },
    path: [class DocometPath] {
      propertyNames: [],
      accessorNames: [
        'rootPath'
      ],
      methodNames: [
        'cwd',
        'findUp',
        'exists',
        'doesNotExist',
        'absolute',
        'relative',
        'normalize',
        'split',
        'splitPosix',
        'join',
        'joinPosix',
        'rebuild',
        'globAsync',
        'glob',
        'parse',
        'new'
      ]
    },
    process: [class DocometProcess] {
      propertyNames: [
        'logging'
      ],
      accessorNames: [],
      methodNames: [
        'setupErrorHandlers',
        'exit',
        'abort',
        'getStdin'
      ]
    },
    stat: [class DocometStat] {
      propertyNames: [],
      accessorNames: [],
      methodNames: [
        'getMtime',
        'needsUpdate',
        'checkMtime'
      ]
    }
  }
}
```
<!-- docomet:/bash -->

## 🤝 コントリビュート

SNSで気軽に質問や相談をしてください。

[![Discord](https://img.shields.io/badge/Discord-docomet-5865F2?style=flat-square&logo=discord&logoColor=white)](https://discord.gg/mJtQWKNdJ6)
[![X](https://img.shields.io/badge/X-%40mskz3110-000000?style=flat-square&logo=x)](https://x.com/mskz3110)

## 📜 ライセンス

[Apache-2.0](LICENSE)

## ❤️ 謝辞

[Google AI Studio](https://aistudio.google.com/)と[Gemini CLI](https://github.com/google-gemini/gemini-cli)のサポートにより開発されました。

また、素晴らしいオープンソースプロジェクトの力によって支えられています。

すべてのメンテナーおよびコントリビューターの方々に心から感謝いたします。

### その他のツール

| ツール名 | 概要 |
| :--- | :--- |
| **[vhs](https://github.com/charmbracelet/vhs)** | ターミナルの操作手順をコード（スクリプト）で記述し、高品質なGIFアニメーションや動画を生成するツール。 |
