# ☄️ docomet - Documentation updates at comet speed.

[![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)

| Feature Name | Development Motivation | Solution |
| :--- | :--- | :--- |
| [Inspectable](#quick-start-inspectable) | I want to check static properties and method names by specifying a class in `console.log()` in the REPL. | Provides a dedicated class that organizes and outputs the internal structure just by inheriting it. |
| [MtimeCheck](#quick-start-mtime-check) | I want to prevent "Update omissions" where the translation source file is updated but the translation destination remains old. | Provides a dedicated method/class/command to determine if an update is necessary by comparing the modification dates of two files. |
| [Replace](#quick-start-replace) | I want to easily update the execution results of commands in a file to the latest state. | Provides a dedicated method/class/command to replace content with the execution results of commands within comment tags. |
| [ClassMap](#quick-start-class-map) | I want to easily visualize class relationships using `mermaid`. | Provides a dedicated method/class/command to generate `mermaid` format text from TSDoc. |

<!-- docomet:ignore -->

## 🚀 Installation

```bash
pnpm install docomet
```

## 💖 Quick Start

`docomet` focuses on "ease of use" and "implementation comfort" for developers.

### Import

```javascript
// REPL support (dynamic import)
const {docomet} = await import('docomet');
```

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

### Inspectable

<img src="docs/assets/add_an_inspect_method.gif" alt="Add an Inspect Method" 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>

| Class Output Result (Default) | Class Output Result (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> |

| Instance Output Result (Default) | Instance Output Result (DocometInspectableObjectClass) |
| :--- | :--- |
| <pre>$ console.log(new Sample());<br>Sample {}</pre> | <pre>$ console.log(new Sample());<br>{<br>  name: 'Sample'<br>}</pre> |

##### Mixin Based on Classes Other Than Object (Such as Array or Map)

```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="Check the Last Updated Date" width="640">

Example command to check if `xyz.log` is newer than `abc.log`.

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

| Output Type | Output Content | Description |
| :--- | :--- | :--- |
| Error | `Outdated: {path}` | The file is outdated. |
| Error | `NotFoundOlderPath: {path}` | The source older file path does not exist. |
| Error | `NotFoundNewerPath: {path}` | The destination newer file path does not exist. |

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

### Replace

<img src="docs/assets/replace_node_version.gif" alt="Replace Node Version" width="640">

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

Example command to replace by executing the `node` version display command.

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

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

| Output Type | Output Content | Description |
| :--- | :--- | :--- |
| Log | `Skipped: {path}` | Skipped because there are no changes in the file content. |
| Log | `Replaced: {path}` | Replaced with new content because there is a difference in the file content. |
| Error | `Unexpected: {tag}` | The tag does not match. |
| Error | `Unclosed: {tag}` | There is no closing tag. |

#### Basic Syntax

Analyzes and executes custom HTML comment tags.

It also recognizes tags within comments such as `#` or `//`.

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

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

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

| Keyword | Description |
| :--- | :--- |
| [**TYPE**](#quick-start-replace-type-list) | The type of operation to execute. |
| **`(LANGUAGE)`** | The language identifier for the code block. |
| **`ARGS...`** | Arguments required for `TYPE`.<br>Specifiable values are described along with `TYPE`. |

If `LANGUAGE` is omitted, the execution result of `TYPE` is output as is.

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

If `LANGUAGE` is specified, the specified string is output as the language identifier for the code block.

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

However, if `none` is specified, nothing is output.

Use this when you want to execute only the command without outputting the result.

```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 List

##### bash

Outputs the execution result of the command specified in `ARGS`.

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

##### file

Outputs the content of the file specified in `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` is not required, and tags enclosed in `ignore` are ignored.

```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

Example command to generate a class map of the `src` directory.

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

| Output Type | Output Content | Description |
| :--- | :--- | :--- |
| Error | `NotFoundProject: {"entryPoints":[""],"rootPath":""}` | The specified project does not exist. |

<!-- docomet:/ignore -->

## 🗺️ Class Map

<!-- 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 -->

## 📋 Class Summary

<!-- 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 -->

## 🤝 Contributing

Feel free to ask questions or consult on 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)

## 📜 License

[Apache-2.0](LICENSE)

## ❤️ Acknowledgments

Developed with the support of [Google AI Studio](https://aistudio.google.com/) and [Gemini CLI](https://github.com/google-gemini/gemini-cli).

Also supported by the power of wonderful open-source projects.

I sincerely thank all maintainers and contributors.

### Other Tools

| Tool Name | Overview |
| :--- | :--- |
| **[vhs](https://github.com/charmbracelet/vhs)** | A tool for describing terminal operation procedures in code (scripts) and generating high-quality GIF animations or videos. |
