├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── ct └── moyo_ct_SUITE.erl ├── doc ├── README.md ├── moyo_application.md ├── moyo_assoc.md ├── moyo_binary.md ├── moyo_char.md ├── moyo_clock.md ├── moyo_command.md ├── moyo_concurrent.md ├── moyo_cond.md ├── moyo_ct.md ├── moyo_file.md ├── moyo_frac.md ├── moyo_fun.md ├── moyo_graph.md ├── moyo_inet.md ├── moyo_list.md ├── moyo_math.md ├── moyo_monad.md ├── moyo_pipe.md ├── moyo_proc.md ├── moyo_rpc.md ├── moyo_string.md ├── moyo_url.md ├── moyo_validator.md ├── moyo_xml.md └── overview.edoc ├── include ├── eunit.hrl ├── guard.hrl └── moyo_internal.hrl ├── rebar.config ├── rebar.lock ├── rebar3 ├── src ├── moyo.app.src ├── moyo_application.erl ├── moyo_assoc.erl ├── moyo_binary.erl ├── moyo_char.erl ├── moyo_clock.erl ├── moyo_command.erl ├── moyo_concurrent.erl ├── moyo_cond.erl ├── moyo_ct.erl ├── moyo_file.erl ├── moyo_frac.erl ├── moyo_fun.erl ├── moyo_graph.erl ├── moyo_inet.erl ├── moyo_list.erl ├── moyo_math.erl ├── moyo_monad.erl ├── moyo_pipe.erl ├── moyo_proc.erl ├── moyo_rpc.erl ├── moyo_string.erl ├── moyo_url.erl ├── moyo_validator.erl └── moyo_xml.erl └── test ├── moyo_application_tests.erl ├── moyo_assoc_tests.erl ├── moyo_binary_tests.erl ├── moyo_char_tests.erl ├── moyo_clock_tests.erl ├── moyo_command_tests.erl ├── moyo_concurrent_tests.erl ├── moyo_cond_tests.erl ├── moyo_eunit_tests.erl ├── moyo_file_tests.erl ├── moyo_frac_tests.erl ├── moyo_fun_tests.erl ├── moyo_graph_tests.erl ├── moyo_guard_tests.erl ├── moyo_inet_tests.erl ├── moyo_list_tests.erl ├── moyo_math_tests.erl ├── moyo_monad_tests.erl ├── moyo_pipe_tests.erl ├── moyo_rpc_tests.erl ├── moyo_string_tests.erl ├── moyo_url_tests.erl ├── moyo_validator_tests.erl ├── moyo_xml_tests.erl └── testdata ├── moyo_command ├── exit_1.sh └── stderr.sh └── moyo_xml └── test.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .eunit 2 | deps 3 | *.o 4 | *.beam 5 | *.plt 6 | *.dump 7 | *.app 8 | *.swp 9 | *~ 10 | doc/* 11 | !doc/overview.edoc 12 | !doc/*.md 13 | tags 14 | .rebar 15 | .idea/ 16 | *.iml 17 | logs 18 | rebar3.crashdump -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: erlang 2 | otp_release: 3 | - 21.0 4 | - 20.3 5 | - 19.3 6 | - 18.3 7 | before_script: 8 | - export TZ=Asia/Tokyo 9 | script: make 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2015 DWANGO Co., Ltd. All Rights Reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 今までの影響で `make` を叩いてしまう人が多そうなので `make` で `rebar3` のコマンドを促すようにする 2 | all: compile xref eunit ct dialyzer edoc 3 | 4 | init: 5 | @echo 'Warning: Deprecated make target' 6 | @echo 'Use `./rebar3 compile`' 7 | @./rebar3 compile 8 | 9 | refresh-deps: 10 | @echo 'Please use `./rebar3 upgrade` or else' 11 | 12 | compile: 13 | @echo 'Warning: Deprecated make target' 14 | @echo 'Use `./rebar3 compile`' 15 | @./rebar3 compile 16 | 17 | xref: 18 | @echo 'Warning: Deprecated make target' 19 | @echo 'Use `./rebar3 xref`' 20 | @./rebar3 xref 21 | 22 | clean: 23 | @echo 'Warning: Deprecated make target' 24 | @echo 'Use `./rebar3 clean`' 25 | @./rebar3 clean 26 | 27 | distclean: 28 | git clean -df 29 | 30 | eunit: 31 | @echo 'Warning: Deprecated make target' 32 | @echo 'Use `./rebar3 eunit`' 33 | @./rebar3 eunit 34 | 35 | ct: 36 | @echo 'Warning: Deprecated make target' 37 | @echo 'Use `./rebar3 ct`' 38 | @./rebar3 ct 39 | 40 | edoc: 41 | @echo 'Warning: Deprecated make target' 42 | @echo 'Use `./rebar3 as dev edoc`' 43 | @./rebar3 as dev edoc 44 | 45 | start: 46 | @echo 'Warning: Deprecated make target' 47 | @echo 'Use `./rebar3 shell`' 48 | @./rebar3 shell 49 | 50 | .dialyzer.plt: 51 | @echo 'Please use `./rebar3 dialyzer` or else' 52 | 53 | dialyzer: 54 | @echo 'Warning: Deprecated make target' 55 | @echo 'Use `./rebar3 dialyzer`' 56 | @./rebar3 dialyzer 57 | 58 | # 互換性維持用 59 | dialyze: dialyzer 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # moyo 2 | 3 | [![Build Status](https://travis-ci.org/dwango/moyo.svg?branch=master)](https://travis-ci.org/dwango/moyo) 4 | 5 | ## 概要 6 | MOYOはErlang用のユーティリティモジュール集です。 7 | 8 | ## 依存関係 9 | * OTPのバージョンが18.0以上である必要があります。 10 | 11 | ## ビルド方法 12 | 13 | ビルドツールには [rebar3](https://github.com/erlang/rebar3) を使用しています。 14 | ※ ただし通常はmakeコマンド経由でrebarが実行されるので、自分で叩く機会は少ないです。 15 | 16 | 17 | ```shell 18 | # リポジトリ取得 19 | $ git clone git@github.com:dwango/moyo.git 20 | $ cd moyo 21 | 22 | # コンパイル 23 | $ make compile 24 | 25 | # ユニットテスト 26 | $ make eunit 27 | 28 | # 型チェック 29 | $ make dialyze 30 | 31 | # reloader(更新モジュールの自動読み込み)付きでErlangシェルを起動する 32 | # => Erlangシェルを立ち上げた状態で, ソースの修正&コンパイルを行うと, 該当モジュールが自動で更新されます 33 | $ make start 34 | > moyo_binary:to_hex(<<"abc">>). 35 | <<"616263">> 36 | ``` 37 | 38 | ## 他のプロジェクトに組み込む方法 39 | 40 | rebar3を使った一般的なライブラリと同様の方法で組み込みが可能です。 41 | 42 | 具体的にはプロジェクトのrebar.configに以下のようなエントリを追加すると、moyoが依存関係に追加され、リポジトリの取得やビルドがrebar3コマンド経由で行えるようになります。 43 | ```erlang 44 | {deps, 45 | [ 46 | {moyo, ".*", {git, "git@github.com:dwango/moyo.git", {tag, "対象バージョン"}}} 47 | ]}. 48 | ``` 49 | 50 | ## モジュール/関数ドキュメント 51 | 各モジュール、各関数に関するドキュメントについては以下のURLより参照して下さい。 52 | 53 | [doc/README.md](doc/README.md) 54 | 55 | ## 運用方針 56 | * **コード規約** は以下を参照 57 | - [MOYOコーディング規約](https://github.com/dwango/moyo/wiki/MOYOコーディング規約) 58 | - 足りない部分は随時更新される。 59 | * **修正はプルリクで** 反映させる。 60 | - 修正した場合はプルリクを投げてリポジトリに反映させることとする。 61 | - 修正時は修正用ブランチ`feature/XXX`を作成して, そのブランチで作業をする。 (XXXには作業内容を入れる。複数単語の場合`-`で繋ぐ)。 62 | - merge先は`master`ブランチにする。 63 | * テストを書く。 64 | - **カバレッジが100%になるように** 。 65 | + Erlangにはユニットテストフレームワークとして **EUnit** が存在する。 (実行: `% make eunit`) 66 | + EUnitを実行するとカバレッジページ(htmlフォーマット)が生成される。 プルリクはこのカバレッジを100%にして投げるようにする。 67 | * ただし100%にすることが困難な場合はプルリクに明記すればその限りではない。 68 | - **dialyzerのチェックをパスするように** 。 69 | + dialyzerはErlangの型の静的解析を行ってくれるツール (リストを期待する関数に整数を渡してたりすると怒ってくれる) 70 | + `make dialyze` を実行してエラーがでないことを確認する。 71 | * **ドキュメントは必須** 72 | - exportしている関数には必ずドキュメントを付ける。 73 | + edoc準拠(詳細は後に記述予定) 74 | + 例外あり(callback関数等) 75 | 76 | ## バージョン管理方法 77 | * バージョンは`v1.2.3`という形でtagを付けることにより管理する。 78 | - `v`: 接頭辞として固定 79 | - `1`: **メジャバージョン** 80 | - `2`: **マイナバージョン** 81 | - `3`: **ビルドバージョン** 82 | * masterに **修正が加えられる毎に** `ビルドバージョン`を増加させる。 83 | - 後方互換性がない修正が入る場合には `マイナバージョン` を増加させて、`ビルドバージョン`は0にリセットする。 84 | 85 | ## その他 86 | * 特になし 87 | 88 | MOYOは、アフリカ東部で使われるスワヒリ語で、心、魂、精神などを意味します。 89 | 90 | Copyright (c) 2013-2014 DWANGO Co., Ltd. All Rights Reserved. 91 | -------------------------------------------------------------------------------- /ct/moyo_ct_SUITE.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2013-2014 DWANGO Co., Ltd. All Rights Reserved. 2 | -module(moyo_ct_SUITE). 3 | 4 | -compile(export_all). 5 | -include_lib("common_test/include/ct.hrl"). 6 | 7 | %%---------------------------------------------------------------------------------------------------------------------- 8 | %% 'common_test' Callback API 9 | %%---------------------------------------------------------------------------------------------------------------------- 10 | 11 | all() -> 12 | moyo_ct:all(?MODULE). 13 | 14 | %%---------------------------------------------------------------------------------------------------------------------- 15 | %% Common Test Functions 16 | %%---------------------------------------------------------------------------------------------------------------------- 17 | 18 | %% ct. 19 | all_ct(_) -> 20 | true = lists:member(all_ct, all()). 21 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # moyo # 4 | 5 | Copyright (c) 2013-2014 DWANGO Co., Ltd. All Rights Reserved. 6 | 7 | MOYOはErlang用のユーティリティモジュール集です。
8 | MOYOは、アフリカ東部で使われるスワヒリ語で、心、魂、精神などを意味します。 9 | 10 | 11 | ## Modules ## 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
moyo_application
moyo_assoc
moyo_binary
moyo_char
moyo_clock
moyo_command
moyo_concurrent
moyo_cond
moyo_ct
moyo_file
moyo_frac
moyo_fun
moyo_graph
moyo_inet
moyo_list
moyo_math
moyo_monad
moyo_pipe
moyo_proc
moyo_rpc
moyo_string
moyo_url
moyo_validator
moyo_xml
39 | 40 | -------------------------------------------------------------------------------- /doc/moyo_application.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module moyo_application # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | アプリケーション関連の処理を集めたモジュール. 10 | 11 | Copyright (c) 2013-2015 DWANGO Co., Ltd. All Rights Reserved. 12 | 13 | 14 | 15 | ## Data Types ## 16 | 17 | 18 | 19 | 20 | ### name() ### 21 | 22 | 23 |

 24 | name() = atom()
 25 | 
26 | 27 | アプリケーション名 28 | 29 | 30 | 31 | ## Function Index ## 32 | 33 | 34 |
ensure_all_loaded/1指定されたアプリケーションおよびそれが依存するプリケーション群がロードされているようにする.
ensure_all_unloaded/1Pred(Application)trueを返したアプリケーションを全て停止してアンロードする.
ensure_loaded/1指定されたアプリケーションが確実にロードされているようにする.
get_key/3applications:get_key/2にデフォルト値を指定可能にしたもの.
get_priv_dir/1code:priv_dir/1の代替となる関数。.
35 | 36 | 37 | 38 | 39 | ## Function Details ## 40 | 41 | 42 | 43 | ### ensure_all_loaded/1 ### 44 | 45 |

 46 | ensure_all_loaded(Application::name()) -> {ok, Loaded} | {error, Reason}
 47 | 
48 | 49 | 50 | 51 | 指定されたアプリケーションおよびそれが依存するプリケーション群がロードされているようにする 52 | 53 | `Loaded`は、関数呼び出し中に新規にロードされたアプリケーション群 54 | 55 | 56 | 57 | ### ensure_all_unloaded/1 ### 58 | 59 |

 60 | ensure_all_unloaded(Pred::fun((Application::atom()) -> boolean())) -> ok | {error, Reason::term()}
 61 | 
62 |
63 | 64 | `Pred(Application)`が`true`を返したアプリケーションを全て停止してアンロードする. 65 | 66 | [`application:loaded_applications/0`](application.md#loaded_applications-0)の結果に基づき、
67 | ロードされているアプリケーションについて`Pred(Application)`を呼び出し、
68 | `Pred(Application)`が`true`を返したアプリケーションを全て停止してアンロードする. 69 | 70 | 一つでもアンロードに失敗した場合は、即座に`{error, Reason}`を返す. 71 | 72 | ※`kernel`と`stdlib`はアンロードしない. 73 | 74 | 75 | 76 | ### ensure_loaded/1 ### 77 | 78 |

 79 | ensure_loaded(Application::name()) -> ok | {error, Reason}
 80 | 
81 | 82 | 83 | 84 | 指定されたアプリケーションが確実にロードされているようにする 85 | 86 | [`application:load/1`](application.md#load-1)とは異なり、既にロード済みのアプリケーションが指定された場合は`ok`が返される 87 | 88 | 89 | 90 | ### get_key/3 ### 91 | 92 |

 93 | get_key(Application::name(), Key::atom(), DefaultValue::term()) -> Value::term()
 94 | 
95 |
96 | 97 | [`applications:get_key/2`](applications.md#get_key-2)にデフォルト値を指定可能にしたもの 98 | 99 | 100 | 101 | ### get_priv_dir/1 ### 102 | 103 |

104 | get_priv_dir(Application::name()) -> {ok, file:filename()} | {error, bad_name}
105 | 
106 |
107 | 108 | [`code:priv_dir/1`](code.md#priv_dir-1)の代替となる関数。 109 | 110 | 標準あるいはERL_LIBS環境変数で指定されたディレクトリ以下に指定したアプリケーションが存在せず 111 | code:priv_dirに失敗した場合もprivディレクトリを推測して値を返す 112 | 113 | -------------------------------------------------------------------------------- /doc/moyo_char.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module moyo_char # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | 文字を扱うライブラリ. 9 | 10 | Copyright (c) 2015 DWANGO Co., Ltd. All Rights Reserved. 11 | 12 | 13 | 14 | ## Function Index ## 15 | 16 | 17 |
is_alpha/1Charが英字[a-z A-Z]かどうかを判定する.
is_alpha_num/1Charが英数字かどうかを判定する.
is_num/1Charが数文字[0-9]かどうかを判定する.
18 | 19 | 20 | 21 | 22 | ## Function Details ## 23 | 24 | 25 | 26 | ### is_alpha/1 ### 27 | 28 |

29 | is_alpha(Char::char()) -> boolean()
30 | 
31 |
32 | 33 | `Char`が英字[a-z A-Z]かどうかを判定する 34 | 35 | 36 | 37 | ### is_alpha_num/1 ### 38 | 39 |

40 | is_alpha_num(Char::char()) -> boolean()
41 | 
42 |
43 | 44 | `Char`が英数字かどうかを判定する 45 | 46 | 47 | 48 | ### is_num/1 ### 49 | 50 |

51 | is_num(Char::char()) -> boolean()
52 | 
53 |
54 | 55 | `Char`が数文字[0-9]かどうかを判定する 56 | 57 | -------------------------------------------------------------------------------- /doc/moyo_command.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module moyo_command # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | 外部コマンドの実行に関する処理を集めたユーティリティモジュール. 10 | 11 | Copyright (c) 2013-2014 DWANGO Co., Ltd. All Rights Reserved. 12 | 13 | 14 | 15 | ## Data Types ## 16 | 17 | 18 | 19 | 20 | ### argument() ### 21 | 22 | 23 |

 24 | argument() = iodata() | integer() | {iodata(), [argument_option(), ...]} | option_element()
 25 | 
26 | 27 | 外部プログラムに渡す引数. 28 | 29 | 30 | 31 | ### argument_option() ### 32 | 33 | 34 |

 35 | argument_option() = long_option | equal | escape | float_option()
 36 | 
37 | 38 | 引数に関するオプション. 39 | 40 | 41 | 42 | ### command() ### 43 | 44 | 45 |

 46 | command() = iodata_or_atom()
 47 | 
48 | 49 | 外部プログラムの実行パス. 50 | 51 | 52 | 53 | ### destination_file_path() ### 54 | 55 | 56 |

 57 | destination_file_path() = binary()
 58 | 
59 | 60 | 標準出力/エラー出力の出力先. 61 | 62 | 63 | 64 | ### float_option() ### 65 | 66 | 67 |

 68 | float_option() = {decimals, Decimals::0..253} | {scientific, Decimals::0..249} | compact
 69 | 
70 | 71 | 小数パラメータに関するオプション. 72 | 73 | 74 | 75 | ### iodata_or_atom() ### 76 | 77 | 78 |

 79 | iodata_or_atom() = iodata() | atom()
 80 | 
81 | 82 | iodata(), または, atom(). 83 | 84 | 85 | 86 | ### option() ### 87 | 88 | 89 |

 90 | option() = port_settings() | escape_all | {stdout, destination_file_path() | stderr} | {stderr, destination_file_path() | stdout} | discard_stderr | {timeout, time()} | {nice, integer()} | {close_function, fun((port()) -> ok)} | {stdout_hook_fun, {stdout_hook_fun(), term()}} | {open_port_module, module()}
 91 | 
92 | 93 | execute/2, execute/3に指定できるオプション. 94 | 95 | 96 | 97 | ### option_argument() ### 98 | 99 | 100 |

101 | option_argument() = iodata() | integer() | none
102 | 
103 | 104 | オプションの引数部分. 105 | 106 | 107 | 108 | ### option_character() ### 109 | 110 | 111 |

112 | option_character() = iodata_or_atom()
113 | 
114 | 115 | オプション文字. 116 | 117 | 118 | 119 | ### option_element() ### 120 | 121 | 122 |

123 | option_element() = {option_character(), option_argument()} | {option_character(), option_argument(), [argument_option()]}
124 | 
125 | 126 | 外部プログラムに渡す引数の中でオプションとして指定するもの. 127 | 128 | 129 | 130 | ### port_settings() ### 131 | 132 | 133 |

134 | port_settings() = term()
135 | 
136 | 137 | open_portに指定できるオプション. 138 | 139 | 140 | 141 | ### stdout_hook_fun() ### 142 | 143 | 144 |

145 | stdout_hook_fun() = fun((binary() | {eol | noeol, binary()}, term()) -> term() | binary()) | fun((exit, term()) -> binary())
146 | 
147 | 148 | 標準出力に対するフィルタ関数. 149 | 150 | 151 | 152 | ### time() ### 153 | 154 | 155 |

156 | time() = non_neg_integer()
157 | 
158 | 159 | timeoutに指定できる数字. 160 | 161 | 162 | 163 | ## Function Index ## 164 | 165 | 166 |
escape_shell_arg/1シングルクォーテーションで両端を囲み、バイナリ中のシングルクォーテーションをエスケープする.
execute/2外部コマンドを実行する.
execute/3外部コマンドを実行する.
generate_command/2オプションリストからコマンド文字列(バイナリ)を生成する.
generate_command/3オプションリストからコマンド文字列(バイナリ)を生成する.
reduce_parameters/1パラメータリストを整理する.
reduce_parameters/2パラメータリストを整理する.
167 | 168 | 169 | 170 | 171 | ## Function Details ## 172 | 173 | 174 | 175 | ### escape_shell_arg/1 ### 176 | 177 |

178 | escape_shell_arg(Binary::binary()) -> EscapedBinary::binary()
179 | 
180 |
181 | 182 | シングルクォーテーションで両端を囲み、バイナリ中のシングルクォーテーションをエスケープする. 183 | 184 | 185 | 186 | ### execute/2 ### 187 | 188 |

189 | execute(Command::command(), ArgumentList::[argument()]) -> {{ok, Output} | {error, Reason}, FullCommandBinary}
190 | 
191 | 192 | 193 | 194 | 外部コマンドを実行する. 195 | 196 | 197 | 198 | ### execute/3 ### 199 | 200 |

201 | execute(Command::command(), ArgumentList::[argument()], OptionList::[option()]) -> {{ok, Output} | {error, Reason}, FullCommandBinary}
202 | 
203 | 204 | 205 | 206 | 外部コマンドを実行する. 207 | 208 | オプション 209 | generate_commandのオプション + open_portのオプション 210 | 211 | 212 | 213 | ### generate_command/2 ### 214 | 215 |

216 | generate_command(Command::command(), ArgumentList::[argument()]) -> binary()
217 | 
218 |
219 | 220 | オプションリストからコマンド文字列(バイナリ)を生成する. 221 | 222 | 223 | 224 | ### generate_command/3 ### 225 | 226 |

227 | generate_command(Command::command(), ArgumentList::[argument()], OptionList::[option()]) -> binary()
228 | 
229 |
230 | 231 | オプションリストからコマンド文字列(バイナリ)を生成する. 232 | 233 | 【argument option】
234 | ● `long_option`: long optionにする("--"でオプションを指定する.).
235 | ● `equal` : オプション文字とオプション引数の間を"="で繋ぐ.
236 | ● `escape` : オプション引数をシングルクォーテーションでエスケープする.
237 | 238 | 【argument option (小数)】
239 | ● `{scientific, 0..253}` : 小数を指数表記で出力する.数字は有効桁数.
240 | ● `{decimals, 0..249}` : 小数を実数表記で出力する.数字は有効桁数.
241 | ● `compact` : 後端のゼロを省く.
242 | (\*) デフォルトは`[{decimals, 4}]`
243 | (\*) 表記方法が複数指定されている場合,最も後に指定された表記方法が採用される.
244 | 245 | 【option】
246 | ● `escape_all` : 全てのオプション引数をエスケープする.
247 | ● `{stdout, Destination}` : 標準出力を指定先のファイルに出力する. 248 | Destinationには出力ファイル先を指定する.
249 | ● `{stderr, Destination}` : 標準エラー出力を指定先のファイルに出力する. 250 | Destinationには出力ファイル先を指定する.
251 | ● `discard_stderr` : 標準エラー出力を/dev/nullに捨てる.
252 | ● `{timeout, Time}` : Time `ミリ秒` で処理が終わらなかった場合, タイムアウトする.
253 | ● `{close_function, Fun}` : timeoutオプションでタイムアウトした時の処理を明示的に指定する.
254 | ● `{stdout_hook_fun, {Fun, Init}}` : 標準出力をフィルタリングする. 255 | Initに初期値を, Funは2引数の関数で第1引数に`exit`が来た場合は`binary`を返す.
256 | ● `{open_port_module, Module}` : erlang:open_port/2の代わりに, 指定したモジュールのopen_port/2を使用する. 257 | 258 | 259 | 260 | ### reduce_parameters/1 ### 261 | 262 |

263 | reduce_parameters(Parameters::[any()]) -> ValidParameters::[any()]
264 | 
265 |
266 | 267 | パラメータリストを整理する. 268 | 269 | パラメータリストから`不必要な要素`を除く. 270 | `不必要な要素`のdefaultは, `undefined`とする. 271 | 272 | 273 | 274 | ### reduce_parameters/2 ### 275 | 276 |

277 | reduce_parameters(Parameters::[any()], UnnecessaryParameters::[any()]) -> ValidParameters::[any()]
278 | 
279 |
280 | 281 | パラメータリストを整理する. 282 | 283 | パラメータリストから不必要な要素を除く. 284 | 285 | -------------------------------------------------------------------------------- /doc/moyo_concurrent.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module moyo_concurrent # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | 並行処理の為のモジュール. 9 | 10 | Copyright (c) 2013-2014 DWANGO Co., Ltd. All Rights Reserved.3409;0c 11 | 12 | 13 | 14 | ## Function Index ## 15 | 16 | 17 |
exec/1並行に複数のコマンドを実行する.
exec/2並行に複数のコマンドを実行する 18 | 返り値は実行が終了した順番で返される.
exec_sort/1並行に複数のコマンドを実行し、その結果を入力の順に返す.
exec_sort/2
19 | 20 | 21 | 22 | 23 | ## Function Details ## 24 | 25 | 26 | 27 | ### exec/1 ### 28 | 29 |

30 | exec(Inputs::[Input]) -> [{Input, RetValue::term()}]
31 | 
32 | 33 | 34 | 35 | 並行に複数のコマンドを実行する 36 | 37 | see: `exec(Input, infinity)` 38 | 39 | 40 | 41 | ### exec/2 ### 42 | 43 |

44 | exec(Inputs::[Input], Timeout) -> [{Input, RetValue::term()}]
45 | 
46 | 47 | 48 | 49 | 並行に複数のコマンドを実行する 50 | 返り値は実行が終了した順番で返される.
51 | また, 1つでも結果がerrorだった場合, その1つのerror結果を呼び出し元に投げ, 他のプロセスは強制終了される. 52 | 53 | 54 | 55 | ### exec_sort/1 ### 56 | 57 |

58 | exec_sort(Inputs::[Input]) -> [RetValue::term()]
59 | 
60 | 61 | 62 | 63 | 並行に複数のコマンドを実行し、その結果を入力の順に返す. 64 | 65 | 1つでも結果がerrorだった場合, その1つのerror結果を呼び出し元に投げ, 他のプロセスは強制終了される. 66 | 67 | 68 | 69 | ### exec_sort/2 ### 70 | 71 |

72 | exec_sort(Inputs::[Input], Timeout) -> [RetValue::term()]
73 | 
74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /doc/moyo_cond.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module moyo_cond # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | 条件分岐処理関連のユーティリティ関数を提供するモジュール. 9 | 10 | Copyright (c) 2013-2014 DWANGO Co., Ltd. All Rights Reserved. 11 | 12 | 13 | 14 | ## Function Index ## 15 | 16 | 17 |
apply_if/3Conditiontrueの場合はThenFunが、falseの場合はElseFunが実行される.
apply_unless/2Conditionfalseの場合はThenFunが実行される.
apply_when/2Conditiontrueの場合はThenFunが実行される.
conditional/3三項演算子と同様の機能を提供する。Conditionがtrueなら2つ目の値が、falseなら3つ目の値が返る.
while/2Acc0Funの定義により処理してAcc1を返す。.
18 | 19 | 20 | 21 | 22 | ## Function Details ## 23 | 24 | 25 | 26 | ### apply_if/3 ### 27 | 28 |

29 | apply_if(Condition::boolean(), ThenFun, ElseFun) -> Result
30 | 
31 | 32 | 33 | 34 | `Condition`が`true`の場合は`ThenFun`が、`false`の場合は`ElseFun`が実行される 35 | 36 | 37 | 38 | ### apply_unless/2 ### 39 | 40 |

41 | apply_unless(Condition::boolean(), ThenFun) -> ok
42 | 
43 | 44 | 45 | 46 | `Condition`が`false`の場合は`ThenFun`が実行される 47 | 48 | 返り値は常に`ok` 49 | 50 | 51 | 52 | ### apply_when/2 ### 53 | 54 |

55 | apply_when(Condition::boolean(), ThenFun) -> ok
56 | 
57 | 58 | 59 | 60 | `Condition`が`true`の場合は`ThenFun`が実行される 61 | 62 | 返り値は常に`ok` 63 | 64 | 65 | 66 | ### conditional/3 ### 67 | 68 |

69 | conditional(Condition::boolean(), TValue::any(), FValue::any()) -> any()
70 | 
71 |
72 | 73 | 三項演算子と同様の機能を提供する。Conditionがtrueなら2つ目の値が、falseなら3つ目の値が返る 74 | 75 | 76 | 77 | ### while/2 ### 78 | 79 |

80 | while(Fun, Acc0) -> Acc1
81 | 
82 | 83 | 84 | 85 | `Acc0`が`Fun`の定義により処理して`Acc1`を返す。 86 | 87 | `Fun`内の処理の結果が`{true, AccOut}`になると、`AccOut`が`Fun`の引数としてもう一度処理される。 88 | 結果が`{false, AccOut}`になると、`AccOut`を返す。 89 | 90 | ``` 91 | > while(fun(X) when X >= 5 -> {false, X}; (X) -> {true, X + 1} end, 1). 92 | 5 93 | > while(fun(X) when X < 2 -> {false, X}; (X) -> {true, X - 1} end, 10). 94 | 1 95 | ``` 96 | 97 | -------------------------------------------------------------------------------- /doc/moyo_ct.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module moyo_ct # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | Common Test Utility. 9 | 10 | Copyright (c) 2013-2014 DWANGO Co., Ltd. All Rights Reserved. 11 | 12 | 13 | 14 | ## Function Index ## 15 | 16 | 17 |
all/1SUITE moduleのCommon TestのCall back Functions以外を取得する.
eunit/1EunitをCommon Testに組み込む場合に使用できる.
18 | 19 | 20 | 21 | 22 | ## Function Details ## 23 | 24 | 25 | 26 | ### all/1 ### 27 | 28 |

29 | all(SuiteModule::module()) -> [Function::atom()]
30 | 
31 |
32 | 33 | SUITE moduleのCommon TestのCall back Functions以外を取得する. 34 | 35 | Arity =:= 1 以外は取得しない為, Eunit対象も外れる. 36 | 37 | 38 | 39 | ### eunit/1 ### 40 | 41 |

42 | eunit(Application::atom()) -> ok
43 | 
44 |
45 | 46 | EunitをCommon Testに組み込む場合に使用できる. 47 | 48 | -------------------------------------------------------------------------------- /doc/moyo_file.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module moyo_file # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | ファイル関連の処理を集めたユーティリティモジュール. 9 | 10 | Copyright (c) 2013-2014 DWANGO Co., Ltd. All Rights Reserved. 11 | 12 | 13 | 14 | ## Function Index ## 15 | 16 | 17 |
close/1{error, _} が返ってきた時にエラーを発生させる版の file:close/1 です.
delete_directory/2指定ディレクトリを削除する.
delete_if_exists/1ファイルを削除する.
get_disk_usage/1指定パスのディスク使用量を取得する.
get_disk_usage_async/3指定パスのディス使用量を非同期に取得する.
make_temp_filepath/0Equivalent to make_temp_filepath(<<"">>).
make_temp_filepath/1ユニークな一時ファイルパスを生成して返す.
open/2{error, _} が返ってきた時にエラーを発生させる版の file:open/2 です.
write/2{error, _} が返ってきた時にエラーを発生させる版の file:write/2 です.
write_or_close/2{error, _} が返ってきた時にファイルを閉じてエラーを発生させる版の file:write/2 です.
18 | 19 | 20 | 21 | 22 | ## Function Details ## 23 | 24 | 25 | 26 | ### close/1 ### 27 | 28 |

 29 | close(IoDevice::file:io_device()) -> ok
 30 | 
31 |
32 | 33 | {error, _} が返ってきた時にエラーを発生させる版の file:close/1 です. 34 | 35 | 36 | 37 | ### delete_directory/2 ### 38 | 39 |

 40 | delete_directory(DirectoryPath::file:name_all(), Recursive::boolean()) -> ok | {error, Reason::term()}
 41 | 
42 |
43 | 44 | 指定ディレクトリを削除する. 45 | 46 | `Recursive`に`true`が指定された場合は、ディレクトリの子要素を含めて再帰的に削除処理を実行する.
47 | なお、指定ディレクトリが存在しない場合は`ok`が返される. 48 | 49 | 50 | 51 | ### delete_if_exists/1 ### 52 | 53 |

 54 | delete_if_exists(FilePath::file:name_all()) -> ok
 55 | 
56 |
57 | 58 | ファイルを削除する. 失敗したらエラーが発生するが, ファイルがない時はエラーは起きず ok を返す. 59 | 60 | (補足)file:delete はファイルがないと {error, enoent} を返すが, この関数はそのような場合何もしないで ok を返す. 61 | 62 | 63 | 64 | ### get_disk_usage/1 ### 65 | 66 |

 67 | get_disk_usage(Path::file:name_all()) -> {ok, UsageBytes::non_neg_integer()} | {error, Reason::term()}
 68 | 
69 |
70 | 71 | 指定パスのディスク使用量を取得する. 72 | 73 | 対象パスが存在しない場合は`{ok, 0}`が返る. 74 | 75 | 76 | 77 | ### get_disk_usage_async/3 ### 78 | 79 |

 80 | get_disk_usage_async(Path::file:name_all(), Pid::pid(), Tag::term()) -> ok
 81 | 
82 |
83 | 84 | 指定パスのディス使用量を非同期に取得する. 85 | 86 | 容量取得処理が終了したタイミングで`Pid`プロセスに`{Tag, get_disk_usage(Path)}`形式のメッセージが送信される. 87 | 88 | 89 | 90 | ### make_temp_filepath/0 ### 91 | 92 |

 93 | make_temp_filepath() -> Path::binary()
 94 | 
95 |
96 | 97 | Equivalent to [`make_temp_filepath(<<"">>)`](#make_temp_filepath-1). 98 | 99 | 100 | 101 | ### make_temp_filepath/1 ### 102 | 103 |

104 | make_temp_filepath(Prefix::binary()) -> Path::binary()
105 | 
106 |
107 | 108 | ユニークな一時ファイルパスを生成して返す. 109 | 110 | 生成されるパスの形式は`/tmp/Prefix_ユニークな文字列`となる. 111 | 112 | なお、この関数自体はファイルの作成を行わないため、以下のように一時ファイルパスの生成から、 113 | 実際のファイル作成までの間に、他のプロセスとの競合が発生する可能性があるので注意が必要. 114 | 115 | ``` 116 | %% 1] 一時ファイルパスを生成 (この時点ではユニーク) 117 | > Path = make_temp_filepath(). 118 |   119 | %% 2] ここで他のプロセスが偶然同じファイル名を使用して file:write_file/2 を呼び出した 120 |   121 | %% 3] Pathにデータを書き込み 122 | => 他のプロセスが書き込んだ内容を上書きしてしまう! 123 | > file:write_file(Path, <<"data">>). 124 | ``` 125 | 126 | 127 | 128 | ### open/2 ### 129 | 130 |

131 | open(File::file:name_all(), Modes::[Mode]) -> file:io_device()
132 | 
133 | 134 | 135 | 136 | {error, _} が返ってきた時にエラーを発生させる版の file:open/2 です. 137 | 138 | 139 | 140 | ### write/2 ### 141 | 142 |

143 | write(IoDevice::file:io_device(), Bytes::iodata()) -> ok
144 | 
145 |
146 | 147 | {error, _} が返ってきた時にエラーを発生させる版の file:write/2 です. 148 | 149 | 150 | 151 | ### write_or_close/2 ### 152 | 153 |

154 | write_or_close(IoDevice::file:io_device(), Bytes::iodata()) -> ok
155 | 
156 |
157 | 158 | {error, _} が返ってきた時にファイルを閉じてエラーを発生させる版の file:write/2 です. 159 | 160 | -------------------------------------------------------------------------------- /doc/moyo_frac.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module moyo_frac # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | 分数を扱うモジュール. 10 | 11 | Copyright (c) 2013-2014 DWANGO Co., Ltd. All Rights Reserved. 12 | 13 | 14 | 15 | ## Description ## 16 | 分母は符号を持たないように、分子に符号を保持するようにする. 17 | 18 | 19 | 20 | ## Data Types ## 21 | 22 | 23 | 24 | 25 | ### fraction() ### 26 | 27 | 28 | __abstract datatype__: `fraction()` 29 | 30 | 31 | 32 | ## Function Index ## 33 | 34 | 35 |
add/2足し算 数(分数|浮動小数点数|整数)同士.
comp/2分数の比較.
denom/1分母を返す.
divide/2割り算 数(分数|浮動小数点数|整数)同士.
from_binary/1バイナリの整数、負数、分数、小数を分数に変換する.
from_float/1浮動小数点数を分数に変換する.
from_float_binary/1バイナリ形式の小数を分数に変換する.
from_integer/1整数を分数に変換する.
from_number/1数値(整数|浮動小数点数)を分数に変換する.
is_fraction/1分数かどうかの判定.
max/2大きい方の値を返す.
min/2小さい方の値を返す.
mul/2掛け算 数(分数|浮動小数点数|整数)同士.
new/2分数を生成する.
num/1分子を返す.
sub/2引き算 数(分数|浮動小数点数|整数)同士.
to_binary/1分数をバイナリに変換する.
to_float/1分数を浮動小数点数に変換する.
to_integer/1整数部分を返す.
to_tuple/1分数をタプル({分子, 分母})に変換する.
36 | 37 | 38 | 39 | 40 | ## Function Details ## 41 | 42 | 43 | 44 | ### add/2 ### 45 | 46 |

 47 | add(A::fraction() | number(), B::fraction() | number()) -> R::fraction()
 48 | 
49 |
50 | 51 | 足し算 数(分数|浮動小数点数|整数)同士. 52 | 53 | 9パターンに対応. 54 | 55 | 56 | 57 | ### comp/2 ### 58 | 59 |

 60 | comp(A::fraction(), B::fraction()) -> R::integer()
 61 | 
62 |
63 | 64 | 分数の比較. 65 | 66 | Aの方が大きい場合は正整数(1), 67 | Bの方が大きい場合は負整数(-1), 68 | 同じ場合は0を返す.
69 | 1, -1ではなく, 正整数, 負整数でチェックすること. 70 | 71 | 72 | 73 | ### denom/1 ### 74 | 75 |

 76 | denom(Frac::fraction()) -> Denom::integer()
 77 | 
78 |
79 | 80 | 分母を返す. 81 | 82 | 83 | 84 | ### divide/2 ### 85 | 86 |

 87 | divide(A::fraction() | number(), B::fraction() | number()) -> R::fraction()
 88 | 
89 |
90 | 91 | 割り算 数(分数|浮動小数点数|整数)同士. 92 | 93 | 9パターンに対応. 94 | 95 | 96 | 97 | ### from_binary/1 ### 98 | 99 |

100 | from_binary(Binary::binary()) -> fraction()
101 | 
102 |
103 | 104 | バイナリの整数、負数、分数、小数を分数に変換する. 105 | 106 | ex: 107 | 108 | ``` 109 | 1> moyo_frac:from_binary(<<"5">>). 110 | {fraction, 5, 1} 111 | 2> moyo_frac:from_binary(<<"-5">>). 112 | {fraction, -5, 1} 113 | 3> moyo_frac:from_binary(<<"1/5">>). 114 | {fraction, 1, 5}. 115 | 4> moyo_frac:from_binary(<<"0.25">>). 116 | {fraction, 1, 4} 117 | ``` 118 | 119 | 120 | 121 | ### from_float/1 ### 122 | 123 |

124 | from_float(Float::float()) -> fraction()
125 | 
126 |
127 | 128 | 浮動小数点数を分数に変換する. 129 | 130 | floatの精度の問題で同じと思っている値を比べても同じと判断されない場合がある. 131 | 132 | ex: 133 | 134 | ``` 135 | 1> moyo_frac:comp(moyo_frac:from_float(3.14), moyo_frac:new(314, 100)). 136 | 1 137 | ``` 138 | 139 | 非正規化数を分数にすることはできるが, to_floatすると割り算でerrorが発生する. 140 | 無限大, NaNに関してはErlangのfloatでは使えないので考慮していない. 141 | 142 | 143 | 144 | ### from_float_binary/1 ### 145 | 146 |

147 | from_float_binary(DecimalBin::binary()) -> fraction()
148 | 
149 |
150 | 151 | バイナリ形式の小数を分数に変換する. 152 | 153 | 小数(有理数)を分数にする関数. 154 | 文字列で見えている範囲で分数にする.
155 | ex: `3.1415 -> 31415/10000 -> 6283/2000`
156 | 注: 約分は行われる. 157 | 158 | ex: 159 | 160 | ``` 161 | 1> moyo_frac:from_float_binary(<<"3.1415">>). 162 | {fraction, 6283, 2000} 163 | ``` 164 | 165 | 166 | 167 | ### from_integer/1 ### 168 | 169 |

170 | from_integer(Int::integer()) -> Frac::fraction()
171 | 
172 |
173 | 174 | 整数を分数に変換する. 175 | 176 | 177 | 178 | ### from_number/1 ### 179 | 180 |

181 | from_number(Number::number()) -> fraction()
182 | 
183 |
184 | 185 | 数値(整数|浮動小数点数)を分数に変換する. 186 | 187 | 188 | 189 | ### is_fraction/1 ### 190 | 191 |

192 | is_fraction(Fraction::term()) -> boolean()
193 | 
194 |
195 | 196 | 分数かどうかの判定. 197 | 198 | 199 | 200 | ### max/2 ### 201 | 202 |

203 | max(A::fraction(), B::fraction()) -> M::fraction()
204 | 
205 |
206 | 207 | 大きい方の値を返す. 208 | 209 | 210 | 211 | ### min/2 ### 212 | 213 |

214 | min(A::fraction(), B::fraction()) -> M::fraction()
215 | 
216 |
217 | 218 | 小さい方の値を返す. 219 | 220 | 221 | 222 | ### mul/2 ### 223 | 224 |

225 | mul(A::fraction() | number(), B::fraction() | number()) -> R::fraction()
226 | 
227 |
228 | 229 | 掛け算 数(分数|浮動小数点数|整数)同士. 230 | 231 | 9パターンに対応. 232 | 233 | 234 | 235 | ### new/2 ### 236 | 237 |

238 | new(Num::integer(), Denom::integer()) -> fraction()
239 | 
240 |
241 | 242 | 分数を生成する. 243 | 244 | 分母に0を与えられた場合, `denominator_0_error`をthrowする. 245 | 246 | 247 | 248 | ### num/1 ### 249 | 250 |

251 | num(Frac::fraction()) -> Num::integer()
252 | 
253 |
254 | 255 | 分子を返す. 256 | 257 | 258 | 259 | ### sub/2 ### 260 | 261 |

262 | sub(A::fraction() | number(), B::fraction() | number()) -> R::fraction()
263 | 
264 |
265 | 266 | 引き算 数(分数|浮動小数点数|整数)同士. 267 | 268 | 9パターンに対応. 269 | 270 | 271 | 272 | ### to_binary/1 ### 273 | 274 |

275 | to_binary(Fraction::fraction()) -> binary()
276 | 
277 |
278 | 279 | 分数をバイナリに変換する. 280 | 281 | 282 | 283 | ### to_float/1 ### 284 | 285 |

286 | to_float(Frac::fraction()) -> Float::float()
287 | 
288 |
289 | 290 | 分数を浮動小数点数に変換する. 291 | 292 | 293 | 294 | ### to_integer/1 ### 295 | 296 |

297 | to_integer(Frac::fraction()) -> Int::integer()
298 | 
299 |
300 | 301 | 整数部分を返す. 302 | 303 | 304 | 305 | ### to_tuple/1 ### 306 | 307 |

308 | to_tuple(Frac::fraction()) -> {Num::integer(), Denom::integer()}
309 | 
310 |
311 | 312 | 分数をタプル({分子, 分母})に変換する. 313 | 314 | -------------------------------------------------------------------------------- /doc/moyo_fun.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module moyo_fun # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | 関数に関する処理を集めたユーティリティモジュール. 10 | 11 | Copyright (c) 2013-2014 DWANGO Co., Ltd. All Rights Reserved. 12 | 13 | 14 | 15 | ## Data Types ## 16 | 17 | 18 | 19 | 20 | ### computation() ### 21 | 22 | 23 |

 24 | computation() = fun(() -> computation_status()) | fun((InputValue::term()) -> computation_status())
 25 | 
26 | 27 | 合成関数を構成する関数一つ一つの定義.個々の関数はarityを0又は1とし,computation_status()を返すものとする. 28 | 29 | 30 | 31 | ### computation_status() ### 32 | 33 | 34 |

 35 | computation_status() = {ok, Response::term()} | ok | {error, Reason::term()} | error
 36 | 
37 | 38 | 合成関数を構成する関数一つ一つの返り値の定義. 39 | 40 | 41 | 42 | ### stack_item() ### 43 | 44 | 45 |

 46 | stack_item() = {Module::module(), Function::atom(), Arity::arity() | (Args::[term()]), Location::[{file, Filename::string()} | {line, Line::pos_integer()}]}
 47 | 
48 | 49 | 50 | 51 | ## Function Index ## 52 | 53 | 54 |
apply_on_exit/4Pidsで指定したプロセスのうちの一つでも死んだら指定の関数を実行する.
apply_on_exit_impl/4
apply_on_exit_receiver/4
composite_apply/1関数のリストを先頭から順に実行する.ただし,先頭の関数は引数をとらない.
composite_apply/2関数のリストを先頭から順に実行する.先頭の関数が引数をとる場合は, 55 | その引数を本関数の第二引数にリストで指定する.
fold_range/4関数に loop X in [From, To] と直前の結果を渡して最後の結果を返す.
map_range/3関数に loop X in [From, To] を渡して各々の結果をリストで返す.
maybe_fold_range/4{error, Reason}を返した場合に途中で処理を中断し, 結果を返す fold_range/4
repeat/3指定した回数だけ関数を実行する.
try_apply/3指定された関数を実行する.
try_apply/4指定された関数を実行する.
try_call/1引数の関数を実行する.
try_call/2引数の関数を実行する.
56 | 57 | 58 | 59 | 60 | ## Function Details ## 61 | 62 | 63 | 64 | ### apply_on_exit/4 ### 65 | 66 |

 67 | apply_on_exit(Pids::[pid()], Module::module(), Function::atom(), Args::[term()]) -> Executor::pid()
 68 | 
69 |
70 | 71 | Pidsで指定したプロセスのうちの一つでも死んだら指定の関数を実行する. 72 | 73 | 74 | 75 | ### apply_on_exit_impl/4 ### 76 | 77 |

 78 | apply_on_exit_impl(Pids::[pid()], Module::module(), Function::atom(), Args::[term()]) -> Executor::pid()
 79 | 
80 |
81 | 82 | 83 | 84 | ### apply_on_exit_receiver/4 ### 85 | 86 |

 87 | apply_on_exit_receiver(RefList::[reference()], Module::module(), Function::atom(), Args::[term()]) -> Executor::pid()
 88 | 
89 |
90 | 91 | 92 | 93 | ### composite_apply/1 ### 94 | 95 |

 96 | composite_apply(FunctionList::[computation()]) -> computation_status()
 97 | 
98 |
99 | 100 | 関数のリストを先頭から順に実行する.ただし,先頭の関数は引数をとらない. 101 | 102 | 103 | 104 | ### composite_apply/2 ### 105 | 106 |

107 | composite_apply(FunctionList::[computation()], Arg::term()) -> computation_status()
108 | 
109 |
110 | 111 | 関数のリストを先頭から順に実行する.先頭の関数が引数をとる場合は, 112 | その引数を本関数の第二引数にリストで指定する. 113 | リスト中の各関数の返り値を,次に実行される関数の引数として渡して 114 | errorを吐くまで実行していく. 115 | 116 | 117 | 118 | ### fold_range/4 ### 119 | 120 |

121 | fold_range(Function, AccIn::term(), From::integer(), To::integer()) -> AccOut::term()
122 | 
123 | 124 | 125 | 126 | 関数に loop X in [From, To] と直前の結果を渡して最後の結果を返す. 127 | 128 | 129 | 130 | ### map_range/3 ### 131 | 132 |

133 | map_range(Function, From::integer(), To::integer()) -> [AccOut::term()]
134 | 
135 | 136 | 137 | 138 | 関数に loop X in [From, To] を渡して各々の結果をリストで返す. 139 | 140 | 141 | 142 | ### maybe_fold_range/4 ### 143 | 144 |

145 | maybe_fold_range(Fun, AccIn::term(), From::integer(), To::integer()) -> {ok, Result::term()} | {error, Reason}
146 | 
147 | 148 | 149 | 150 | `{error, Reason}`を返した場合に途中で処理を中断し, 結果を返す [`fold_range/4`](#fold_range-4) 151 | 152 | 153 | 154 | ### repeat/3 ### 155 | 156 |

157 | repeat(Function, InitState::term(), MaxIndex::non_neg_integer()) -> FinalState::term()
158 | 
159 | 160 | 161 | 162 | 指定した回数だけ関数を実行する. 関数には loop index in [0, N) が渡される 163 | 164 | 165 | 166 | ### try_apply/3 ### 167 | 168 |

169 | try_apply(Module::module(), Function::atom(), Args::[term()]) -> FunctionResult | ErrorResult
170 | 
171 | 172 | 173 | 174 | 指定された関数を実行する. 実行中に例外が発生した場合は`{error, {`EXIT', {Class, Reason, Stacktrace}}}'を返す 175 | 176 | 177 | 178 | ### try_apply/4 ### 179 | 180 |

181 | try_apply(Module::module(), Function::atom(), Args::[term()], ErrorResult) -> FunctionResult | ErrorResult
182 | 
183 | 184 | 185 | 186 | 指定された関数を実行する. 実行中に例外が発生した場合は`ErrorResult`を返す 187 | 188 | 189 | 190 | ### try_call/1 ### 191 | 192 |

193 | try_call(Fun::function()) -> FunctionResult | ErrorResult
194 | 
195 | 196 | 197 | 198 | 引数の関数を実行する. 実行中に例外が発生した場合は`{error, {`EXIT', {Class, Reason, Stacktrace}}}'を返す 199 | 200 | 201 | 202 | ### try_call/2 ### 203 | 204 |

205 | try_call(Fun::function(), ErrorResult) -> FunctionResult | ErrorResult
206 | 
207 | 208 | 209 | 210 | 引数の関数を実行する. 実行中に例外が発生した場合は`ErrorResult`を返す 211 | 212 | -------------------------------------------------------------------------------- /doc/moyo_graph.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module moyo_graph # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | マップに関する処理を集めたユーティリティモジュール. 10 | 11 | Copyright (c) 2017 DWANGO Co., Ltd. All Rights Reserved. 12 | 13 | 14 | 15 | ## Data Types ## 16 | 17 | 18 | 19 | 20 | ### any_error() ### 21 | 22 | 23 |

 24 | any_error() = {error, term()}
 25 | 
26 | 27 | 28 | 29 | 30 | ### graph() ### 31 | 32 | 33 |

 34 | graph() = graph(vertex())
 35 | 
36 | 37 | 38 | 39 | 40 | ### graph() ### 41 | 42 | 43 |

 44 | graph(V) = #{V => [V]}
 45 | 
46 | 47 | 48 | 49 | 50 | ### vertex() ### 51 | 52 | 53 |

 54 | vertex() = term()
 55 | 
56 | 57 | 58 | 59 | 60 | ### vertex_not_found() ### 61 | 62 | 63 |

 64 | vertex_not_found() = {error, {vertex_not_found, vertex()}}
 65 | 
66 | 67 | 68 | 69 | ## Function Index ## 70 | 71 | 72 |
dfs_post_order/3有向グラフに対してpost-orderedな深さ優先探索を行う.
graph_with_vertices/2mapで表現されたグラフに、存在しない頂点を追加する.
73 | 74 | 75 | 76 | 77 | ## Function Details ## 78 | 79 | 80 | 81 | ### dfs_post_order/3 ### 82 | 83 |

 84 | dfs_post_order(Graph::graph(vertex()), RootVertex::vertex(), VertexFun) -> DFSRet
 85 | 
86 | 87 | 88 | 89 | `Graph`: `map`形式で格納されたグラフ.
`RootVertex`: 探索を開始する頂点.
`VertexFun`: 訪れた頂点を処理する関数.
90 | 91 | 有向グラフに対してpost-orderedな深さ優先探索を行う. 92 | 93 | `Graph`は`始点=>[終点のリスト]`という要素を持つ`map`で頂点間の接続関係が記述されている. 94 | 95 | 一度訪問した頂点は訪問対象から除外されるため,結果として行われるのは, 96 | `RootVertex`から到達可能なサブグラフから得られるスパニングツリーに対する探索となる. 97 | 98 | post-orderedな探索であるため,葉から先に探索され,次に共通の先祖へと遡ってゆく. 99 | 100 | 探索された頂点に対しては順次`VertexFun`が適用される. 101 | 兄弟関係にある頂点について適用された`VertexFun`の戻り値は`list`でコレクションされ, 102 | 親の頂点に適用される`VertexFun`の第二引数へと渡される. 103 | 104 | 時間計算量: `O(|E|+|V|log|V|), |E|:枝の数, |V|:頂点の数` 105 | 106 | 空間計算量: `O(|V|), |V|:頂点の数` 107 | 108 | 109 | 110 | ### graph_with_vertices/2 ### 111 | 112 |

113 | graph_with_vertices(Graph::graph(vertex()), Vertices::[vertex()]) -> graph(vertex())
114 | 
115 |
116 | 117 | `Graph`: `map`形式で格納されたグラフ.
`Vertices`: 追加したい頂点のリスト.
118 | 119 | `map`で表現されたグラフに、存在しない頂点を追加する. 120 | 121 | `Graph`は`頂点=>[頂点のリスト]`という要素を持つ`map`で頂点間の接続関係が記述されている. 122 | 123 | 追加する頂点は,`Vertices`としてリストの形で渡す. 124 | 125 | `Graph`が既に持っている頂点は追加しない. 126 | 127 | 時間計算量: `O(|V|log|Vall|), |V|:追加しようとしている頂点の数, |Vall|:すべての頂点の数` 128 | 129 | 空間計算量: `O(|Vall|), |Vall|:すべての頂点の数` 130 | 131 | -------------------------------------------------------------------------------- /doc/moyo_inet.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module moyo_inet # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | `inet`の拡張ライブラリ. 9 | 10 | Copyright (c) 2013-2015 DWANGO Co., Ltd. All Rights Reserved. 11 | 12 | 13 | 14 | ## Function Index ## 15 | 16 | 17 |
find_free_port/0現在空いているポートを1つ返す.
find_free_port/1現在空いているポートを引数として与えられた個数返す.
18 | 19 | 20 | 21 | 22 | ## Function Details ## 23 | 24 | 25 | 26 | ### find_free_port/0 ### 27 | 28 |

29 | find_free_port() -> {ok, inet:port_number()} | {error, Reason::term()}
30 | 
31 |
32 | 33 | 現在空いているポートを1つ返す 34 | 35 | この関数を呼び出した際の空きポートを返す為, そのポートが他のアプリケーションによって使用されてしまい, 使えない可能性がある. 36 | 37 | 38 | 39 | ### find_free_port/1 ### 40 | 41 |

42 | find_free_port(Count::non_neg_integer()) -> {ok, [inet:port_number()]} | {error, Reason::term()}
43 | 
44 |
45 | 46 | 現在空いているポートを引数として与えられた個数返す. 順不同. 47 | 現在の空きポート以上の数を要求した場合は, `{error, system_limit}` が返る. 48 | 49 | この関数を呼び出した際の空きポートを返す為, そのポートが他のアプリケーションによって使用されてしまい, 使えない可能性がある. 50 | 51 | -------------------------------------------------------------------------------- /doc/moyo_math.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module moyo_math # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | 数学的な関数を集めたモジュール. 10 | 11 | Copyright (c) 2013-2015 DWANGO Co., Ltd. All Rights Reserved. 12 | 13 | 14 | 15 | ## Data Types ## 16 | 17 | 18 | 19 | 20 | ### random_sequence_symbols() ### 21 | 22 | 23 |

 24 | random_sequence_symbols() = alphabetical | numeric | alphanumeric
 25 | 
26 | 27 | 28 | 29 | ## Function Index ## 30 | 31 | 32 |
ceil/1数(number)を切り上げて整数を返す.
divmod/2除算した商と剰余を求める関数.
floor/1数(number)を切り下げて整数を返す.
gcd/2最大公約数を求める.
pow_int/2累乗関数.
random_sequence/1Equivalent to random_sequence(Length, []).
random_sequence/2ランダム文字列を返す.
33 | 34 | 35 | 36 | 37 | ## Function Details ## 38 | 39 | 40 | 41 | ### ceil/1 ### 42 | 43 |

 44 | ceil(Number::number()) -> integer()
 45 | 
46 |
47 | 48 | 数(number)を切り上げて整数を返す. 49 | 50 | 指定した以上の整数で最小のものを返す. 51 | 52 | ``` 53 | > ceil(1.0). 54 | 1. 55 | > ceil(0.5). 56 | 1. 57 | > ceil(0.0). 58 | 0. 59 | > ceil(-0.5). 60 | 0. 61 | > ceil(-1.0). 62 | -1. 63 | ``` 64 | 65 | 66 | 67 | ### divmod/2 ### 68 | 69 |

 70 | divmod(A::integer(), B::integer()) -> {Quotient::integer(), Remainder::integer()}
 71 | 
72 |
73 | 74 | 除算した商と剰余を求める関数. 75 | 76 | 除数が0である場合, `badarith` errorが発生する. 77 | 78 | 79 | 80 | ### floor/1 ### 81 | 82 |

 83 | floor(Number::number()) -> integer()
 84 | 
85 |
86 | 87 | 数(number)を切り下げて整数を返す. 88 | 89 | 指定した以下の整数で最大のものを返す. 90 | 91 | ``` 92 | > floor(1.0). 93 | 1. 94 | > floor(0.5). 95 | 0. 96 | > floor(0.0). 97 | 0. 98 | > floor(-0.5). 99 | -1. 100 | > floor(-1.0). 101 | -1. 102 | ``` 103 | 104 | 105 | 106 | ### gcd/2 ### 107 | 108 |

109 | gcd(A::integer(), B::integer()) -> GCD::integer()
110 | 
111 |
112 | 113 | 最大公約数を求める. 114 | 115 | 両方の引数が0の場合, `both_0_error`をthrowする. 116 | 117 | 118 | 119 | ### pow_int/2 ### 120 | 121 |

122 | pow_int(Base::integer(), Exponent::non_neg_integer()) -> Value::integer()
123 | 
124 |
125 | 126 | 累乗関数. 127 | 128 | 計算結果がinteger()になる計算のみ行える. 129 | 具体的には、引数は整数のみで、第2引数は0以上のみを扱う. 130 | 131 | 132 | 133 | ### random_sequence/1 ### 134 | 135 |

136 | random_sequence(Length::non_neg_integer()) -> binary()
137 | 
138 |
139 | 140 | Equivalent to [`random_sequence(Length, [])`](#random_sequence-2). 141 | 142 | 143 | 144 | ### random_sequence/2 ### 145 | 146 |

147 | random_sequence(Length::non_neg_integer(), Options) -> binary()
148 | 
149 | 150 | 151 | 152 | ランダム文字列を返す 153 | 154 | DataTypeで出力形式を指定し、Symbolで出力内容を指定する. 155 | 156 | -------------------------------------------------------------------------------- /doc/moyo_monad.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module moyo_monad # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | モナドに関する処理を集めたユーティリティモジュール. 10 | 11 | Copyright (c) 2013-2014 DWANGO Co., Ltd. All Rights Reserved. 12 | 13 | 14 | 15 | ## Data Types ## 16 | 17 | 18 | 19 | 20 | ### maybe() ### 21 | 22 | 23 |

24 | maybe() = {just, term()} | nothing
25 | 
26 | 27 | なにか or Nothing を格納するデータ構造 28 | 29 | 30 | 31 | ### maybe_fun() ### 32 | 33 | 34 |

35 | maybe_fun() = {just, function()} | nothing
36 | 
37 | 38 | 何らかの関数 or Nothing を格納するデータ構造 39 | 40 | 41 | 42 | ## Function Index ## 43 | 44 | 45 |
apply_maybe/3MaybeFunが{just, Function}なら apply(Function, ArgList) を実行します.
maybe/1maybe()を作ります.
maybe_fun/1maybe()を作ります.
46 | 47 | 48 | 49 | 50 | ## Function Details ## 51 | 52 | 53 | 54 | ### apply_maybe/3 ### 55 | 56 |

57 | apply_maybe(MaybeFun::maybe_fun(), ArgList::[any()], DefaultValue::any()) -> any()
58 | 
59 |
60 | 61 | MaybeFunが{just, Function}なら apply(Function, ArgList) を実行します. MaybeFunがnothingならDefaultValue を返します 62 | 63 | 64 | 65 | ### maybe/1 ### 66 | 67 |

68 | maybe(Value::term()) -> maybe()
69 | 
70 |
71 | 72 | maybe()を作ります. 73 | 74 | {just nothing} は作れないので直接作ってください. 75 | 76 | 77 | 78 | ### maybe_fun/1 ### 79 | 80 |

81 | maybe_fun(Fun::nothing | function()) -> maybe()
82 | 
83 |
84 | 85 | maybe()を作ります. 86 | 87 | -------------------------------------------------------------------------------- /doc/moyo_pipe.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module moyo_pipe # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | ポート(外部コマンド)に対するパイプ入出力機能を提供するためのモジュール. 10 | 11 | Copyright (c) 2013-2014 DWANGO Co., Ltd. All Rights Reserved. 12 | 13 | 14 | 15 | ## Description ## 16 | 現在は出力機能にのみ対応済み 17 | 18 | 19 | ## Data Types ## 20 | 21 | 22 | 23 | 24 | ### output_data_generate_fun() ### 25 | 26 | 27 |

28 | output_data_generate_fun() = fun((State::term()) -> {ok, OutputData::iodata(), NextState::term()} | stop)
29 | 
30 | 31 | 32 | 33 | 34 | ### output_option() ### 35 | 36 | 37 |

38 | output_option() = {interval, moyo_clock:non_neg_milliseconds()}
39 | 
40 | 41 | interval: 各出力データ送信後にスリープする時間(ミリ秒). デフォルト値は 10. 42 | 43 | 44 | 45 | ## Function Index ## 46 | 47 | 48 |
output_start/3指定ポートに対して固定データを出力し続けるプロセスを生成する.
output_start/4指定ポートに対してデータ出力を行い続けるプロセスを生成する.
49 | 50 | 51 | 52 | 53 | ## Function Details ## 54 | 55 | 56 | 57 | ### output_start/3 ### 58 | 59 |

60 | output_start(Port::port(), Data, Options) -> OutputProcessId
61 | 
62 | 63 | 64 | 65 | 指定ポートに対して固定データを出力し続けるプロセスを生成する. 66 | 67 | `output_start(Port, fun (State) -> {ok, Data, State} end, InitialState, Options)`と等価なので、詳細はそちらのドキュメントを参照.
68 | 69 | 70 | 71 | ### output_start/4 ### 72 | 73 |

74 | output_start(Port::port(), DataGenerateFun, InitialState, Options) -> OutputProcessId
75 | 
76 | 77 | 78 | 79 | 指定ポートに対してデータ出力を行い続けるプロセスを生成する. 80 | 81 | 生成されたプロセスは、ポートの実行終了に伴い、自動で終了する.
82 | また`DataGenerateFun`が`stop`を返した場合もプロセスは終了する (この際にポートの停止は行われない).
83 | 84 | -------------------------------------------------------------------------------- /doc/moyo_proc.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module moyo_proc # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | 7 | プロセス関連の処理や型を集めたユーティリティモジュール. 8 | 9 | Copyright (c) 2013-2015 DWANGO Co., Ltd. All Rights Reserved. 10 | 11 | 12 | 13 | ## Data Types ## 14 | 15 | 16 | 17 | 18 | ### otp_name() ### 19 | 20 | 21 |

22 | otp_name() = {local, Name::atom()} | {global, Name::term()} | {via, module(), Name::term()}
23 | 
24 | 25 | [`gen_server:start_link/4`](gen_server.md#start_link-4)等に指定可能な起動プロセスの名前 26 | 27 | 28 | 29 | ### otp_ref() ### 30 | 31 | 32 |

33 | otp_ref() = (Name::atom()) | {Name::atom(), node()} | {global, Name::term()} | {via, module(), Name::term()} | pid()
34 | 
35 | 36 | [`gen_server:cast/2`](gen_server.md#cast-2)等に指定可能な宛先プロセスの参照 37 | 38 | -------------------------------------------------------------------------------- /doc/moyo_rpc.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module moyo_rpc # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | ノード間のやりとりをするユーティリティ関数. 9 | 10 | Copyright (c) 2017 DWANGO Co., Ltd. All Rights Reserved. 11 | 12 | 13 | 14 | ## Function Index ## 15 | 16 | 17 |
ensure_loaded/2Equivalent to ensure_loaded(Node, Module, infinity).
ensure_loaded/3指定したノードにおいて, 指定したモジュールが確実にロードされるようにする.
function_exported/2Equivalent to function_exported(Node, MFA, infinity).
function_exported/3指定したノードに指定した関数が存在するかどうかを確認する.
is_function_callable/2Equivalent to is_function_callable(Node, MFA, infinity).
is_function_callable/3指定したノードにおいて, 指定した関数が呼び出し可能かどうかを確認する.
18 | 19 | 20 | 21 | 22 | ## Function Details ## 23 | 24 | 25 | 26 | ### ensure_loaded/2 ### 27 | 28 |

 29 | ensure_loaded(Node::node(), Module::module()) -> {ok, {module, Module}} | {error, term()}
 30 | 
31 | 32 | 33 | 34 | Equivalent to [`ensure_loaded(Node, Module, infinity)`](#ensure_loaded-3). 35 | 36 | 37 | 38 | ### ensure_loaded/3 ### 39 | 40 |

 41 | ensure_loaded(Node::node(), Module, Timeout::integer() | infinity) -> {ok, {module, Module}} | {error, term()}
 42 | 
43 | 44 | 45 | 46 | returns: 47 | 48 | ノードとの通信に成功した場合, 元々モジュールがロードされていた場合や, 49 | モジュールのロードに成功した場合には`{ok, {module Module}}`を, 50 | 失敗すれば`{error, Reason}`を返す. 51 | `Reason`は`code:ensure_loaded/1`と同様のものとなる. 52 | 53 | ノードとの通信に失敗した場合, `{error, {badrpc, BadRpcReason}}`を返す. 54 | `BadRpcReason`は`rpc:call/5`と同様のものとなる. 55 | 56 | 指定したノードにおいて, 指定したモジュールが確実にロードされるようにする. 57 | 58 | `code:ensure_loaded/1`のRPC版. 59 | 60 | 61 | 62 | ### function_exported/2 ### 63 | 64 |

 65 | function_exported(Node::node(), MFA::mfa()) -> {ok, boolean()} | {error, term()}
 66 | 
67 |
68 | 69 | Equivalent to [`function_exported(Node, MFA, infinity)`](#function_exported-3). 70 | 71 | 72 | 73 | ### function_exported/3 ### 74 | 75 |

 76 | function_exported(Node::node(), MFA::mfa(), Timeout::integer() | infinity) -> {ok, boolean()} | {error, term()}
 77 | 
78 |
79 | 80 | returns: 81 | 82 | ノードとの通信に成功した場合, 指定された関数が存在すれば`{ok, true}`, 存在しなければ`{ok, false}`を返す. 83 | 84 | ノードとの通信に失敗した場合, `{error, {badrpc, BadRpcReason}}`を返す. 85 | `BadRpcReason`は`rpc:call/5`と同様のものとなる. 86 | 87 | 指定したノードに指定した関数が存在するかどうかを確認する. 88 | 89 | `erlang:function_exported/3`のRPC版. 90 | 91 | 92 | 93 | ### is_function_callable/2 ### 94 | 95 |

 96 | is_function_callable(Node::node(), MFA::mfa()) -> {ok, boolean()} | {error, nodedown | timeout | term()}
 97 | 
98 |
99 | 100 | Equivalent to [`is_function_callable(Node, MFA, infinity)`](#is_function_callable-3). 101 | 102 | 103 | 104 | ### is_function_callable/3 ### 105 | 106 |

107 | is_function_callable(Node::node(), MFA::mfa(), Timeout::integer() | infinity) -> {ok, boolean()} | {error, term()}
108 | 
109 |
110 | 111 | returns: 112 | 113 | ノードとの通信に成功した場合, 指定された関数が呼び出し可能ならば`{ok, true}`, 不可能ならば`{ok, false}`を返す. 114 | 115 | ノードとの通信に失敗した場合, `{error, {badrpc, BadRpcReason}}`を返す. 116 | `BadRpcReason`は`rpc:call/5`と同様のものとなる. 117 | 代表的なものは以下の通り: 118 | 119 | 120 | 121 |
nodedown
122 | 123 | 124 | 125 |
ノードが落ちている
126 | 127 | 128 | 129 | 130 |
timeout
131 | 132 | 133 | 134 |
タイムアウトした
135 | 136 | 137 | 138 | 指定したノードにおいて, 指定した関数が呼び出し可能かどうかを確認する. 139 | 140 | [`function_exported/3`](#function_exported-3)の場合, まだロードされていないモジュールの関数に対しては`{ok, false}`を返すため, 141 | その関数を呼び出せるかどうかの判定としては不十分である. 142 | そこで`is_function_callable/3`では, 指定したモジュールのロードを試みた後に, 143 | 指定した関数が存在するかどうかを確認する. これにより, 呼び出し可能かどうかを確実に判定することができる. 144 | 145 | -------------------------------------------------------------------------------- /doc/moyo_string.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module moyo_string # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | 文字列(整数値のリスト)に関する処理を集めたユーティリティモジュール. 10 | 11 | Copyright (c) 2013-2014 DWANGO Co., Ltd. All Rights Reserved. 12 | 13 | 14 | 15 | ## Data Types ## 16 | 17 | 18 | 19 | 20 | ### encode_option() ### 21 | 22 | 23 |

 24 | encode_option() = print | {float_format, [float_format_option()]}
 25 | 
26 | 27 | 28 | 29 | 30 | ### float_format_option() ### 31 | 32 | 33 |

 34 | float_format_option() = {scientific, Decimals::0..249} | {decimals, Decimals::0..253} | compact
 35 | 
36 | 37 | 38 | 39 | ## Function Index ## 40 | 41 | 42 |
format/2指定されたフォーマットの文字列を生成して返す.
is_ascii_string/1引数の値がASCII文字列であるかどうかを判定する.
is_iodata/1引数の値がiodataであるかどうかを判定する.
is_iolist/1引数の値がiolistであるかどうかを判定する.
to_string/1Equivalent to to_string(V, []).
to_string/2Erlangの項を文字列(数値のリスト)に、指定されたオプションに従って変換する.
43 | 44 | 45 | 46 | 47 | ## Function Details ## 48 | 49 | 50 | 51 | ### format/2 ### 52 | 53 |

 54 | format(Format, Data) -> string()
 55 | 
56 | 57 | 58 | 59 | 指定されたフォーマットの文字列を生成して返す. 60 | 61 | `lists:flatten(io_lib:format(Format, Data))`と同等. 62 | 63 | 64 | 65 | ### is_ascii_string/1 ### 66 | 67 |

 68 | is_ascii_string(Value::term()) -> boolean()
 69 | 
70 |
71 | 72 | 引数の値がASCII文字列であるかどうかを判定する 73 | 74 | ASCII文字列とは 0 から 127 までのコード値の文字で構成されている文字列のこと 75 | 76 | 77 | 78 | ### is_iodata/1 ### 79 | 80 |

 81 | is_iodata(Value::term()) -> boolean()
 82 | 
83 |
84 | 85 | 引数の値が`iodata`であるかどうかを判定する 86 | 87 | `iodata()`の型は`binary() | iolist()`. 88 | 89 | 90 | 91 | ### is_iolist/1 ### 92 | 93 |

 94 | is_iolist(Value::term()) -> boolean()
 95 | 
96 |
97 | 98 | 引数の値が`iolist`であるかどうかを判定する 99 | 100 | `iolist()`の型は `maybe_improper_list(byte() | binary() | iolist(), binary() | [])`.
101 | See: [`http://www.erlang.org/doc/reference_manual/typespec.html`](http://www.erlang.org/doc/reference_manual/typespec.html) 102 | 103 | 104 | 105 | ### to_string/1 ### 106 | 107 | `to_string(V) -> any()` 108 | 109 | Equivalent to [`to_string(V, [])`](#to_string-2). 110 | 111 | 112 | 113 | ### to_string/2 ### 114 | 115 |

116 | to_string(V::term(), Rest::[encode_option()]) -> string()
117 | 
118 |
119 | 120 | Erlangの項を文字列(数値のリスト)に、指定されたオプションに従って変換する 121 | 122 | 入力値が非負の数値リストの場合は、変換は行われずにそのまま返される。
123 | ユニコード値のリストから、UTF-8のリストへ変換したい場合等は unicode モジュールを使用する必要がある。
124 | 125 | 入力値がタプルや深いリストならば `print` オプションを指定することで
126 | io_lib:format/2 のフォーマット`"~p"`に従った表現で変換することができる。
127 | デフォルト値は`"~w"`。
128 | 129 | 入力値が浮動小数点数ならば float_to_list/2 で指定できるオプション
130 | [{scientific, Decimals} | {decimals, Decimals} | compact]
131 | を利用して変換方式を指定することができる。
132 | {scientific, Decimals} と {decimals, Decimals} が同時に指定された場合は、最後に指定されたものが採用される。
133 | 例: 134 | 135 | ``` 136 | > moyo_string:to_string(12.34, [{float_format, [{scientific, 6}]}]). 137 | "1.234000e+01" 138 | > moyo_string:to_string(12.34, [{float_format, [{decimals, 6}]}]). 139 | "12.340000" 140 | > moyo_string:to_string(12.34, [{float_format, [{decimals, 6}, compact]}]). 141 | "12.34" 142 | ``` 143 | 144 | -------------------------------------------------------------------------------- /doc/moyo_url.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module moyo_url # 4 | * [Description](#description) 5 | * [Function Index](#index) 6 | * [Function Details](#functions) 7 | 8 | URL関連の処理を集めたユーティリティモジュール. 9 | 10 | Copyright (c) 2013-2014 DWANGO Co., Ltd. All Rights Reserved. 11 | 12 | 13 | 14 | ## Function Index ## 15 | 16 | 17 |
build_qs/1連想リストからHTTPのクエリ文字列を生成する.
parse_query/1URLのクエリストリング部をパースして、対応する連想リストを取得する.
parse_scheme/1URLのschemeを取得する.
parse_url/1URLをparseし、URLの要素をmapで返す.
urldecode_base64/1base64url形式でエンコードされたバイナリをデコードする.
urldecode_rfc3986/1RFC3986形式でエンコードされているバイナリをデコードする.
urlencode_base64/1Equivalent to urlencode_base64(PlainText, []).
urlencode_base64/2バイナリをbase64url形式にエンコードする.
urlencode_rfc3986/1テキストを RFC3986 にもとづいてエンコードする.
18 | 19 | 20 | 21 | 22 | ## Function Details ## 23 | 24 | 25 | 26 | ### build_qs/1 ### 27 | 28 |

 29 | build_qs(AssocList::moyo_assoc:assoc_list()) -> binary()
 30 | 
31 |
32 | 33 | 連想リストからHTTPのクエリ文字列を生成する. 34 | 35 | URLエンコード処理には[`urlencode_rfc3986/1`](#urlencode_rfc3986-1)が使用される.
36 | 連想リストの要素のキーおよび値が文字列ではない場合は[`moyo_string:to_string/1`](moyo_string.md#to_string-1)を使って文字列に変換される. 37 | 38 | 39 | 40 | ### parse_query/1 ### 41 | 42 |

 43 | parse_query(QueryString::binary()) -> [{Key::binary(), Value::binary()}]
 44 | 
45 |
46 | 47 | URLのクエリストリング部をパースして、対応する連想リストを取得する 48 | 49 | 基本的には、標準の[`httpd:parse_query/1`](httpd.md#parse_query-1)と同じ動作を取るが、 50 | 戻り値の連想リストのキー及び値の型が文字列からバイナリに変わっている。
51 | また、標準の関数は入力サイズが大きくなると極端に処理速度が遅くなるために、標準関数の使用は推奨されない。 52 | 53 | クエリストリング内の各パラメータのキーおよび値に含まれる'+'は半角スペースに展開され、 54 | '%xx'は対応するコードの文字に展開される。 55 | 56 | また、キーに対応する値が存在しない場合は、空文字(バイナリ)が値として使用される。 57 | 58 | ``` 59 | > parse_query(<<"a&b=10">>). 60 | [{<<"a">>, <<"">>}, {<<"b">>, <<"10">>}] 61 | なお、入力として"%a=b"のように不正なパーセントエンコーディング文字が渡された場合の挙動は未定義。
62 | (何らかの解釈でデコードされた結果が返るかもしれないし、エラーとなるかもしれない) 63 | ``` 64 | 65 | 66 | 67 | ### parse_scheme/1 ### 68 | 69 |

 70 | parse_scheme(URL::binary()) -> {ok, {Scheme::binary(), Rest::binary()}} | error
 71 | 
72 |
73 | 74 | URLのschemeを取得する 75 | 76 | 77 | 78 | ### parse_url/1 ### 79 | 80 |

 81 | parse_url(URL::binary()) -> {ok, ParsedURL} | {error, Reason::term()}
 82 | 
83 | 84 | 85 | 86 | URLをparseし、URLの要素をmapで返す 87 | 88 | URLに必須なschemeとhostが入力に含まれない場合、もしくはperseに失敗した場合errorとなる 89 | http_uri:parseとの違い 90 | + 入出力の文字列をバイナリ型で取り扱う 91 | string()で取り扱わない 92 | + schemeをバイナリで返す 93 | http_uri版はschemeをatomで返すため、ユーザ入力を受け付ける場合、無限にatomが生成される恐れがあったが、 94 | この関数はバイナリで返すためleakの心配がない 95 | + デフォルトのport指定を持たない 96 | http_uri版は"http://foo/"の様なhttp+port指定なし入力の場合、戻り値のportに80自動指定するが、 97 | この関数ではしない。parserとしての機能としては大きすぎ、parseしたいだけなのにschemeとportの組を設定する必要がある等邪魔になることがあったので外した 98 | 99 | 100 | 101 | ### urldecode_base64/1 ### 102 | 103 |

104 | urldecode_base64(EncodedText) -> PlainText
105 | 
106 | 107 | 108 | 109 | base64url形式でエンコードされたバイナリをデコードする. 110 | 111 | base64url形式については [`urlencode_base64/2`](#urlencode_base64-2) のドキュメントを参照のこと。
112 | `EncodedText`の末尾にパディング文字("=")が不足している場合は、デコード時に自動で補われる。 113 | 114 | 115 | 116 | ### urldecode_rfc3986/1 ### 117 | 118 |

119 | urldecode_rfc3986(EncodedText::binary()) -> PlainText::binary()
120 | 
121 |
122 | 123 | RFC3986形式でエンコードされているバイナリをデコードする. 124 | 125 | なお、RFC3986に正式に準拠していないバイナリが渡された場合でも、デコードに支障がないようであれば、特にエラーとはならない.
126 | (ex. "|"のように本来パーセントエスケープされるべき文字が生のまま含まれていても、エラーとはならない)
127 | 128 | RFC3986に関しては[`urlencode_rfc3986`](urlencode_rfc3986.md)を参照のこと. 129 | 130 | 131 | 132 | ### urlencode_base64/1 ### 133 | 134 | `urlencode_base64(PlainText) -> any()` 135 | 136 | Equivalent to [`urlencode_base64(PlainText, [])`](#urlencode_base64-2). 137 | 138 | 139 | 140 | ### urlencode_base64/2 ### 141 | 142 |

143 | urlencode_base64(PlainText, Options) -> EncodedText
144 | 
145 | 146 | 147 | 148 | バイナリをbase64url形式にエンコードする. 149 | 150 | base64url形式とは、通常のbase64エンコードの結果文字列の"+"および"/"を、それぞれ"-"および"_"に置き換えたもの。
151 | (この処理によって、base64エンコード結果をURL内に安全に含めることができるようになる)
152 | 153 | base64urlの詳細は [`http://tools.ietf.org/html/rfc4648#section-5`](http://tools.ietf.org.md/rfc4648#section-5) を参照のこと. 154 | 155 | オプションで`no_padding`が指定された場合は、base64エンコード結果の文字列から末尾のパディング文字("=")が除外される. 156 | 157 | ``` 158 | > urlencode_base64(<<"this is a pen">>, []). 159 | <<"dGhpcyBpcyBhIHBlbg==">> 160 | > urlencode_base64(<<"this is a pen">>, [no_padding]). 161 | <<"dGhpcyBpcyBhIHBlbg">> 162 | ``` 163 | 164 | 165 | 166 | ### urlencode_rfc3986/1 ### 167 | 168 |

169 | urlencode_rfc3986(PlainText::binary()) -> EncodedText::binary()
170 | 
171 |
172 | 173 | テキストを RFC3986 にもとづいてエンコードする. 174 | 175 | [a-zA-Z0-9_.~-]を除く全ての文字は`%XX`形式の文字列で置換される.
176 | RFC3986: [`http://www.faqs.org/rfcs/rfc3986.html`](http://www.faqs.org/rfcs/rfc3986.md) 177 | 178 | -------------------------------------------------------------------------------- /doc/moyo_xml.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Module moyo_xml # 4 | * [Description](#description) 5 | * [Data Types](#types) 6 | * [Function Index](#index) 7 | * [Function Details](#functions) 8 | 9 | XMLに関する処理を集めたユーティリティモジュール. 10 | 11 | Copyright (c) 2013-2014 DWANGO Co., Ltd. All Rights Reserved. 12 | 13 | 14 | 15 | ## Description ## 16 | 17 | 18 | #### 【XMLの表現形式】 #### 19 | 20 | このモジュールでは、XMLの表現形式として、以下のような三要素のタプルが採用されている。 21 | 22 | ``` 23 | {要素名, 属性連想リスト, 子要素およびテキストのリスト} 24 | ``` 25 | 26 | また要素名や属性のキー及び値、テキストは、原則としてバイナリで保持されるようになっている。
27 | (ただし、要素名や属性のキーは、アトムで保持することも可能。
28 | また、XML文字列を生成する場合の入力値としては、もう少し制限の緩い表現が使用可能となっている) 29 | 30 | 例えば `"text1text2"` といったXML文字列をパースすると次のような結果が返ってくる: 31 | 32 | ``` 33 | > moyo_xml:parse_binary(<<"text1text2">>, []). 34 | {{<<"root">>, 35 | [{<<"attr">>,<<"1">>}], 36 | [<<"text1">>,{<<"child">>,[],[]},<<"text2">>]}, 37 | <<>>} 38 | ``` 39 | 40 | 41 | 42 | ## Data Types ## 43 | 44 | 45 | 46 | 47 | ### parse_option() ### 48 | 49 | 50 |

 51 | parse_option() = {key_type, binary | atom | existing_atom} | {allow_external_entity, true | false}
 52 | 
53 | 54 | __[key_type オプション]__
55 | パース結果XMLの要素名および属性名をどのような型で表現するかを指定する.
56 | `binary`ならバイナリ型、`atom`ならアトム型.
57 | `existing_atom`の場合は、名前に対応するアトムが既に存在する場合はアトム型、存在しないならバイナリ型となる.
58 | デフォルト値は`binary`. 59 | 60 | __[allow_external_entity オプション]__
61 | 外部エンティティ参照を許可するかどうかを指定する.
62 | `true` であれば外部エンティティ参照が許可される.
63 | `false` とすることで外部エンティティ参照を禁止できる.
64 | デフォルト値は`false`. 65 | 66 | 67 | 68 | ### xml() ### 69 | 70 | 71 |

 72 | xml() = xml_element()
 73 | 
74 | 75 | 76 | 77 | 78 | ### xml_attribute() ### 79 | 80 | 81 |

 82 | xml_attribute() = {xml_attribute_key(), xml_attribute_value()}
 83 | 
84 | 85 | 86 | 87 | 88 | ### xml_attribute_key() ### 89 | 90 | 91 |

 92 | xml_attribute_key() = atom() | binary()
 93 | 
94 | 95 | 96 | 97 | 98 | ### xml_attribute_value() ### 99 | 100 | 101 |

102 | xml_attribute_value() = iodata() | term()
103 | 
104 | 105 | parse系関数の結果としては常にバイナリが返る 106 | 107 | 108 | 109 | ### xml_content() ### 110 | 111 | 112 |

113 | xml_content() = xml_element() | xml_text()
114 | 
115 | 116 | 117 | 118 | 119 | ### xml_element() ### 120 | 121 | 122 |

123 | xml_element() = {xml_element_name(), [xml_attribute()], [xml_content()]}
124 | 
125 | 126 | 127 | 128 | 129 | ### xml_element_name() ### 130 | 131 | 132 |

133 | xml_element_name() = atom() | binary()
134 | 
135 | 136 | 137 | 138 | 139 | ### xml_text() ### 140 | 141 | 142 |

143 | xml_text() = iodata() | term()
144 | 
145 | 146 | parse系関数の結果としては常にバイナリが返る 147 | 148 | 149 | 150 | ## Function Index ## 151 | 152 | 153 |
parse_binary/2XML文字列(バイナリ)をパースする.
parse_file/2XMLファイルをパースする.
to_iolist/1XMLをiolist形式の文字列に変換する.
to_iolist/2XMLをiolist形式の文字列に、指定されたオプションに従って変換する.
154 | 155 | 156 | 157 | 158 | ## Function Details ## 159 | 160 | 161 | 162 | ### parse_binary/2 ### 163 | 164 |

165 | parse_binary(InputXml, Options::[parse_option()]) -> {xml(), RestXml}
166 | 
167 | 168 | 169 | 170 | XML文字列(バイナリ)をパースする. 171 | 172 | パース結果XMLの属性値およびテキストの型は常にバイナリとなる.
173 | パースに失敗した場合は例外が送出される. 174 | 175 | 176 | 177 | ### parse_file/2 ### 178 | 179 |

180 | parse_file(FilePath, Options::[parse_option()]) -> {xml(), RestXml}
181 | 
182 | 183 | 184 | 185 | XMLファイルをパースする. 186 | 187 | パース結果XMLの属性値およびテキストの型は常にバイナリとなる.
188 | パースに失敗した場合は例外が送出される. 189 | 190 | 191 | 192 | ### to_iolist/1 ### 193 | 194 |

195 | to_iolist(Xml::xml()) -> XmlString::iolist()
196 | 
197 |
198 | 199 | XMLをiolist形式の文字列に変換する 200 | 201 | 変換に失敗した場合は例外が送出される.
202 | 要素の属性値や内容は[`moyo_string:to_string/1`](moyo_string.md#to_string-1)によって、適宜文字列に変換される. 203 | 204 | 205 | 206 | ### to_iolist/2 ### 207 | 208 |

209 | to_iolist(Xml::xml(), Options::[moyo_string:encode_option()]) -> XmlString::iolist()
210 | 
211 |
212 | 213 | XMLをiolist形式の文字列に、指定されたオプションに従って変換する 214 | 215 | 変換に失敗した場合は例外が送出される.
216 | 要素の属性値や内容は[`moyo_string:to_string/2`](moyo_string.md#to_string-2)によって、適宜文字列に変換される. 217 | 218 | -------------------------------------------------------------------------------- /doc/overview.edoc: -------------------------------------------------------------------------------- 1 | @copyright 2013-2014 DWANGO Co., Ltd. All Rights Reserved. 2 | @title moyo 3 | @doc 4 | MOYOはErlang用のユーティリティモジュール集です。
5 | MOYOは、アフリカ東部で使われるスワヒリ語で、心、魂、精神などを意味します。 6 | -------------------------------------------------------------------------------- /include/eunit.hrl: -------------------------------------------------------------------------------- 1 | %% @copyright 2013-2018 DWANGO Co., Ltd. All Rights Reserved. 2 | %% 3 | %% @doc eunit用の追加マクロ 4 | %% 5 | %% このファイルをincludeすれば`eunit/include/eunit.hrl'でdefineされているマクロと合わせて使用ができる. 6 | %% 7 | %% timeoutは3000にしている. (eunitのtimeoutが5sなので, それ以下の適当な値) 8 | 9 | -ifndef(MOYO_EUNIT_HRL). 10 | -define(MOYO_EUNIT_HRL, true). 11 | 12 | -include_lib("eunit/include/eunit.hrl"). 13 | 14 | -ifdef(NOASSERT). 15 | -define(assertMatch2(Guard, Expr), ok). 16 | -else. 17 | -define(assertMatch2(Guard, Expr), 18 | (fun() -> 19 | try 20 | MOYO_ASSERT___MATCH2_TMP = Expr, 21 | 22 | %% `Guard'内に変数が含まれる場合に、OTP18で"unused変数警告"が出力されるのを抑制するために、二度`Guard'を使っている。 23 | %% (一度目で束縛された変数が、二度目は使われるので警告がでなくなる) 24 | %% 25 | %% なお`Guard = Guard = MOYO_ASSERT___MATCH2_TMP'のようにまとめて書くと、`Guard'にバイナリリテラルが含まれる場合に、 26 | %% OTP18で別のコンパイラ警告が出てしまうので、別々に分けている。 27 | Guard = MOYO_ASSERT___MATCH2_TMP, 28 | Guard = MOYO_ASSERT___MATCH2_TMP, 29 | ok 30 | catch 31 | error:{badmatch, MOYO_EUNIT___V} -> 32 | erlang:error({assertMatch_failed, 33 | [{module, ?MODULE}, 34 | {line, ?LINE}, 35 | {expression, (??Expr)}, 36 | {pattern, (??Guard)}, 37 | {value, (MOYO_EUNIT___V)} 38 | ]}) 39 | end 40 | end)()). 41 | -endif. 42 | -define(_assertMatch2(Guard, Expr), ?_test(?assertMatch2(Guard, Expr))). 43 | %% ?assertMatch(_, Value) は書くことができない.
44 | %% assertマクロを書く際にこれがあると便利なので定義している
45 | %% 基本的な動作はassertMatchと同じ. 46 | 47 | -ifdef(NOASSERT). 48 | -define(assignMatch(Guard, Expr), Guard = Expr). 49 | -else. 50 | -define(assignMatch(Guard, Expr), 51 | begin 52 | Guard = (fun() -> 53 | MOYO_EUNIT___TMP = Expr, 54 | ?assertMatch2(Guard, MOYO_EUNIT___TMP), 55 | MOYO_EUNIT___TMP 56 | end)() 57 | end). 58 | -endif. 59 | -define(_assignMatch(Guard, Expr), ?_test(?assignMatch(Guard, Expr))). 60 | %% 以下の構文と同義のことが実現できる 61 | %% ``` 62 | %% ?assertMatch({ok, _}, Ret), 63 | %% {ok, Pid} = Ret 64 | %% ''' 65 | 66 | -ifdef(NOASSERT). 67 | -define(assertTerminated(Pid, Reason), ok). 68 | -else. 69 | -define(assertTerminated(Pid, Reason), 70 | (fun() -> 71 | receive 72 | {'EXIT', Pid, MOYO_EUNIT___RetReason} -> 73 | ?assertMatch2(Reason, MOYO_EUNIT___RetReason) 74 | after 3000 -> 75 | ?assert(timeout) 76 | end 77 | end)()). 78 | -endif. 79 | -define(_assertTerminated(Pid, Reason), ?_test(?assertTerminated(Pid, Reason))). 80 | %% プロセスから'EXIT'が一定時間以内に投げられることを確認する. 81 | %% 82 | %% ``` 83 | %% _ = process_flag(trap_exit, true), 84 | %% Pid = spawn_link(fun hoge/0), 85 | %% ?assertTerminated(Pid, _) 86 | %% ''' 87 | 88 | -ifdef(NOASSERT). 89 | -define(assertDown(Ref, Reason), ok). 90 | -else. 91 | -define(assertDown(Ref, Reason), 92 | (fun() -> 93 | receive 94 | {'DOWN', Ref, process, _, MOYO_EUNIT___RetReason} -> 95 | ?assertMatch2(Reason, MOYO_EUNIT___RetReason) 96 | after 3000 -> 97 | ?assert(timeout) 98 | end 99 | end)()). 100 | -endif. 101 | -define(_assertDown(Ref, Reason), ?_test(?assertDown(Ref, Reason))). 102 | %% 'DOWN'が一定時間以内に投げられることを確認する. 103 | %% 104 | %% ``` 105 | %% {_, Ref} = spawn_monitor(fun hoge/0), 106 | %% ?assertDown(Ref, _) 107 | %% ''' 108 | %% 109 | -ifdef(NOASSERT). 110 | -define(assertContain(Element, List), ok). 111 | -else. 112 | -define(assertContain(Element, List), 113 | (fun() -> 114 | case is_list(List) andalso lists:member(Element, List) of 115 | true -> 116 | ok; 117 | false -> 118 | Expression = lists:flatten(io_lib:format("~p contains ~p", [List, Element])), 119 | erlang:error({assert, 120 | [{module, ?MODULE}, 121 | {line, ?LINE}, 122 | {expression, Expression}, 123 | {expected, true}, 124 | {value, false}]}) 125 | end 126 | end)()). 127 | -endif. 128 | -define(_assertContain(Element, List), ?_test(?assertContain(Element, List))). 129 | %% ListがElementを含むことを確認する 130 | %% 131 | %% ``` 132 | %% ?assertContain(1, [2,1,3]) 133 | %% ''' 134 | 135 | -ifdef(NOASSERT). 136 | -define(ensureExited(Pid, Reason), ok). 137 | -else. 138 | -define(ensureExited(Pid, Reason), 139 | (fun() -> 140 | MOYO_EUNIT___Pid = Pid, 141 | MOYO_EUNIT___Old = process_flag(trap_exit, true), 142 | MOYO_EUNIT___Ref = monitor(process, MOYO_EUNIT___Pid), 143 | exit(MOYO_EUNIT___Pid, Reason), 144 | ?assertDown(MOYO_EUNIT___Ref, _), 145 | process_flag(trap_exit, MOYO_EUNIT___Old) 146 | end)()). 147 | -endif. 148 | -define(_ensureExited(Pid, Reason), ?_test(?ensureExited(Pid, Reason))). 149 | %% プロセスが生きていたら`Reason'という理由で終了させる.(`exit/2'を発行する) 150 | %% 一定時間以内に終了できなかった場合はtest failureになる. 151 | %% 152 | %% ``` 153 | %% Pid = spwan(fun hoge/0), 154 | %% ?ensureExited(Pid) 155 | %% ''' 156 | 157 | -define(ensureExited(Pid), ?ensureExited(Pid, kill)). 158 | -define(_ensureExited(Pid), ?_test(?ensureExited(Pid))). 159 | %% @equiv ?ensureExited(Pid, kill) 160 | 161 | -ifdef(NOASSERT). 162 | -define(assertLinked(Pid1, Pid2), ok). 163 | -else. 164 | -define(assertLinked(Pid1, Pid2), 165 | (fun () -> 166 | {_, MOYO__Links} = erlang:process_info(Pid2, links), 167 | ?assert(lists:member(Pid1, MOYO__Links)) 168 | end)()). 169 | -endif. 170 | -define(_assertLinked(Pid1, Pid2), ?_test(?assertLinked(Pid1, Pid2))). 171 | %% 二つのプロセス(`Pid1'と`Pid2')の間にリンクが貼られているかを確認する. 172 | %% 173 | %% ``` 174 | %% Pid = spawn_link(fun hoge/0), 175 | %% ?assertLinked(self(), Pid). 176 | %% ''' 177 | 178 | -define(assertLinked(Pid), ?assertLinked(self(), Pid)). 179 | -define(_assertLinked(Pid), ?_test(?assertLinked(Pid))). 180 | %% @equiv ?assertLinked(self(), Pid) 181 | 182 | -endif. % ifndef MOYO_EUNIT_HRL 183 | -------------------------------------------------------------------------------- /include/guard.hrl: -------------------------------------------------------------------------------- 1 | %% @copyright 2013-2015 DWANGO Co., Ltd. All Rights Reserved. 2 | %% 3 | %% @doc ガード用マクロ 4 | %% 5 | 6 | 7 | -ifndef(MOYO_GUARD_MACROS_HRL). 8 | -define(MOYO_GUARD_MACROS_HRL, true). 9 | 10 | -define(IS_ALPHA(Char), ($a =< Char andalso Char =< $z orelse $A =< Char andalso Char =< $Z)). 11 | -define(IS_NUM(Char), ($0 =< Char andalso Char =< $9)). 12 | -define(IS_ALPHA_NUM(Char), (?IS_ALPHA(Char) orelse ?IS_NUM(Char))). 13 | 14 | -endif. 15 | -------------------------------------------------------------------------------- /include/moyo_internal.hrl: -------------------------------------------------------------------------------- 1 | -ifdef('FUN_STACKTRACE'). 2 | -define(CAPTURE_STACKTRACE, ). 3 | -define(GET_STACKTRACE, erlang:get_stacktrace()). 4 | -else. 5 | -define(CAPTURE_STACKTRACE, :__StackTrace). 6 | -define(GET_STACKTRACE, __StackTrace). 7 | -endif. 8 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | %% vim: set filetype=erlang : -*- erlang -*- 2 | {require_min_otp_vsn, "OTP18"}. 3 | 4 | %% erlangのコンパイルオプション. 5 | {erl_opts, [ 6 | warnings_as_errors, 7 | warn_untyped_record, 8 | {platform_define, "^(R|1|20)", 'FUN_STACKTRACE'} 9 | ]}. 10 | 11 | 12 | %% link時バリデーションオプション. 13 | {xref_checks, [ 14 | fail_on_warning, 15 | undefined_function_calls 16 | ]}. 17 | 18 | %% clean時の削除対象. 19 | {clean_files, [".eunit/*", "ebin/*.beam"]}. 20 | 21 | 22 | %% テスト時にコードカバレッジの測定を有効にするかどうか. 23 | {cover_enabled, true}. 24 | 25 | %% edocのオプション. 26 | {edoc_opts, [ 27 | {doclet, edown_doclet}, 28 | {dialyzer_specs, all}, 29 | {report_missing_type, true}, 30 | {report_type_mismatch, true}, 31 | {pretty_print, erl_pp}, 32 | {preprocess, true} 33 | ]}. 34 | {validate_app_modules, true}. 35 | 36 | %% `rebar3 shell`実行時の起動アプリケーション. 37 | {shell, [{apps, [moyo]}]}. 38 | 39 | %% Dialyzerのオプション. 40 | {dialyzer, 41 | [ 42 | {plt_extra_apps, [eunit]}, 43 | {warnings, [error_handling, unmatched_returns, unknown, no_improper_lists]} 44 | ]}. 45 | 46 | {ct_dirs, "ct"}. 47 | 48 | %% 依存ライブラリ群. 49 | {profiles, 50 | [{test, 51 | [{deps, 52 | [ 53 | meck 54 | ]} 55 | ]}, 56 | {dev, 57 | [{deps, 58 | [ 59 | edown 60 | ]}]} 61 | ]}. 62 | -------------------------------------------------------------------------------- /rebar.lock: -------------------------------------------------------------------------------- 1 | []. 2 | -------------------------------------------------------------------------------- /rebar3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dwango/moyo/9be1a57969dd7ab738eecb7af51d07f6334559bd/rebar3 -------------------------------------------------------------------------------- /src/moyo.app.src: -------------------------------------------------------------------------------- 1 | %% vim: set filetype=erlang : -*- erlang -*- 2 | {application, moyo, 3 | [ 4 | {description, "Erlang Utility Library for DWANGO"}, 5 | {vsn, git}, 6 | {registered, []}, 7 | {applications, [ 8 | kernel, 9 | stdlib, 10 | crypto, 11 | xmerl 12 | ]}, 13 | {env, []} 14 | ]}. 15 | -------------------------------------------------------------------------------- /src/moyo_application.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2013-2015 DWANGO Co., Ltd. All Rights Reserved. 2 | %% 3 | %% @doc アプリケーション関連の処理を集めたモジュール 4 | -module(moyo_application). 5 | 6 | %%---------------------------------------------------------------------------------------------------------------------- 7 | %% Exported API 8 | %%---------------------------------------------------------------------------------------------------------------------- 9 | -export([ 10 | ensure_loaded/1, 11 | ensure_all_loaded/1, 12 | ensure_all_unloaded/1, 13 | get_key/3, 14 | get_priv_dir/1 15 | ]). 16 | 17 | -export_type([ 18 | name/0 19 | ]). 20 | 21 | %%---------------------------------------------------------------------------------------------------------------------- 22 | %% Types 23 | %%---------------------------------------------------------------------------------------------------------------------- 24 | -type name() :: atom(). % アプリケーション名 25 | 26 | %%---------------------------------------------------------------------------------------------------------------------- 27 | %% Exported Functions 28 | %%---------------------------------------------------------------------------------------------------------------------- 29 | %% @doc 指定されたアプリケーションが確実にロードされているようにする 30 | %% 31 | %% {@link application:load/1}とは異なり、既にロード済みのアプリケーションが指定された場合は`ok'が返される 32 | -spec ensure_loaded(name()) -> ok | {error, Reason} when 33 | Reason :: term(). 34 | ensure_loaded(Application) -> 35 | case application:load(Application) of 36 | {error, {already_loaded, _}} -> ok; 37 | Other -> Other 38 | end. 39 | 40 | %% @doc 指定されたアプリケーションおよびそれが依存するプリケーション群がロードされているようにする 41 | %% 42 | %% `Loaded'は、関数呼び出し中に新規にロードされたアプリケーション群 43 | -spec ensure_all_loaded(name()) -> {ok, Loaded} | {error, Reason} when 44 | Loaded :: ordsets:ordset(name()), 45 | Reason :: term(). 46 | ensure_all_loaded(Application) -> 47 | AlreadyLoaded = gb_sets:from_list([Name || {Name, _, _} <- application:loaded_applications()]), 48 | case ensure_all_loaded_impl(gb_sets:singleton(Application), gb_sets:empty()) of 49 | {error, Reason} -> {error, Reason}; 50 | {ok, Loaded} -> 51 | %% NOTE: `gb_sets:to_list/1'が返り値の順序保証を仕様に明記していないので、 52 | %% 念のために`ordsets:from_list/1'を呼び出しておく 53 | LoadedDiff = ordsets:from_list(gb_sets:to_list(gb_sets:difference(Loaded, AlreadyLoaded))), 54 | {ok, LoadedDiff} 55 | end. 56 | 57 | %% @doc `Pred(Application)'が`true'を返したアプリケーションを全て停止してアンロードする. 58 | %% 59 | %% {@link application:loaded_applications/0}の結果に基づき、
60 | %% ロードされているアプリケーションについて`Pred(Application)'を呼び出し、
61 | %% `Pred(Application)'が`true'を返したアプリケーションを全て停止してアンロードする. 62 | %% 63 | %% 一つでもアンロードに失敗した場合は、即座に`{error, Reason}'を返す. 64 | %% 65 | %% ※`kernel'と`stdlib'はアンロードしない. 66 | -spec ensure_all_unloaded(fun((Application :: atom()) -> boolean())) -> ok | {error, Reason :: term()}. 67 | ensure_all_unloaded(Pred) -> 68 | try 69 | ensure_all_unloaded_or_error(Pred) 70 | catch 71 | throw:Reason -> {error, Reason} 72 | end. 73 | 74 | %% @doc {@link applications:get_key/2}にデフォルト値を指定可能にしたもの 75 | -spec get_key(name(), atom(), term()) -> Value::term(). 76 | get_key(Application, Key, DefaultValue) -> 77 | case application:get_key(Application, Key) of 78 | undefined -> DefaultValue; 79 | {ok, Value} -> Value 80 | end. 81 | 82 | %% @doc {@link code:priv_dir/1}の代替となる関数。 83 | %% 84 | %% 標準あるいはERL_LIBS環境変数で指定されたディレクトリ以下に指定したアプリケーションが存在せず 85 | %% code:priv_dirに失敗した場合もprivディレクトリを推測して値を返す 86 | -spec get_priv_dir(name()) -> {ok, file:filename()} | {error, bad_name}. 87 | get_priv_dir(Application) -> 88 | case code:priv_dir(Application) of 89 | {error, bad_name} -> 90 | case application:get_key(Application, modules) of % 代替方法でprivディレクトリのパスを推測する 91 | undefined -> {error, bad_name}; 92 | {ok, [M |_]} -> {ok, filename:join([filename:dirname(filename:dirname(code:which(M))), "priv"])} 93 | end; 94 | Dir -> {ok, Dir} 95 | end. 96 | 97 | %%---------------------------------------------------------------------------------------------------------------------- 98 | %% Internal Functions 99 | %%---------------------------------------------------------------------------------------------------------------------- 100 | -spec ensure_all_loaded_impl(Queue, Loaded) -> {ok, Loaded} | {error, Reason} when 101 | Queue :: gb_sets:set(name()), % 未処理のアプケーションセット (キュー) 102 | Loaded :: gb_sets:set(name()), % 処理済みのアプリケーションセット 103 | Reason :: term(). 104 | ensure_all_loaded_impl(Queue0, Loaded0) -> 105 | case gb_sets:is_empty(Queue0) of 106 | true -> {ok, Loaded0}; 107 | false -> 108 | {Application, Queue1} = gb_sets:take_smallest(Queue0), 109 | case ensure_loaded(Application) of 110 | {error, Reason} -> {error, Reason}; 111 | ok -> 112 | Loaded1 = gb_sets:add(Application, Loaded0), 113 | Dependings = gb_sets:from_list(get_key(Application, applications, [])), 114 | UnloadedDependings = gb_sets:difference(Dependings, Loaded1), 115 | ensure_all_loaded_impl(gb_sets:union(Queue1, UnloadedDependings), Loaded1) 116 | end 117 | end. 118 | 119 | -spec ensure_all_unloaded_or_error(fun((Application :: atom()) -> boolean())) -> ok. 120 | ensure_all_unloaded_or_error(Pred) -> 121 | [ 122 | begin 123 | ok = case application:stop(Application) of 124 | ok -> ok; 125 | {error, {not_started, Application}} -> ok 126 | end, 127 | ok = case application:unload(Application) of 128 | ok -> ok; 129 | {error, {not_loaded, Application}} -> ok; 130 | {error, {running, Application}} -> throw({running, Application}) 131 | end 132 | end 133 | || {Application, _, _} <- 134 | application:loaded_applications(), 135 | Application =/= kernel andalso Application =/= stdlib andalso Pred(Application) 136 | ], 137 | ok. 138 | -------------------------------------------------------------------------------- /src/moyo_char.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2015 DWANGO Co., Ltd. All Rights Reserved. 2 | %% 3 | %% @doc 文字を扱うライブラリ. 4 | -module(moyo_char). 5 | 6 | %%---------------------------------------------------------------------------------------------------------------------- 7 | %% Exported API 8 | %%---------------------------------------------------------------------------------------------------------------------- 9 | -export([is_alpha_num/1, is_num/1, is_alpha/1 10 | ]). 11 | 12 | -export_type([ 13 | ]). 14 | 15 | %%---------------------------------------------------------------------------------------------------------------------- 16 | %% Macros & Records & Types 17 | %%---------------------------------------------------------------------------------------------------------------------- 18 | 19 | %%---------------------------------------------------------------------------------------------------------------------- 20 | %% Exported Functions 21 | %%---------------------------------------------------------------------------------------------------------------------- 22 | 23 | 24 | %% @doc `Char'が英字[a-z A-Z]かどうかを判定する 25 | -spec is_alpha(Char :: char()) -> boolean(). 26 | is_alpha(Char) -> 27 | $a =< Char andalso Char =< $z orelse $A =< Char andalso Char =< $Z. 28 | 29 | %% @doc `Char'が数文字[0-9]かどうかを判定する 30 | -spec is_num(Char :: char()) -> boolean(). 31 | is_num(Char) -> 32 | $0 =< Char andalso Char =< $9. 33 | 34 | 35 | %% @doc `Char'が英数字かどうかを判定する 36 | -spec is_alpha_num(Char :: char()) -> boolean(). 37 | is_alpha_num(Char) -> 38 | is_alpha(Char) orelse is_num(Char). 39 | 40 | %%---------------------------------------------------------------------------------------------------------------------- 41 | %% Internal Function 42 | %%---------------------------------------------------------------------------------------------------------------------- 43 | -------------------------------------------------------------------------------- /src/moyo_concurrent.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2013-2014 DWANGO Co., Ltd. All Rights Reserved.3409;0c 2 | %% 3 | %% @doc 並行処理の為のモジュール 4 | 5 | -module(moyo_concurrent). 6 | 7 | %%---------------------------------------------------------------------------------------------------------------------- 8 | %% Exported API 9 | %%---------------------------------------------------------------------------------------------------------------------- 10 | 11 | -export([ 12 | exec/1, 13 | exec/2, 14 | exec_sort/1, 15 | exec_sort/2, 16 | exec_map/1, 17 | exec_map/2 18 | ]). 19 | 20 | %%---------------------------------------------------------------------------------------------------------------------- 21 | %% Exported Functions 22 | %%---------------------------------------------------------------------------------------------------------------------- 23 | 24 | %% @doc 並行に複数のコマンドを実行し、その結果を入力の順に返す. 25 | %% 26 | %% 1つでも結果がerrorだった場合, その1つのerror結果を呼び出し元に投げ, 他のプロセスは強制終了される. 27 | -spec exec_sort([Input]) -> [RetValue :: term()] when 28 | Input :: {module(), Function :: atom(), Args :: [term()]}. 29 | exec_sort(Inputs) -> 30 | exec_sort(Inputs, infinity). 31 | 32 | -spec exec_sort([Input], Timeout) -> [RetValue :: term()] when 33 | Input :: {module(), Function :: atom(), Args :: [term()]}, 34 | Timeout :: timeout(). 35 | exec_sort(Inputs, Timeout) -> 36 | exec_impl(Inputs, Timeout, fun exec_order_by_input/1). 37 | 38 | %% @doc 並行に複数のコマンドを実行する 39 | %% 40 | %% see: `exec(Input, infinity)' 41 | -spec exec([Input]) -> [{Input, RetValue :: term()}] when 42 | Input :: {module(), Function :: atom(), Args :: [term()]}. 43 | exec(Inputs) -> 44 | exec(Inputs, infinity). 45 | 46 | %% @doc 並行に複数のコマンドを実行する 47 | %% 返り値は実行が終了した順番で返される.
48 | %% また, 1つでも結果がerrorだった場合, その1つのerror結果を呼び出し元に投げ, 他のプロセスは強制終了される. 49 | -spec exec([Input], Timeout) -> [{Input, RetValue :: term()}] when 50 | Input :: {module(), Function :: atom(), Args :: [term()]}, 51 | Timeout :: timeout(). 52 | exec(Inputs, Timeout) -> 53 | exec_impl(Inputs, Timeout, fun exec_order_by_finish/1). 54 | 55 | %% @doc 並行に複数のコマンドを実行する 56 | %% 57 | %% see: `exec_map([Input], infinity)' 58 | -spec exec_map([Input]) -> [RetValue :: term() | {'EXIT', Signal :: term()}] when 59 | Input :: {module(), Function :: atom(), Args :: [term()]}. 60 | exec_map(Inputs) -> 61 | exec_map(Inputs, infinity). 62 | 63 | %% @doc 並行に複数のコマンドを実行する 64 | %% 返り値は Inputs の順番で返される.
65 | %% 1つでも結果がerrorだった場合もすべての実行が完了を待ち, 結果のリストは Inputs の写像となる. 66 | -spec exec_map([Input], Timeout) -> [RetValue :: term() | {'EXIT', Signal :: term()}] when 67 | Input :: {module(), Function :: atom(), Args :: [term()]}, 68 | Timeout :: timeout(). 69 | exec_map(Inputs, Timeout) -> 70 | exec_impl(Inputs, Timeout, fun exec_order_preserved_by_input/1). 71 | 72 | %%---------------------------------------------------------------------------------------------------------------------- 73 | %% Internal Functions 74 | %%---------------------------------------------------------------------------------------------------------------------- 75 | 76 | %% @doc exec_order/execの実装部分. exec_order_by_finish / exec_order_by_inputを第3引数に指定する. 77 | %% 78 | %% 返り値は第3引数の返り値になる. 79 | -spec exec_impl([Input], Timeout, fun(([Input]) -> Result)) -> Result when 80 | Input :: {module(), Function :: atom(), Args :: [term()]}, 81 | Timeout :: timeout(), 82 | Result :: [{Input, RetValue}] | [RetValue], 83 | RetValue :: term(). 84 | exec_impl(Inputs, Timeout, Func) -> 85 | Self = self(), 86 | {Pid, Ref} = spawn_monitor(fun() -> 87 | Self ! {self(), Func(Inputs)} 88 | end), 89 | receive 90 | {'DOWN', Ref, process, Pid, normal} -> 91 | receive {Pid, Result} -> Result end; 92 | {'DOWN', Ref, process, Pid, {Reason, Args}} -> error(Reason, Args); 93 | {'DOWN', Ref, process, Pid, Reason} -> error(Reason) 94 | after Timeout -> 95 | _ = exit(Pid, shutdown), 96 | receive {'DOWN', Ref, process, Pid, _} -> error(timeout) end 97 | end. 98 | 99 | %% @doc Input要素の関数をプロセスを立てて実行し, その結果を終了順に返す. 100 | -spec exec_order_by_finish([Input]) -> [{Input, RetValue :: term()}] when 101 | Input :: {module(), Function :: atom(), Args :: [term()]}. 102 | exec_order_by_finish(Inputs) -> 103 | Self = self(), 104 | _ = [spawn_link(fun() -> 105 | Self ! {{Module, Fun, Args}, apply(Module, Fun, Args)} 106 | end) 107 | || {Module, Fun, Args} <- Inputs], 108 | [receive {Input, RetValue} -> {Input, RetValue} end || _ <- Inputs]. 109 | 110 | %% @doc Input要素の関数をプロセスを立てて実行し, 結果を入力順に返す. 111 | -spec exec_order_by_input([Input]) -> [RetValue :: term()] when 112 | Input :: {module(), Function :: atom(), Args :: [term()]}. 113 | exec_order_by_input(Inputs) -> 114 | Self = self(), 115 | _ = [spawn_link(fun() -> 116 | Self ! {{Module, Fun, Args}, apply(Module, Fun, Args)} 117 | end) 118 | || {Module, Fun, Args} <- Inputs], 119 | [receive {Input, RetValue} -> RetValue end || Input <- Inputs]. 120 | 121 | %% @doc Input要素の関数をプロセスを立てて実行し, 結果を入力順に返す. エラーが起きても停止しない. 122 | -spec exec_order_preserved_by_input([Input]) -> [RetValue :: term() | {'EXIT', Signal :: term()}] when 123 | Input :: {module(), Function :: atom(), Args :: [term()]}. 124 | exec_order_preserved_by_input(Inputs) -> 125 | Self = self(), 126 | process_flag(trap_exit, true), 127 | Indices = lists:seq(1, length(Inputs)), 128 | Pids = [spawn_link(fun() -> 129 | Self ! {Index, apply(Module, Fun, Args)} 130 | end) 131 | || {Index, {Module, Fun, Args}} <- lists:zip(Indices, Inputs)], 132 | [receive 133 | {Index, Result} -> 134 | receive %% wait normal exit 135 | {'EXIT', Pid, normal} -> Result 136 | end; 137 | {'EXIT', Pid, Signal} -> {'EXIT', Signal} 138 | end || {Index, Pid} <- lists:zip(Indices, Pids)]. 139 | -------------------------------------------------------------------------------- /src/moyo_cond.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2013-2014 DWANGO Co., Ltd. All Rights Reserved. 2 | %% 3 | %% @doc 条件分岐処理関連のユーティリティ関数を提供するモジュール 4 | -module(moyo_cond). 5 | 6 | %%---------------------------------------------------------------------------------------------------------------------- 7 | %% Exported API 8 | %%---------------------------------------------------------------------------------------------------------------------- 9 | -export([ 10 | apply_if/3, 11 | apply_when/2, 12 | apply_unless/2, 13 | conditional/3, 14 | while/2 15 | ]). 16 | 17 | %%---------------------------------------------------------------------------------------------------------------------- 18 | %% Exported Functions 19 | %%---------------------------------------------------------------------------------------------------------------------- 20 | %% @doc `Condition'が`true'の場合は`ThenFun'が、`false'の場合は`ElseFun'が実行される 21 | -spec apply_if(Condition::boolean(), ThenFun, ElseFun) -> Result when 22 | ThenFun :: fun (() -> Result), 23 | ElseFun :: fun (() -> Result), 24 | Result :: term(). 25 | apply_if(true, ThenFun, _) -> ThenFun(); 26 | apply_if(false, _, ElseFun) -> ElseFun(). 27 | 28 | %% @doc `Condition'が`true'の場合は`ThenFun'が実行される 29 | %% 30 | %% 返り値は常に`ok' 31 | -spec apply_when(Condition::boolean(), ThenFun) -> ok when 32 | ThenFun :: fun (() -> any()). 33 | apply_when(true, ThenFun) -> _ = ThenFun(), ok; 34 | apply_when(false, _) -> ok. 35 | 36 | %% @doc `Condition'が`false'の場合は`ThenFun'が実行される 37 | %% 38 | %% 返り値は常に`ok' 39 | -spec apply_unless(Condition::boolean(), ThenFun) -> ok when 40 | ThenFun :: fun (() -> any()). 41 | apply_unless(true, _) -> ok; 42 | apply_unless(false, ThenFun) -> _ = ThenFun(), ok. 43 | 44 | %% @doc 三項演算子と同様の機能を提供する。Conditionがtrueなら2つ目の値が、falseなら3つ目の値が返る 45 | -spec conditional(Condition::boolean(),TValue::any(), FValue::any() ) -> any(). 46 | conditional( true, TValue, _ ) -> TValue; 47 | conditional( false, _ , FValue) -> FValue. 48 | 49 | %% @doc `Acc0'が`Fun'の定義により処理して`Acc1'を返す。 50 | %% 51 | %% `Fun'内の処理の結果が`{true, AccOut}'になると、`AccOut'が`Fun'の引数としてもう一度処理される。 52 | %% 結果が`{false, AccOut}'になると、`AccOut'を返す。 53 | %% ``` 54 | %% > while(fun(X) when X >= 5 -> {false, X}; (X) -> {true, X + 1} end, 1). 55 | %% 5 56 | %% > while(fun(X) when X < 2 -> {false, X}; (X) -> {true, X - 1} end, 10). 57 | %% 1 58 | %% ''' 59 | -spec while(Fun, Acc0) -> Acc1 when 60 | Fun :: fun ((AccIn) -> {Continue :: boolean(), AccOut}), 61 | Acc0 :: AccIn, 62 | Acc1 :: AccOut, 63 | AccIn :: term(), 64 | AccOut :: term(). 65 | while(Fun, Acc0) -> 66 | case Fun(Acc0) of 67 | {false, Acc1} -> Acc1; 68 | {true, Acc1} -> while(Fun, Acc1) 69 | end. 70 | -------------------------------------------------------------------------------- /src/moyo_ct.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2013-2014 DWANGO Co., Ltd. All Rights Reserved. 2 | %% 3 | %% @doc Common Test Utility 4 | -module(moyo_ct). 5 | 6 | %%---------------------------------------------------------------------------------------------------------------------- 7 | %% Exported API 8 | %%---------------------------------------------------------------------------------------------------------------------- 9 | -export([ 10 | all/1, 11 | eunit/1 12 | ]). 13 | 14 | %%---------------------------------------------------------------------------------------------------------------------- 15 | %% Exported Functions 16 | %%---------------------------------------------------------------------------------------------------------------------- 17 | 18 | %% @doc SUITE moduleのCommon TestのCall back Functions以外を取得する. 19 | %% 20 | %% Arity =:= 1 以外は取得しない為, Eunit対象も外れる. 21 | -spec all(SuiteModule :: module()) -> [Function :: atom()]. 22 | all(SuiteModule) -> 23 | [X || {X, 1} <- SuiteModule:module_info(exports), 24 | %% common test 25 | X =/= all, 26 | X =/= groups, 27 | X =/= suite, 28 | X =/= init_per_suite, 29 | X =/= end_per_suite, 30 | X =/= init_per_group, 31 | X =/= end_per_group, 32 | X =/= init_per_testcase, 33 | X =/= end_per_testcase, 34 | %% other 35 | X =/= module_info, 36 | X =/= test]. 37 | 38 | %% @doc EunitをCommon Testに組み込む場合に使用できる. 39 | -spec eunit(Application :: atom()) -> ok. 40 | eunit(Application) -> 41 | ok = eunit:test({application, Application}). 42 | -------------------------------------------------------------------------------- /src/moyo_graph.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2017 DWANGO Co., Ltd. All Rights Reserved. 2 | %% 3 | %% @doc マップに関する処理を集めたユーティリティモジュール. 4 | -module(moyo_graph). 5 | 6 | %%---------------------------------------------------------------------------------------------------------------------- 7 | %% Exported API 8 | %%---------------------------------------------------------------------------------------------------------------------- 9 | -export([ 10 | dfs_post_order/3, 11 | graph_with_vertices/2 12 | ]). 13 | 14 | -export_type([ 15 | graph/1, 16 | graph/0, 17 | vertex/0, 18 | vertex_not_found/0, 19 | any_error/0 20 | ]). 21 | 22 | %%---------------------------------------------------------------------------------------------------------------------- 23 | %% Macros & Records & Types 24 | %%---------------------------------------------------------------------------------------------------------------------- 25 | -type graph(V) :: #{V => [V]}. 26 | -type graph() :: graph(vertex()). 27 | -type vertex() :: term(). 28 | -type vertex_not_found() :: {error, {vertex_not_found, vertex()}}. 29 | -type any_error() :: {error, term()}. 30 | 31 | %%---------------------------------------------------------------------------------------------------------------------- 32 | %% Exported Functions 33 | %%---------------------------------------------------------------------------------------------------------------------- 34 | 35 | %% @doc 有向グラフに対してpost-orderedな深さ優先探索を行う. 36 | %% 37 | %% `Graph'は`始点=>[終点のリスト]'という要素を持つ`map'で頂点間の接続関係が記述されている. 38 | %% 39 | %% 一度訪問した頂点は訪問対象から除外されるため,結果として行われるのは, 40 | %% `RootVertex'から到達可能なサブグラフから得られるスパニングツリーに対する探索となる. 41 | %% 42 | %% post-orderedな探索であるため,葉から先に探索され,次に共通の先祖へと遡ってゆく. 43 | %% 44 | %% 探索された頂点に対しては順次`VertexFun'が適用される. 45 | %% 兄弟関係にある頂点について適用された`VertexFun'の戻り値は`list'でコレクションされ, 46 | %% 親の頂点に適用される`VertexFun'の第二引数へと渡される. 47 | %% 48 | %% 時間計算量: `O(|E|+|V|log|V|), |E|:枝の数, |V|:頂点の数' 49 | %% 50 | %% 空間計算量: `O(|V|), |V|:頂点の数' 51 | %% 52 | %% @param Graph `map'形式で格納されたグラフ. 53 | %% @param RootVertex 探索を開始する頂点. 54 | %% @param VertexFun 訪れた頂点を処理する関数. 55 | -spec dfs_post_order(graph(vertex()), vertex(), VertexFun) -> DFSRet when 56 | VertexFun :: fun((vertex(), [VertexFunOK]) -> VertexFunRet), 57 | VertexFunRet :: VertexFunOK | any_error(), 58 | VertexFunOK :: term(), 59 | DFSRet :: VertexFunRet | vertex_not_found(). 60 | dfs_post_order(Graph, RootVertex, VertexFun) -> 61 | case dfs_post_order(Graph, RootVertex, VertexFun, sets:add_element(RootVertex, sets:new())) of 62 | {error, Reason} -> 63 | {error, Reason}; 64 | {Ret, _} -> 65 | Ret 66 | end. 67 | 68 | %% @doc `map'で表現されたグラフに、存在しない頂点を追加する. 69 | %% 70 | %% `Graph'は`頂点=>[頂点のリスト]'という要素を持つ`map'で頂点間の接続関係が記述されている. 71 | %% 72 | %% 追加する頂点は,`Vertices'としてリストの形で渡す. 73 | %% 74 | %% `Graph'が既に持っている頂点は追加しない. 75 | %% 76 | %% 時間計算量: `O(|V|log|Vall|), |V|:追加しようとしている頂点の数, |Vall|:すべての頂点の数' 77 | %% 78 | %% 空間計算量: `O(|Vall|), |Vall|:すべての頂点の数' 79 | %% 80 | %% @param Graph `map'形式で格納されたグラフ. 81 | %% @param Vertices 追加したい頂点のリスト. 82 | -spec graph_with_vertices(graph(vertex()), [vertex()]) -> graph(vertex()). 83 | graph_with_vertices(Graph, Vertices) -> 84 | lists:foldl(fun (V, Acc) -> maps:put(V, [], Acc) end, Graph, 85 | lists:filter(fun (V) -> not maps:is_key(V, Graph) end, Vertices)). 86 | 87 | %%---------------------------------------------------------------------------------------------------------------------- 88 | %% Internal Function 89 | %%---------------------------------------------------------------------------------------------------------------------- 90 | 91 | %% @doc dfs_post_order/3の実体. 92 | -spec dfs_post_order(graph(vertex()), vertex(), VertexFun, sets:set(vertex())) -> DFSRet when 93 | VertexFun :: fun((vertex(), [VertexFunOK]) -> VertexFunRet), 94 | VertexFunRet :: VertexFunOK | any_error(), 95 | VertexFunOK :: term(), 96 | DFSRet :: {VertexFunOK, sets:set(vertex())} | any_error(). 97 | dfs_post_order(Graph, RootVertex, VertexFun, VisitedVertices) -> 98 | case maps:find(RootVertex, Graph) of 99 | error -> 100 | {error, {vertex_not_found, RootVertex}}; 101 | {ok, Children} -> 102 | FoldedFun = fun (V, Acc) -> 103 | {ElderSibsRet, VisitedVerticesBefore} = Acc, 104 | case sets:is_element(V, VisitedVerticesBefore) of 105 | false -> 106 | case dfs_post_order(Graph, V, VertexFun, sets:add_element(V, VisitedVerticesBefore)) of 107 | {error, Reason} -> 108 | {error, Reason}; 109 | {Ret, VisitedVerticesAfter} -> 110 | {ok, {[Ret | ElderSibsRet], VisitedVerticesAfter}} 111 | end; 112 | true -> 113 | {ok, Acc} 114 | end 115 | end, 116 | case moyo_list:maybe_foldl(FoldedFun, {[], VisitedVertices}, Children) of 117 | {error, Reason} -> 118 | {error, Reason}; 119 | {ok, {ConvertedChildren, VisitedVerticesRet}} -> 120 | case VertexFun(RootVertex, lists:reverse(ConvertedChildren)) of 121 | {error, Reason} -> 122 | {error, Reason}; 123 | VertexFunRet -> 124 | {VertexFunRet, VisitedVerticesRet} 125 | end 126 | end 127 | end. 128 | -------------------------------------------------------------------------------- /src/moyo_inet.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2013-2015 DWANGO Co., Ltd. All Rights Reserved. 2 | %% 3 | %% @doc `inet'の拡張ライブラリ 4 | -module(moyo_inet). 5 | 6 | %%---------------------------------------------------------------------------------------------------------------------- 7 | %% Exported API 8 | %%---------------------------------------------------------------------------------------------------------------------- 9 | 10 | -export([ 11 | find_free_port/0, find_free_port/1 12 | ]). 13 | 14 | %%---------------------------------------------------------------------------------------------------------------------- 15 | %% Exported Functions 16 | %%---------------------------------------------------------------------------------------------------------------------- 17 | 18 | %% @doc 現在空いているポートを1つ返す 19 | %% 20 | %% この関数を呼び出した際の空きポートを返す為, そのポートが他のアプリケーションによって使用されてしまい, 使えない可能性がある. 21 | -spec find_free_port() -> {ok, inet:port_number()} | {error, Reason :: term()}. 22 | find_free_port() -> 23 | case find_free_port(1) of 24 | {ok, [Port]} -> {ok, Port}; % find_free_port(1) は ok を返す場合は必ず1つのポートになる 25 | {error, Reason} -> {error, Reason} 26 | end. 27 | 28 | %% @doc 現在空いているポートを引数として与えられた個数返す. 順不同. 29 | %% 現在の空きポート以上の数を要求した場合は, `{error, system_limit}' が返る. 30 | %% 31 | %% この関数を呼び出した際の空きポートを返す為, そのポートが他のアプリケーションによって使用されてしまい, 使えない可能性がある. 32 | -spec find_free_port(Count :: non_neg_integer()) -> {ok, [inet:port_number()]} | {error, Reason :: term()}. 33 | find_free_port(Count) -> 34 | case find_free_port_rec(Count, []) of 35 | {ok, SocketPortPairs} -> 36 | Ports = close_sockets_return_ports(SocketPortPairs), 37 | {ok, Ports}; 38 | {error, Reason, SocketPortPairs} -> 39 | _ = close_sockets_return_ports(SocketPortPairs), 40 | {error, Reason} 41 | end. 42 | 43 | %%---------------------------------------------------------------------------------------------------------------------- 44 | %% Internal Functions 45 | %%---------------------------------------------------------------------------------------------------------------------- 46 | -spec find_free_port_rec(Count :: non_neg_integer(), Acc :: [SocketPortPair]) -> Result when 47 | Result :: {ok, [SocketPortPair]} | {error, Reason :: term(), [SocketPortPair]}, 48 | SocketPortPair :: {gen_tcp:socket(), inet:port_number()}. 49 | find_free_port_rec(Count, Acc) when Count =< 0 -> {ok, Acc}; 50 | find_free_port_rec(Count, Acc) -> 51 | case gen_tcp:listen(0, []) of 52 | {ok, Socket} -> 53 | case inet:port(Socket) of 54 | {ok, Port} -> find_free_port_rec(Count - 1, [{Socket, Port}|Acc]); 55 | {error, Reason} -> {error, Reason, Acc} 56 | end; 57 | {error, Reason} -> {error, Reason, Acc} 58 | end. 59 | 60 | -spec close_sockets_return_ports([{gen_tcp:socket(), inet:port_number()}]) -> [inet:port_number()]. 61 | close_sockets_return_ports(SocketPortPairs) -> 62 | lists:map(fun({Socket, Port}) -> 63 | ok = gen_tcp:close(Socket), 64 | Port 65 | end, SocketPortPairs). 66 | -------------------------------------------------------------------------------- /src/moyo_math.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2013-2015 DWANGO Co., Ltd. All Rights Reserved. 2 | %% 3 | %% @doc 数学的な関数を集めたモジュール. 4 | -module(moyo_math). 5 | 6 | %%---------------------------------------------------------------------------------------------------------------------- 7 | %% Exported API 8 | %%---------------------------------------------------------------------------------------------------------------------- 9 | -export([ 10 | ceil/1, 11 | floor/1, 12 | 13 | gcd/2, 14 | pow_int/2, 15 | divmod/2, 16 | random_sequence/1, 17 | random_sequence/2 18 | ]). 19 | 20 | -export_type([ 21 | random_sequence_symbols/0 22 | ]). 23 | 24 | %%---------------------------------------------------------------------------------------------------------------------- 25 | %% Macros & Records & Types 26 | %%---------------------------------------------------------------------------------------------------------------------- 27 | %% ランダム文字列の記号 28 | -type random_sequence_symbols() :: alphabetical | numeric | alphanumeric. 29 | 30 | %%---------------------------------------------------------------------------------------------------------------------- 31 | %% Exported Functions 32 | %%---------------------------------------------------------------------------------------------------------------------- 33 | %% @doc 数(number)を切り上げて整数を返す. 34 | %% 35 | %% 指定した以上の整数で最小のものを返す. 36 | %% ``` 37 | %% > ceil(1.0). 38 | %% 1. 39 | %% > ceil(0.5). 40 | %% 1. 41 | %% > ceil(0.0). 42 | %% 0. 43 | %% > ceil(-0.5). 44 | %% 0. 45 | %% > ceil(-1.0). 46 | %% -1. 47 | %% ''' 48 | -spec ceil(number()) -> integer(). 49 | ceil(Number) when Number > 0 -> 50 | Trunc = trunc(Number), 51 | case Number - Trunc == 0 of 52 | true -> Trunc; 53 | false -> Trunc + 1 54 | end; 55 | ceil(Number) -> trunc(Number). 56 | 57 | %% @doc 数(number)を切り下げて整数を返す. 58 | %% 59 | %% 指定した以下の整数で最大のものを返す. 60 | %% ``` 61 | %% > floor(1.0). 62 | %% 1. 63 | %% > floor(0.5). 64 | %% 0. 65 | %% > floor(0.0). 66 | %% 0. 67 | %% > floor(-0.5). 68 | %% -1. 69 | %% > floor(-1.0). 70 | %% -1. 71 | %% ''' 72 | -spec floor(number()) -> integer(). 73 | floor(Number) when Number > 0 -> 74 | trunc(Number); 75 | floor(Number) -> 76 | Trunc = trunc(Number), 77 | case Number - Trunc == 0 of 78 | true -> Trunc; 79 | false -> Trunc -1 80 | end. 81 | 82 | %% @doc 最大公約数を求める. 83 | %% 84 | %% 両方の引数が0の場合, `both_0_error'をthrowする. 85 | -spec gcd(A::integer(), B::integer()) -> GCD::integer(). 86 | gcd(0, 0) -> throw(both_0_error); 87 | gcd(A, B) -> gcd_impl(abs(A), abs(B)). 88 | 89 | %% @doc 累乗関数. 90 | %% 91 | %% 計算結果がinteger()になる計算のみ行える. 92 | %% 具体的には、引数は整数のみで、第2引数は0以上のみを扱う. 93 | -spec pow_int(Base::integer(), Exponent::non_neg_integer()) -> Value::integer(). 94 | pow_int(Base, Exponent) when Exponent >= 0, is_integer(Exponent) -> pow_int_impl(Base, Exponent, 1). 95 | 96 | %% @doc 除算した商と剰余を求める関数. 97 | %% 98 | %% 除数が0である場合, `badarith' errorが発生する. 99 | -spec divmod(A::integer(), B::integer()) -> {Quotient::integer(), Remainder::integer()}. 100 | divmod(A, B) -> {A div B, A rem B}. 101 | 102 | %% @equiv random_sequence(Length, []) 103 | -spec random_sequence(Length::non_neg_integer())-> binary(). 104 | random_sequence(Length) -> random_sequence(Length, [{symbol, alphabetical}]). 105 | 106 | %% @doc ランダム文字列を返す 107 | %% 108 | %% DataTypeで出力形式を指定し、Symbolで出力内容を指定する. 109 | -spec random_sequence(Length::non_neg_integer(), Options) -> binary() when 110 | Options :: [{symbol, Symbols}], 111 | Symbols::random_sequence_symbols(). 112 | random_sequence(Length, []) -> random_sequence(Length, [{symbol, alphabetical}]); 113 | random_sequence(Length, [{symbol, Symbols}]) -> 114 | case Symbols of 115 | alphabetical -> random_alphabetical_sequence(Length); 116 | numeric -> random_numeric_sequence(Length); 117 | alphanumeric -> random_alphanumeric_sequence(Length) 118 | end. 119 | 120 | 121 | %%---------------------------------------------------------------------------------------------------------------------- 122 | %% Internal Function 123 | %%---------------------------------------------------------------------------------------------------------------------- 124 | -spec gcd_impl(non_neg_integer(), non_neg_integer()) -> non_neg_integer(). 125 | gcd_impl(A, 0) -> A; 126 | gcd_impl(A, B) -> 127 | Q = A div B, 128 | R = A - Q*B, 129 | gcd_impl(B, R). 130 | 131 | %% @private 132 | %% 133 | %% pow_int_impl(Base, Exponent, K) = Base^Exponent*K 134 | -spec pow_int_impl(Base::integer(), Exponent::non_neg_integer(), K::integer()) -> Value::integer(). 135 | pow_int_impl(_, 0, K) -> K; 136 | pow_int_impl(Base, Exponent, K) when Exponent rem 2 =:= 1 -> pow_int_impl(Base*Base, Exponent div 2, Base*K); 137 | pow_int_impl(Base, Exponent, K) -> pow_int_impl(Base*Base, Exponent div 2, K). 138 | 139 | %% アルファベットの乱数列を返す 140 | -spec random_alphabetical_sequence(Length::non_neg_integer()) -> binary(). 141 | random_alphabetical_sequence(Length) -> 142 | random_sequence_with_table(Length, <<"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ">>). 143 | 144 | %% 数字の乱数列を返す 145 | -spec random_numeric_sequence(Length::non_neg_integer()) -> binary(). 146 | random_numeric_sequence(Length) -> 147 | random_sequence_with_table(Length, <<"0123456789">>). 148 | 149 | %% アルファベットと数字の乱数列を返す 150 | -spec random_alphanumeric_sequence(Length::non_neg_integer()) -> binary(). 151 | random_alphanumeric_sequence(Length) -> 152 | random_sequence_with_table(Length, <<"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789">>). 153 | 154 | %% 乱数列を返す 155 | -spec random_sequence_with_table(Length::non_neg_integer(), Table::binary()) -> binary(). 156 | random_sequence_with_table(Length, Table) -> 157 | TailPos = byte_size(Table) - 1, 158 | << <<(binary:at(Table, rand:uniform(TailPos)))>> || _ <- lists:seq(1, Length)>>. 159 | -------------------------------------------------------------------------------- /src/moyo_monad.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2013-2014 DWANGO Co., Ltd. All Rights Reserved. 2 | %% 3 | %% @doc モナドに関する処理を集めたユーティリティモジュール. 4 | -module(moyo_monad). 5 | 6 | %%---------------------------------------------------------------------------------------------------------------------- 7 | %% Exported API 8 | %%---------------------------------------------------------------------------------------------------------------------- 9 | -export([ 10 | apply_maybe/3, 11 | maybe_val/1, 12 | maybe_fun/1 13 | ]). 14 | 15 | -export_type([ 16 | maybe_val/0, 17 | maybe_fun/0 18 | ]). 19 | 20 | %%---------------------------------------------------------------------------------------------------------------------- 21 | %% Types 22 | %%---------------------------------------------------------------------------------------------------------------------- 23 | -type maybe_val() :: {just, term()} | nothing. 24 | %% なにか or Nothing を格納するデータ構造 25 | -type maybe_fun() :: {just, fun()} | nothing. 26 | %% 何らかの関数 or Nothing を格納するデータ構造 27 | 28 | %%---------------------------------------------------------------------------------------------------------------------- 29 | %% Exported Functions 30 | %%---------------------------------------------------------------------------------------------------------------------- 31 | %% @doc maybe_val()を作ります. 32 | %% 33 | %% {just nothing} は作れないので直接作ってください. 34 | -spec maybe_val(term()) -> maybe_val(). 35 | maybe_val(nothing) -> nothing; 36 | maybe_val(Value) -> {just, Value}. 37 | 38 | 39 | %% @doc maybe()を作ります. 40 | -spec maybe_fun(nothing | fun()) -> maybe_val(). 41 | maybe_fun(nothing) -> nothing; 42 | maybe_fun(Fun) when is_function(Fun) -> {just, Fun}; 43 | maybe_fun(Fun) -> error({invalid_function, Fun}). 44 | 45 | 46 | %% @doc MaybeFunが{just, Function}なら apply(Function, ArgList) を実行します. MaybeFunがnothingならDefaultValue を返します 47 | -spec apply_maybe(maybe_fun(), list(any()), any()) -> any(). 48 | apply_maybe(MaybeFun, ArgList, DefaultValue) -> 49 | case MaybeFun of 50 | nothing -> DefaultValue; 51 | {just, Fun} when is_function(Fun, length(ArgList)) -> 52 | apply(Fun, ArgList); 53 | {just, Fun} -> error({invalid_function, Fun}); 54 | M -> error({invalid_maybe, M}) 55 | end. 56 | 57 | 58 | %%---------------------------------------------------------------------------------------------------------------------- 59 | %% Internal Functions 60 | %%---------------------------------------------------------------------------------------------------------------------- 61 | -------------------------------------------------------------------------------- /src/moyo_pipe.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2013-2014 DWANGO Co., Ltd. All Rights Reserved. 2 | %% 3 | %% @doc ポート(外部コマンド)に対するパイプ入出力機能を提供するためのモジュール 4 | %% 5 | %% 現在は出力機能にのみ対応済み 6 | -module(moyo_pipe). 7 | 8 | -include("moyo_internal.hrl"). 9 | 10 | %%---------------------------------------------------------------------------------------------------------------------- 11 | %% Exported API 12 | %%---------------------------------------------------------------------------------------------------------------------- 13 | -export([ 14 | output_start/3, 15 | output_start/4 16 | ]). 17 | 18 | -export_type([ 19 | output_option/0, 20 | output_data_generate_fun/0 21 | ]). 22 | 23 | %%---------------------------------------------------------------------------------------------------------------------- 24 | %% Types 25 | %%---------------------------------------------------------------------------------------------------------------------- 26 | 27 | -type output_option() :: {interval, moyo_clock:non_neg_milliseconds()}. 28 | %% interval: 各出力データ送信後にスリープする時間(ミリ秒). デフォルト値は 10. 29 | 30 | -type output_data_generate_fun() :: fun ((State::term()) -> {ok, OutputData::iodata(), NextState::term()} | stop). 31 | 32 | %%---------------------------------------------------------------------------------------------------------------------- 33 | %% Records 34 | %%---------------------------------------------------------------------------------------------------------------------- 35 | -record(output_state, 36 | { 37 | port :: port(), 38 | data_generate_state :: term(), 39 | data_generate_fun :: output_data_generate_fun(), 40 | interval = 10 :: moyo_clock:non_neg_milliseconds() 41 | }). 42 | 43 | %%---------------------------------------------------------------------------------------------------------------------- 44 | %% Functions 45 | %%---------------------------------------------------------------------------------------------------------------------- 46 | %% @doc 指定ポートに対して固定データを出力し続けるプロセスを生成する. 47 | %% 48 | %% `output_start(Port, fun (State) -> {ok, Data, State} end, InitialState, Options)'と等価なので、詳細はそちらのドキュメントを参照.
49 | -spec output_start(port(), Data, Options) -> OutputProcessId when 50 | Data :: iodata(), 51 | Options :: [output_option()], 52 | OutputProcessId :: pid(). 53 | output_start(Port, Data, Options) -> 54 | output_start(Port, fun (State) -> {ok, Data, State} end, undefined, Options). 55 | 56 | %% @doc 指定ポートに対してデータ出力を行い続けるプロセスを生成する. 57 | %% 58 | %% 生成されたプロセスは、ポートの実行終了に伴い、自動で終了する.
59 | %% また`DataGenerateFun'が`stop'を返した場合もプロセスは終了する (この際にポートの停止は行われない).
60 | -spec output_start(port(), DataGenerateFun, InitialState, Options) -> OutputProcessId when 61 | DataGenerateFun :: output_data_generate_fun(), 62 | InitialState :: term(), 63 | Options :: [output_option()], 64 | OutputProcessId :: pid(). 65 | output_start(Port, DataGenerateFun, InitialState, Options) -> 66 | case parse_output_options(Options) of 67 | {error, Reason} -> error({wrong_options, Reason}); 68 | {ok, Options2} -> 69 | OutputLoopState = 70 | #output_state{ 71 | port = Port, 72 | data_generate_state = InitialState, 73 | data_generate_fun = DataGenerateFun, 74 | interval = moyo_assoc:fetch(interval, Options2) 75 | }, 76 | spawn(fun () -> output_loop_start(OutputLoopState) end) 77 | end. 78 | 79 | %%---------------------------------------------------------------------------------------------------------------------- 80 | %% Inner Function 81 | %%---------------------------------------------------------------------------------------------------------------------- 82 | -spec output_loop_start(#output_state{}) -> no_return(). 83 | output_loop_start(State) -> 84 | true = link(State#output_state.port), 85 | output_loop(State). 86 | 87 | -spec output_loop(#output_state{}) -> no_return(). 88 | output_loop(State) -> 89 | receive 90 | Msg -> 91 | %% キューが溜まらないように、受信したメッセージは破棄する (そもそもメッセージ受信は意図していない) 92 | ok = error_logger:error_msg("unknown message: ~p~n", [Msg]), 93 | output_loop(State) 94 | after 0 -> 95 | %% ポートの標準入力にデータを送る処理 96 | #output_state{port = Port, interval = Interval, 97 | data_generate_state = GenState, data_generate_fun = GenFun} = State, 98 | case GenFun(GenState) of 99 | stop -> 100 | exit(normal); 101 | {ok, Data, GenState2} -> 102 | try 103 | true = port_command(Port, Data) 104 | catch 105 | error:badarg ?CAPTURE_STACKTRACE -> 106 | case erlang:port_info(Port) of 107 | undefined -> 108 | %% ポートが閉じている(コマンドの実行が終了している)ので、出力プロセスも終了 109 | exit(normal); 110 | _ -> 111 | erlang:raise(error, badarg, ?GET_STACKTRACE) 112 | end 113 | end, 114 | ok = timer:sleep(Interval), 115 | State2 = State#output_state{data_generate_state = GenState2}, 116 | output_loop(State2) 117 | end 118 | end. 119 | 120 | -spec parse_output_options([output_option()]) -> {ok, [output_option()]} | {error, Reason::term()}. 121 | parse_output_options(Options) -> 122 | Spec = [ 123 | {interval, {integer, [non_negative]}, [{default, 10}]} 124 | ], 125 | moyo_assoc:lookup_entries_as(Spec, Options). 126 | -------------------------------------------------------------------------------- /src/moyo_proc.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2013-2015 DWANGO Co., Ltd. All Rights Reserved. 2 | %% 3 | %% @doc プロセス関連の処理や型を集めたユーティリティモジュール 4 | -module(moyo_proc). 5 | 6 | %%---------------------------------------------------------------------------------------------------------------------- 7 | %% Exported API 8 | %%---------------------------------------------------------------------------------------------------------------------- 9 | -export_type([ 10 | otp_name/0, 11 | otp_ref/0 12 | ]). 13 | 14 | %%---------------------------------------------------------------------------------------------------------------------- 15 | %% Types 16 | %%---------------------------------------------------------------------------------------------------------------------- 17 | -type otp_name() :: {local, Name :: atom()} 18 | | {global, Name :: term()} 19 | | {via, module(), Name :: term()}. 20 | %% {@link gen_server:start_link/4}等に指定可能な起動プロセスの名前 21 | 22 | -type otp_ref() :: (Name :: atom()) 23 | | {Name :: atom(), node()} 24 | | {global, Name :: term()} 25 | | {via, module(), Name :: term()} 26 | | pid(). 27 | %% {@link gen_server:cast/2}等に指定可能な宛先プロセスの参照 28 | -------------------------------------------------------------------------------- /src/moyo_rpc.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2017 DWANGO Co., Ltd. All Rights Reserved. 2 | %% 3 | %% @doc ノード間のやりとりをするユーティリティ関数. 4 | -module(moyo_rpc). 5 | 6 | %%---------------------------------------------------------------------------------------------------------------------- 7 | %% Exported API 8 | %%---------------------------------------------------------------------------------------------------------------------- 9 | -export([is_function_callable/2, 10 | is_function_callable/3, 11 | function_exported/2, 12 | function_exported/3, 13 | ensure_loaded/2, 14 | ensure_loaded/3 15 | ]). 16 | 17 | %%---------------------------------------------------------------------------------------------------------------------- 18 | %% Exported Functions 19 | %%---------------------------------------------------------------------------------------------------------------------- 20 | 21 | %% @doc 指定したノードにおいて, 指定した関数が呼び出し可能かどうかを確認する. 22 | %% 23 | %% {@link function_exported/3}の場合, まだロードされていないモジュールの関数に対しては`{ok, false}'を返すため, 24 | %% その関数を呼び出せるかどうかの判定としては不十分である. 25 | %% そこで`is_function_callable/3'では, 指定したモジュールのロードを試みた後に, 26 | %% 指定した関数が存在するかどうかを確認する. これにより, 呼び出し可能かどうかを確実に判定することができる. 27 | %% 28 | %% @returns 29 | %% ノードとの通信に成功した場合, 指定された関数が呼び出し可能ならば`{ok, true}', 不可能ならば`{ok, false}'を返す. 30 | %% 31 | %% ノードとの通信に失敗した場合, `{error, {badrpc, BadRpcReason}}'を返す. 32 | %% `BadRpcReason'は`rpc:call/5'と同様のものとなる. 33 | %% 代表的なものは以下の通り: 34 | %%
35 | %%
nodedown
ノードが落ちている
36 | %%
timeout
タイムアウトした
37 | %%
38 | -spec is_function_callable(Node :: node(), MFA :: mfa(), Timeout :: integer() | infinity) -> 39 | {ok, boolean()} | {error, term()}. 40 | is_function_callable(Node, {Module, Func, Arity}, Timeout) -> 41 | case ensure_loaded(Node, Module, Timeout) of 42 | {error, {badrpc, Reason}} -> 43 | % RPCエラーはそのままエラーとする. 44 | {error, {badrpc, Reason}}; 45 | {error, _} -> 46 | % リモートノードでのensure_loadedに失敗して, モジュールがロードできていない場合には{ok, false}とする. 47 | {ok, false}; 48 | {ok, {module, Module}} -> 49 | % モジュールが元々ロードされていた場合や, ロードに成功した場合には, 関数がエクスポートされているか調べる. 50 | function_exported(Node, {Module, Func, Arity}, Timeout) 51 | end. 52 | 53 | %% @equiv is_function_callable(Node, MFA, infinity) 54 | -spec is_function_callable(Node :: node(), MFA :: mfa()) -> {ok, boolean()} | {error, nodedown | timeout | term()}. 55 | is_function_callable(Node, MFA) -> is_function_callable(Node, MFA, infinity). 56 | 57 | %% @doc 指定したノードに指定した関数が存在するかどうかを確認する. 58 | %% 59 | %% `erlang:function_exported/3'のRPC版. 60 | %% 61 | %% @returns 62 | %% ノードとの通信に成功した場合, 指定された関数が存在すれば`{ok, true}', 存在しなければ`{ok, false}'を返す. 63 | %% 64 | %% ノードとの通信に失敗した場合, `{error, {badrpc, BadRpcReason}}'を返す. 65 | %% `BadRpcReason'は`rpc:call/5'と同様のものとなる. 66 | -spec function_exported(Node :: node(), MFA :: mfa(), Timeout :: integer() | infinity) -> 67 | {ok, boolean()} | {error, term()}. 68 | function_exported(Node, {Module, Func, Arity}, Timeout) -> 69 | case rpc:call(Node, erlang, function_exported, [Module, Func, Arity], Timeout) of 70 | {badrpc, Reason} -> 71 | {error, {badrpc, Reason}}; 72 | Boolean -> 73 | {ok, Boolean} 74 | end. 75 | 76 | %% @equiv function_exported(Node, MFA, infinity) 77 | -spec function_exported(Node :: node(), MFA :: mfa()) -> {ok, boolean()} | {error, term()}. 78 | function_exported(Node, MFA) -> function_exported(Node, MFA, infinity). 79 | 80 | 81 | %% @doc 指定したノードにおいて, 指定したモジュールが確実にロードされるようにする. 82 | %% 83 | %% `code:ensure_loaded/1'のRPC版. 84 | %% 85 | %% @returns 86 | %% ノードとの通信に成功した場合, 元々モジュールがロードされていた場合や, 87 | %% モジュールのロードに成功した場合には`{ok, {module Module}}'を, 88 | %% 失敗すれば`{error, Reason}'を返す. 89 | %% `Reason'は`code:ensure_loaded/1'と同様のものとなる. 90 | %% 91 | %% ノードとの通信に失敗した場合, `{error, {badrpc, BadRpcReason}}'を返す. 92 | %% `BadRpcReason'は`rpc:call/5'と同様のものとなる. 93 | -spec ensure_loaded(Node :: node(), Module, Timeout :: integer() | infinity) -> 94 | {ok, {module, Module}} | {error, term()} when 95 | Module :: module(). 96 | ensure_loaded(Node, Module, Timeout) -> 97 | case rpc:call(Node, code, ensure_loaded, [Module], Timeout) of 98 | {badrpc, Reason} -> 99 | {error, {badrpc, Reason}}; 100 | {error, E} -> 101 | {error, E}; 102 | {module, Module} -> 103 | {ok, {module, Module}} 104 | end. 105 | 106 | %% @equiv ensure_loaded(Node, Module, infinity) 107 | -spec ensure_loaded(Node :: node(), Module :: module()) -> {ok, {module, Module}} | {error, term()} when 108 | Module :: module(). 109 | ensure_loaded(Node, Module) -> 110 | ensure_loaded(Node, Module, infinity). 111 | -------------------------------------------------------------------------------- /src/moyo_string.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2013-2014 DWANGO Co., Ltd. All Rights Reserved. 2 | %% 3 | %% @doc 文字列(整数値のリスト)に関する処理を集めたユーティリティモジュール. 4 | -module(moyo_string). 5 | 6 | %%---------------------------------------------------------------------------------------------------------------------- 7 | %% Exported API 8 | %%---------------------------------------------------------------------------------------------------------------------- 9 | -export([ 10 | to_string/1, 11 | to_string/2, 12 | 13 | format/2, 14 | 15 | is_ascii_string/1, 16 | is_iodata/1, 17 | is_iolist/1 18 | ]). 19 | 20 | %%---------------------------------------------------------------------------------------------------------------------- 21 | %% Exported Types 22 | %%---------------------------------------------------------------------------------------------------------------------- 23 | -export_type([ 24 | encode_option/0 25 | ]). 26 | 27 | %%---------------------------------------------------------------------------------------------------------------------- 28 | %% Types 29 | %%---------------------------------------------------------------------------------------------------------------------- 30 | -type float_format_option() :: {scientific, Decimals :: 0..249} 31 | | {decimals, Decimals :: 0..253} 32 | | compact. 33 | 34 | -type encode_option() :: print | {float_format, [float_format_option()]}. 35 | 36 | %%---------------------------------------------------------------------------------------------------------------------- 37 | %% Exported Functions 38 | %%---------------------------------------------------------------------------------------------------------------------- 39 | 40 | %% @equiv to_string(V, []) 41 | to_string(V) -> 42 | to_string(V, []). 43 | 44 | %% @doc Erlangの項を文字列(数値のリスト)に、指定されたオプションに従って変換する 45 | %% 46 | %% 入力値が非負の数値リストの場合は、変換は行われずにそのまま返される。
47 | %% ユニコード値のリストから、UTF-8のリストへ変換したい場合等は unicode モジュールを使用する必要がある。
48 | %% 49 | %% 入力値がタプルや深いリストならば `print' オプションを指定することで
50 | %% io_lib:format/2 のフォーマット`"~p"'に従った表現で変換することができる。
51 | %% デフォルト値は`"~w"'。
52 | %% 53 | %% 入力値が浮動小数点数ならば float_to_list/2 で指定できるオプション
54 | %% [{scientific, Decimals} | {decimals, Decimals} | compact]
55 | %% を利用して変換方式を指定することができる。
56 | %% {scientific, Decimals} と {decimals, Decimals} が同時に指定された場合は、最後に指定されたものが採用される。
57 | %% 例: 58 | %% ``` 59 | %% > moyo_string:to_string(12.34, [{float_format, [{scientific, 6}]}]). 60 | %% "1.234000e+01" 61 | %% > moyo_string:to_string(12.34, [{float_format, [{decimals, 6}]}]). 62 | %% "12.340000" 63 | %% > moyo_string:to_string(12.34, [{float_format, [{decimals, 6}, compact]}]). 64 | %% "12.34" 65 | %% ''' 66 | -spec to_string(term(), [encode_option()]) -> string(). 67 | to_string(V, []) -> 68 | to_string_impl(V, "~w"); 69 | to_string(V, [print | _]) -> 70 | to_string_impl(V, "~p"); 71 | to_string(V, [{float_format, FloatFormatOptions} | _]) when is_float(V) -> 72 | float_to_list(V, FloatFormatOptions); 73 | to_string(V, [_ | Rest]) -> 74 | to_string(V, Rest). 75 | 76 | %% @doc 指定されたフォーマットの文字列を生成して返す. 77 | %% 78 | %% `lists:flatten(io_lib:format(Format, Data))'と同等. 79 | -spec format(Format, Data) -> string() when 80 | Format :: io:format(), 81 | Data :: [term()]. 82 | format(Format, Data) -> 83 | lists:flatten(io_lib:format(Format, Data)). 84 | 85 | %% @doc 引数の値がASCII文字列であるかどうかを判定する 86 | %% 87 | %% ASCII文字列とは 0 から 127 までのコード値の文字で構成されている文字列のこと 88 | -spec is_ascii_string(Value::term()) -> boolean(). 89 | is_ascii_string(X) when is_list(X) -> lists:all(fun (C) -> is_integer(C) andalso C >= 0 andalso C =< 127 end, X); 90 | is_ascii_string(_) -> false. 91 | 92 | %% @doc 引数の値が`iolist'であるかどうかを判定する 93 | %% 94 | %% `iolist()'の型は `maybe_improper_list(byte() | binary() | iolist(), binary() | [])'.
95 | %% See: [http://www.erlang.org/doc/reference_manual/typespec.html] 96 | -spec is_iolist(Value::term()) -> boolean(). 97 | is_iolist([]) -> true; 98 | is_iolist([X]) -> is_iolist_element(X); 99 | is_iolist([H | T]) when is_list(T) -> is_iolist_element(H) andalso is_iolist(T); 100 | is_iolist([H | T]) -> is_iolist_element(H) andalso is_binary(T); 101 | is_iolist(_Other) -> false. 102 | 103 | %% @doc 引数の値が`iodata'であるかどうかを判定する 104 | %% 105 | %% `iodata()'の型は`binary() | iolist()'. 106 | -spec is_iodata(Value::term()) -> boolean(). 107 | is_iodata(X) -> is_binary(X) orelse is_iolist(X). 108 | 109 | %%---------------------------------------------------------------------------------------------------------------------- 110 | %% Internal Functions 111 | %%---------------------------------------------------------------------------------------------------------------------- 112 | 113 | %% @doc 引数の値が`iolist'の要素になり得るものかどうかを判定する 114 | -spec is_iolist_element(term()) -> boolean(). 115 | is_iolist_element(X) when is_binary(X) -> true; 116 | is_iolist_element(X) when is_integer(X) andalso (0 =< X andalso X =< 255) -> true; 117 | is_iolist_element(X) -> is_iolist(X). 118 | 119 | %% @private 120 | %% @doc Erlangの項を文字列(数値のリスト)に変換する 121 | -spec to_string_impl(term(), io:format()) -> string(). 122 | to_string_impl(V, _) when is_atom(V) -> atom_to_list(V); 123 | to_string_impl(V, _) when is_binary(V) -> binary_to_list(V); 124 | to_string_impl(V, _) when is_float(V) -> float_to_list(V); 125 | to_string_impl(V, _) when is_integer(V) -> integer_to_list(V); 126 | to_string_impl(V, _) when is_pid(V) -> pid_to_list(V); 127 | to_string_impl(V, _) when is_function(V) -> erlang:fun_to_list(V); 128 | to_string_impl(V, _) when is_port(V) -> erlang:port_to_list(V); 129 | to_string_impl(V, _) when is_reference(V) -> erlang:ref_to_list(V); 130 | to_string_impl(V, IoLibFormat) when is_list(V) -> 131 | IsNonNegInteger = fun (C) -> is_integer(C) andalso C >= 0 end, 132 | case lists:all(IsNonNegInteger, V) of 133 | true -> V; 134 | false -> format(IoLibFormat, [V]) 135 | end; 136 | to_string_impl(V, IoLibFormat) -> 137 | format(IoLibFormat, [V]). 138 | -------------------------------------------------------------------------------- /test/moyo_application_tests.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2013-2015 DWANGO Co., Ltd. All Rights Reserved. 2 | -module(moyo_application_tests). 3 | 4 | -include_lib("eunit/include/eunit.hrl"). 5 | 6 | %%---------------------------------------------------------------------------------------------------------------------- 7 | %% Unit Tests 8 | %%---------------------------------------------------------------------------------------------------------------------- 9 | ensure_loaded_test_() -> 10 | [ 11 | {"未ロードのアプリケーションをロードできる", 12 | fun () -> 13 | ok = ensure_unloaded(crypto), 14 | ?assertEqual(false, lists:keymember(crypto, 1, application:loaded_applications())), 15 | ?assertEqual(ok, moyo_application:ensure_loaded(crypto)) 16 | end}, 17 | {"ロード済みのアプリケーションが指定された場合は単に無視される", 18 | fun () -> 19 | ?assertEqual(true, lists:keymember(stdlib, 1, application:loaded_applications())), 20 | ?assertEqual({error, {already_loaded, stdlib}}, application:load(stdlib)), 21 | ?assertEqual(ok, moyo_application:ensure_loaded(stdlib)) 22 | end}, 23 | {"存在しないアプリケーションが指定された場合はエラーとなる", 24 | fun () -> 25 | ?assertMatch({error, _}, moyo_application:ensure_loaded('HOGE')) 26 | end} 27 | ]. 28 | 29 | 30 | ensure_all_loaded_test_() -> 31 | [ 32 | {"「依存しており」かつ「未ロード」のアプリケーションを全てロードする", 33 | fun () -> 34 | ?assertMatch({ok, _}, moyo_application:ensure_all_loaded(moyo)), 35 | 36 | ok = ensure_unloaded(moyo), 37 | ok = ensure_unloaded(crypto), 38 | ?assertEqual({ok, [crypto, moyo]}, moyo_application:ensure_all_loaded(moyo)) 39 | end}, 40 | {"存在しないアプリケーションが指定された場合はエラーとなる", 41 | fun () -> 42 | ?assertMatch({error, _}, moyo_application:ensure_all_loaded('HOGE')) 43 | end} 44 | ]. 45 | 46 | ensure_all_unloaded_test_() -> 47 | {ok, RE} = re:compile("^rebar.*$"), 48 | [ 49 | {"ロードされているアプリケーションを全てアンロードする.", 50 | fun () -> 51 | %% ロードしておく. 52 | ?assertEqual(ok, moyo_application:ensure_loaded(moyo)), 53 | ?assertEqual(ok, moyo_application:ensure_loaded(crypto)), 54 | %% 全てアンロードする(rebar関連以外). 55 | ?assertEqual(ok, moyo_application:ensure_all_unloaded(fun(Application) -> re:run(atom_to_list(Application), RE, [{capture, none}]) =/= match end)), 56 | %% 全てアンロードされている. 57 | Applications = application:loaded_applications(), 58 | ?assertEqual(false, lists:keymember(moyo, 1, Applications)), 59 | ?assertEqual(false, lists:keymember(crypto, 1, Applications)), 60 | %% リロードしておく. 61 | ?assertEqual(ok, moyo_application:ensure_loaded(moyo)) 62 | end}, 63 | {"ロードされている特定のアプリケーションをアンロードする.", 64 | fun () -> 65 | %% ロードしておく. 66 | ?assertEqual(ok, moyo_application:ensure_loaded(moyo)), 67 | ?assertEqual(ok, moyo_application:ensure_loaded(crypto)), 68 | %% `crypto'をアンロードする. 69 | ?assertEqual(ok, moyo_application:ensure_all_unloaded(fun(Application) -> Application =:= crypto end)), 70 | %% `crypto'がアンロードされている. 71 | Applications = application:loaded_applications(), 72 | ?assertEqual(true, lists:keymember(moyo, 1, Applications)), % `moyo'はアンロードされない. 73 | ?assertEqual(false, lists:keymember(crypto, 1, Applications)) 74 | end}, 75 | {"`kernel'と`stdlib'はアンロードされない.", 76 | fun () -> 77 | %% 全てアンロードする(rebar関連以外). 78 | ?assertEqual(ok, moyo_application:ensure_all_unloaded(fun(Application) -> re:run(atom_to_list(Application), RE, [{capture, none}]) =/= match end)), 79 | %% cryptoがアンロードされている. 80 | Applications = application:loaded_applications(), 81 | ?assertEqual(true, lists:keymember(kernel, 1, Applications)), 82 | ?assertEqual(true, lists:keymember(stdlib, 1, Applications)), 83 | %% リロードしておく. 84 | ?assertEqual(ok, moyo_application:ensure_loaded(moyo)) 85 | end}, 86 | {"アンロードに失敗した場合.", 87 | fun () -> 88 | ok = meck:new(application, [passthrough, unstick]), 89 | ok = meck:expect(application, unload, 1, {error, {running, moyo}}), 90 | ?assertEqual({error, {running, moyo}}, moyo_application:ensure_all_unloaded(fun(Application) -> Application =:= moyo end)), 91 | [application] = meck:unload() 92 | end} 93 | ]. 94 | 95 | get_key_test_() -> 96 | [ 97 | {"*.appに項目が存在する場合は`application:get_key/2'と同じ挙動となる", 98 | fun () -> 99 | Default = 'DEFAULT', 100 | ?assertMatch({ok, Value} when Value =/= Default, application:get_key(moyo, vsn)), % 存在する 101 | ?assertEqual({ok, moyo_application:get_key(moyo, vsn, Default)}, application:get_key(moyo, vsn)) 102 | end}, 103 | {"*.appに項目が存在しない場合は、デフォルト値が返される", 104 | fun () -> 105 | Default = 'DEFAULT', 106 | ?assertEqual(undefined, application:get_key(moyo, unexisting_key)), % 存在しない 107 | ?assertEqual(Default, moyo_application:get_key(moyo, unexisting_key, Default)) 108 | end} 109 | ]. 110 | 111 | get_priv_dir_test_() -> 112 | {setup, 113 | fun() -> _ = meck:new(application, [unstick, passthrough]) end, 114 | fun(_) -> _ = meck:unload() end, 115 | [ 116 | {"privディレクトリが存在する場合は`code:priv_dir/1`と同じ値を返す", 117 | fun () -> 118 | ?assertEqual({ok, code:priv_dir(crypto)}, moyo_application:get_priv_dir(crypto)) 119 | end}, 120 | {"アプリケーションが存在しない場合はエラー値が返される", 121 | fun () -> 122 | ?assertEqual({error, bad_name}, moyo_application:get_priv_dir(not_existing_app)) 123 | end}, 124 | {"アプリケーションが存在し、privディレクトリが存在しない場合は推測されたパスが返される", 125 | fun () -> 126 | ok = meck:expect(application, get_key, 2, {ok, [crypto]}), % 存在するモジュールを返す 127 | ?assertEqual({ok, code:priv_dir(crypto)}, moyo_application:get_priv_dir(app_without_priv_dir)) 128 | end} 129 | ]}. 130 | 131 | %%---------------------------------------------------------------------------------------------------------------------- 132 | %% Internal Functions 133 | %%---------------------------------------------------------------------------------------------------------------------- 134 | -spec ensure_unloaded(moyo_application:name()) -> ok | {error, Reason::term()}. 135 | ensure_unloaded(Application) -> 136 | case ensure_stopped(Application) of 137 | {error, Reason} -> {error, Reason}; 138 | ok -> 139 | case application:unload(Application) of 140 | {error, {not_loaded, _}} -> ok; 141 | Other -> Other 142 | end 143 | end. 144 | 145 | -spec ensure_stopped(moyo_application:name()) -> ok | {error, Reason::term()}. 146 | ensure_stopped(Application) -> 147 | case application:stop(Application) of 148 | {error, {not_started, _}} -> ok; 149 | Other -> Other 150 | end. 151 | -------------------------------------------------------------------------------- /test/moyo_char_tests.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2015 DWANGO Co., Ltd. All Rights Reserved. 2 | -module(moyo_char_tests). 3 | 4 | -include_lib("eunit/include/eunit.hrl"). 5 | 6 | %%---------------------------------------------------------------------------------------------------------------------- 7 | %% Unit Tests 8 | %%---------------------------------------------------------------------------------------------------------------------- 9 | 10 | is_alpha_test_() -> [ 11 | {"文字が英字か調べる", 12 | fun() -> 13 | Input = "aAzZTest", 14 | [?assert(moyo_char:is_alpha(X)) || X <- Input], 15 | Input2 = "09 +*_~", 16 | [?assert(not moyo_char:is_alpha(X)) || X <- Input2] 17 | end} 18 | ]. 19 | 20 | is_num_test_() -> [ 21 | {"文字が数字か調べる", 22 | fun() -> 23 | Input = "0123456789", 24 | [?assert(moyo_char:is_num(X)) || X <- Input], 25 | Input2 = "aAzZ +*_~", 26 | [?assert(not moyo_char:is_num(X)) || X <- Input2] 27 | end} 28 | ]. 29 | 30 | is_alpha_num_test_() -> [ 31 | {"文字が英数字か調べる", 32 | fun() -> 33 | Input = "aAzZ09Test42", 34 | [?assert(moyo_char:is_alpha_num(X)) || X <- Input], 35 | Input2 = " +*_~", 36 | [?assert(not moyo_char:is_alpha_num(X)) || X <- Input2] 37 | end} 38 | ]. 39 | -------------------------------------------------------------------------------- /test/moyo_concurrent_tests.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2013-2014 DWANGO Co., Ltd. All Rights Reserved. 2 | 3 | -module(moyo_concurrent_tests). 4 | 5 | -include_lib("../include/eunit.hrl"). 6 | 7 | -on_load(init/0). 8 | 9 | %%---------------------------------------------------------------------------------------------------------------------- 10 | %% Unit Tests 11 | %%---------------------------------------------------------------------------------------------------------------------- 12 | 13 | exec_test_() -> 14 | {spawn, 15 | [ 16 | {"timeoutを指定できる", 17 | fun() -> 18 | ?assertError(timeout, moyo_concurrent:exec([{timer, sleep, [infinity]}], 1)) 19 | end}, 20 | {"複数実行", 21 | fun() -> 22 | %% Input, Expected 23 | TestData = [{{lists, reverse, [[1,2,3]]}, [3,2,1]}, 24 | {{lists, reverse, [[3,4,5]]}, [5,4,3]}, 25 | {{lists, member, [1, [1,2,3]]}, true} 26 | ], 27 | Assoc = moyo_concurrent:exec([Input || {Input, _} <- TestData]), 28 | %% 結果が求める値になっているかのチェック 29 | [?assertEqual(Expected, moyo_assoc:fetch(Input, Assoc)) 30 | || {Input, Expected} <- TestData] 31 | end}, 32 | {"errorになるものがある場合", 33 | fun() -> 34 | Inputs = [{lists, reverse, [[1,2,3]]}, 35 | {erlang, error, [hoge]}], 36 | ?assertError(hoge, moyo_concurrent:exec(Inputs)) 37 | end}, 38 | {"throwになるものがある場合", 39 | fun() -> 40 | Inputs = [{lists, reverse, [[1,2,3]]}, 41 | {erlang, throw, [hoge]}], 42 | ?assertError({nocatch, hoge}, moyo_concurrent:exec(Inputs)) 43 | end}, 44 | {"exitになるものがある場合", 45 | fun() -> 46 | Inputs = [{lists, reverse, [[1,2,3]]}, 47 | {erlang, exit, [hoge]}], 48 | ?assertError(hoge, moyo_concurrent:exec(Inputs)) 49 | end} 50 | ]}. 51 | 52 | exec_sort_test_() -> 53 | {spawn, 54 | [ 55 | {"timeoutを指定できる", 56 | fun() -> 57 | ?assertError(timeout, moyo_concurrent:exec_sort([{timer, sleep, [infinity]}], 1)) 58 | end}, 59 | {"複数実行", 60 | fun() -> 61 | %% Input, Expected 62 | TestData = [{{lists, reverse, [[1,2,3]]}, [3,2,1]}, 63 | {{lists, reverse, [[3,4,5]]}, [5,4,3]}, 64 | {{lists, member, [1, [1,2,3]]}, true} 65 | ], 66 | %% 結果の一致 67 | ExpectedList = [Expected || {_, Expected} <- TestData], 68 | ?assertEqual(ExpectedList, moyo_concurrent:exec_sort([Input || {Input, _} <- TestData])) 69 | end}, 70 | {"errorになるものがある場合", 71 | fun() -> 72 | Inputs = [{lists, reverse, [[1,2,3]]}, 73 | {erlang, error, [hoge]}], 74 | ?assertError(hoge, moyo_concurrent:exec_sort(Inputs)) 75 | end}, 76 | {"throwになるものがある場合", 77 | fun() -> 78 | Inputs = [{lists, reverse, [[1,2,3]]}, 79 | {erlang, throw, [hoge]}], 80 | ?assertError({nocatch, hoge}, moyo_concurrent:exec_sort(Inputs)) 81 | end}, 82 | {"exitになるものがある場合", 83 | fun() -> 84 | Inputs = [{lists, reverse, [[1,2,3]]}, 85 | {erlang, exit, [hoge]}], 86 | ?assertError(hoge, moyo_concurrent:exec_sort(Inputs)) 87 | end} 88 | ]}. 89 | 90 | exec_map_test_() -> 91 | {spawn, 92 | [ 93 | {"timeoutを指定できる", 94 | fun() -> 95 | ?assertError(timeout, moyo_concurrent:exec([{timer, sleep, [infinity]}], 1)) 96 | end}, 97 | {"複数実行", 98 | fun() -> 99 | %% Input, Expected 100 | TestData = [{{lists, reverse, [[1,2,3]]}, [3,2,1]}, 101 | {{lists, reverse, [[3,4,5]]}, [5,4,3]}, 102 | {{lists, member, [1, [1,2,3]]}, true} 103 | ], 104 | Actual = moyo_concurrent:exec_map([Input || {Input, _} <- TestData]), 105 | ?assertEqual([E || {_, E} <- TestData], Actual) 106 | end}, 107 | {"Input が同じ場合", 108 | fun() -> 109 | %% Input, Expected 110 | TestData = [{{lists, reverse, [[a,b,c]]}, [c,b,a]}, 111 | {{lists, reverse, [[1,2,3]]}, [3,2,1]}, 112 | {{lists, reverse, [[1,2,3]]}, [3,2,1]}, 113 | {{lists, reverse, [[1,2,3]]}, [3,2,1]}, 114 | {{lists, reverse, [[1,2,3]]}, [3,2,1]}, 115 | {{lists, reverse, [[1,2,3]]}, [3,2,1]}, 116 | {{lists, reverse, [[1,2,3]]}, [3,2,1]}, 117 | {{lists, reverse, [[1,2,3]]}, [3,2,1]}, 118 | {{lists, reverse, [[1,2,3]]}, [3,2,1]}, 119 | {{lists, reverse, [[1,2,3]]}, [3,2,1]}, 120 | {{lists, reverse, [[x,y,z]]}, [z,y,x]}], 121 | Actual = moyo_concurrent:exec_map([Input || {Input, _} <- TestData]), 122 | ?assertEqual([E || {_, E} <- TestData], Actual) 123 | end}, 124 | {"error, throw, exit になるものがある場合", 125 | fun() -> 126 | Input = [{lists, reverse, [[1,2,3]]}, 127 | {lists, reverse, [[3,4,5]]}, 128 | {erlang, error, [hoge]}, 129 | {erlang, throw, [fuga]}, 130 | {erlang, exit, [piyo]}], 131 | Result = moyo_concurrent:exec_map(Input), 132 | %% order sensitive 133 | ?assertEqual([3, 2, 1], lists:nth(1, Result)), 134 | ?assertEqual([5, 4, 3], lists:nth(2, Result)), 135 | ?assertMatch2({'EXIT', {hoge, _}}, lists:nth(3, Result)), 136 | ?assertMatch2({'EXIT', {{nocatch, fuga}, _}}, lists:nth(4, Result)), 137 | ?assertMatch2({'EXIT', piyo}, lists:nth(5, Result)) 138 | end}, 139 | {"signal が突き抜けない", 140 | fun() -> 141 | Input = [{erlang, apply, [fun() -> 142 | spawn_link(fun() -> exit(self(), kill) end), 143 | ok 144 | end, []]}], 145 | Result = moyo_concurrent:exec_map(Input), 146 | ?assertMatch2([ok], Result) 147 | end} 148 | ]}. 149 | 150 | %%---------------------------------------------------------------------------------------------------------------------- 151 | %% Internal Functions 152 | %%---------------------------------------------------------------------------------------------------------------------- 153 | -spec init() -> ok. 154 | init() -> 155 | _ = error_logger:tty(false), % 実行時のノイズとなるので、ログ出力は抑制する 156 | ok. 157 | -------------------------------------------------------------------------------- /test/moyo_cond_tests.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2013-2014 DWANGO Co., Ltd. All Rights Reserved. 2 | -module(moyo_cond_tests). 3 | 4 | -include_lib("eunit/include/eunit.hrl"). 5 | 6 | apply_if_test_() -> 7 | [ 8 | {"条件節の値がtrueの場合は、THEN節で渡した関数が実行される", 9 | fun () -> 10 | ?assertEqual('then', moyo_cond:apply_if(true, fun () -> 'then' end, fun () -> 'else' end)) 11 | end}, 12 | {"条件節の値がfalseの場合は、ELSE節で渡した関数が実行される", 13 | fun () -> 14 | ?assertEqual('else', moyo_cond:apply_if(false, fun () -> 'then' end, fun () -> 'else' end)) 15 | end} 16 | ]. 17 | 18 | apply_when_test_() -> 19 | [ 20 | {"条件節の値がtrueの場合は、THEN節で渡した関数が実行される", 21 | fun () -> 22 | Msg = make_ref(), 23 | ?assertEqual(ok, moyo_cond:apply_when(true, fun () -> self() ! Msg end)), 24 | receive 25 | Msg -> ?assert(true) 26 | after 0 -> ?assert(false) 27 | end 28 | end}, 29 | {"条件節の値がfalseの場合は、何も処理は実行されない", 30 | fun () -> 31 | Msg = make_ref(), 32 | ?assertEqual(ok, moyo_cond:apply_when(false, fun () -> self() ! Msg end)), 33 | receive 34 | Msg -> ?assert(false) 35 | after 0 -> ?assert(true) 36 | end 37 | end} 38 | ]. 39 | 40 | apply_unless_test_() -> 41 | [ 42 | {"条件節の値がfalseの場合は、THEN節で渡した関数が実行される", 43 | fun () -> 44 | Msg = make_ref(), 45 | ?assertEqual(ok, moyo_cond:apply_unless(false, fun () -> self() ! Msg end)), 46 | receive 47 | Msg -> ?assert(true) 48 | after 0 -> ?assert(false) 49 | end 50 | end}, 51 | {"条件節の値がtrueの場合は、何も処理は実行されない", 52 | fun () -> 53 | Msg = make_ref(), 54 | ?assertEqual(ok, moyo_cond:apply_unless(true, fun () -> self() ! Msg end)), 55 | receive 56 | Msg -> ?assert(false) 57 | after 0 -> ?assert(true) 58 | end 59 | end} 60 | ]. 61 | 62 | conditional_test_() -> 63 | [ 64 | {"booleanに対応したintegerを返す", 65 | fun() -> 66 | A = 1, 67 | B = 2, 68 | ?assertEqual(A, moyo_cond:conditional(true, A, B)), 69 | ?assertEqual(B, moyo_cond:conditional(false, A, B)) 70 | end 71 | }, 72 | {"booleanに対応したlistを返す", 73 | fun() -> 74 | A = [1, 2, 3], 75 | B = [4, 5, 6], 76 | ?assertEqual(A, moyo_cond:conditional(true, A, B)), 77 | ?assertEqual(B, moyo_cond:conditional(false, A, B)) 78 | end 79 | }, 80 | {"booleanに対応したfunctionを返す", 81 | fun() -> 82 | A = fun (X) -> X * 2 end, 83 | B = fun (Y) -> Y * 2 end, 84 | ?assertEqual(A, moyo_cond:conditional(true, A, B)), 85 | ?assertEqual(B, moyo_cond:conditional(false, A, B)) 86 | end 87 | } 88 | ]. 89 | 90 | while_test_() -> 91 | [ 92 | {"数字の累進", 93 | fun() -> 94 | Fun = fun ({Cur, _, Dst}) when Cur > Dst -> {false, Cur}; 95 | ({Cur, Step, Dst}) -> {true, {Cur + Step, Step, Dst}} end, 96 | ?assertEqual(6, moyo_cond:while(Fun, {1, 1, 5})), 97 | ?assertEqual(22, moyo_cond:while(Fun, {10, 2, 20})) 98 | end 99 | }, 100 | {"別関数の実行", 101 | fun() -> 102 | FunA = fun ({_, Cur, Dst}) when Cur > Dst -> {false, Cur}; 103 | ({Fun, Cur, Dst}) -> {true, {Fun, Fun(Cur), Dst}} end, 104 | FunB = fun (Num) -> Num + 1 end, 105 | ?assertEqual(6, moyo_cond:while(FunA, {FunB, 1, 5})) 106 | end 107 | } 108 | ]. 109 | -------------------------------------------------------------------------------- /test/moyo_eunit_tests.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2013-2018 DWANGO Co., Ltd. All Rights Reserved. 2 | -module(moyo_eunit_tests). 3 | 4 | -include("eunit.hrl"). 5 | %% 従来であれば 6 | %% -include_lib("moyo/include/eunit.hrl"). 7 | 8 | moyo_eunit_test_() -> 9 | {foreach, 10 | fun() -> process_flag(trap_exit, false) end, 11 | fun(Old) -> process_flag(trap_exit, Old) end, 12 | [ 13 | {"assertMatch2が正しく動作する", 14 | fun() -> 15 | ?assertMatch2(_, hoge), 16 | ?assertMatch2(hoge, hoge), 17 | ?assertError({assertMatch_failed, _}, ?assertMatch2([], lists:seq(1,3))) 18 | end}, 19 | {"assertMatch2の引数に関数を入れた時、2回実行されない", 20 | fun() -> 21 | Fun = fun() -> self() ! ok end, 22 | ?assertMatch2(ok, Fun()), 23 | 24 | ReceiveCounter = fun Counter(Count) -> receive ok -> Counter(Count + 1) after 0 -> Count end end, 25 | ?assertEqual(1, ReceiveCounter(0)) 26 | end}, 27 | {"assertTerminatedが正常に実行できる", 28 | fun() -> 29 | _ = process_flag(trap_exit, true), 30 | Pid = spawn_link(fun() -> exit(abort) end), 31 | ?assertTerminated(Pid, abort) 32 | end}, 33 | {"assertTerminatedが2回使用できる", 34 | fun() -> 35 | _ = process_flag(trap_exit, true), 36 | 37 | Pid = spawn_link(fun() -> exit(abort) end), 38 | ?assertTerminated(Pid, abort), 39 | 40 | Pid2 = spawn_link(fun() -> exit(abort) end), 41 | ?assertTerminated(Pid2, abort) 42 | end}, 43 | {"assertDownが正常に実行できる", 44 | fun() -> 45 | {Pid, Ref} = spawn_monitor(fun() -> exit(abort) end), 46 | ?assertDown(Ref, abort), 47 | ?assertNot(is_process_alive(Pid)) 48 | end}, 49 | {"assertDownが2回使用できる", 50 | fun() -> 51 | {Pid, Ref} = spawn_monitor(fun() -> exit(abort) end), 52 | ?assertDown(Ref, abort), 53 | ?assertNot(is_process_alive(Pid)), 54 | 55 | {Pid2, Ref2} = spawn_monitor(fun() -> exit(abort) end), 56 | ?assertDown(Ref2, abort), 57 | ?assertNot(is_process_alive(Pid2)) 58 | end}, 59 | {"ensureExitedが正常に実行できる", 60 | fun() -> 61 | Pid = spawn(fun() -> timer:sleep(infinity) end), 62 | ?ensureExited(Pid), 63 | ?assertNot(is_process_alive(Pid)) 64 | end}, 65 | {"ensureExitedでPidに関数を入れても2回実行されない", 66 | fun() -> 67 | ?ensureExited(spawn_link(fun() -> ?assertEqual(true, register(moyo_eunit_ensure_exited_test, self())), timer:sleep(infinity) end)) 68 | end}, 69 | {"プロセスの終了理由が指定できる", 70 | fun() -> 71 | Pid = spawn(fun() -> timer:sleep(infinity) end), 72 | Monitor = monitor(process, Pid), 73 | ?ensureExited(Pid, hoge), 74 | receive 75 | {'DOWN', Monitor, _, _, Reason} -> ?assertEqual(hoge, Reason) 76 | after 0 -> ?assert(timeout) 77 | end 78 | end}, 79 | {"ensureExitedはlinkしていても親プロセスが落ちない", 80 | fun() -> 81 | Pid = spawn_link(fun() -> timer:sleep(infinity) end), 82 | ?ensureExited(Pid), 83 | ?assertNot(is_process_alive(Pid)) 84 | end}, 85 | {"ensureExitedが2回使用できる", 86 | fun() -> 87 | Pid = spawn(fun() -> timer:sleep(infinity) end), 88 | ?ensureExited(Pid), 89 | ?assertNot(is_process_alive(Pid)), 90 | 91 | Pid2 = spawn(fun() -> timer:sleep(infinity) end), 92 | ?ensureExited(Pid2), 93 | ?assertNot(is_process_alive(Pid2)) 94 | end}, 95 | {"assignMatchが正常に利用できる", 96 | fun() -> 97 | L = lists:seq(1, 5), 98 | ?assignMatch([H | _], L), 99 | ?assertEqual(H, 1), 100 | 101 | L2 = lists:seq(1, 0), 102 | ?assertError({assertMatch_failed, _}, ?assignMatch([_ | _], L2)) 103 | end}, 104 | {"assignMatchを使ったリテラル同士のマッチ", 105 | fun () -> 106 | ?assignMatch(1, 1), 107 | ?assignMatch(a, a), 108 | ?assignMatch(<<"a">>, <<"a">>), 109 | ?assignMatch([a], [a]), 110 | ?assignMatch({a}, {a}) 111 | end}, 112 | {"assignMatchが部分が単なる代入でなくても利用できる", 113 | fun() -> 114 | [1 | T] = lists:seq(1,5), 115 | ?assignMatch([1 | T], lists:seq(1,5)), 116 | ?assignMatch([1 | T2], lists:seq(1,5)), 117 | ?assertEqual(T, T2) 118 | end}, 119 | {"assignMatchの引数に関数を入れた時、2回実行されない", 120 | fun() -> 121 | Fun = fun() -> self() ! ok end, 122 | ?assignMatch(ok, Fun()), 123 | 124 | ReceiveCounter = fun Counter(Count) -> receive ok -> Counter(Count + 1) after 0 -> Count end end, 125 | ?assertEqual(1, ReceiveCounter(0)) 126 | end}, 127 | {"assignMatchで変数が既に束縛されているときも利用できる", 128 | fun() -> 129 | H = 1, 130 | L = lists:seq(1, 5), 131 | ?assignMatch([H | _], L) 132 | end}, 133 | {"自プロセスと指定のプロセスの間にリンクが貼られているかどうかを確認できる", 134 | fun () -> 135 | Pid = spawn(timer, sleep, [infinity]), 136 | ?assertError(_, ?assertLinked(Pid)), % not-linked, 137 | 138 | true = link(Pid), 139 | ?assertLinked(Pid) % linked 140 | end}, 141 | {"指定のプロセス間にリンクが貼られているかどうかを確認できる", 142 | fun () -> 143 | Parent = self(), 144 | 145 | Pid1 = spawn(timer, sleep, [infinity]), 146 | Pid2 = spawn(timer, sleep, [infinity]), 147 | ?assertError(_, ?assertLinked(Pid1, Pid2)), % not-linked, 148 | 149 | Pid3 = spawn(fun () -> 150 | true = link(Pid1), 151 | Parent ! linked, 152 | timer:sleep(infinity) 153 | end), 154 | receive linked -> ok end, 155 | ?assertLinked(Pid1, Pid3) % linked 156 | end}, 157 | {"assertContainが正しく動作する", 158 | fun() -> 159 | ?assertContain(1, [2,1,3]), 160 | ?assertError({assert, _}, ?assertContain(0, lists:map(fun(N)->N+1 end, [0,1,2]))), 161 | ?assertError({assert, _}, ?assertContain(0, hoge)) 162 | end} 163 | ]}. 164 | -------------------------------------------------------------------------------- /test/moyo_file_tests.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2013-2014 DWANGO Co., Ltd. All Rights Reserved. 2 | %% 3 | -module(moyo_file_tests). 4 | 5 | -include_lib("eunit/include/eunit.hrl"). 6 | 7 | delete_directory_test_() -> 8 | [ 9 | {"空のディレクトリを削除する", 10 | fun () -> 11 | Dir = moyo_file:make_temp_filepath(<<"moyo_file_test_">>), 12 | ok = file:make_dir(Dir), 13 | 14 | ?assert(filelib:is_dir(Dir)), 15 | ?assertEqual(ok, moyo_file:delete_directory(Dir, false)), 16 | ?assert(not filelib:is_dir(Dir)) 17 | end}, 18 | {"ディレクトリを再帰的に削除する", 19 | fun () -> 20 | Dir = make_dummy_directory(), 21 | 22 | %% 再帰フラグがfalseの場合は削除に失敗する 23 | ?assertMatch({error, eexist}, moyo_file:delete_directory(Dir, false)), 24 | 25 | ?assert(filelib:is_dir(Dir)), 26 | ?assertEqual(ok, moyo_file:delete_directory(Dir, true)), 27 | ?assert(not filelib:is_dir(Dir)) 28 | end}, 29 | {"ディレクトリ以外に適用した場合は失敗する", 30 | fun () -> 31 | Path = moyo_file:make_temp_filepath(<<"moyo_file_test_">>), 32 | ok = file:write_file(Path, <<"dummy data">>), 33 | 34 | ?assert(filelib:is_file(Path)), 35 | ?assertMatch({error, {not_directory, _}}, moyo_file:delete_directory(Path, false)), 36 | ?assertMatch({error, {not_directory, _}}, moyo_file:delete_directory(Path, true)), 37 | ?assert(filelib:is_file(Path)), 38 | 39 | ok = file:delete(Path) 40 | end} 41 | ]. 42 | 43 | delete_if_exists_test_() -> 44 | [ 45 | {"存在しないファイルを削除する", 46 | fun () -> 47 | %% examle.txt が存在しない状態にする 48 | case filelib:is_file("example.txt") of 49 | true -> ?assertEqual(ok, file:delete("example.txt")); 50 | false -> ok 51 | end, 52 | %% example.txt が存在しないけど失敗しない 53 | ?assertEqual(ok, moyo_file:delete_if_exists("example.txt")) 54 | end}, 55 | {"存在するファイルを削除する", 56 | fun () -> 57 | ?assertEqual(ok, file:write_file("example.txt", "foo")), 58 | ?assertEqual(ok, moyo_file:delete_if_exists("example.txt")) 59 | end} 60 | ]. 61 | 62 | get_disk_usage_test_() -> 63 | [ 64 | {"指定ディレクトリの容量を取得する", 65 | fun () -> 66 | Dir = make_dummy_directory(), 67 | ?assertMatch({ok, Size} when is_integer(Size), moyo_file:get_disk_usage(Dir)), 68 | ok = moyo_file:delete_directory(Dir, true) 69 | end}, 70 | {"指定ファイルのサイズを取得する", 71 | fun () -> 72 | Path = moyo_file:make_temp_filepath(), 73 | Data = <<"dummy data">>, 74 | ok = file:write_file(Path, Data), 75 | 76 | ?assertEqual({ok, byte_size(Data)}, moyo_file:get_disk_usage(Path)), 77 | 78 | ok = file:delete(Path) 79 | end}, 80 | {"指定ファイルが存在しない場合は、エラーではなくサイズ0扱いになる", 81 | fun () -> 82 | Path = moyo_file:make_temp_filepath(), 83 | ?assertEqual({ok, 0}, moyo_file:get_disk_usage(Path)) 84 | end} 85 | ]. 86 | 87 | get_disk_usage_async_test_() -> 88 | [ 89 | {"非同期でディスク容量を取得する", 90 | fun () -> 91 | Dir = make_dummy_directory(), 92 | Tag = disk_usage_result, 93 | 94 | %% 最初の呼び出しは常にokを返す 95 | ?assertEqual(ok, moyo_file:get_disk_usage_async(Dir, self(), Tag)), 96 | 97 | receive 98 | {disk_usage_result, Result} -> 99 | ?assertMatch({ok, Size} when is_integer(Size), Result) 100 | end, 101 | 102 | ok = moyo_file:delete_directory(Dir, true) 103 | end} 104 | ]. 105 | 106 | open_test_() -> 107 | [ 108 | {"成功 -> io_device", 109 | fun () -> 110 | ok = meck:new(file, [unstick, passthrough]), 111 | ok = meck:expect(file, open, 2, {ok, dummy_io_device}), 112 | 113 | ?assertEqual(dummy_io_device, moyo_file:open("example.txt", [read])), 114 | ?assert(meck:called(file, open, ["example.txt", [read]])), 115 | 116 | ok = meck:unload(file) 117 | end}, 118 | {"失敗 -> raise error", 119 | fun () -> 120 | ok = meck:new(file, [unstick, passthrough]), 121 | ok = meck:expect(file, open, 2, {error, some_reason}), 122 | 123 | ?assertError({failed_to_open_file, _, _}, moyo_file:open("example.txt", [read])), 124 | ?assert(meck:called(file, open, ["example.txt", [read]])), 125 | 126 | ok = meck:unload(file) 127 | end} 128 | ]. 129 | 130 | write_test_() -> 131 | [ 132 | {"成功 -> ok", 133 | fun () -> 134 | ok = meck:new(file, [unstick, passthrough]), 135 | ok = meck:expect(file, write, 2, ok), 136 | 137 | ?assertEqual(ok, moyo_file:write(dummy_io_device, "foo")), 138 | ?assert(meck:called(file, write, [dummy_io_device, "foo"])), 139 | 140 | ok = meck:unload(file) 141 | end}, 142 | {"失敗 -> エラー発生", 143 | fun () -> 144 | ok = meck:new(file, [unstick, passthrough]), 145 | ok = meck:expect(file, write, 2, {error, some_reason}), 146 | 147 | ?assertError({failed_to_write, _}, moyo_file:write(dummy_io_device, "foo")), 148 | ?assert(meck:called(file, write, [dummy_io_device, "foo"])), 149 | 150 | ok = meck:unload(file) 151 | end} 152 | ]. 153 | 154 | write_or_close_test_() -> 155 | [ 156 | {"成功 -> ok", 157 | fun () -> 158 | ok = meck:new(file, [unstick, passthrough]), 159 | ok = meck:expect(file, write, 2, ok), 160 | ok = meck:expect(file, close, 1, ok), 161 | 162 | ?assertEqual(ok, moyo_file:write_or_close(dummy_io_device, "foo")), 163 | ?assert(meck:called(file, write, [dummy_io_device, "foo"])), 164 | %% 成功したので file:close は呼ばれなかった 165 | ?assertNot(meck:called(file, close, [dummy_io_device])), 166 | 167 | ok = meck:unload(file) 168 | end}, 169 | {"失敗 -> file:close が呼ばれ write のエラー発生", 170 | fun () -> 171 | ok = meck:new(file, [unstick, passthrough]), 172 | ok = meck:expect(file, write, 2, {error, write_error_reason}), 173 | ok = meck:expect(file, close, 1, ok), 174 | 175 | ?assertError({failed_to_write, write_error_reason}, moyo_file:write_or_close(dummy_io_device, "foo")), 176 | ?assert(meck:called(file, write, [dummy_io_device, "foo"])), 177 | %% エラーを返す前に file:close もちゃんと呼ばれている 178 | ?assert(meck:called(file, close, [dummy_io_device])), 179 | 180 | ok = meck:unload(file) 181 | end}, 182 | {"write 失敗 -> file:close が呼ばれる -> file:closeも失敗した場合 -> write, close に関するエラー発生", 183 | fun () -> 184 | ok = meck:new(file, [unstick, passthrough]), 185 | ok = meck:expect(file, write, 2, {error, write_error_reason}), 186 | ok = meck:expect(file, close, 1, {error, close_error_reason}), 187 | 188 | ?assertError([{failed_to_write, write_error_reason}, {failed_to_close, close_error_reason}], 189 | moyo_file:write_or_close(dummy_io_device, "foo")), 190 | ?assert(meck:called(file, write, [dummy_io_device, "foo"])), 191 | %% エラーを返す前に file:close もちゃんと呼ばれている 192 | ?assert(meck:called(file, close, [dummy_io_device])), 193 | 194 | ok = meck:unload(file) 195 | end} 196 | ]. 197 | 198 | close_test_() -> 199 | [ 200 | {"成功 -> ok", 201 | fun () -> 202 | ok = meck:new(file, [unstick, passthrough]), 203 | ok = meck:expect(file, close, 1, ok), 204 | 205 | ?assertEqual(ok, moyo_file:close(dummy_io_device)), 206 | ?assert(meck:called(file, close, [dummy_io_device])), 207 | 208 | ok = meck:unload(file) 209 | end}, 210 | {"失敗 -> エラー発生", 211 | fun () -> 212 | ok = meck:new(file, [unstick, passthrough]), 213 | ok = meck:expect(file, close, 1, {error, some_reason}), 214 | 215 | ?assertError({failed_to_close, some_reason}, moyo_file:close(dummy_io_device)), 216 | ?assert(meck:called(file, close, [dummy_io_device])), 217 | 218 | ok = meck:unload(file) 219 | end} 220 | ]. 221 | 222 | 223 | %%---------------------------------------------------------------------------------------------------------------------- 224 | %% Internal Functions 225 | %%---------------------------------------------------------------------------------------------------------------------- 226 | make_dummy_directory() -> 227 | Dir = moyo_file:make_temp_filepath(<<"moyo_file_test_">>), 228 | ok = file:make_dir(Dir), 229 | ok = file:make_dir(filename:join(Dir, "a")), 230 | ok = file:make_dir(filename:join(Dir, "b")), 231 | ok = file:write_file(filename:join([Dir, "a", "c.txt"]), <<"dummy data">>), 232 | Dir. 233 | -------------------------------------------------------------------------------- /test/moyo_graph_tests.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2017 DWANGO Co., Ltd. All Rights Reserved. 2 | -module(moyo_graph_tests). 3 | 4 | -include_lib("moyo/include/eunit.hrl"). 5 | 6 | %%---------------------------------------------------------------------------------------------------------------------- 7 | %% Unit Tests 8 | %%---------------------------------------------------------------------------------------------------------------------- 9 | 10 | dfs_post_order_test_() -> 11 | [ 12 | {"単純な木構造をタプルに変換するテスト", 13 | fun () -> 14 | G = #{a => [b, c], b => [d], c => [], d => []}, 15 | F = fun(V, Cs) -> {vertex, V, children, Cs} end, 16 | ?assertEqual({vertex, a, children, [{vertex, b, children, [{vertex, d, children, []}]}, 17 | {vertex, c, children, []}]}, 18 | moyo_graph:dfs_post_order(G, a, F)), 19 | ?assertEqual({vertex, b, children, [{vertex, d, children, []}]}, 20 | moyo_graph:dfs_post_order(G, b, F)) 21 | end}, 22 | {"ループ時停止性の確認", 23 | fun () -> 24 | G = #{a => [b, c], b => [d], c => [], d => [a, b]}, 25 | F = fun(V, Cs) -> {vertex, V, children, Cs} end, 26 | ?assertEqual({vertex, a, children, [{vertex, b, children, [{vertex, d, children, []}]}, 27 | {vertex, c, children, []}]}, 28 | moyo_graph:dfs_post_order(G, a, F)), 29 | ?assertEqual({vertex, b, children, [{vertex, d, children, 30 | [{vertex, a, children, [{vertex, c, children, []}]}]}]}, 31 | moyo_graph:dfs_post_order(G, b, F)), 32 | ?assertEqual({vertex, c, children, []}, moyo_graph:dfs_post_order(G, c, F)), 33 | ?assertEqual({vertex, d, children, [{vertex, a, children, [{vertex, b, children, []}, 34 | {vertex, c, children, []}]}]}, 35 | moyo_graph:dfs_post_order(G, d, F)) 36 | end}, 37 | {"木ではない真性のDAGをタプルに変換するテスト", 38 | fun () -> 39 | G = #{a => [b, c], b => [d], c => [d], d => []}, 40 | F = fun (V, Cs) -> {vertex, V, children, Cs} end, 41 | ?assertEqual({vertex, a, children, [{vertex, b, children, [{vertex, d, children, []}]}, 42 | {vertex, c, children, []}]}, 43 | moyo_graph:dfs_post_order(G, a, F)) 44 | end}, 45 | {"存在しない頂点を引いた時のテスト", 46 | fun () -> 47 | G = #{a => [b, c], b => [d], c => [d], d => [xxx]}, 48 | F = fun (V, Cs) -> {vertex, V, children, Cs} end, 49 | ?assertEqual({error, {vertex_not_found, xxx}}, moyo_graph:dfs_post_order(G, a, F)), 50 | ?assertEqual({error, {vertex_not_found, yyy}}, moyo_graph:dfs_post_order(G, yyy, F)) 51 | end}, 52 | {"多重辺で重複した訪問が起らないことを確認するテスト", 53 | fun () -> 54 | G = #{a => [b, b, c], b => [d], c => [], d => []}, 55 | F = fun (V, Cs) -> {vertex, V, children, Cs} end, 56 | ?assertEqual({vertex, a, children, [{vertex, b, children, [{vertex, d, children, []}]}, 57 | {vertex, c, children, []}]}, 58 | moyo_graph:dfs_post_order(G, a, F)) 59 | end}, 60 | {"VertexFunがエラーを返した時のテスト", 61 | fun () -> 62 | G = #{a => [b, c], b => [d], c => [e], d => [], e => []}, 63 | F = fun (V, Cs) -> case V of c -> {error, {break, V, Cs}}; _ -> {vertex, V, children, Cs} end end, 64 | ?assertEqual({error, {break, c, [{vertex, e, children, []}]}}, moyo_graph:dfs_post_order(G, a, F)) 65 | end} 66 | ]. 67 | 68 | 69 | graph_with_vertices_test_() -> 70 | [ 71 | {"存在しない頂点が追加され、存在する頂点は変更されないことを確認するテスト", 72 | fun () -> 73 | ?assertEqual(#{a => [b, c], b => [c], c => [], d => []}, 74 | moyo_graph:graph_with_vertices(#{a => [b, c], b => [c], c => []}, [a, b, c, d])) 75 | end} 76 | ]. 77 | -------------------------------------------------------------------------------- /test/moyo_guard_tests.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2013-2015 DWANGO Co., Ltd. All Rights Reserved. 2 | %% 3 | %% @doc guard_macrosモジュールのユニットテスト 4 | -module(moyo_guard_tests). 5 | -include_lib("eunit/include/eunit.hrl"). 6 | -include("guard.hrl"). 7 | 8 | 9 | -define(checkGuard(Expr, Guard), 10 | ( 11 | fun(__TMP) -> 12 | case __TMP of 13 | __TMP2 when Guard(__TMP2) -> true; 14 | __TMP2 when not Guard(__TMP2) -> false 15 | end 16 | end 17 | )(Expr) 18 | ). 19 | 20 | is_alpha_test_() -> 21 | [ 22 | {"文字が英字か調べる", 23 | fun() -> 24 | Input = "AaQqZz", 25 | [?assert(?checkGuard(X, ?IS_ALPHA)) || X <- Input], 26 | Input2="19*; ", 27 | [?assert(not ?checkGuard(X, ?IS_ALPHA)) || X <- Input2] 28 | end 29 | } 30 | ]. 31 | 32 | is_num_test_() -> 33 | [ 34 | {"文字が数文字か調べる", 35 | fun() -> 36 | Input = "1234567890", 37 | [?assert(?checkGuard(X, ?IS_NUM)) || X <- Input], 38 | Input2="AaQqZz*; ", 39 | [?assert(not ?checkGuard(X, ?IS_NUM)) || X <- Input2] 40 | end 41 | } 42 | ]. 43 | 44 | is_alpha_num_test_() -> 45 | [ 46 | {"文字が英数字か調べる", 47 | fun() -> 48 | Input = "AaQqZz19", 49 | [?assert(?checkGuard(X, ?IS_ALPHA_NUM)) || X <- Input], 50 | Input2="*; ", 51 | [?assert(not ?checkGuard(X, ?IS_ALPHA_NUM)) || X <- Input2] 52 | end 53 | } 54 | ]. 55 | -------------------------------------------------------------------------------- /test/moyo_inet_tests.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2013-2015 DWANGO Co., Ltd. All Rights Reserved. 2 | -module(moyo_inet_tests). 3 | 4 | -include("eunit.hrl"). 5 | 6 | %%---------------------------------------------------------------------------------------------------------------------- 7 | %% Unit Tests 8 | %%---------------------------------------------------------------------------------------------------------------------- 9 | 10 | find_free_port_test_() -> 11 | {foreach, 12 | fun setup/0, 13 | fun clean/1, 14 | [ 15 | {"返されたポートでlistenができる", 16 | fun() -> 17 | ?assignMatch({ok, Port}, moyo_inet:find_free_port()), 18 | ?assignMatch({ok, Socket}, gen_tcp:listen(Port, [])), 19 | ?assertEqual(ok, gen_tcp:close(Socket)) 20 | end}, 21 | {"find_free_port/1では, `Count'個の相異なるポートが返り, 返されたすべてのポートでlistenができる", 22 | fun() -> 23 | Count = 10, 24 | ?assignMatch({ok, Ports}, moyo_inet:find_free_port(Count)), 25 | ?assertEqual(Count, length(Ports)), % `Count'個のポート 26 | ?assertEqual(Ports, moyo_list:uniq(Ports)), % 相異なるポート 27 | %% gen_tcp:close が呼ばれているか確かめることで, すべてのポートが 28 | %% 閉じられていることを確かめる (= すべてのポートでlistenができる) 29 | [?assert(meck:called(gen_tcp, close, [{socket, Port}])) || Port <- Ports] 30 | end}, 31 | {"find_free_port/1では, 空きポートの数が`Count'より小さい場合は`{error, system_limit}'が返る. 処理途中でlistenしたポートはすべて閉じられている.", 32 | fun() -> 33 | Exceeded = 11, 34 | ?assertEqual({error, system_limit}, moyo_inet:find_free_port(Exceeded)), % 空きポートは10まで 35 | %% gen_tcp:close が呼ばれているか確かめることで, すべてのポートが閉じられていることを確かめる 36 | [?assert(meck:called(gen_tcp, close, [{socket, Port}])) || Port <- lists:seq(1, 10)] 37 | end}, 38 | {"find_free_port/1では, `Count'に-1を指定しても空のリストが返る", 39 | fun() -> 40 | Count = -1, 41 | ?assertEqual({ok, []}, moyo_inet:find_free_port(Count)) 42 | end} 43 | ]}. 44 | 45 | %%---------------------------------------------------------------------------------------------------------------------- 46 | %% Internal Functions 47 | %%---------------------------------------------------------------------------------------------------------------------- 48 | 49 | %% gen_tcp:listen, gen_tcp:close, inet:port のスタブを作る 50 | %% 1~10 までのポートが空いていて, それ以上は system_limit 51 | -spec setup() -> ok. 52 | setup() -> 53 | ok = meck:new([gen_tcp, inet], [unstick]), 54 | Seq = meck:seq([{ok, {socket, Port}} || Port <- lists:seq(1, 10)] ++ [{error, system_limit}]), 55 | ok = meck:expect(gen_tcp, listen, 2, Seq), 56 | ok = meck:expect(gen_tcp, close, 1, ok), 57 | ok = meck:expect(inet, port, fun({socket, Port}) -> {ok, Port} end). 58 | 59 | -spec clean(term()) -> ok. 60 | clean(_) -> _ = meck:unload(). 61 | -------------------------------------------------------------------------------- /test/moyo_math_tests.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2013-2014 DWANGO Co., Ltd. All Rights Reserved. 2 | %% 3 | %% @doc moyo_mathモジュールのユニットテスト. 4 | -module(moyo_math_tests). 5 | 6 | -include_lib("eunit/include/eunit.hrl"). 7 | 8 | ceil_test_() -> 9 | [ 10 | {"1.0を切り上げ", 11 | ?_assertEqual(1, moyo_math:ceil(1.0))}, 12 | {"0.5を切り上げ", 13 | ?_assertEqual(1, moyo_math:ceil(0.5))}, 14 | {"0.0を切り上げ", 15 | ?_assertEqual(0, moyo_math:ceil(0.0))}, 16 | {"-0.5を切り上げ", 17 | ?_assertEqual(0, moyo_math:ceil(-0.5))}, 18 | {"-1.0を切り上げ", 19 | ?_assertEqual(-1, moyo_math:ceil(-1.0))}, 20 | {"整数をを切り上げ", 21 | ?_assertEqual(-2, moyo_math:ceil(-2))} 22 | ]. 23 | 24 | floor_test_() -> 25 | [ 26 | {"1.0を切り下げ", 27 | ?_assertEqual(1, moyo_math:floor(1.0))}, 28 | {"0.5を切り下げ", 29 | ?_assertEqual(0, moyo_math:floor(0.5))}, 30 | {"0.0を切り下げ", 31 | ?_assertEqual(0, moyo_math:floor(0.0))}, 32 | {"-0.5を切り下げ", 33 | ?_assertEqual(-1, moyo_math:floor(-0.5))}, 34 | {"-1.0を切り下げ", 35 | ?_assertEqual(-1, moyo_math:floor(-1.0))}, 36 | {"整数をを切り下げ", 37 | ?_assertEqual(-2, moyo_math:floor(-2))} 38 | ]. 39 | 40 | gcm_test_() -> 41 | [ 42 | {"最大公約数を求める", 43 | fun () -> 44 | Result = moyo_math:gcd(366, 12), 45 | ?assertEqual(6, Result) 46 | end}, 47 | 48 | {"約数を求める時片方が0", 49 | fun () -> 50 | ?assertEqual(1234, moyo_math:gcd(0, 1234)), 51 | ?assertEqual(5678, moyo_math:gcd(5678, 0)) 52 | end}, 53 | 54 | {"約数を求める時両方が0", 55 | fun () -> ?assertThrow(both_0_error, moyo_math:gcd(0, 0)) end}, 56 | 57 | {"約数を求める元の数が負", 58 | fun () -> 59 | %% 第一引数が負 60 | Result1 = moyo_math:gcd(-12, 9), 61 | ?assertEqual(3, Result1), 62 | 63 | %% 第二引数が負 64 | Result2 = moyo_math:gcd(45, -36), 65 | ?assertEqual(9, Result2), 66 | 67 | %% 第三引数が負 68 | Result2 = moyo_math:gcd(45, -36), 69 | Result3 = moyo_math:gcd(-72, -56), 70 | ?assertEqual(8, Result3) 71 | end} 72 | ]. 73 | 74 | pow_int_test_() -> 75 | [ 76 | {"aのn乗を返す", 77 | fun () -> 78 | %% nは整数 79 | ?assertEqual(1, moyo_math:pow_int(2, 0)), 80 | ?assertEqual(2, moyo_math:pow_int(2, 1)), 81 | ?assertEqual(4, moyo_math:pow_int(2, 2)), 82 | ?assertEqual(8, moyo_math:pow_int(2, 3)), 83 | ?assertEqual(16, moyo_math:pow_int(2, 4)), 84 | ?assertEqual(32, moyo_math:pow_int(2, 5)), 85 | ?assertEqual(3, moyo_math:pow_int(3, 1)), 86 | ?assertEqual(9, moyo_math:pow_int(3, 2)), 87 | ?assertEqual(78125, moyo_math:pow_int(5, 7)), 88 | ?assertEqual(96889010407, moyo_math:pow_int(7, 13)), 89 | ?assertEqual(3211838877954855105157369, moyo_math:pow_int(13, 22)), 90 | 91 | %% nは負数 92 | ?assertError(function_clause, moyo_math:pow_int(3, -1)), 93 | 94 | %% nはfloat型 95 | ?assertError(function_clause, moyo_math:pow_int(3, 0.5)) 96 | end} 97 | ]. 98 | 99 | div_test_() -> 100 | [ 101 | {"整数の除算を行い商と剰余を求める", 102 | fun () -> 103 | ?assertEqual({1, 0}, moyo_math:divmod(10, 10)), 104 | ?assertEqual({3, 1}, moyo_math:divmod(10, 3)), 105 | ?assertEqual({12499999887, 339506163}, moyo_math:divmod(12345678901234567890, 987654321)), 106 | ?assertEqual({-3, -1}, moyo_math:divmod(-10, 3)), 107 | ?assertEqual({-3, 1}, moyo_math:divmod(10, -3)), 108 | ?assertEqual({3, -1}, moyo_math:divmod(-10, -3)), 109 | ?assertEqual({0, 0}, moyo_math:divmod(0, 3)) 110 | end}, 111 | {"0除算を行うと例外が投げられる", 112 | fun () -> 113 | ?assertError(badarith, moyo_math:divmod(10, 0)), 114 | ?assertError(badarith, moyo_math:divmod(0, 0)) 115 | end} 116 | ]. 117 | 118 | random_sequence_test_() -> 119 | [ 120 | {"指定したタイプ毎にランダムの文字列を返し、毎回異なる", 121 | fun () -> 122 | rand:seed(exs64, {0, 0, 0}), 123 | ?assertEqual(<<"MHfDhiSQxxFVWKSBCoIr">>, moyo_math:random_sequence(20, [{symbol, alphabetical}])), 124 | ?assertEqual(<<"PsSmPLDJexwnbeqvSMzR">>, moyo_math:random_sequence(20, [{symbol, alphabetical}])), 125 | ?assertEqual(<<"15767488576692587821">>, moyo_math:random_sequence(20, [{symbol, numeric}])), 126 | ?assertEqual(<<"97958138928329877399">>, moyo_math:random_sequence(20, [{symbol, numeric}])), 127 | ?assertEqual(<<"DZmObX6ugXOwATd87YzG">>, moyo_math:random_sequence(20, [{symbol, alphanumeric}])), 128 | ?assertEqual(<<"cZGW3oKb4jzY6JANkWAE">>, moyo_math:random_sequence(20, [{symbol, alphanumeric}])) 129 | end}, 130 | {"指定した長さで指定した文字しか存在しない文字列を返す", 131 | fun () -> 132 | Length = 1000, 133 | ?assertMatch({_,[{0, Length}]}, re:run(moyo_math:random_sequence(Length, [{symbol, alphabetical}]), "[a-zA-Z]+")), 134 | ?assertMatch({_,[{0, Length}]}, re:run(moyo_math:random_sequence(Length, [{symbol, numeric}]), "[0-9]+")), 135 | ?assertMatch({_,[{0, Length}]}, re:run(moyo_math:random_sequence(Length, [{symbol, alphanumeric}]), "[a-zA-Z0-9]+")) 136 | end}, 137 | {"Symbolを省略し、デフォルト値alphabeticalで利用できる", 138 | fun () -> 139 | rand:seed(exs64, {0, 0, 0}), 140 | R1 = moyo_math:random_sequence(20, [{symbol, alphabetical}]), 141 | rand:seed(exs64, {0, 0, 0}), 142 | R2 = moyo_math:random_sequence(20), 143 | ?assertEqual(R1, R2) 144 | end} 145 | ]. 146 | -------------------------------------------------------------------------------- /test/moyo_monad_tests.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2013-2014 DWANGO Co., Ltd. All Rights Reserved. 2 | -module(moyo_monad_tests). 3 | 4 | -include_lib("eunit/include/eunit.hrl"). 5 | 6 | maybe_constructor_test_() -> 7 | [ 8 | {"maybe_val, maybe_funを作る", 9 | fun () -> 10 | ?assertEqual(nothing, moyo_monad:maybe_val(nothing)), 11 | ?assertEqual({just, 123}, moyo_monad:maybe_val(123)), 12 | ?assertEqual(nothing, moyo_monad:maybe_fun(nothing)), 13 | SomeFun = fun(A, B)->A+B end, 14 | ?assertEqual({just, SomeFun}, moyo_monad:maybe_fun(SomeFun)), 15 | ?assertError({invalid_function, _}, moyo_monad:maybe_fun(123)) 16 | end} 17 | ]. 18 | 19 | apply_maybe_test_() -> 20 | [ 21 | {"nothingを適用 -> default valueが返ってくる", 22 | fun () -> 23 | ?assertEqual(defaultValue, moyo_monad:apply_maybe(nothing, [1,2], defaultValue)) 24 | end}, 25 | {"関数を適用 -> 適用結果が返ってくる", 26 | fun () -> 27 | Add = fun(A, B) -> A+B end, 28 | ?assertEqual(3, moyo_monad:apply_maybe({just, Add}, [1,2], defaultValue)) 29 | end}, 30 | {"関数じゃないものを適用 -> error", 31 | fun () -> 32 | ?assertError({invalid_function, _}, moyo_monad:apply_maybe({just, 777}, [1,2], defaultValue)) 33 | end}, 34 | {"引数の個数が異なる関数を適用 -> error", 35 | fun () -> 36 | SomeFun = fun(A, B, C) -> A+B*C end, 37 | ?assertError({invalid_function, _}, moyo_monad:apply_maybe({just, SomeFun}, [1,2], defaultValue)) 38 | end}, 39 | {"{just,X}でもnothingでもないものを適用 -> error", 40 | fun () -> 41 | ?assertError({invalid_maybe, _}, moyo_monad:apply_maybe(888, [1,2], defaultValue)) 42 | end} 43 | ]. 44 | -------------------------------------------------------------------------------- /test/moyo_pipe_tests.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2013-2014 DWANGO Co., Ltd. All Rights Reserved. 2 | %% 3 | -module(moyo_pipe_tests). 4 | 5 | -include("eunit.hrl"). 6 | 7 | output_start_test_() -> 8 | [ 9 | {"ポートに固定データを出力し続ける", 10 | fun () -> 11 | Port = open_port({spawn, "cat"}, [binary]), 12 | Data = <<"pipe_data\n">>, 13 | 14 | _OutputPid = moyo_pipe:output_start(Port, Data, []), 15 | 16 | receive 17 | {Port, {data, RecvData}} -> 18 | ?assertMatch(<<"pipe_data\n", _/binary>>, RecvData) 19 | end 20 | end}, 21 | {"パイププロセスに送られたメッセージは単に捨てられる", 22 | fun () -> 23 | %% moyo_pipeが不正なメッセージを受け取った際に出す警告を抑制しておく 24 | error_logger:delete_report_handler(error_logger_tty_h), 25 | 26 | Port = open_port({spawn, "cat"}, [binary]), 27 | Data = <<"pipe_data\n">>, 28 | 29 | OutputPid = moyo_pipe:output_start(Port, Data, []), 30 | 31 | ?assertEqual({message_queue_len, 0}, erlang:process_info(OutputPid, message_queue_len)), 32 | 33 | OutputPid ! hello, 34 | OutputPid ! world, 35 | 36 | timer:sleep(5), % `OutputPid'がメッセージを処理するまで待機する (sleepで調整するのはあまり良くない処理) 37 | ?assertEqual({message_queue_len, 0}, erlang:process_info(OutputPid, message_queue_len)), % キューにメッセージは溜まらない 38 | 39 | receive 40 | {Port, {data, RecvData}} -> 41 | ?assertMatch(<<"pipe_data\n", _/binary>>, RecvData) 42 | end 43 | end}, 44 | {"ポートが閉じた場合は、パイププロセスも終了する", 45 | fun () -> 46 | Port = open_port({spawn, "cat"}, [binary, stderr_to_stdout]), 47 | Data = <<"pipe_data\n">>, 48 | 49 | OutputPid = moyo_pipe:output_start(Port, Data, []), 50 | Ref = monitor(process, OutputPid), 51 | timer:sleep(10), 52 | true = port_close(Port), 53 | ?assertDown(Ref, normal) 54 | end}, 55 | {"パイププロセスが(異常)終了した場合は、ポートも閉じる", 56 | fun () -> 57 | process_flag(trap_exit, true), 58 | 59 | Port = open_port({spawn, "cat"}, [binary, stderr_to_stdout]), 60 | Data = <<"pipe_data\n">>, 61 | 62 | OutputPid = moyo_pipe:output_start(Port, Data, []), 63 | timer:sleep(10), 64 | 65 | exit(OutputPid, kill), 66 | receive 67 | {'EXIT', Port, Reason} -> 68 | ?assertEqual(killed, Reason) 69 | end 70 | end} 71 | ]. 72 | -------------------------------------------------------------------------------- /test/moyo_rpc_tests.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2017 DWANGO Co., Ltd. All Rights Reserved. 2 | -module(moyo_rpc_tests). 3 | 4 | -include_lib("moyo/include/eunit.hrl"). 5 | 6 | %%---------------------------------------------------------------------------------------------------------------------- 7 | %% Unit Tests 8 | %%---------------------------------------------------------------------------------------------------------------------- 9 | 10 | -define(FUNCTION_EXPORTED_COMMON(NAME, F), 11 | NAME() -> 12 | [ 13 | {"確実にロードされているモジュールでのテスト", 14 | fun () -> 15 | ?assertEqual({ok, true}, 16 | F(node(), {moyo_rpc, function_exported, 2})), 17 | ?assertEqual({ok, true}, 18 | F(node(), {moyo_rpc, function_exported, 3})), 19 | ?assertEqual({ok, false}, 20 | F(node(), {moyo_rpc, function_exported, 0})) 21 | end}, 22 | {"存在しないノードに対するテスト", 23 | fun () -> 24 | ?assertEqual({error, {badrpc, nodedown}}, 25 | F(unknown_node_name, {moyo_rpc, function_exported, 2})) 26 | end}, 27 | {"存在しないモジュールに対するテスト", 28 | fun () -> 29 | ?assertEqual({ok, false}, 30 | F(node(), {monyo, monyo, 0})) 31 | end} 32 | ]). 33 | 34 | -define(ENSURE_LOADED_COMMON(NAME, F), 35 | NAME() -> 36 | [ 37 | {"確実にロードされているモジュールでのテスト", 38 | fun () -> 39 | ?assertEqual({ok, {module, moyo_rpc}}, 40 | F(node(), moyo_rpc)) 41 | end}, 42 | {"存在しないノードに対するテスト", 43 | fun () -> 44 | ?assertEqual({error, {badrpc, nodedown}}, 45 | F(unknown_node_name, moyo_rpc)) 46 | end}, 47 | {"存在しないモジュールに対するテスト", 48 | fun () -> 49 | ?assertEqual({error, nofile}, 50 | F(node(), monyo)) 51 | end} 52 | ]). 53 | 54 | ?FUNCTION_EXPORTED_COMMON(function_exported_2_test_, moyo_rpc:function_exported). 55 | ?FUNCTION_EXPORTED_COMMON(function_exported_3_test_, function_exported_3_stub). 56 | ?ENSURE_LOADED_COMMON(ensure_loaded_2_test_, moyo_rpc:ensure_loaded). 57 | ?ENSURE_LOADED_COMMON(ensure_loaded_3_test_, ensure_loaded_3_stub). 58 | ?FUNCTION_EXPORTED_COMMON(is_function_callable_2_test_, moyo_rpc:is_function_callable). 59 | ?FUNCTION_EXPORTED_COMMON(is_function_callable_3_test_, is_function_callable_3_stub). 60 | 61 | function_exported_3_stub(N, MFA) -> moyo_rpc:function_exported(N, MFA, infinity). 62 | ensure_loaded_3_stub(N, M) -> moyo_rpc:ensure_loaded(N, M, infinity). 63 | is_function_callable_3_stub(N, MFA) -> moyo_rpc:is_function_callable(N, MFA, infinity). 64 | -------------------------------------------------------------------------------- /test/moyo_string_tests.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2013-2014 DWANGO Co., Ltd. All Rights Reserved. 2 | %% 3 | %% @doc moyo_stringモジュールのユニットテスト 4 | -module(moyo_string_tests). 5 | 6 | -include_lib("eunit/include/eunit.hrl"). 7 | 8 | to_string_test_() -> 9 | [ 10 | {"バイナリが文字列に変換できる", 11 | fun () -> 12 | Input = <<"hello world">>, 13 | Expected = "hello world", 14 | ?assertEqual(Expected, moyo_string:to_string(Input)) 15 | end}, 16 | {"アトムが文字列に変換できる", 17 | fun () -> 18 | Input = hello_world, 19 | Expected = "hello_world", 20 | ?assertEqual(Expected, moyo_string:to_string(Input)) 21 | end}, 22 | {"整数が文字列に変換できる", 23 | fun () -> 24 | Input = 1234, 25 | Expected = "1234", 26 | ?assertEqual(Expected, moyo_string:to_string(Input)) 27 | end}, 28 | {"浮動小数点数が文字列に変換できる", 29 | fun () -> 30 | Input = 12.34, 31 | Expected = "1.23399999999999998579e+01", 32 | ?assertEqual(Expected, moyo_string:to_string(Input)) 33 | end}, 34 | {"PIDが文字列に変換できる", 35 | fun () -> 36 | Input = c:pid(0,1,0), 37 | Expected = "<0.1.0>", 38 | ?assertEqual(Expected, moyo_string:to_string(Input)) 39 | end}, 40 | {"非負の整数値リストは文字列として扱われる", 41 | fun () -> 42 | ?assertEqual([1,2,3], moyo_string:to_string([1,2,3])), 43 | ?assertEqual([10000000], moyo_string:to_string([10000000])) 44 | end}, 45 | {"負数が混じったリストは文字列として扱われない", 46 | fun () -> 47 | Input = [1, -2, 3], 48 | Expected = "[1,-2,3]", 49 | ?assertEqual(Expected, moyo_string:to_string(Input)) 50 | end}, 51 | {"整数以外が混じったリストは文字列として扱われない", 52 | fun () -> 53 | Input = [1, one, 3], 54 | Expected = "[1,one,3]", 55 | ?assertEqual(Expected, moyo_string:to_string(Input)) 56 | end}, 57 | {"複雑なデータ構造も文字列に変換可能", 58 | fun () -> 59 | Input1 = [{1,2,3}, c:pid(0,1,0), [[[[[<<"abc">>]], {hello}]]]], 60 | Expected1 = "[{1,2,3},<0.1.0>,[[[[[<<97,98,99>>]],{hello}]]]]", 61 | ?assertEqual(Expected1, moyo_string:to_string(Input1)), 62 | 63 | Input2 = {{1,2,3}, c:pid(0,1,0), [[[[[<<"abc">>]], {hello}]]]}, 64 | Expected2 = "{{1,2,3},<0.1.0>,[[[[[<<97,98,99>>]],{hello}]]]}", 65 | ?assertEqual(Expected2, moyo_string:to_string(Input2)) 66 | end}, 67 | {"複雑なデータ構造をプリント表現に変換可能", 68 | fun() -> 69 | Input1 = [{1,2,3}, c:pid(0,1,0), [[[[[<<"abc">>]], {hello}]]]], 70 | Expected1 = "[{1,2,3},<0.1.0>,[[[[[<<\"abc\">>]],{hello}]]]]", 71 | ?assertEqual(Expected1, moyo_string:to_string(Input1, [print])), 72 | 73 | Input2 = {{1,2,3}, c:pid(0,1,0), [[[[[<<"abc">>]], {hello}]]]}, 74 | Expected2 = "{{1,2,3},<0.1.0>,[[[[[<<\"abc\">>]],{hello}]]]}", 75 | ?assertEqual(Expected2, moyo_string:to_string(Input2, [print])) 76 | end}, 77 | {"関数を文字列に変換", 78 | fun () -> 79 | Input = fun () -> ok end, 80 | ?assert(is_list(moyo_string:to_string(Input))) % 具体的な文字列表現は環境依存なのでテストしない 81 | end}, 82 | {"ポートを文字列に変換", 83 | fun () -> 84 | Input = open_port({spawn, "ls"}, []), 85 | ?assert(is_list(moyo_string:to_string(Input))) % 具体的な文字列表現は環境依存なのでテストしない 86 | end}, 87 | {"リファレンスを文字列に変換", 88 | fun () -> 89 | Input = make_ref(), 90 | ?assert(is_list(moyo_string:to_string(Input))) % 具体的な文字列表現は環境依存なのでテストしない 91 | end}, 92 | {"浮動小数点数が文字列に変換できる: 有効数字6桁の指数形式", 93 | fun () -> 94 | Input = 12.34, 95 | Expected = "1.234000e+01", 96 | ?assertEqual(Expected, moyo_string:to_string(Input, [{float_format, [{scientific, 6}]}])) 97 | end}, 98 | {"浮動小数点数が文字列に変換できる: 小数点以下6桁の非指数形式", 99 | fun () -> 100 | Input = 12.34, 101 | Expected = "12.340000", 102 | ?assertEqual(Expected, moyo_string:to_string(Input, [{float_format, [{decimals, 6}]}])) 103 | end}, 104 | {"浮動小数点数が文字列に変換できる: 小数点以下6桁未満(0は可能な限り省略)の非指数形式", 105 | fun () -> 106 | Input = 12.34, 107 | Expected = "12.34", 108 | ?assertEqual(Expected, moyo_string:to_string(Input, [{float_format, [{decimals, 6},compact]}])) 109 | end}, 110 | {"浮動小数点数用オプションを指定しても無視して文字列に変換できる", 111 | fun () -> 112 | Input = {1, 2, 3}, 113 | Expected = "{1,2,3}", 114 | ?assertEqual(Expected, moyo_string:to_string(Input, [{float_format, [{decimals, 6},compact]}])) 115 | end} 116 | ]. 117 | 118 | format_test_() -> 119 | [ 120 | {"io_lib:format/2 の出力を文字列に変換", 121 | fun () -> 122 | Output = moyo_string:format("~p + ~p = ~p", [1 , 2, 1 + 2]), 123 | ?assertEqual("1 + 2 = 3", Output) 124 | end} 125 | ]. 126 | 127 | is_iolist_test_() -> 128 | [ 129 | {"空リストは iolist() と判定される", 130 | fun () -> 131 | Input = [], 132 | ?assert(moyo_string:is_iolist(Input)) 133 | end}, 134 | {"ASCII文字列は iolist() と判定される", 135 | fun () -> 136 | Input = "test", 137 | ?assert(moyo_string:is_iolist(Input)) 138 | end}, 139 | {"バイナリは iolist() とは判定されない", 140 | fun () -> 141 | Input = <<"test">>, 142 | ?assert(not moyo_string:is_iolist(Input)) 143 | end}, 144 | {"バイナリを含むリストは iolist() と判定される", 145 | fun () -> 146 | Input = [<<"test">>, <<"test">>], 147 | ?assert(moyo_string:is_iolist(Input)) 148 | end}, 149 | {"末尾要素がバイナリの場合は、真リストではなくても良い", 150 | fun () -> 151 | Input = [<<"test">> | <<"test">>], 152 | ?assert(moyo_string:is_iolist(Input)), 153 | 154 | %% 末尾が文字だとダメ 155 | ?assert(not moyo_string:is_iolist([<<"test">> | $a])) 156 | end}, 157 | {"文字列とバイナリの混合は許可される", 158 | fun () -> 159 | Input = [<<"test">>, "test"], 160 | ?assert(moyo_string:is_iolist(Input)) 161 | end}, 162 | {"iolist()は、iolist()を要素として持つことができる", 163 | fun () -> 164 | Input = [<<"test">>, ["test", [<<"test">>], "test" | <<"test">>], "test"], 165 | ?assert(moyo_string:is_iolist(Input)) 166 | end}, 167 | {"ユニコード文字列は iolist() とは判定されない", 168 | fun () -> 169 | Input = [28450,23383], % "漢字" 170 | ?assert(not moyo_string:is_iolist(Input)) 171 | end}, 172 | {"UTF-8文字列は iolist() と判定される", 173 | fun () -> 174 | Input = binary_to_list(unicode:characters_to_binary([28450,23383])), % "漢字" 175 | ?assert(moyo_string:is_iolist(Input)) 176 | end} 177 | ]. 178 | 179 | is_iodatat_test_() -> 180 | %% NOTE: is_iodatat_test_/0 との差分のみ実施 181 | [ 182 | {"バイナリは iodata() とは判定される", 183 | fun () -> 184 | Input = <<"test">>, 185 | ?assert(moyo_string:is_iodata(Input)) 186 | end} 187 | ]. 188 | 189 | is_ascii_string_test_() -> 190 | [ 191 | {"ASCII文字列かどうかを判定する", 192 | fun () -> 193 | ?assert(moyo_string:is_ascii_string("abc_123#!")), % ASCII文字列 194 | ?assert(not moyo_string:is_ascii_string("日本語")), % UTF-8文字列 (非ASCII) 195 | ?assert(not moyo_string:is_ascii_string(123)) % 文字列ではない 196 | end} 197 | ]. 198 | -------------------------------------------------------------------------------- /test/moyo_xml_tests.erl: -------------------------------------------------------------------------------- 1 | %% @copyright 2013-2014 DWANGO Co., Ltd. All Rights Reserved. 2 | %% 3 | %% @doc moyo_xmlモジュールのユニットテスト 4 | -module(moyo_xml_tests). 5 | 6 | -include_lib("eunit/include/eunit.hrl"). 7 | 8 | -define(UTF8(Chars), unicode:characters_to_binary(Chars)). 9 | 10 | parse_binary_test_() -> 11 | [ 12 | {"空要素のみの場合のパース", 13 | fun () -> 14 | Input = <<"">>, 15 | Expected = {test, [], []}, 16 | 17 | ?assertEqual({Expected, <<>>}, 18 | moyo_xml:parse_binary(Input, [{key_type,atom}])) 19 | end}, 20 | {"属性を含む場合のパース", 21 | fun () -> 22 | Input = <<"">>, 23 | Expected = {test, 24 | [{attr1, <<"10">>}, 25 | {attr2, <<"20">>}], 26 | []}, 27 | 28 | ?assertEqual({Expected, <<>>}, 29 | moyo_xml:parse_binary(Input, [{key_type,atom}])) 30 | end}, 31 | {"テキストを含む場合のパース", 32 | fun () -> 33 | Input = ?UTF8("テキスト"), 34 | Expected = {test, 35 | [{attr, ?UTF8("属性値")}], 36 | [?UTF8("テキスト")]}, 37 | 38 | ?assertEqual({Expected, <<>>}, 39 | moyo_xml:parse_binary(Input, [{key_type,atom}])) 40 | end}, 41 | {"子要素を含む場合のパース", 42 | fun () -> 43 | Input = ?UTF8("テキスト子テキスト"), 44 | Expected = {test, 45 | [{attr, ?UTF8("属性値")}], 46 | [ 47 | ?UTF8("テキスト"), 48 | {child, [], [?UTF8("子テキスト")]} 49 | ]}, 50 | 51 | ?assertEqual({Expected, <<>>}, 52 | moyo_xml:parse_binary(Input, [{key_type,atom}])) 53 | end}, 54 | {"改行やコメントは無視される", 55 | fun () -> 56 | Input = ?UTF8(" 57 | 58 | 子テキスト 59 | "), 60 | Expected = {test, 61 | [], 62 | [ 63 | {child, [], [?UTF8("子テキスト")]} 64 | ]}, 65 | 66 | ?assertEqual({Expected, <<>>}, 67 | moyo_xml:parse_binary(Input, [{key_type,atom}])) 68 | end}, 69 | {"入力データの途中でXMLのパースが終了した場合は、残り(未パース)のデータが返り値に含まれる", 70 | fun () -> 71 | Input = ?UTF8("テキスト次のXML"), 72 | Expected = {test, 73 | [], 74 | [ 75 | ?UTF8("テキスト") 76 | ]}, 77 | RestText = ?UTF8("次のXML"), 78 | 79 | ?assertEqual({Expected, RestText}, 80 | moyo_xml:parse_binary(Input, [{key_type,atom}])) 81 | end}, 82 | {"デフォルトでは、要素名および属性名の型はバイナリとなる", 83 | fun () -> 84 | Input = <<"">>, 85 | Expected = {<<"test">>, 86 | [{<<"attr">>, <<"10">>}], 87 | []}, 88 | 89 | %% key_typeの指定なし 90 | ?assertEqual({Expected, <<>>}, 91 | moyo_xml:parse_binary(Input, [])), 92 | 93 | %% 値に binary を指定しても同様 94 | ?assertEqual({Expected, <<>>}, 95 | moyo_xml:parse_binary(Input, [{key_type, binary}])) 96 | end}, 97 | {"key_typeオプションに atom を指定すると、要素名および属性名の型がアトムとなる", 98 | fun () -> 99 | Input = <<"">>, 100 | Expected = {test, 101 | [{attr, <<"10">>}], 102 | []}, 103 | 104 | ?assertEqual({Expected, <<>>}, 105 | moyo_xml:parse_binary(Input, [{key_type, atom}])) 106 | end}, 107 | {"key_typeオプションに existing_atom を指定すると、要素名および属性名が既にアトムとして存在する場合は、型がアトムとなる", 108 | fun () -> 109 | Input = ?UTF8(""), 110 | Expected = {test, 111 | [{?UTF8("アトムとして存在しない属性名"), <<"10">>}], 112 | []}, 113 | 114 | ?assertEqual({Expected, <<>>}, 115 | moyo_xml:parse_binary(Input, [{key_type, existing_atom}])) 116 | end}, 117 | {"XMLとして不正な文字列を渡した場合はパースに失敗する", 118 | fun () -> 119 | Input = ?UTF8("閉じタグの要素名が異なっている"), 120 | 121 | ?assertError(_, moyo_xml:parse_binary(Input, [])) 122 | end}, 123 | {"外部エンティティが許可されていない場合は例外を吐く", 124 | fun () -> 125 | Input = <<"]> &hoge;\n">>, 126 | 127 | ?assertError({parse_xml_failed, {error, _, "external_entity_not_allowed : hoge", _, _}, _}, 128 | moyo_xml:parse_binary(Input, [{allow_external_entity, false}])) 129 | end} 130 | ]. 131 | 132 | parse_file_test_() -> 133 | [ 134 | {"XMLファイルをパースする", 135 | fun () -> 136 | Expected = {test, 137 | [{attr, ?UTF8("属性値")}], 138 | [ 139 | ?UTF8("テキスト"), 140 | {child, [], [?UTF8("子テキスト")]} 141 | ]}, 142 | 143 | ?assertEqual({Expected, <<>>}, 144 | moyo_xml:parse_file( 145 | filename:dirname(?FILE) ++ "/testdata/moyo_xml/test.xml", 146 | [{key_type, atom}])) 147 | end} 148 | ]. 149 | 150 | to_iolist_test_() -> 151 | [ 152 | {"xml()型のデータをiolistに変換する", 153 | fun () -> 154 | Xml = {test, 155 | [{<<"attr1">>, ?UTF8("値")}, 156 | {attr2, 1}, 157 | {attr3, val}], 158 | [ 159 | ?UTF8("テキスト"), 160 | {<<"child">>, [], [there, <<" are ">>, 2, [" ", "pens"]]}, 161 | ?UTF8("数値"), 162 | {<<"float">>, [], [12.34]} 163 | ]}, 164 | Expected1 = ?UTF8("テキストthere are 2 pens数値1.23399999999999998579e+01"), 165 | Expected2 = ?UTF8("テキストthere are 2 pens数値12.34"), 166 | ?assertEqual(Expected1, list_to_binary(moyo_xml:to_iolist(Xml))), 167 | ?assertEqual(Expected2, list_to_binary(moyo_xml:to_iolist(Xml, [{float_format, [{decimals, 6}, compact]}]))) 168 | end}, 169 | {"変換に失敗した場合は例外が送出される", 170 | fun () -> 171 | %% 不正な入力の一例 172 | Xml1 = {test, "wrong attributes", []}, % 属性の形式が不正 173 | ?assertError({invalid_xml_attribute, _}, moyo_xml:to_iolist(Xml1)), 174 | 175 | Xml2 = {1, [], []}, % 要素名の型が不正 176 | ?assertError({invalid_xml_element_name, _}, moyo_xml:to_iolist(Xml2)), 177 | 178 | Xml3 = {test, [{1, 2}], []}, % 属性名の型が不正 179 | ?assertError({invalid_xml_attribute_name, _}, moyo_xml:to_iolist(Xml3)) 180 | end} 181 | ]. 182 | -------------------------------------------------------------------------------- /test/testdata/moyo_command/exit_1.sh: -------------------------------------------------------------------------------- 1 | exit 1 2 | -------------------------------------------------------------------------------- /test/testdata/moyo_command/stderr.sh: -------------------------------------------------------------------------------- 1 | echo stderr 1>&2 2 | -------------------------------------------------------------------------------- /test/testdata/moyo_xml/test.xml: -------------------------------------------------------------------------------- 1 | 2 | テキスト子テキスト 3 | --------------------------------------------------------------------------------