LinuxサーバをVPN(L2TP)クライアントにする

こんにちは、岡野です。

先日、さくらインターネットのVPSと社内ネットワーク(VPN構築済み)をL2TPで接続しました。 特に問題なく接続できたのですが手順を共有します。

前提条件

VPN: L2TP

VPS: Ubuntu 20.04、ufwで通信制限中(incoming & outgoing)

接続手順

  1. VPNサーバの対応アルゴリズムを調べる

NetworkManager-l2tp Wikiにあるシェルスクリプトを保存後、以下を実行。VPNサーバの対応アルゴリズムが表示される。

$ chmod +x ike-scan.sh
$ sudo ./ike-scan.sh <VPNサーバのIP> | grep SA=
抜粋
SA=(Enc=AES Hash=SHA1 Auth=PSK Group=2:modp1024 KeyLength=256 LifeType=Seconds LifeDuration(4)=0x00007080)
  1. VPSのネットワーク設定を確認する

さくらインターネットで設定済みのネットワーク設定を確認。あとで使用する。

$ cat /etc/netplan/01-netcfg.yaml
network:
version: 2
renderer: networkd
ethernets:
  ens3:
    addresses: [***.***.***.***/23]
    gateway4: ***.***.***.***
    nameservers:
      addresses: [***.***.***.***, ***.***.***.***]
  1. L2TPソフトウェアをインストールする

$ sudo apt install network-manager-l2tp
  1. NetworkManager管理に切り替える

$ sudo vi /etc/netplan/01-netcfg.yaml
network:
version: 2
renderer: NetworkManager
# 残りは削除
$ sudo reboot
  1. NetworkManagerの接続情報を設定する

以下、VPSコンソールから実施。

$ sudo nmtui
# 「2. VPSのネットワーク設定を確認する」の内容を設定する
$ sudo reboot
  1. VPNの接続情報を設定する

VPN接続情報を以下の通り設定する。IKE対応アルゴリズムには「1. VPNの対応アルゴリズムを調べる」で調べた中で、より強力なものを使用する(例:aes256-sha1-modp1024)。

$ sudo nmcli connection add \
type vpn \
con-name <接続名、適当に。例:vpn> \
autoconnect no \
ifname -- \
ipv4.method auto \
vpn-type l2tp \
vpn.secrets password=<パスワード> \
vpn.data \
"password-flags = 0, require-mppe = yes, user = <ユーザ名>, refuse-chap = yes, refuse-mschap = yes, gateway = <VPNサーバのIP>, refuse-pap = yes, ipsec-enabled = yes, ipsec-psk = <共有鍵>, ipsec-ike=<IKE対応アルゴリズム>, ipsec-esp=aes128-sha1"
  1. VPN接続する

ログを表示しながらVPN接続を試みる。

$ journalctl -f &
$ sudo nmcli conn up vpn

以下の様なエラーが出力されるため

Jul 19 09:04:16 *** kernel: [148247.538797] [UFW BLOCK] IN= OUT=ens3 SRC=***.***.***.*** DST=***.***.***.*** LEN=268 TOS=0x00 PREC=0x00 TTL=64 ID=13477 DF PROTO=UDP SPT=500 DPT=500 LEN=248

ufwで解除設定しながらsudo nmcli conn up vpnを繰り返す。具体的には以下の様なufw設定になる。

$ sudo ufw status
***.***.***.*** 500/udp     ALLOW OUT   Anywhere                   # vpn (ipsec)
***.***.***.*** 1701/udp   ALLOW OUT   Anywhere                   # vpn (l2tp)
***.***.***.***/esp         ALLOW OUT   Anywhere                   # vpn (esp)

必要に応じてルーティング設定も行う。

  1. VPN切断する

VPN切断は以下。

$ sudo nmcli conn down vpn

[仮想環境] VirtualBox, Vagrantの関係について

こんにちは。初投稿のふくまです。
直近の投稿者のお2人と同様、入社して3か月目です。
私は後楽園駅に通勤しているのですが、春日駅もすぐ近くにあるため乗り換え可能な路線が多く、便利な場所だなーと日々感じております。

VirtualBox, Vagrantについてまとめようと思ったきっかけ

弊社の開発本部では、開発のためにPC上に仮想環境を構築します。その際に、VirtualBoxとVagrantを使用します。 powershell / コマンドプロンプトでvagrantコマンドを叩いて仮想マシンを作成するのですが、その際に以下のような疑問を持ちました。
  • VirtualBoxってソフトをインストールはしたけど、環境構築の過程で一度も使ってないのでは?
  • VirtualBox, Vagrantの関係って何なの?
  • 仮想マシンっていうけど、どこにあるの?
環境構築中にこのような疑問を持っていましたが、きちんと内容を理解しながら開発をすることが出来ていませんでした。 このような疑問に答えられるような形で、まとめてみます。

[疑問1] VirtualBoxは環境構築で一度も使わない?

A. 使用しています。ただし、Vagrantを通して使用しています。 PC上にゲストOSを立ち上げることの出来る仮想化ですが、仮想化を行う機能自体はVirtualBoxのものです。

[疑問2] VirtualBox, Vagrantの関係って何?

A. Vagrantには設定ファイルとvagrantコマンドを使用して、VirtualBoxを通じ仮想環境を構築してくれる役割があります。 言い換えると、VagrantはVirtualBoxを利用できるツールということです。

[疑問3] 仮想マシンってどこにあるの?

A. PC内部にあります。ホストOSの仮想化ソフト(VirtualBox等)の上で、ゲストOS(Linux等)として動作することになります。(下図イメージ) 弊社の場合、WindowsOS上で仮想化ソフトVirtualBoxとそれを操作するVagrantをインストールします。そして、Vagrantの設定ファイルに仮想マシンの設定やゲストOSに何を指定するかなどを記載します。あとはvagrantコマンドを打てば環境を作ってくれるので、作成後はsshで仮想マシンにログインして開発に関連する作業を実施します。

まとめ

  • VirtualBoxは仮想マシンを作ってくれる。
  • VagrantはVirtualBoxを便利に利用できる。
  • VirtualBoxとVagrantを合わせて使用することで、仮想環境の構築がより簡単になる。

WSLについて

こんにちは、イメージマジックのSuzukiです。 入社して早3カ月が過ぎました。
コロナの影響で運動不足になりがちな日々を過ごしております。
業務ではWSLでも検証等を行っています。
その為、今回はWSLについて書いていこうと思います。
WSLとは
WSLはWindowsOS上でLinuxの実行環境を実現するサブシステムです。
最近、WSL2というものがリリースされたようです。
以下の記事を参考にしてWSLをインストールしました。
参考:https://qiita.com/Brutus/items/f26af71d3cc6f50d1640
  • WSLはwindowsと親和性が高い
    起動が早い、Windowsとのファイルアクセスがしやすい、などが挙げられます。
  • WSL2はWSL1と共存ができる
    WSL2はWSL1を置き換えるものではありません。
    ディストリビューションごとにWSL1またはWSL 2のどちらで動作させるかを変更できます。任意のタイミングでWSL1とWSL2を切り替えることも可能です。
    具体的には、PowerShellを管理者権限で起動し、以下コマンドを実行して任意のディストリビューションをWSL2へ変換します。
wsl --set-version ディストリビューション名 2
 
  • 専用の仮想マシン内で起動
    WSL1は仮想マシンではなく、LinuxのシステムコールをWindowsのAPIに変換し、バイナリを実行する形式でした。
    しかしWSL2は仮想マシンでバイナリを実行するものに改められています。
    またWSL2ではDockerなどカーネル依存するソフトの動作が、最大のメリットです。
 
最後に
入社以来変わったことですが
以前よりコマンドラインツールに対して耐性ができたように思います。
しかし英語はまだまだ苦手意識があるため、修練の必要性を非常に感じました。
特にIntelliJは英語メインで辞書を片側に訳しながら作業を進めています。
やるべき事は多々ありますが、これからも頑張っていきます!
参考:https://qiita.com/poramal/items/3562472d52fe60f61c56

MySQLで文字列結合しようとしたら何も表示されなかった話

こんにちは~。たにすぎです。
入社してそろそろ3か月経ちますが、入ったときには既にみんなマスク着用だったので、何かの拍子に外した姿を見ると「誰だ……?」と2秒くらい固まります。
いやあ、早く素顔で笑える日が来てほしいですね。

さて、タイトルで既に原因お分かりの方もいるかと思いますが、
NULLな文字列をCONCATでつなげたら悲しい思いをしたのでその話をします。

今回の状況

「苗字(半角スペース)名前」の形で氏名を表示する時に何も考えずにCONCATでつなげた結果を表示しようとしたら痛い目にあいました。
こういうデータがあって、 サンプルのユーザーテーブル 以下のようなクエリを書きました。
SELECT CONCAT(name_last, ' ', name_first) AS name FROM scores s
 

結果

素直にconcatだけ使った結果
あれ……? 1番下が何も表示されないな。

なんで消えちゃうの

思ってたのと違う結果になったのでドキュメント読みます。 https://dev.mysql.com/doc/refman/5.6/ja/string-functions.html#function_concat
引数のいずれかかが NULL である場合、CONCAT() は NULL を返します。
はい。
つなげる文字列の中で1つでもNULLがあると、結果がNULLになるんですね。知らなかった……

書き換え案

氏名で片方がない場合はあまりなさそうですが、住所で建物名がないとかは割とありそうなのでなんとかしたいです。 

IFNULL

IFNULLをつかってNULLの場合は空に置き換えてから繋ぐように書き換えてみます。
SELECT
CONCAT(name_last, ' ', name_first) AS 'IFNULL使ってないname',
CONCAT( IFNULL(name_last,''), ' ', IFNULL(name_first, '') ) AS 'IFNULL使ったname'
FROM users u
できたできた。

COALESCE

COALESCEでも結果は同じだけど、なんだかもったいないような……
SELECT
CONCAT(name_last, ' ', name_first) AS 'そのままCONCAT',
CONCAT( COALESCE(name_last,''), ' ', COALESCE(name_first, '') ) AS 'COALESCE使ったname'
FROM users u
coalesceで繋いだ例

GROUP_CONCAT

GROUP_CONCATは複数行の結果をカンマ区切りで返してくれる関数です。
こちらはどうなるんでしょう?
SELECT GROUP_CONCAT(name_last), GROUP_CONCAT(name_first)
FROM users
group_concatにnull
あ、GROUP_CONCATの方はNULLは気にしないでくれるみたいですね。
https://dev.mysql.com/doc/refman/5.6/ja/group-by-functions.html#function_group-concat

おわりに

めんどくさがるのよくないですね!
何らかの理由でIF_NULLが使えない場合はCASEとIS_NULLで対応したらいいのかな……

おまけ: PostgreSQLの場合は?

これは完全に言い訳なんですが、ここ数年はPostgreSQLをメインに使っていて、そっちはNULL含む項目をCONCATで繋げても無視した結果を返してくれていたんです……
でもそういえばCONCAT使わずに「||」(パイプ)で繋げた場合はNULLになってましたね……

SELECT CONCAT(name_last, ' ', name_first) AS "CONCATの結果", name_last || ' ' || name_first AS "パイプの結果" FROM users

vim+uniteからneovim+deniteへの移行

社内の傾向としてはエディタはIntelliJやvscodeが主流で、私は元々vi系エディタのユーザなので最近はvscodeをvimのキーバインドで使ってます。

IDEは多機能なのはいいんですが、正直使いこなせてない感がすごいし、ぶっちゃけほぼeclipseしか使ったことない。
vscodeはvimキーバインドでもかなり使えるんですが、ちょいちょい気になる瞬間があり、なによりエディタは慣れたやつのほうがいい。
というか正直vimも使い切れてない。なので、まあそれならと比較的指が慣れてるvimの実装の一つでありずっと気になっていたneovimをこのブログ書く機会に一念発起してつかってみることにしました。

ubuntu18.04でapt使って入れようとすると、何もしないとneovimのバージョンが0.2系なのですが、リポジトリを追加することでneovim0.3系がinstallできます。その前にpython関係のやつもわすれずにいれときます。
sudo apt-get install python-dev python-pip python4-dev python3-pip 
sudo apt-get install software-properties-common 
sudo add-apt-repository ppa:neovim-ppa/stable 
sudo apt-get update 
sudo apt-get install neovim
今まで利用していた.vimrcをそのまま.config/nvim/init.vimにコピーしてまずは起動してみると、起動自体はできたのですが、プラグインを動かそうとするとTERMの設定がおかしというエラーが。
:checkhealthしてみろ、と親切なメッセージがでていたのでやってみると、TERMの設定がtmuxとzshの間で設定が異なっているのが問題だったらしく以下の設定を追加。

.tmux.conf
set-option -g default-terminal "screen-256color"
 
vimのpluginとしておなじみ便利なuniteをつかってたんですが、この機会に後継?っぽいdeniteを使ってみることにします。(これがneovim0.2系だと動かない
 
.vimrc
"unite.vim関連


" バッファ一覧
nnoremap <silent> ,ub :<C-u>Unite buffer<CR>
" ファイル一覧
nnoremap <silent> ,uf :<C-u>UniteWithBufferDir -buffer-name=files file<CR>
" レジスタ一覧
nnoremap <silent> ,ur :<C-u>Unite -buffer-name=register register<CR>
" 最近使用したファイル一覧
nnoremap <silent> ,um :<C-u>Unite file_mru<CR>
" 常用セット
nnoremap <silent> ,uu :<C-u>Unite buffer file_mru<CR>
" 全部乗せ
nnoremap <silent> ,ua :<C-u>UniteWithBufferDir -buffer-name=files buffer file_mru bookmark file<CR>


" ウィンドウを分割して開く
au FileType unite nnoremap <silent> <buffer> <expr> <C-j> unite#do_action('split')
au FileType unite inoremap <silent> <buffer> <expr> <C-j> unite#do_action('split')
" ウィンドウを縦に分割して開く
au FileType unite nnoremap <silent> <buffer> <expr> <C-l> unite#do_action('vsplit')
au FileType unite inoremap <silent> <buffer> <expr> <C-l> unite#do_action('vsplit')
" ESCキーを2回押すと終了する
au FileType unite nnoremap <silent> <buffer> <ESC><ESC> :q<CR>
au FileType unite inoremap <silent> <buffer> <ESC><ESC> <ESC>:q<CR>
uniteの時につかってた設定。これを。。。
 
init.vim
" denite.vim関連


" バッファ一覧
nnoremap <silent> ,ub :<C-u>Denite buffer<CR>
" ファイル一覧
nnoremap <silent> ,uf :<C-u>DeniteWithBufferDir -buffer-name=files file<CR>
" レジスタ一覧
nnoremap <silent> ,ur :<C-u>Denite -buffer-name=register register<CR>
" 最近使用したファイル一覧
nnoremap <silent> ,um :<C-u>Denite file_mru<CR>
" 常用セット
nnoremap <silent> ,uu :<C-u>Denite buffer file_mru<CR>
" 全部乗せ
nnoremap <silent> ,ua :<C-u>DeniteBufferDir -buffer-name=files buffer file_mru bookmark file<CR>


" ウィンドウを分割して開く
au FileType denite nnoremap <silent> <buffer> <expr> <C-j> denite#do_action('split')
au FileType denite inoremap <silent> <buffer> <expr> <C-j> denite#do_action('split')
" ウィンドウを縦に分割して開く
au FileType denite nnoremap <silent> <buffer> <expr> <C-l> denite#do_action('vsplit')
au FileType denite inoremap <silent> <buffer> <expr> <C-l> denite#do_action('vsplit')
" ESCキーを2回押すと終了する
au FileType denite nnoremap <silent> <buffer> <ESC><ESC> :q<CR>
au FileType denite inoremap <silent> <buffer> <ESC><ESC> <ESC>:q<CR>
 
こうだなきっと(uniteをdeniteに変えただけ

なんとこんな雑な変換のやり方でも一覧系はだいたい全部うごいたんですが、ファイル一覧だけ動かない。DeniteWithBufferDirがないっぽいので、:help DeniteしてみてみるとDeniteBufferDirという名前でほしいのが取れそう。
 
なので該当箇所をこれに変更します。あとなぜかbookmarkがなかったので全部乗せからbookmarkも外しときます(uniteだと定義がなくてもエラーになってなかった
 
init.vim
" ファイル一覧
nnoremap <silent> ,uf :<C-u>DeniteBufferDir -buffer-name=files file<CR>
 :
" 全部乗せ
nnoremap <silent> ,ua :<C-u>DeniteBufferDir -buffer-name=files buffer file_mru file<CR>
 
これだけだと動かなくて、checkhealthした際に出ていたアドバイスに従って、:UpdateRemotePluginを実行。これでDenite自体は起動できました。

ただ、なんかinsertモードから戻るときのescapeの反応が異様に遅い。。
しらべてるとtmuxのescape-timeのせいだという記事をみかけたのでとりあえずそれのせいにしとこう。

.tmux.conf
set -s escape-time 10
 
よしこれで終わりかと思ったら、deniteのbufferから該当箇所にとべない?
denite.txtにあるexampleからまるっと以下の設定をコピー

init.vim
  autocmd FileType denite call s:denite_my_settings()
  function! s:denite_my_settings() abort
    nnoremap <silent><buffer><expr> <CR>
    \ denite#do_map('do_action')
    nnoremap <silent><buffer><expr> d
    \ denite#do_map('do_action', 'delete')
    nnoremap <silent><buffer><expr> p
    \ denite#do_map('do_action', 'preview')
    nnoremap <silent><buffer><expr> q
    \ denite#do_map('quit')
    nnoremap <silent><buffer><expr> i
    \ denite#do_map('open_filter_buffer')
    nnoremap <silent><buffer><expr> <Space>
    \ denite#do_map('toggle_select').'j'
  endfunction
 
。。。ってここで気づいたんですが分割して開くやつも動いてなかった。
 
 
au FileType denite nnoremap <silent> <buffer> <expr> <C-j> denite#do_action('split')
これをこんなかんじでdenite_my_settingsの中に張り付けなおしていきます。
 
init.vim
autocmd FileType denite call s:denite_my_settings()
function! s:denite_my_settings() abort
             :
  nnoremap <silent><buffer><expr><C-j> denite#do_map('do_action','split')
             :
endfunction
これで検索結果のバッファからEnterキーでとんだりsplitして画面を開いたりできるようになりました。


ついでに最近でも開発が続いてるripgrepをdeniteから使うようにしてみたいと思います。
つかってるubuntuのバージョンだとaptにはなかったし、snapは利用できない環境だし、しょうがないのでdebian向けのパッケージでいれます。
curl -LO https://github.com/BurntSushi/ripgrep/releases/download/11.0.2/ripgrep_11.0.2_amd64.deb
sudo dpkg -i ripgrep_11.0.2_amd64.deb

以下の設定をinit.vimに追加。
 
init.vim
nnoremap <silent> ,ug :<C-u>Denite grep -buffer-name=grep-buffer-denite<CR>
nnoremap <silent> ,uw :<C-u>DeniteCursorWord grep -buffer-name=grep-buffer-denite<CR>
nnoremap <silent> ,ue :<C-u>Denite -resume -buffer-name=grep-buffer-denite<CR>
nnoremap <silent> ,un :<C-u>Denite -resume -buffer-name=grep-buffer-denite -select=+1 -immediately<CR>
nnoremap <silent> ,up :<C-u>Denite -resume -buffer-name=grep-buffer-denite -select=-1 -immediately<CR>


call denite#custom#var('grep', 'command', ['rg'])
call denite#custom#var('grep', 'recursive_opts', [])
call denite#custom#var('grep', 'pattern_opt', [])
call denite#custom#var('grep', 'default_opts', ['-S', '--vimgrep','--no-heading'])
 

これでripgrep結果のdenite bufferからファイルにとんだり、キャッシュした検索結果からfilterして探したりとできるようになりました。

これだけだとなんなので、次に補完機能などを強力にしてくれるvim-lspを導入します。
vim-lsp-settingsで簡略化できるようですが、まずはそのありがたみと、素の設定のめんどくささを知るためにそのまま設定してみます。とりあえずtypescriptとphpでチャレンジ。
vundleユーザなので以下をpluginとして追加。
asynccompleteでtypescriptを補完するためにtscompletejob、asynccomplete-tscompletejob(長い)を入れます
 
init.vim
Plugin 'prabirshrestha/async.vim'
Plugin 'prabirshrestha/vim-lsp'
Plugin 'prabirshrestha/asynccomplete.vim'
Plugin 'prabirshrestha/asynccomplete-lsp.vim'
Plugin 'runoshun/tscompletejob'
Plugin 'prabirshrestha/asynccomplete-tscompletejob.vim'
このあたり(https://github.com/prabirshrestha/vim-lsp/wiki/Servers-TypeScript)を参考に入れていきます。まずはlanguageサーバをインストール。*1
npm install -g typescript-language-server intelephense
plugin設定に追加。intelephenseはプラグイン設定いらない模様。
 
init.vim
Plugin 'ryanolsonx/vim-lsp-typescript'
各language server設定。このあたりは記載されてるまま。*2
init.vim
" php 
if executable('intelephense')
  augroup LspPHPIntelephense
    au!
    au User lsp_setup call lsp#register_server({
        \ 'name': 'intelephense',
        \ 'cmd': {server_info->[&shell, &shellcmdflag, 'intelephense --stdio']},
        \ 'whitelist': ['php'],
        \ 'initialization_options': {'storagePath': '/tmp/intelephense'},
        \ 'workspace_config': {
        \   'intelephense': {
        \     'files': {
        \       'maxSize': 1000000,
        \       'associations': ['*.php', '*.phtml'],
        \       'exclude': [],
        \     },
        \     'completion': {
        \       'insertUseDeclaration': v:true,
        \       'fullyQualifyGlobalConstantsAndFunctions': v:false,
        \       'triggerParameterHints': v:true,
        \       'maxItems': 100,
        \     },
        \     'format': {
        \       'enable': v:true
        \     },
        \   },
        \ }
        \})
  augroup END
endif



" typescript
if executable('typescript-language-server')
    au User lsp_setup call lsp#register_server({
        \ 'name': 'typescript-language-server',
        \ 'cmd': {server_info->[&shell, &shellcmdflag, 'typescript-language-server --stdio']},
        \ 'root_uri':{server_info->lsp#utils#path_to_uri(lsp#utils#find_nearest_parent_file_directory(lsp#utils#get_buffer_path(), 'tsconfig.json'))},
        \ 'whitelist': ['typescript', 'typescript.tsx'],
        \ })
endif
 
vim-lspの設定。これもとりあえずはそのまま。g dでカーソル箇所のワードの定義箇所を探してくれます便利。F2でシンボル一括リネーム。
 
init.vim
function! s:on_lsp_buffer_enabled() abort
    setlocal omnifunc=lsp#complete
    setlocal signcolumn=yes
    nmap <buffer> gd <plug>(lsp-definition)
    nmap <buffer> <f2> <plug>(lsp-rename)
    " refer to doc to add more commands
endfunction


augroup lsp_install
    au!
    " call s:on_lsp_buffer_enabled only for languages that has the server registered.
    autocmd User lsp_buffer_enabled call s:on_lsp_buffer_enabled()
augroup END
 
エラー表示と補完候補のリフレッシュタイミングの設定はいろいろためしてみましたが、結局デフォルトの挙動が好みだったのと、popup更新もう少しゆっくりでもいいかなってことでg:asyncomplete_popup_delayの値を少し増やすだけにしました。
 
で、結局lsp-serverと補完の設定とか、言語を追加するたびにペタペタやらんといかんわけですね。手順は決まってるといえ、確かに長いし、なにより面倒くさい。そこでvim-lsp-settingsの出番。
 
init.vim
Plugin 'mattn/vim-lsp-settings'
を:VundleInstallして、
 
init.vim
if empty(globpath(&rtp, 'autoload/lsp.vim'))
  finish
endif
 
でおわり。*1,*2の設定はいらなくなります。 あとは使いたい言語編集時に:LspInstallServerするようにすれば裏で設定してくれます。ありがたや。
 
 
思ったより長くなってしまったので、ここまでにしたいと思います。最後までご覧いただきありがとうございました。

AsciiDoc使ってみた

こんにちわ。コロナウイルスの影響で電車がガラガラの日々が続いていましたが、GW明けから徐々に出勤している人が増えているみたいですね。
電車混まないでえええ!!ということで、テックブログ今週の担当はuraです。

ドキュメント作るのつらい

皆さんは仕様書やマニュアルなどのドキュメントを作りますか?
私は現在受注プロジェクトのPMをしており、頻繁にドキュメントを作る機会があります。
一般的に、ドキュメントはWordやExcel等で作成するかと思います。WordやExcelは見やすいですよね。好きですか?私は嫌いです。

  • Gitで履歴管理しても簡単に文章を検索できない。差分も見えない。
  • チームで同じファイルを同時に編集しづらい。(GoogleDocsを使えばなんとかなるけど)
  • 書式を整えるのが面倒。気づいたら一部だけフォントが異なるとかよくある。
  • 顧客用のドキュメントであれば、修正の度にファイルを送信する必要がある。
ではどうするか?Markdownで作成してGitで管理しよう。
いちいちファイルを送るのは面倒なので、GitにプッシュしたらHTMLに変換してWebページに公開しよう。

いや、でもMarkdownだとテーブルのセル結合すらできないから、ちょっと複雑なドキュメントになると表現力が足りない・・・。

そんなことを考えているときに見つけたのが、今回のテーマのAsciiDocです。

AsciiDocとは?

Asciidocは軽量マークアップ言語の一つです。Markdownの仲間ですね。
以下のような採用事例があります。

Markdownと比較すると以下のような点が優れています。

  • テーブルのセル結合ができる
  • 相互参照が使える
  • 外部ファイルを読み込める(ソースコードをそのまま読み込んで表示したり、分けて作成したドキュメントをマージしたり・・・)
  • PlantUMLを直接書ける

使ってみる

それではさっそく使ってみます。

セクション

「=」の数で表現します。
== セクション1
=== セクション1.1
==== セクション1.1.1
== セクション2
== セクション3

リスト

「*」の数で表現します。
* リスト1
* リスト2
** インデント
*** さらにインデント

テーブル

[cols="1a,1a,1a",options="autowidth"]
|===
|1A|1B|1C
2+|「数値+」で横結合|2C
.2+|「.数値+」で縦結合|3B|3C
|4B
|* 4C
* リストを
* 入れることも可能
|===

画像

image::https://techblog.imagemagic.jp/wp-content/uploads/2018/11/%E6%8E%A1%E7%94%A8%E3%83%90%E3%83%8A%E3%83%BC.png[alt=代替テキスト, width="200" align="center"]

アンカー

アンカーは二重の「[」で表現します。
[[hogehoge]]

リンク

link:https://imagemagic.jp/recruit/[イメージ・マジックではエンジニアを募集中です。]

<<hogehoge, アンカーへのリンクは二重の「<」で表現します。>>

外部ファイルの読み込み

include::hogehoge.adoc[]

まとめ

AsciiDocの特徴や簡単な使い方について紹介しました。
現在マニュアル作成で使っていますが、やはりWordよりはラクをさせてもらっています。

インストール方法や詳しいリファレンスについては公式サイトをご覧ください。

デザインに使える比率の話

皆さんこんにちは。イメージマジック三浦です。 新型コロナウイルスに対して、弊社でも在宅勤務等の感染予防対策が進んできて今までと違う状況が日常になりつつあります。一日も早い収束を願いつつも、日々自分にできることをやっていこうと思います。 今回は「デザインに使える比率の話」と題して、黄金比と白銀比を紹介します。

黄金比

1:(1+√5)/2≒1:1.6
(1+√5)/2は黄金数とも呼ばれ、フィボナッチ数列の一般項表記に出てくる数です。整数値で比率を表すと5:8となります。
Web画面を作る時、通常表示向けと強調表示向けのフォントサイズを5:8にすることがありますが、通常表示側が奇数になる場合は以下2つの内、1.6に近い方を使うようにしています。
  • 強調表示向け÷(通常表示向け+1)
  • 強調表示向け÷(通常表示向け-1)

白銀比

貴金属比による定義…1:(1+√2)≒1:2.4
直角二等辺三角形の斜辺と底辺の比…1:√2≒1:1.4
「1:√2」は紙の寸法に出てくる他、日本の建築物に多く取り入れられています。また、東京スカイツリーの「東京スカイツリー展望回廊」とタワー全体の高さが約1:1.41で、これも白銀比になっています。

少し数学的な話

第n貴金属比…1:{n+√(n^2)+4}/2
黄金比や白銀比を更に一般化した概念として、貴金属比というものがあります。第1貴金属比(n=1)が黄金比、第2貴金属比(n=2)が白銀比です。ちなみに第3貴金属比(n=3)…1:{3+√13}/2 には青銅比という別名がついています。
(この並び、昔のアニメを思い出します)

おまけ

貴金属比に属さないながら、貴金属の名前を持つ比として白金比というものがあります。
白金比…1:√3
これは、正三角形の底辺の中点と頂点を結んでできる直角三角形の、斜辺を除く辺の比として出てきます。斜辺は短い方の辺のちょうど2倍になっていて、これは三平方の定理からも分かります。

最後に

少し前の話になりますが、京都大学数理解析研究所の望月新一教授による「ABC予想」の証明が認められたと話題になりました。「ABC予想」を真とすると、「フェルマーの最終定理(ワイルズの定理)」をあっという間に証明できるという話があるので紹介します。詳しい説明をしているサイトが他にありますので、ここでは定理と証明の概略を紹介するに留めます。
【フェルマーの最終定理(ワイルズの定理)】
3以上の自然数nについて、(x^n)+(y^n)=(z^n) を満たす自然数の組(x, y, z)は存在しない。
【証明の概略】
・ABC予想を真とすると、n>=6で(x^n)+(y^n)=(z^n) を満たす自然数の組(x, y, z)は存在しないことが証明できる。
・n=3,4,5の場合は、先人達によって個別に証明されている。
・よって3以上の自然数について、(x^n)+(y^n)=(z^n) を満たす自然数の組(x, y, z)は存在しない。
証明に360年かかった命題を、ここまで簡単に証明してしまえるのは驚くばかりです。しかしながら全てではなく、先人達が残した実績との合体技で証明です(これも昔の某アニメを思い出す)。どんなに優れた知見が出ても、そこに至るまでの先人達の歩みを無視することはできないと感じます。
今回は、ここで終わりとします。

JavaScriptって括弧のお化け

はじめに

コロナウイルスに戦々恐々の廣田です。
皆様も体にはお気をつけて…

憂鬱な今日この頃ですが、最近の業務中の癒しはslackが未読を始末した時に励ましてくれることです。
癒されすぎてキャプチャを集めちゃいました。

さて、今回はJavaScriptが括弧の有無で挙動が全然違うので調べてみました。

コールバック関数の書き方

Vue.jsで画面上の検索ボタンを押したらモーダル画面が開いて一覧から検索を行えるような機能を作っていた時のこと。 つぎのようなコードで上手く動かなかったのですが、どこが問題でしょうか🤔
<!--HTML側--> 
<div id="js-mainArea">
<button type="button" @click="search">検索</button> 
</div>
//本体のVueインスタンス
new Vue({
   el: '#js-mainArea',
   data: {},
   methods: {
       search() {
           EventBus.$emit('open-search-modal');
       },
   },
});
//モーダルのVueインスタンス
new Vue({
   el: '#js-search-modal',
   data: {
       isOpen = false
   },
   created: function () {
       EventBus.$on('open-search-modal', this.open())
   },
   methods: {
       open() {
           console.log('open');
           this.isOpen = true;
       },
  },
});
すごく初歩的なところだと思うのですが、検索ボタンを押したときemitしたイベントを捕捉している以下の箇所が原因でした。
EventBus.$on('open-search-modal', this.open()) 
イベントハンドラとしてコールバック関数を指定したいので正しくはこうですよね。
EventBus.$on('open-search-modal', this.open)
はい。括弧を書いていたのが間違いでした。

うっかり括弧付きで書いてしまうことがあるのですが、どうして括弧付きで書くとダメなんでしょう。

「括弧をつけるとメソッドが即座に実行されてしまう」
「コールバック関数を書く時は括弧無しで書く」

コールバック関数として渡したいときは括弧書いちゃいけないんですね
わかりました~

…でもなんで?
納得できなかったので調べてみました🧐

具体的な例で考えてみる

わかりやすいようにSquare関数を用意します。
function Square(number) {
  return number*number;
}
これは以下と同等です。
const Square = function(number) {
  return number*number;
}
2つめのように表現すると関数も他の定数や数値のように受け渡しが可能なものだということがわかりやすくなりました。(こういうのを第一級オブジェクトというらしいです。)

括弧ありでの挙動

let number = 3;
let result = Square(number);
このときSquare関数が引数にnumberをとって実行され、変数resultには「9」がセットされます。

括弧なしでの挙動

let number = 3;
let result = Square;
このとき変数resultにはSquare関数そのものがセットされます。
つまりresult()とすることでSquare関数を実行できるということです。
let output = result(3); 
console.log(output); // equal to 9
これは以下と同等です。
let output = Square(3);
resultを呼びだすとき、実際にはSquare関数を参照しているんですね。


 

今回の事例に戻って

さて最初のVueの例に戻ります。
created: function () {
       EventBus.$on('open-search-modal', this.open())
   },
   methods: {
       open() {
           console.log('open');
           this.isOpen = true;
       },
  },
},
上記のように書いた時、createdの段階でopen関数が実行されてコンソールに”open”が表示されます。
open関数は変数isOpenをtrueにするのみの関数のなのでその実行結果がイベントハンドラとして渡されても、イベント捕捉時に何も実行されません。

open() { 
    console.log('open'); 
    this.isOpen = true; 
},
//これは以下と同じ
open = function() {
    console.log('open'); 
    this.isOpen = true;
}
 
なのでイベントハンドラにopen関数自体をコールバック関数として渡すためには括弧無しのthis.openと書かなければいけません。
納得できましたε-(‘∀`;)ホッ


とってとっても参考になりました。
https://stackoverflow.com/questions/3246928/in-javascript-does-it-make-a-difference-if-i-call-a-function-with-parentheses

アロー関数とかほんと括弧のお化け。arimasさんに同意します。
https://qiita.com/arimas/items/448f411e78d12f7d5208

Java14とJavaFXを使い普通のブロック崩しを作る

はじめに

安藤です。
世の中の情勢が厳しい昨今ですがいかがお過ごしでしょうか。私は元気です。
今回は3/17にリリースされたJava14を早速使ってみたという話です。

Java14について

全容はきしださんがまとめてくれています。いつもお世話になっています。
Java 14新機能まとめ
個人的に注目したいのが、ついに正式版になったPackaging Toolです。
およそ1年前にこちらの記事でも触れましたが、ようやく公式のパッケージャが登場しました。

今回作ったもの

本題です。
この記事のためだけにJavaFXを使ったシンプルなブロック崩しを作りました。
javafx-breakout
breakout.zip
実は前職で新卒研修時に暇だったので似たようなものを作ったことがあり、それを思い出しながら作っていたのですが、想像の3倍くらい大変でした。
新しい構文を使うことと、できるだけ内部状態の変更を起こさないことをコンセプトに作りました。

全体の構成

ProcessingのようにCanvasで画面を描画していきます。fxmlの記述はこれだけです。

<?xml version="1.0" encoding="UTF-8"?>


<?import javafx.scene.canvas.Canvas?>
<?import javafx.scene.Group?>
<Group xmlns="http://javafx.com/javafx/11.0.2" xmlns:fx="http://javafx.com/fxml/1"
       fx:controller="jp.imagemagic.breakout.FieldController"
       onMouseMoved="#mouseMove" onMouseClicked="#mouseClick">
    <children>
        <Canvas fx:id="canvas" focusTraversable="true" width="400" height="600"/>
    </children>
</Group>

画面にはブロック、ボール、バーがありますが、統一的な計算のために以下を用意しました。
Recordはイミュータブルな推移をさせるためにうってつけです。

package jp.imagemagic.breakout;

import javafx.scene.paint.Color;

interface Drawable {
    Vector pos();

    Vector size();

    default DrawType drawType() {
        return DrawType.Rect;
    }

    default Color fill() {
        return Color.WHITE;
    }

    default Color stroke() {
        return Color.BLACK;
    }
}


package jp.imagemagic.breakout;

public record Vector(double x, double y) {
    public Vector move(double dx, double dy) {
        return new Vector(x + dx, y + dy);
    }

    public Vector move(Vector v) {
        return move(v.x, v.y);
    }

    public Vector scalar(double n) {
        return scalar(n, n);
    }

    public Vector scalar(double nx, double ny) {
        return new Vector(x * nx, y * ny);
    }
}

クラス構成の大枠は以下の通りです。これはtetrixの影響を受けた構成になっています。
  • 画面からの入力の処理と一定時間ごとに画面の推移を行うFieldController
  • 現在の状態を保持するState
  • Stateの推移ロジックをもつField
  • FieldControllerとStateを仲介するViewUnit

package jp.imagemagic.breakout;

import java.util.function.Consumer;
import java.util.function.UnaryOperator;

final class ViewUnit {
    private final Field field;
    private State state;

    ViewUnit(double w, double h) {
        field = new Field(new Vector(w, h));
        state = field.initState();
    }

    void trans(UnaryOperator<State> trans) {
        state = trans.apply(state);
    }

    UnaryOperator<State> moveBar(double x) {
        return field.moveBar(x);
    }

    UnaryOperator<State> moveBall() {
        return field.moveBall();
    }

    UnaryOperator<State> transStatus() {
        return field.transStatus();
    }

    void drawView(Consumer<Drawable> draw, Consumer<Status> statusDraw) {
        state.view().objects().forEach(draw);
        statusDraw.accept(state.status());
    }
}


// 一部抜粋
public class FieldController implements Initializable {
    @FXML
    private Canvas canvas;
    private GraphicsContext gc;
    private ViewUnit unit;

    public void mouseMove(MouseEvent e) {
        transAndDraw(unit.moveBar(e.getSceneX()));
    }

    public void mouseClick() {
        transAndDraw(unit.transStatus());
    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        gc = canvas.getGraphicsContext2D();
        unit = new ViewUnit(canvas.getWidth(), canvas.getHeight());
        final var timeline = new Timeline(new KeyFrame(new Duration(10), e -> transAndDraw(unit.moveBall())));
        timeline.setCycleCount(Timeline.INDEFINITE);
        timeline.play();
        drawView();
    }
}

補記: jpackageによるパッケージング

予めjlinkでランタイムの大きさを削っておくのがお勧めです。
インストーラを伴わないものを作ろうと色々と試した結果、このようなコマンドになりました。
jpackage -t app-image -n breakout -d dist -i app --main-class jp.imagemagic.breakout.Main --main-jar jp.imagemagic.breakout.jar --runtime-image jregram.min\ --java-options "--add-exports javafx.base/com.sun.javafx.reflect=ALL-UNNAMED --add-reads javafx.base=ALL-UNNAMED --add-reads javafx.graphics=ALL-UNNAMED --enable-preview"
appにjarファイルがあり、distにbreakoutというディレクトリができます。
jarファイルにしていますが、オプションを変えればクラスファイルでも行けると思います。

UWPアプリからOpenCVしてみる(その1)

矢野です。

プロトタイプ的にWindows用のアプリケーションを新たに作成するにあたり、ちょっと考えました。Windows Formsなら自分もそれなり多少は経験もあるけど、新規でつくるのに今さらFormsというのもどうなのかしら? でもWPFやUWPも結局のところあまり普及してきている感じがしないし、枯れたFormsに比べれば、情報はかなり少ない。XAML Islands? 何それ?
このプロトタイプは、PCに接続したWebカメラで撮影した画像をOpenCVでちょちょいと弄れるだけでいいのですが、でもFormsからカメラなどメディア関連の制御をするのは結構大変です。DirectShowとかMedia Foundationなどを使ってC++でゴリゴリ書く気になれば色々細かいところまで自由になるけれど、プロトタイプなのにそのへんをめちゃくちゃ頑張る気にはなれないし、とはいっても入手が容易なC#向けのラッパーだとちょっと古かったり、ちょっと不便だったり……。
で、けっきょくUWPアプリをC#で書くことにしました。それでも周回遅れなのかもしれませんが、せっかくの機会ですから少しでも新しい仕組みに追いついておきたいということで。
  • UWPアプリ上でMediaCaptureを使ってWebカメラにアクセス
  • Webカメラから取り込んだ画像をOpenCVで処理
  • 処理した画像を表示
これらを実現する簡単なアプリをつくってみたいと思います。 Visual Studio 2019を起動し、C#/Windows/UWPの「空白のアプリ(ユニバーサルWindows)」を新しいプロジェクトとして作成。
ソリューションエクスプローラーからPackage.appxmanifestをダブルクリック→機能タブの[Webカメラ]と「マイク]にチェックを入れて保存。
MainPage上にツールボックスからCaptureElementを配置し、適当な名前(captureElement)を付ける。StretchプロパティをUniformToFillにしておく。 MainPageのOnNavigatedTo(NavigationEventArgs)メソッドをオーバーライドする(awaitな呼び出しを行うので、メソッドにasync修飾子を追加します)。
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
    var capture = new MediaCapture();

    await capture.InitializeAsync();

    var props = capture.VideoDeviceController
        .GetMediaStreamProperties(MediaStreamType.VideoPreview) as VideoEncodingProperties;

    await capture.VideoDeviceController
        .SetMediaStreamPropertiesAsync(MediaStreamType.VideoPreview, props);

    captureElement.Source = capture;
    await capture.StartPreviewAsync();
}
プロジェクトをビルドして起動します。

アクセス許可を確認するダイアログが表示された場合は、許可します。
  • マイクへのアクセスを許可しますか?→[はい]
  • カメラにアクセスすることを許可しますか?→[はい]
たったこれだけで、リアルタイムにカメラの画像が表示されました! すごい! 泣きそうなりながらC++でMedia Foundationで書いた時とは大違い!(大げさ)
その2に続きます。