sphinxcontrib-xlsxtableの画像対応
Excelファイルから表を作るSphinx拡張について、埋め込み画像に対応しました。
特にオプションなどは必要なく、画像が使用されたExcelファイルを指定すれば、そのまま画像が利用されます。
.. xlsx-table:: :file: path/to/xlsx/embedded-images.xlsx
以下のようにレンダリングされます。
内部処理
内部処理としては、以下のようにしています。
- xlsxファイルを解析して、画像データを抜き出し、ファイルに保存
Ezcelファイル名.xlsx.media
のフォルダを作成し、保存します
- 画像アンカーの位置からセル (表の位置) を特定し、
image
ディレクティブを追加する
モジュール実行すると、以下のようになります。
% python -m sphinxcontrib.xlsxtable sample-images.xlsx --sheet v +-------+------------------------------------------------+ | Red | .. image:: sample-images.xlsx.media/image1.png | +-------+------------------------------------------------+ | Green | .. image:: sample-images.xlsx.media/image2.png | +-------+------------------------------------------------+ | Blue | .. image:: sample-images.xlsx.media/image3.png | +-------+------------------------------------------------+
リンク
AnsibleでSSH接続のポートを指定する
ansible_port
変数の値をPlaybook/インベントリで指定するansible-playbook
の--extra-vars
で指定ansible-playbook
の--ssh-common-args
理解すると、まあ、その通りなのだけど、だいぶ迷走したのでまとめとく。
環境
- Ansible 2.9.6
- Ubuntu 20.04.1 LTS (WSL2)
ansible_port
変数の値を指定する
SSHで使用するポート番号は、ansible_port
という「変数」で指定することができる。「変数」なので、変数を指定する方法で指定できる。
なお、ansible_ssh_port
でも動作するが、2.0以降はdeprecatedになっている。
Playbookで指定
- hosts: servers remote_user: root vars: ansible_port: 1234
インベントリから引っ張ってくることもできる。
- hosts: servers remote_user: "{{ user_name }}" vars: ansible_port: "{{ ssh_port }}"
今回やりたかったのはこれ。以下のような運用を考えたため。
- サーバーのセットアップは22で行って、それ以降は作業用ユーザー、変更したポートで行う
- インベントリファイルを共有したい
- セットアップでユーザー作成とポート変更を行い、同じインベントリを使って、以降の作業用Playbookを使用する
インベントリで指定
インベントリファイルでも指定できる。
[servers] 192.168.10.100 ansible_port=1234 [servers:vars] ansible_port=1234
ansible-playbook
の引数で変更
ansible-playbook
の実行時に、--extra-vars
で指定できる。
$ ansible-playbook -i inventory.ini playbook.yml --extra-vars "ansible_port=1234"
ansible-playbook
の--ssh-common-args
ansible-playbook
の実行時に、--ssh-common-args
でSSH実行時の引数を指定できる。ただし、sftp
/scp
/ssh
のポートの指定方法の微妙な違いが問題になる。
--ssh-common-args
はsftp
/scp
/ssh
で利用されるが、ssh
は-p
、sftp
/scp
は-P
で異なるため、厳密には利用できない--ssh-extra-args
等で個別に利用できるが、だいぶ面倒
--ssh-common-args "-p 1234"
で動作はするものの、WARNINGとなって厳密にはうまくない。ので、ansible_port
変数を指定するほうが良い。
podmanでdocker-compose使用時のビルドエラーに対応する
podman
でdocker-compose
を使用すると、イメージのビルド時に「FileNotFoundError: [Errno 2] No such file or directory: 'docker':」が出る- イメージビルド時に
docker
コマンドを実行するが、docker
コマンドが無いため /usr/local/bin
などにdocker
という名前で、podman
を実行するシェルスクリプトを作成する
環境
- CentOS 8 Stream
- podman 3.3.1 (rootで実行)
- docker-compose 1.29.2
podmanでdocker-composeを使う
以下参照。ルートレスモードはまだ試して無いです。
イメージビルド時のエラー
手順に従ってセットアップしdocker-compose up -d
を実行するとエラーとなる。少し調べると、イメージのビルド (build:/Dockerfileの指定) が含まれているとエラーになることがわかる。
File "/usr/local/lib/python3.6/site-packages/compose/service.py", line 1133, in build output_stream=output_stream) File "/usr/local/lib/python3.6/site-packages/compose/service.py", line 1943, in build universal_newlines=True) as p: File "/usr/lib64/python3.6/subprocess.py", line 729, in __init__ restore_signals, start_new_session) File "/usr/lib64/python3.6/subprocess.py", line 1364, in _execute_child raise child_exception_type(errno_num, err_msg, err_filename) FileNotFoundError: [Errno 2] No such file or directory: 'docker': 'docker'
エラーメッセージはイマイチピンと来ないのでソースコード (service.pyの1943行目あたり)をみてみると、イメージのビルドはsubprocess.Popen
でdocker
コマンドを呼び出していることがわかる。
podman
環境でdocker
コマンドは存在しない。ただ、コマンド自体には互換性があるので、代わりにpodman
を実行できるようにシェルスクリプトを準備することにする。
ちなみに、イメージのビルドが実行されなければエラーにならない。事前にpodman build
でイメージを作成しておくと、docker-compose
実行時にイメージビルドが走らないので、エラーにならずに実行できる。
dockerシェルスクリプトを準備する
/usr/local/bin
(とかのパスが通っているところ) にdocker
という名前でpodman
を実行するシェルスクリプトを準備する。
# vim /usr/local/bin/docker #!/bin/bash podman $@
実行権限もつけとく。
# chmod +x /usr/local/bin/docker
再度docker-compose
を実行。イメージのビルドが実行され、コンテナが立ち上がる。
# docker-compose up -d Building http STEP 1/7: FROM httpd:latest STEP 2/7: ENV DATAACCEPTOR_HOST=127.0.0.1 --> 201026ad839 ...
Creative Commons Licenseのテキストファイル
GitHubで適用する時など用。身も蓋もないけど「plaintext」で検索かければ引っかかる。
- 「Creative Commons License plaintext」で検索
- Plaintext versions of Creative Commons 4.0 licenses
公式サイトのフッターの検索で「plaintext」を検索してもOK。
URLに「.txt」をつける
URLを見ればわかるが、Legal Text / LicenseのURLに「.txt」を付ければ辿り着ける。
以下のように、選択サイト -> サマリー -> 全文 -> テキストファイル。
- https://creativecommons.org/choose/
- https://creativecommons.org/licenses/by/4.0/
- https://creativecommons.org/licenses/by/4.0/legalcode
- https://creativecommons.org/licenses/by/4.0/legalcode.txt
CC0のテキストファイル
CC0の場合も同じ構造になっている。パブリックドメインにする場合はこっち。
- https://creativecommons.org/publicdomain/zero/1.0/legalcode
- https://creativecommons.org/publicdomain/zero/1.0/legalcode.txt
CC0の場合はGitHubでリポジトリを作るときにも選べる。
plusctx (処理履歴の保存と参照)
ライブラリというよりは実装アイデアという感じ。ロギングの課題を解決するために考えていところ、構造的に割と面白かったので組んでみた。
組んだ後でスタックトレースで良いのでは?とも思ったけど、まあ、マルチプラットフォームで取ったり、整形したり、任意の情報乗っけたりとか考えたら、一応用途はありそう。
- https://github.com/kkAyataka/plusctx
- より下位の関数内で、どの関数から (どういったコンテキストで) 呼び出されたかを取得できる
- 自前のスタックトレースのような感じ
- ロギング用途を想定
サンプル
- 関数の冒頭で
PLUSCTX_CTX("name");
としてコンテキストを定義する- コンテキストはスレッド毎 (thread_local) にスタックとして保存される
- ブロックコープを抜けると (関数を抜けると) スタックから破棄される
- サブ関数内でコンテキストスタックを確認すると、上位の (呼び出し元) の情報を得ることができる
#include "plusctx/plusctx.hpp" #include <iostream> #include <sstream> // コンテキストスタックから文字列を生成 // スタックはスレッド毎に管理 std::string get_ctx_string() { const auto stack = plusctx::get_context_stack(); std::ostringstream ss; for (const auto c : stack) { ss << ">" << c->name; } return ss.str(); } void sub1(); // main int main() { PLUSCTX_CTX("main"); // mainを詰んだのでmainだけ std::cout << get_ctx_string() << std::endl; // >main sub1(); // コンテキストの寿命はブロック単位なので、 // sub1コンテキストは削除されてる std::cout << get_ctx_string() << std::endl; // >main } void sub1() { PLUSCTX_CTX("sub1"); // mainから呼び出されたことがわかる std::cout << get_ctx_string() << std::endl; // >main>sub1 }
plusctx::Contextクラス
ロギング用途を想定しているので、コンテキストには関数名やファイル名などを保存している。コンテキスト情報の取り扱いについては既定していないので、どう使うかは別の問題。
// 任意の名前だけでなく、関数名やファイル名も保存する class Context { public: Context( const std::string name, const std::string func_name, const std::string rich_func_name, const std::string file_name, const std::size_t line_no ); // 簡易にインスタンス化できるようにマクロを準備 #define PLUSCTX_CTX(name) plusctx::Context plusctx_ctx__( \ name, \ __func__, \ __FUNCSIG__, \ plusctx::detail::shorten_file_name(__FILE__), \ __LINE__)
その他
Google Testでデータドリブンテスト
- Google TestのValue-Parameterized Tests
testing::TestWithParam
、TEST_P
、INSTANTIATE_TEST_SUITE_P
を使う
他の (言語の) テスティングフレームワークで、同一のテストを異なるデータで実行するデータドリブンテストの機能があって、これGoogle Testでも無いんかいな、と調べたところあった。
Google Testでは「Value-Parameterized Tests」として説明されている。説明内に「data-driven testing」って記述があるので、検索すれば引っかかる。
Value-Parameterized Tests
使用する機能さえ把握してしまえば難しくない。シンプルに書けるようにうまく設計されてる。
// テストデータクラス struct MyTestParam { int v1; int v2; int ok_v; MyTestParam( const int v1, const int v2, const int ok_v ) : v1(v1), v2(v2), ok_v(ok_v) { } }; // TestWithParamにテストデータのクラスを渡して、継承する class MyTest : public testing::TestWithParam<MyTestParam> { }; // TEST_Pでテストを書く // GetParamでテストデータクラスのインスタンスを得る TEST_P(MyTest, test) { const auto p = GetParam(); EXPECT_EQ(p.v1 + p.v2, p.ok_v); } // データをインスタンス化して、テストを作る INSTANTIATE_TEST_SUITE_P(OkTest, MyTest, testing::Values( MyTestParam(1, 2, 3), MyTestParam(2, 3, 5) ) );
実行結果。
Note: Google Test filter = */MyTest.* [==========] Running 2 tests from 1 test suite. [----------] Global test environment set-up. [----------] 2 tests from OkTest/MyTest [ RUN ] OkTest/MyTest.test/0 [ OK ] OkTest/MyTest.test/0 (0 ms) [ RUN ] OkTest/MyTest.test/1 [ OK ] OkTest/MyTest.test/1 (0 ms) [----------] 2 tests from OkTest/MyTest (2 ms total) [----------] Global test environment tear-down [==========] 2 tests from 1 test suite ran. (3 ms total) [ PASSED ] 2 tests.
OkTest/MyTest.test/0
といった感じで出力される。この文字列でフィルタ出来るので、--gtest_filter=*/MyTest.*/0
で、1つ目のデータだけで実行できる。
テストデータに名前を付ける (出力する)
/0
、/1
の部分の文字列を置き換える。仕組み上任意の文字列...とまではできないのだけど、ある程度読みやすくなる。
簡単なのがテストデータに対してoperator<<
を用意してあげる方法。合わせて、INSTANTIATE_TEST_SUITE_P
の最後に、testing::PrintToStringParamName()
を渡す。
struct MyTestParam { std::string desc; // 追加 int v1; int v2; int ok_v; MyTestParam( const std::string & desc, // こっちも更新 const int v1, const int v2, const int ok_v ) : desc(desc), v1(v1), v2(v2), ok_v(ok_v) { } }; // operator<<を作って、単純にdescを返す std::ostream & operator<<(std::ostream & stream, const MyTestParam & p) { return stream << p.desc; } class MyTest : public testing::TestWithParam<MyTestParam> { }; TEST_P(MyTest, test) { const auto p = GetParam(); EXPECT_EQ(p.v1 + p.v2, p.ok_v); } INSTANTIATE_TEST_SUITE_P(OkTest, MyTest, testing::Values( MyTestParam( "1p2eq3", // テスト名をつける 1, 2, 3 )), testing::PrintToStringParamName() // 追加 );
実行結果。/0
だったところに指定したテスト名が出力されている。
Note: Google Test filter = */MyTest.* [==========] Running 1 test from 1 test suite. [----------] Global test environment set-up. [----------] 1 test from OkTest/MyTest [ RUN ] OkTest/MyTest.test/1p2eq3 [ OK ] OkTest/MyTest.test/1p2eq3 (0 ms) [----------] 1 test from OkTest/MyTest (1 ms total) [----------] Global test environment tear-down [==========] 1 test from 1 test suite ran. (2 ms total) [ PASSED ] 1 test.
テストデータだけだと何を狙ったデータなのかの情報がロストする。コメントでもいいけど、なんとなく把握できるテスト名を適当につけとくと、コード的にもテスト結果的にも内容を把握する手助けになる。
sphinxcontrib-xlsxtableに行・列指定オプションを追加
- 使用する / 使用しない、行 / 列を指定するオプションを追加
- v1.0.0としてリリース
Excelファイルから表を作るSphinx拡張について、v1.0.0としてリリースしました。 が、足りない機能があるような無いような...(まあ、必要性がはっきりしたら追加すれば...)
使用する行 / 列と使用しない行 / 列の指定
include-rows
、exclude-rows
、include-columns
、exclude-columns
を使って、指定できるようにしました。
.. xlsx-table:: :file: path/to/xlsx/file.xlsx :include-rows: 1-2 4 8 :exclude-rows: 3 5-7
.. xlsx-table:: :file: path/to/xlsx/file.xlsx :include-columns: A-B 4 :exclude-columns: C 5-6
矩形範囲指定のオプションは実装していませんが、オプションの組み合わせで同等のことはできます。
.. B2-D3の範囲 .. xlsx-table:: :file: path/to/xlsx/file.xlsx :include-rows: 2-3 :include-columns: B-D
表の分割表示を想定
Excelを表として取り込むときに分割して取り込むのを想定していました。 可読性もですが、特にPDF化を想定した場合、ページに収める細工が必要そうで。
.. xlsx-table:: 表1 :file: path/to/xlsx/file.xlsx :include-columns: A-G .. xlsx-table:: 表2 :file: path/to/xlsx/file.xlsx :include-columns: H-N
また、既存のExcelファイルをSphinx化するときに都合よく取り出したり。 メタデータも同時に記載されている場合に、メタデータとデータの表を分けるとかはありかと。
includeかexcludeか
include指定は明示的ですが、Excelファイルを更新しても、自動更新されなくなります。 逆にexcludeはExcelファイルにしたがって更新されますが、表示が崩れるとも言えます。 用途に合わせた指定が必要です。