Просмотр исходного кода

Merge pull request #6886 from k32/schema-spellcheck

Schema spellcheck
k32 4 лет назад
Родитель
Сommit
d955ec59ff

+ 14 - 14
apps/emqx/src/emqx_schema.erl

@@ -387,7 +387,7 @@ after idling for 'Keepalive * backoff * 2'."""
     , {"max_inflight",
        sc(range(1, 65535),
           #{ default => 32,
-             desc => "Maximum size of the Inflight Window storing QoS1/2 messages delivered but unacked."
+             desc => "Maximum size of the Inflight Window storing QoS1/2 messages delivered but un-acked."
            })
       }
     , {"retry_interval",
@@ -550,7 +550,7 @@ fields("overload_protection") ->
            })}
     , {"backoff_delay",
        sc(range(0, inf),
-          #{ desc => "Some unimporant tasks could be delayed"
+          #{ desc => "Some unimportant tasks could be delayed "
                      "for execution, here set the delays in ms"
            , default => 1
            })}
@@ -904,7 +904,7 @@ fields("broker") ->
            })}
     , {"perf",
        sc(ref("broker_perf"),
-          #{ desc => "Broker performance tuning pamaters"
+          #{ desc => "Broker performance tuning parameters"
            })
       }
     ];
@@ -1032,35 +1032,35 @@ in the VM exceeds this value"
     , {"db_hostname",
        sc(string(),
          #{ mapping => "system_monitor.db_hostname"
-          , desc => "Hostname of the postgres database that collects the data points"
+          , desc => "Hostname of the PostgreSQL database that collects the data points"
           })
       }
     , {"db_port",
        sc(integer(),
          #{ mapping => "system_monitor.db_port"
           , default => 5432
-          , desc => "Port of the postgres database that collects the data points"
+          , desc => "Port of the PostgreSQL database that collects the data points"
           })
       }
     , {"db_username",
        sc(string(),
          #{ mapping => "system_monitor.db_username"
           , default => "system_monitor"
-          , desc    => "EMQX user name in the postgres database"
+          , desc    => "EMQX user name in the PostgreSQL database"
           })
       }
     , {"db_password",
        sc(binary(),
          #{ mapping => "system_monitor.db_password"
           , default => "system_monitor_password"
-          , desc    => "EMQX user password in the postgres database"
+          , desc    => "EMQX user password in the PostgreSQL database"
           })
       }
     , {"db_name",
        sc(string(),
          #{ mapping => "system_monitor.db_name"
           , default => "postgres"
-          , desc    => "Postgres database name"
+          , desc    => "PostgreSQL database name"
           })
       }
     ];
@@ -1111,7 +1111,7 @@ fields("trace") ->
         default => text,
         desc => """
 Determine the format of the payload format in the trace file.<br>
-`text`: Text-based protocol or plain text protocol. It is recommended when payload is json encode.<br>
+`text`: Text-based protocol or plain text protocol. It is recommended when payload is JSON encoded.<br>
 `hex`: Binary hexadecimal encode. It is recommended when payload is a custom binary protocol.<br>
 `hidden`: payload is obfuscated as `******`
         """
@@ -1264,7 +1264,7 @@ keyfile is password-protected."""
           #{ default => default_tls_vsns(maps:get(versions, Defaults, tls_all_available))
            , desc =>
 """All TLS/DTLS versions to be supported.<br>
-NOTE: PSK ciphers are suppresed by 'tlsv1.3' version config<br>
+NOTE: PSK ciphers are suppressed by 'tlsv1.3' version config<br>
 In case PSK cipher suites are intended, make sure to configured
 <code>['tlsv1.2', 'tlsv1.1']</code> here.
 """
@@ -1361,7 +1361,7 @@ client_ssl_opts_schema(Defaults) ->
             , desc =>
 """Specify the host name to be used in TLS Server Name Indication extension.<br>
 For instance, when connecting to \"server.example.net\", the genuine server
-which accedpts the connection and performs TLS handshake may differ from the
+which accepts the connection and performs TLS handshake may differ from the
 host the TLS client initially connects to, e.g. when connecting to an IP address
 or when the host has multiple resolvable DNS records <br>
 If not specified, it will default to the host name string which is used
@@ -1405,7 +1405,7 @@ Selecting a good cipher suite is critical for the
 application's data security, confidentiality and performance.
 
 The names should be in OpenSSL string format (not RFC format).
-All default values and examples proveded by EMQ X config
+All default values and examples provided by EMQ X config
 documentation are all in OpenSSL format.<br>
 
 NOTE: Certain cipher suites are only compatible with
@@ -1415,7 +1415,7 @@ For instance, if only 'tlsv1.3' is given in the <code>versions</code>,
 configuring cipher suites for other versions will have no effect.
 <br>
 
-NOTE: PSK ciphers are suppresed by 'tlsv1.3' version config<br>
+NOTE: PSK ciphers are suppressed by 'tlsv1.3' version config<br>
 If PSK cipher suites are intended, 'tlsv1.3' should be disabled from <code>versions</code>.<br>
 PSK cipher suites: <code>\"RSA-PSK-AES256-GCM-SHA384,RSA-PSK-AES256-CBC-SHA384,
 RSA-PSK-AES128-GCM-SHA256,RSA-PSK-AES128-CBC-SHA256,
@@ -1469,7 +1469,7 @@ ref(Module, Field) -> hoconsc:ref(Module, Field).
 
 mk_duration(Desc, OverrideMeta) ->
     DefaultMeta = #{desc => Desc ++ " Time span. A text string with number followed by time units:
-                    `ms` for milli-seconds,
+                    `ms` for milliseconds,
                     `s` for seconds,
                     `m` for minutes,
                     `h` for hours;

+ 3 - 3
apps/emqx_authz/README.md

@@ -2,7 +2,7 @@
 
 ## Configure
 
-File: etc/pulgins/authz.conf
+File: etc/plugins/authz.conf
 
 ```json
 authz:{
@@ -72,7 +72,7 @@ authz:{
 
 ## Database Management
 
-#### Mysql
+#### MySQL
 
 Create Example Table
 
@@ -133,7 +133,7 @@ HSET mqtt_authz:emqx '$SYS/#' subscribe
 
 A rule of Redis AuthZ defines `publish`, `subscribe`, or `all `information. All lists in the rule are **allow** lists.
 
-#### Mongo
+#### MongoDB
 
 Create Example BSON documents
 ```sql

+ 1 - 1
apps/emqx_authz/src/emqx_authz_postgresql.erl

@@ -43,7 +43,7 @@
                        ?PH_CERT_SUBJECT]).
 
 description() ->
-    "AuthZ with Postgresql".
+    "AuthZ with PostgreSQL".
 
 init(#{query := SQL0} = Source) ->
     {SQL, PlaceHolders} = emqx_authz_utils:parse_sql(

+ 2 - 2
apps/emqx_bridge/src/emqx_bridge_schema.erl

@@ -98,7 +98,7 @@ fields("metrics") ->
     , {"rate", mk(float(), #{desc => "The rate of matched, times/second"})}
     , {"rate_max", mk(float(), #{desc => "The max rate of matched, times/second"})}
     , {"rate_last5m", mk(float(),
-        #{desc => "The average rate of matched in last 5 mins, times/second"})}
+        #{desc => "The average rate of matched in the last 5 minutes, times/second"})}
     ];
 
 fields("node_metrics") ->
@@ -113,7 +113,7 @@ fields("status") ->
     , {"rate", mk(float(), #{desc => "The rate of matched, times/second"})}
     , {"rate_max", mk(float(), #{desc => "The max rate of matched, times/second"})}
     , {"rate_last5m", mk(float(),
-        #{desc => "The average rate of matched in last 5 mins, times/second"})}
+        #{desc => "The average rate of matched in the last 5 minutes, times/second"})}
     ];
 
 fields("node_status") ->

+ 13 - 13
apps/emqx_conf/etc/emqx_conf.md

@@ -7,7 +7,7 @@ and a superset of JSON.
 EMQ X configuration consists of 3 layers.
 From bottom up:
 
-1. Immutable base: `emqx.conf` + `EMQX_` prfixed environment variables.<br>
+1. Immutable base: `emqx.conf` + `EMQX_` prefixed environment variables.<br>
    Changes in this layer require a full node restart to take effect.
 1. Cluster override: The path of which is configured by `cluster_override_conf_file`.<br>
    Overrides made from management APIs (or dashboard) for all nodes in the cluster.
@@ -18,7 +18,7 @@ For detailed override rules, see [Config overlay rules](#config-overlay-rules).
 
 ## Syntax
 
-In config file the values can be notated as JSON like ojbects, such as
+In config file the values can be notated as JSON like objects, such as
 ```
 node {
     name = "emqx@127.0.0.1"
@@ -36,7 +36,7 @@ node.cookie="mysecret"
 This flat format is almost backward compatible with EMQ X's config file format
 in 4.x series (the so called 'cuttlefish' format).
 
-It is 'almost' compabile because the often HOCON requires strings to be quoted,
+It is 'almost' compatible because the often HOCON requires strings to be quoted,
 while cuttlefish treats all characters to the right of the `=` mark as the value.
 
 e.g. cuttlefish: `node.name = emqx@127.0.0.1`, HOCON: `node.name = "emqx@127.0.0.1"`
@@ -44,11 +44,11 @@ e.g. cuttlefish: `node.name = emqx@127.0.0.1`, HOCON: `node.name = "emqx@127.0.0
 Strings without special characters in them can be unquoted in HOCON too,
 e.g. `foo`, `foo_bar`, `foo_bar_1`:
 
-For more HOCON syntax, pelase refer to the [specification](https://github.com/lightbend/config/blob/main/HOCON.md)
+For more HOCON syntax, please refer to the [specification](https://github.com/lightbend/config/blob/main/HOCON.md)
 
 ## Schema
 
-To make the HOCON objects type-safe, EMQ X introduded a schema for it.
+To make the HOCON objects type-safe, EMQ X introduced a schema for it.
 The schema defines data types, and data fields' names and metadata for config value validation
 and more. In fact, this config document itself is generated from schema metadata.
 
@@ -56,10 +56,10 @@ and more. In fact, this config document itself is generated from schema metadata
 
 There are 4 complex data types in EMQ X's HOCON config:
 
-1. Struct: Named using an unquoted string, followed by a pre-defined list of fields,
+1. Struct: Named using an unquoted string, followed by a predefined list of fields,
    fields can not start with a number, and are only allowed to use
    lowercase letters and underscores as word separator.
-1. Map: Map is like Struct, however the fields are not pre-defined.
+1. Map: Map is like Struct, however the fields are not predefined.
    1-based index number can also be used as map keys for an alternative
    representation of an Array.
 1. Union: `MemberType1 | MemberType2 | ...`
@@ -69,7 +69,7 @@ There are 4 complex data types in EMQ X's HOCON config:
 
 Complex types define data 'boxes' which may contain other complex data
 or primitive values.
-There are quite some different primitive types, to name a fiew:
+There are quite some different primitive types, to name a few:
 
 * `atom()`
 * `boolean()`
@@ -101,7 +101,7 @@ zone.zone1.max_packet_size="10M"
 authentication.1.enable=true
 ```
 
-### Environment varialbes
+### Environment variables
 
 Environment variables can be used to define or override config values.
 
@@ -112,7 +112,7 @@ And a the `EMQX_` prefix is used as the namespace.
 
 For example `node.name` can be represented as `EMQX_NODE__NAME`
 
-Environment varialbe values are parsed as hocon values, this allows users
+Environment variable values are parsed as HOCON values, this allows users
 to even set complex values from environment variables.
 
 For example, this environment variable sets an array value.
@@ -136,14 +136,14 @@ because the field name is `enable`, not `enabled`.
 HOCON objects are overlaid, in general:
 
 - Within one file, objects defined 'later' recursively override objects defined 'earlier'
-- When layered, 'later' (hihger lalyer) objects override objects defined 'earlier' (lower layer)
+- When layered, 'later' (higher layer) objects override objects defined 'earlier' (lower layer)
 
 Below are more detailed rules.
 
 #### Struct Fields
 
 Later config values overwrites earlier values.
-For example, in below config, the last line `debug` overwrites `errro` for
+For example, in below config, the last line `debug` overwrites `error` for
 console log handler's `level` config, but leaving `enable` unchanged.
 ```
 log {
@@ -161,7 +161,7 @@ log.console_handler.level=debug
 #### Map Values
 
 Maps are like structs, only the files are user-defined rather than
-the config schema. For instance, `zone1` in the exampele below.
+the config schema. For instance, `zone1` in the example below.
 
 ```
 zone {

+ 3 - 3
apps/emqx_conf/src/emqx_conf_schema.erl

@@ -74,7 +74,7 @@ roots() ->
     [ {"node",
        sc(hoconsc:ref("node"),
           #{ desc => "Node name, cookie, config & data directories "
-                     "and the Eralng virtual machine (beam) boot parameters."
+                     "and the Erlang virtual machine (BEAM) boot parameters."
            })}
     , {"cluster",
        sc(hoconsc:ref("cluster"),
@@ -840,9 +840,9 @@ In EMQ X, MQTT client access control is extremely flexible.<br>
 An out of the box set of authorization data sources are supported.
 For example,<br>
 'file' source is to support concise and yet generic ACL rules in a file;<br>
-'built-in-database' source can be used to store per-client customisable rule sets,
+'built-in-database' source can be used to store per-client customizable rule sets,
 natively in the EMQ X node;<br>
 'http' source to make EMQ X call an external HTTP API to make the decision;<br>
-'postgresql' etc. to look up clients or rules from external databases;<br>
+'PostgreSQL' etc. to look up clients or rules from external databases;<br>
 """ })},
     lists:keyreplace("authorization", 1, Roots, Authz).

+ 3 - 3
apps/emqx_connector/README.md

@@ -6,10 +6,10 @@ A `connector` is a callback module of `emqx_resource` that maintains the data re
 external resources. Put all resource related callback modules in a single application is good as
 we can put some util functions/modules here for reusing purpose.
 
-For example, a mysql connector is an emqx resource that maintains all the mysql connection
-related parameters (configs) and the TCP connections to the mysql server.
+For example, a MySQL connector is an emqx resource that maintains all the MySQL connection
+related parameters (configs) and the TCP connections to the MySQL server.
 
-An mysql connector can be used as following:
+An MySQL connector can be used as following:
 
 ```
 (emqx@127.0.0.1)5> emqx_resource:list_instances_verbose().

+ 1 - 1
apps/emqx_connector/src/mqtt/emqx_connector_mqtt_schema.erl

@@ -130,7 +130,7 @@ Template with variables is allowed."""
     , {hookpoint,
         sc(binary(),
            #{ desc => """
-The hookpoint will be triggered when there's any message received from the remote broker.
+The hook point will be triggered when there's any message received from the remote broker.
 """
             })}
     ] ++ common_inout_confs();

+ 3 - 3
apps/emqx_gateway/src/emqx_gateway_schema.erl

@@ -164,7 +164,7 @@ fields(coap) ->
        sc(duration(),
           #{ default => <<"30s">>
            , desc =>
-"The gateway server required minimum hearbeat interval.<br>
+"The gateway server required minimum heartbeat interval.<br>
 When connection mode is enabled, this parameter is used to set the minimum
 heartbeat interval for the connection to be alive."
            })}
@@ -377,7 +377,7 @@ fields(udp_tcp_listeners) ->
     ];
 
 fields(tcp_listener) ->
-    [ %% some special confs for tcp listener
+    [ %% some special configs for tcp listener
       {acceptors, sc(integer(), #{default => 16})}
     ] ++
     tcp_opts() ++
@@ -394,7 +394,7 @@ fields(ssl_listener) ->
 
 fields(udp_listener) ->
     [
-     %% some special confs for udp listener
+     %% some special configs for udp listener
     ] ++
     udp_opts() ++
     common_listener_opts();

+ 2 - 2
apps/emqx_gateway/src/lwm2m/README.md

@@ -107,7 +107,7 @@ The MQTT message will be translated to an LwM2M DISCOVER command and sent to the
       - "execute": LwM2M Execute
       - "create": LwM2M Create
       - "delete": LwM2M Delete
-    - {?Data}: Json Object, its value depends on the {?MsgType}:
+    - {?Data}: JSON Object, its value depends on the {?MsgType}:
       - **If {?MsgType} = "read" or "discover"**:
         ```json
         {
@@ -212,7 +212,7 @@ The MQTT message will be translated to an LwM2M DISCOVER command and sent to the
     - "create": LwM2M Create
     - "delete": LwM2M Delete
     - **"ack"**: [CoAP Empty ACK](https://tools.ietf.org/html/rfc7252#section-5.2.2)
-  - {?Data}: Json Object, its value depends on {?MsgType}:
+  - {?Data}: JSON Object, its value depends on {?MsgType}:
     - **If {?MsgType} = "write", "write-attr", "execute", "create", "delete", or "read"(when response without content)**:
       ```json
       {

+ 2 - 2
apps/emqx_modules/src/emqx_modules_schema.erl

@@ -90,10 +90,10 @@ fields("event_message") ->
     #{fields => Fields,
       desc => """
 Enable/Disable system event messages.
-The messages are plublished to '$event' prefixed topics.
+The messages are published to '$event' prefixed topics.
 For example, if `client_disconnected` is set to `true`,
 a message is published to `$event/client_connected` topic
-whenver a client is connected.
+whenever a client is connected.
 """};
 
 fields("topic_metrics") ->

+ 1 - 1
apps/emqx_plugins/src/emqx_plugins_schema.erl

@@ -51,7 +51,7 @@ state_fields() ->
     [ {name_vsn,
        hoconsc:mk(string(),
                   #{ desc => "The {name}-{version} of the plugin.<br>"
-                             "It should match the plugin application name-vsn as the "
+                             "It should match the plugin application name-version as the "
                              "for the plugin release package name<br>"
                              "For example: my_plugin-0.1.0."
                    , nullable => false

+ 3 - 2
apps/emqx_psk/src/emqx_psk_schema.erl

@@ -49,7 +49,7 @@ fields() ->
     ].
 
 enable(type) -> boolean();
-enable(desc) -> <<"Whether to enable tls psk support">>;
+enable(desc) -> <<"Whether to enable TLS PSK support">>;
 enable(default) -> false;
 enable(_) -> undefined.
 
@@ -58,7 +58,8 @@ init_file(desc) ->
     <<"If init_file is specified, emqx will import PSKs from the file ",
       "into the built-in database at startup for use by the runtime. ",
       "The file has to be structured line-by-line, each line must be in ",
-      "the format of 'PSKIdentity:SharedSecret' for example: mydevice1:c2VjcmV0">>;
+      "the format of 'PSKIdentity:SharedSecret' for example: ",
+      "<code>mydevice1:c2VjcmV0</code>">>;
 init_file(nullable) -> true;
 init_file(_) -> undefined.
 

+ 1 - 1
apps/emqx_rule_engine/src/emqx_rule_api_schema.erl

@@ -87,7 +87,7 @@ fields("metrics") ->
     , {"sql.matched.rate", sc(float(), #{desc => "The rate of matched, times/second"})}
     , {"sql.matched.rate.max", sc(float(), #{desc => "The max rate of matched, times/second"})}
     , {"sql.matched.rate.last5m", sc(float(),
-        #{desc => "The average rate of matched in last 5 mins, times/second"})}
+        #{desc => "The average rate of matched in last 5 minutes, times/second"})}
     , {"sql.passed", sc(integer(), #{desc => "How much times the SQL is passed"})}
     , {"sql.failed", sc(integer(), #{desc => "How much times the SQL is failed"})}
     , {"sql.failed.exception", sc(integer(), #{

+ 1 - 1
apps/emqx_rule_engine/src/emqx_rule_engine_schema.erl

@@ -101,7 +101,7 @@ Then there are 3 variables available: <code>clientid</code>, <code>qos</code> an
     }
 </code>
 When the rule is triggered by an MQTT message with payload = \"hello\", qos = 1,
-clientid = \"steve\", the rule will republish a new MQTT message to topic \"t/steve\",
+clientid = \"Steve\", the rule will republish a new MQTT message to topic \"t/Steve\",
 payload = \"msg: hello\", and qos = 1.
 """
          , default => #{}

+ 1 - 1
apps/emqx_slow_subs/src/emqx_slow_subs_schema.erl

@@ -26,7 +26,7 @@ fields("slow_subs") ->
        sc(emqx_schema:duration_ms(),
           "0s",
           "The interval for pushing statistics table records to the system topic. "
-          "publish topk list to $SYS/brokers/${node}/slow_subs per notice_interval. "
+          "publish top-k list to $SYS/brokers/${node}/slow_subs per notice_interval. "
           "publish is disabled if set to 0s."
          )}
     , {notice_qos,

+ 246 - 0
scripts/dict/.aspell.en

@@ -0,0 +1,246 @@
+personal_ws-1.1 en 254
+ACL
+AES
+APIs
+BPAPI
+BSON
+Backplane
+CHACHA
+CLI
+CMD
+CN
+CONNACK
+CoAP
+Cygwin
+DES
+DN
+DNS
+DTLS
+DevOps
+Dialyzer
+Diffie
+EIP
+EMQ
+EPMD
+ERL
+ETS
+FIXME
+GCM
+Gw
+HOCON
+HTTPS
+JSON
+Kubernetes
+LwM
+MQTT
+Makefile
+MitM
+Multicast
+NIF
+OTP
+PEM
+PINGREQ
+PSK
+PUBREL
+QoS
+RESTful
+ROADMAP
+RSA
+Req
+Riak
+SHA
+SMS
+Struct
+TCP
+TLS
+TTL
+UDP
+URI
+XMLs
+acceptors
+ack
+acked
+addr
+api
+apiserver
+arg
+args
+async
+attr
+auth
+authenticator
+authenticators
+authn
+authz
+autoclean
+autoheal
+backend
+backends
+backoff
+backplane
+backtrace
+badarg
+badkey
+bcrypt
+behaviour
+bhvr
+boolean
+bytesize
+cacert
+cacertfile
+certfile
+ci
+clientid
+clientinfo
+cmake
+coap
+conf
+config
+configs
+confirmable
+conn
+connectionless
+cors
+cpu
+ctx
+customizable
+desc
+dir
+dns
+downlink
+downlink
+dtls
+ekka
+emqx
+enablement
+enqueue
+enqueued
+env
+eof
+epmd
+erl
+erts
+escript
+etcd
+eval
+exe
+executables
+exhook
+exproto
+extensibility
+formatter
+gRPC
+github
+goto
+grpcbox
+hocon
+hoconsc
+hostname
+hrl
+http
+https
+iface
+img
+impl
+inet
+inflight
+ini
+init
+ip
+ipv
+jenkins
+jq
+kb
+keepalive
+libcoap
+lifecycle
+localhost
+lwm
+mnesia
+mountpoint
+mqueue
+mria
+msg
+multicalls
+multicasts
+namespace
+natively
+nodelay
+nodetool
+nullable
+num
+os
+params
+peerhost
+peername
+perf
+powershell
+procmem
+procs
+progname
+prometheus
+proto
+ps
+psk
+pubsub
+qlen
+qmode
+qos
+quic
+ratelimit
+rebar
+recbuf
+relup
+replayq
+replicant
+repo
+reuseaddr
+rh
+rlog
+rootdir
+rpc
+runtime
+sc
+scalable
+seg
+setcookie
+sharded
+shareload
+sn
+sndbuf
+sockname
+sql
+src
+ssl
+statsd
+structs
+subprotocol
+subprotocols
+superset
+sys
+sysmem
+sysmon
+tcp
+ticktime
+tlog
+tls
+tlsv
+travis
+trie
+ttl
+typerefl
+udp
+uid
+unsub
+uplink
+url
+utc
+util
+ver
+vm
+vsn
+wakaama
+websocket
+ws
+wss
+xml

+ 72 - 0
scripts/spellcheck

@@ -0,0 +1,72 @@
+#!/bin/bash
+# shellcheck disable=SC2015
+set -euo pipefail
+
+aspell -v > /dev/null && [ "$#" -eq 1 ] || {
+    echo "Usage:
+  $(basename "$0") check
+or
+  $(basename "$0") fix
+
+Note: this script needs aspell to run"
+    exit 1
+}
+
+action=$1
+
+dict_dir="$(git rev-parse --show-toplevel)/$(dirname "$0")/dict"
+echo "${dict_dir}"
+dict="${dict_dir}/.aspell.en"
+
+export fail=0
+
+aspellcmd() {
+    local mode
+    mode="${1}"
+    shift
+    aspell --mode "${mode}" --camel-case --add-filter html --add-html-skip code -p "$dict" "$@"
+}
+
+check() {
+    local mode file typos ntypos
+    mode="$1"
+    file="$2"
+
+    echo "!! Spellchecking ${file}"
+    typos="$(mktemp)"
+    echo "!! Typos:"
+    aspellcmd "$mode" list < "$file" |
+        sort -u |
+        tee "$typos"
+    ntypos="$(wc -l "$typos")"
+    rm "$typos"
+    [ "$ntypos" = 0 ] || export fail=1
+}
+
+fix() {
+    local mode file
+    mode=$1
+    file=$2
+
+    aspellcmd "$mode" check "$file"
+}
+
+case $action in
+    fix)
+        for i in $(git ls-tree -r --name-only HEAD | grep -E '_schema.erl$'); do
+            fix perl "$i"
+        done
+        # for i in $(git ls-tree -r --name-only HEAD | grep -E '.md$'); do
+        #     fix markdown $i
+        # done
+    ;;
+    *)
+        check markdown _build/emqx/lib/emqx_dashboard/priv/www/static/config.md
+esac
+
+
+if [ $fail -eq 1 ]; then
+    echo
+    echo "!! Bad spelling in the documentation. Run script in fix mode to resolve problems."
+    exit 1
+fi