ヘヴィメタル・エンジニアリング

AWS特化型エンジニアのほのぼのヘヴィメタルブログ

ヘヴィメタル・エンジニアリング

クラウド特化型ヘヴィメタルエンジニアのほのぼのブログ

文章添削を簡単にできるシステムをDockerで作ってみた ~textlint~

はじめに

私は弊社のTech Blogを運営させていただいていて、その際に大きな課題となっているのが添削です。

複数人で1つの記事を添削するのですが、誤字脱字や表現間違い、typoなど多くミスがあり、人間の目だけでチェックするのには限界があり、見落としてしまうこともあります。

そんなときに自動でチェックしてくれるツールがないかと調べたときに見つけたのが、textlintでした。

今回はそのtextlintを使った文章チェック環境をDockerで作成するところまで書いていこうと思います。

textlint とは

npmで提供されているOSSライブラリで、textlintコマンドを使うと簡単に文章内のミスを検知してくれます。

github.com

textlintは予め用意されているルールをnpm installすることで使えます。

github.com

そして、自分でルールを作成してローカルや、publicに公開して使うこともできます。

textlint 使い方

使い方は簡単です。

まずtextlintをinstallします。

$ npm install textlint

次に適当なルールをインストールします。試しに文中で助詞が複数回連続で出てくるミスを検知してくれるルールを選択。

$ npm install textlint-rule-no-doubled-joshi

あとはファイルをチェックするだけ

$ echo "AWSでCloudWatchでログを収集します。" > text.md
$ textlint --rule no-doubled-joshi text.md
/Users/xxx/text.md
  1:15  error  一文に二回以上利用されている助詞 "で" がみつかりました。  no-doubled-joshi

✖ 1 problem (1 error, 0 warnings)

このように連続で助詞を使うような間違った使い方を指摘してくれます。

.textlintrc

.textlintにルールを予め書いておくことでtextlintコマンドを用いるときにルールの指定をする必要がありません。

jsonyaml形式で記述できます。

{
    "rules": {
        "no-doubled-joshi": true
    }
}
$ textlint text.md
/Users/xxx/text.md
  1:15  error  一文に二回以上利用されている助詞 "で" がみつかりました。  no-doubled-joshi

✖ 1 problem (1 error, 0 warnings)

大まかな使い方の説明は以上です。

Docker環境作成

今回作ったものはGitHub上に公開しています。

github.com

text-checkerというシステムを作成しました。

これをgit cloneしてDocker環境を立ち上げれば誰でも簡単にセットアップされたtextlintが使えるようにしていて、例としてカスタムルールも作成しています。

詳しく作る過程を説明していきます。

カスタムルールの作成

今回作成する環境では自分でカスタマイズしたルールも使えるようにします。

先ほど説明したように、textlintには便利なルールが多数用意されています。

ですがほしいルールがなかった場合は、自分で作成することをおすすめします。

作成したルールはnpmパッケージとしてパブリックに公開することもできますし、ローカルのみで使うこともできます。

まずは、create−textlint−ruleをインストールします。

$ npm install create-textlint-rule

次にルールを作成します。

$ create-textlint-rule test-rule

できたルールを見ると以下のような構成です。

.
├── README.md
├── lib
│   ├── index.js
│   └── index.js.map
├── package.json
├── package-lock.json
├── node_modules
├── src
│   └── index.js
└── test
    └── index-test.js

index.jsにルール、index−test.jsにtestのテンプレートが用意されています。

今回は特定の文字を見つけたらアラートを出すルールを作成します。

以下のようにiosIOSといった誤った表記(正しくはiOS)のものをcheckします。

index.ts

import { TextlintRuleModule } from '@textlint/types';
import { tokenize } from "kuromojin";

// https://github.com/MosasoM/inappropriate-words-ja
const inappropriateWords = [
    'ios',
    'IOS'
]

const module: TextlintRuleModule = (context) => {
    const { getSource, report, RuleError, Syntax } = context;

    return {
      async [Syntax.Str](node) {
        const text = getSource(node);
        const tokens = await tokenize(text);

        tokens.forEach(({ surface_form, word_position }) => {
          if (!inappropriateWords.includes(surface_form)) {
            return;
          }

          const index = word_position - 1;

          const ruleError = new RuleError(
            `不適切表現「${surface_form}」が含まれています。`,
            { index }
          );

          report(node, ruleError);
        });
      }
    }
  };
export default module;

typescriptも利用できます。

1からカスタムルールを作成することもできますが、既存で作成されているカスタムルールを参考にすると楽です。textlintはそのやり方を推奨しています。

私はこちらのカスタムルールを参考にさせていただきました。

github.com

次にpackage.jsonとtsconfig.jsonです。

package.json

{
    "version": "1.0.0",
    "keywords": [
      "textlintrule"
    ],
    "main": "lib/index.js",
    "files": [
      "lib/",
      "src/"
    ],
    "scripts": {
      "build": "textlint-scripts build",
      "test": "textlint-scripts test",
      "watch": "textlint-scripts build --watch",
      "tsc": "tsc"
    },
    "dependencies": {
      "kuromojin": "^2.0.0"
    },
    "devDependencies": {
      "@textlint/types": "^1.3.1",
      "@types/node": "^12.12.39",
      "textlint-scripts": "^3.0.0",
      "textlint-tester": "^5.1.15",
      "ts-node": "^8.10.1",
      "typescript": "^3.6.4"
    },
    "name": "textlint-rule-mistaken-ward-check",
    "directories": {
      "test": "test"
    },
    "author": "",
    "license": "ISC",
    "description": ""
  }

tsconfig.json

{
    "compilerOptions": {
      /* Basic Options */
      "module": "commonjs",
      "moduleResolution": "node",
      "esModuleInterop": true,
      "noEmit": true,
      "target": "es2015",
      /* Strict Type-Checking Options */
      "strict": true,
      /* Additional Checks */
      /* Report errors on unused locals. */
      "noUnusedLocals": true,
      /* Report errors on unused parameters. */
      "noUnusedParameters": true,
      /* Report error when not all code paths in function return a value. */
      "noImplicitReturns": true,
      /* Report errors for fallthrough cases in switch statement. */
      "noFallthroughCasesInSwitch": true
    }
  }

これで環境は整いました。

以下のコマンドでセットアップすれば使えるようになります。

$ npm install
$ npm run build

Dockerfile

実際にカスタムルールを作成できたので、それとマネージドルールを使って文章チェックの環境を作成してみましょう。

以下が作成したDockerfileです。

FROM node:14-slim

# setting
RUN apt-get update
RUN apt-get -y install vim
RUN { \
        echo 'set encoding=utf-8'; \
        echo 'set fileencodings=utf-8'; \
        echo 'set fileformats=unix,dos,mac'; \
    } > ~/.vimrc
RUN . ~/.vimrc

# copy
COPY package*.json /text-checker/
COPY .textlintrc /text-checker/
COPY textlint-rule-mistaken-ward-check /text-checker/textlint-rule-mistaken-ward-check

WORKDIR /text-checker
RUN npm install

# custom rule setting
WORKDIR /text-checker/textlint-rule-mistaken-ward-check
RUN npm install
RUN npm run build

WORKDIR /text-checker
RUN npm install ./textlint-rule-mistaken-ward-check

# path textlint
WORKDIR /usr/bin
RUN touch textlint
RUN ln -s --force /text-checker/node_modules/.bin/textlint textlint


WORKDIR /

いくつかポイントがあります。

vimを使う際のエンコード

環境内でvimエディターを使うことにします。

今回は日本語の添削をするので、vimを使う際に.vimrcutf-8エンコードするような処理を入れる必要があります。

これをしないと正しく日本語チェックをtextlintでできません。

RUN apt-get -y install vim
RUN { \
        echo 'set encoding=utf-8'; \
        echo 'set fileencodings=utf-8'; \
        echo 'set fileformats=unix,dos,mac'; \
    } > ~/.vimrc
RUN . ~/.vimrc

textlintのパスを通す

実際にnpm installでtextlintをインストールしただけではtextlintコマンドを使うことはできないのでPATHを通しましょう。

以下のようにusr/binにコマンドの設定が配置されているので、そこにtextlintファイルを作成して、機能へのシンボリックリンクを貼ります。

実際は/text-checker/node_modules/.bin/textlintを打つことで機能は使えますが、usr/binにコマンド設定を追加したことでtextlintコマンドとして使うことができます。

WORKDIR /usr/bin
RUN touch textlint
RUN ln -s --force /text-checker/node_modules/.bin/textlint textlint

これでDockerfileは作成完了です。

カスタムルールのサブモジュール化

実際にこの環境をローカルに落として使うときに、カスタムルールはサブモジュール化することをおすすめします。

そうでないと環境本体の更新カスタムルールの更新が煩雑になり、機能開発維持に支障が出ます。

サブモジュール化することでカスタムルールは別のgitリポジトリとして捉えられるので別個のシステムとして組み込み、機能開発のプロセスを分けることができます。

サブモジュール化するのは簡単で、以下のようなコマンドを打ちます。

$ git submodule add https://github.com/KenFujimoto12/textlint-rule-mistaken-ward-check.git textlint-rule-mistaken-ward-check

これによってルートディレクトリのように以下のような設定が作成されて、カスタムルールのサブモジュール化は完了です。

.gitmodules

[submodule "textlint-rule-mistaken-ward-check"]
    path = textlint-rule-mistaken-ward-check
    url = https://github.com/KenFujimoto12/textlint-rule-mistaken-ward-check.git

動作

実際に使ってみましょう。

今回の環境は以下のように取得、立ち上げをします。

$ git clone git@github.com:KenFujimoto12/text-checker.git
$ cd text-checker
$ git clone --recursive git@github.com:KenFujimoto12/textlint-rule-mistaken-ward-check.git
$ docker build -t text-checker .
$ docker run -it --name text-checker text-checker:latest bin/bash

docker runで環境に入ることができたら、実際にtextファイルを作成してチェックしてみましょう。

> cd text-checker
> vim test.txt
## copy and paste blog text into xxx.txt
> textlint --rulesdir node_modules/textlint-rule-mistaken-ward-check/lib/ test.txt -f pretty-error

index: 不適切表現「ios」が含まれています。
/text-checker/test.txt:3:6
                 v
    2.
    3. こんかいはiosについての話をしたいと思います。
    4.
                 ^

ja-no-weak-phrase: 弱い表現: "思います" が使われています。
/text-checker/test.txt:3:20
                                           v
    2.
    3. こんかいはiosについての話をしたいと思います。
    4.
                                           ^

index: 不適切表現「ios」が含まれています。
/text-checker/test.txt:5:1
       v
    4.
    5. iosアプリは実際はそんなに難しいものでもなく、誰でも簡単に開発できちゃいます。
    6.
       ^

✖ 3 problems (3 errors, 0 warnings)

ちゃんと意図したようなチェックをしてくれています。

終わりに

実際にtextlintを使ったweb添削サービスはいくつかあるみたいですが、自分でオリジナルの添削環境が作成できるので、今回のようにDockerを使った環境構築がおすすめです。

他にもいろんなルールを作成していきましょう。