Pureminder (リマインダ)とZsh socket programming

Tasqueにはリマインダ機能がない。

Taskcoachということも考えはするが、残念ながらTaskcoachは私の環境ではSystem trayに収まってくれないため、非常に邪魔である。

そこで、リマインダを作ってみた。GitHubで公開している。今回はどちらかというとNoddy寄りだが、きちんとした形で公開している。

PureminderはZshで書かれている、クライアント/サーバーである。
サーバーはメッセージを受け取り、SOX(play)で音を再生し、Zenityで画面に表示する。

わざわざクライアントサーバーモデルをとっているのは、atでの利用に問題があるためだ。

単にatでcallすると、Zenityは表示すべきディスプレイサーバーを見つけられない。
$DISPLAY変数によって指定することはできるが、マルチユーザー環境での動作が信用できない。

そこで、確実に機能させるため、現在のデスクトップ上で起動し、ユーザー別に作られるUNIXドメインソケットでメッセージを受け取る、という仕様とした。

だが、PureminderはZshだ。
一般にはなかなか見かけることのない、Zsh socket programmingをちょっと紹介しよう。

まず、モジュールをロードする。

zmodload zsh/net/socket

次にzsocket -lでソケットを作成する。

zsocket -l "$sockfile"

$REPLYにファイルデスクリプタ(Integer)が入るので、とっておく。

typeset -g sock=$REPLY

zsocket -aで接続を待ち受ける。引数はソケットのファイルデスクリプタ。

zsocket -va $sock

$REPLYに接続のファイルデスクリプタが入る。この接続は全二重。
閉じる時は次のようにする。

exec {fd}>& -

シェルでrcファイル更新時に既存シェルに再読み込みさせる

にゃおきゃっとさん(@nyaocat])がbashrcの更新に合わせて全bashに反映させたいというツイートをしていたので、

exec bashする方法を提案してみた。

で、結構うまくいくようなので、採用されたみたい。

この点について解説してみる。

exec(2)は現在のプロセスを置き換えるシステムコールで、execコマンドは引数コマンドを現在のプロセス実行し、実行中プロセスと置き換える。

これによって新規bashが起動される。この際

  • PIDは変わらない
  • プロセスの親子関係も変わらない
  • execされた場合、bashはジョブに対してSIGHUPを送らない(子プロセスが終了したりはしない)
  • ジョブテーブルは現行プロセスに固有の情報なので、引き継がれない。バックグラウンドジョブがあった場合、再読み込みされたシェルでjobsしてもリストされない
  • シェル起動後に覚えさせた変数、関数などは引き継がれない。環境変数も消滅する
  • 既に動いている子プロセスに対する影響は全く無い

子プロセスに影響がないのは、親プロセスの変更は子プロセスに伝播しないためだ。 環境変数の変更はあくまでそのプロセスと、そのプロセスから生成されるプロセスに対する影響である。プロセス生成時に引き継がれるだけだからだ。

それどころか、親プロセスが死んで孤児になると、initが引き継ぐが、それでも子プロセスには影響しない。

問題は、積極的にインタラクティブシェルで変数や関数やエイリアスを活用している人は、それらが全てリセットされてしまうことだろう。 あと、ジョブテーブルが消えるのも、ジョブを活用している人には痛い。

.zshrcなら以下で、SIGHUPを送った時に読みなおすようになる。

TRAPHUP() {
  exec zsh -l
}

連番ファイルの並べ替え、差し込み

彼女に書籍の電子化作業を頼んだのだが、あまりにも雑でかなり困った。

まず大量にあった、ページ順が逆になっているものについては、次のスクリプトをかいた

#!/usr/bin/zsh

files=(*)
dfiles=(${(aO)files})

print -l $dfiles

for i in "$files[@]"
do
  mv -v "$i" "$i.tmp"
done

integer index

for (( index = 1; index <= ${#files} ; index++ ))
do
mv -v "$files[$(( index ))].tmp" "$dfiles[ $(( index )) ]"
done

逆順はまだしも、差し込みはきつかった。 どこで何をしたかは説明したくないので、打ったコマンドをまとめて。 (Zsh)

for i in *
do
mv $i $i[6,8].jpg
done


for i in <100->*
do
mv $i $(( ${i:r} + 36 )).jpg
done

for i in <23-99>*
do
mv $i 0$(( ${i[2,3]:r} + 36 )).jpg
done

for i in *
do
mv $i $i[6,8].jpg                 
done

files=(*)

for i in ${(aO)files}
do
mv $i 0$(( ${i[2,3]:r} + 17 )).jpg
done

for i in *
do
mv $i $i[6,8].jpg                 
done

for i in <100->*
do
mv $i $(( ${i:r} + 4 )).jpg 
done

for i in <93-99>*    
do
mv $i 0$(( ${i[2,3]:r} + 4 )).jpg   
done

for i in 01??.*
do
mv $i $i[2,-1]
done

for i in *
do
mv $i 0$(( ${i[3]:r} + 90 )).jpg
done

重要なのは

  • 必ず後ろからやる
  • nnnなので、100をこえるものは先にやる
  • 0nnなものはArithmatic expansionの前にちゃんと0をつける

そして、紙がまとめて通ってしまったものはどうしようもないし、ページが何の順番でもないものは再スキャンしたほうが早いので、ゴミ箱から探して組み直し。 所要時間はだいたい4時間。

ほんっとに疲れた。

BerryjackをTwitpic対応に

これはあくまで学術研究目的のレポートである。活用は自己責任にてお願いしたい。

まずはdiff

diff --git a/berryjack b/berryjack
index 378a970..c00e889 100755
--- a/berryjack
+++ b/berryjack
@@ -124,11 +124,12 @@ function get_media_url()
       fi
     done
     # image
-    for image in $( grep -Eo 'https://pbs\.twimg\.com/media/[a-zA-Z0-9_\-]+\.(jpg|png)' $tmp | sort | uniq | \
-      (if [ $orig -eq 0 ]; then
-        cat
+    for image in $( grep -Eo 'https://pbs\.twimg\.com/media/[a-zA-Z0-9_\-]+\.(jpg|png)|https?://twitpic.com/[a-z0-9]*' $tmp | sort | uniq | \
+      (
+      if [ $orig -eq 0 ]; then
+        sed 's#twitpic.com/\([0-9a-z]*\)#twitpic.com/show/full/\1#'
       else
-        sed 's/$/:orig/g'
+        sed 's#twitpic.com/\([0-9a-z]*\)#twitpic.com/show/full/\1#' | sed '/pbs\.twimg\.com/ s/$/:orig/g'
       fi
       ) )
     do
     

TwitpicのURIを拾うのは、単純にorでgrepがTwitpicのURIを拾ってくれるようにすれば良い。 だが、TwitpicのURIは画像のURIではなくページのURIなので、sedで画像のURIに変換している。 pbs.twimg.comのURIも対象にしているが、該当部分はおそらくないため、単純に無視される。 なお、URIはhttps?://twitpic.com/[a-z0-9]*に合致するもののみを想定している。

これで取得できる。 Crawlするためのシンプルなコード。保存するベースとなるディレクトリにおき、<userid>/.idというファイルを作成する。

#!/usr/bin/zsh

for idfile in */.id
do
  (
 	  print -- "-*- -*- -*- ${${idfile:h}:t} -*- -*- -*-"
   	cd "${idfile:h}"
   	berryjack "${${idfile:h}:t}"
  )
done

Manjaro 0.8.13 セットアップ作業 part2

パッケージの順次インストール

  • umplayer-svn
  • kuickshow
  • gqview
  • chromium-pepper-flash
  • smtube
  • youtube-dl
  • atomicparsley
  • gnome-terminal
  • cutemarked
  • haroopad
  • screen
  • at
  • nemo-share
  • unbound

screenは、.zshrcでSSH接続に対してscreenを呼んでいるので必要。

atはrsoftmirrorが使っているので必要。

Gnome Terminalは、Nemoが必要とするため(Gnome Terminalがないと、Nemoで「端末で開く」とした時に開くことができない)に導入した。

ファイアウォール

とりあえず、

# touch /etc/iptables/iptables.rules

した上で

# iptables -P OUTPUT ACCEPT
# iptables -P FORWARD DROP
# iptables -N INCOMING
# iptables -A INCOMING -m state --state ESTABLISHED,RELATED -j ACCEPT
# iptables -A OUTPUT -o lo -j ACCEPT
# iptables -A INPUT -j INCOMING
# iptables -A INCOMING -p udp --dport 53 -j ACCEPT
# iptables -A INCOMING -p udp --dport 5353 -j ACCEPT
# iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# iptables -P INPUT DROP
# iptables-save > /etc/iptables/iptables-rules

して

# systemctl start iptables

だが、ルールがどうにも気に入らなかったので、結局gufwを使ってしまった。 なんとか早くnftをマスターしたいところではある。

SpamAssasinのham学習

Spamはだいたい固めてあるが、Hamは非常に広い階層にあるため、次のスクリプトで処理した。

setopt EXTENDED_GLOB
integer ind

for i in ~/Mail/inbox/**/*~*/SystemMessage(#q/)
do
{
#  print $i
for j in "$i"/<->(#q.[1,100])
do
	cp -v "$j" ~/tmp/ham/$(( ++ind ))
done
} always { TRY_BLOCK_ERROR=0 }
done

これでMHフォルダ内にある全てのメールが集約できる (事前にspamは排除しておく必要あり) ため、あとはディレクトリをhamで学習させれば良い。

マウスカーソルが歪む問題とCinnamon

相変わらず出現する。XFceだと、それなりの頻度ででるようだ。 KDEに関してはそこまで使い続けたことがないため、出るかどうかは分からない。 KDE4では最も出現しやすかったが。

結局、UIが改善されたXFceでもKDEでもなく、Cinnamonに戻ることとなった。 LINEのフォント問題も、なぜかCinnamonで起動すると綺麗に表示される。

ちなみに、以前から感じていたことだが、Cinnamonのフォントレンダリングが異様に綺麗だ。 サブピクセルレンダリングの設定に関わらず。 これは一体なんなんだろう。

また、以前は機能しなかったCinnamonのスクリーンショット機能もきちんと動作した。 もしかして以前はGnome-Screenshotが入っていなかったりしたのだろうか。

Gnome-ScreenshootはXDG-PICTURES以下に自動で保存するため、ガンガン撮れる代わりに、ちゃんと分類しないと後で痛い目を見る。

Cinnamonに戻したため、またウィンドウショッピング(GUIの外観の設定)に時間を費やすこととなった。 壁紙を前回から引き継ぎ、黒ベースのテーマとしてかなり綺麗に仕上がった(以前はそもそも設定していなかった)のだが、そうするとウィンドウ色などを独自に定義するものとうまく噛み合わなかったりする。 Mikutterに関してはダークテーマに設定したが、Geanyが常に白背景に白字を使うのだが、にも関わらず入力中の部分についてはGTKのテキスト背景色を使う。ダークテーマだとテキスト背景色は黒に近いため、見えない。 変換するとシステムカラーになるため問題ないが…

結局、日本語テキストが多い場合は、meditを使う、というスタイルに戻ることになりそうだ。

マウスカーソルテーマでLCDテーマに青がないため、黒を使っているがテキストエリアでロストすることがちらほらあるのも残念。

メインシステムをEncFS on BtrfsからBtrfs on dm-cryptに変更

概要

散々やるよといっていた構成変更だが、ついに実行した。

作業はかなり面倒で、1時間半ほどかかってしまった。

今までは次のようになっていた。

  • 4ディスクによるBtrfsを編成
  • Btrfsはサブボリュームを持つ
  • ひとつのサブボリュームを丸々EncFSとする
  • .zshrcでencfsコマンドを呼び、マウントする

この方法のメリットは次のようなものだ

  • 強力で柔軟な暗号化
  • ファイル単位でのバックアップが、暗号化したまま可能
  • 手軽
  • rootでもユーザーデータにアクセスできないので、かなり安全

一方デメリットは次のようなもの

  • rootでユーザーデータにアクセスできないとKVMが暗号化されたディスクにイメージを置けないなど結構不便
  • 特にバックアップで困る
  • ファイルアクセスは常にEncFSがトップ。どのプロセスがIO負荷になっているのかわからない

下準備

先日のrsoftmirrorとrsyncを使ってデータをすべてスレーブにバックアップ

dm-crypt

4kBのurandomデータをめちゃくちゃに暗号しまくって、その中から512Bを切り出す方法で鍵ファイルを作成。これを使う。

cryptsetup --hash=sha512 --key-size=512 --ciper="twofish-xts-plain" --offset=0 key-file=/etc/worldencmount/keyfile --type=plain open device name

単にopen。LUKSではluksFormatという専用のアクションがあるが、プレーンだとない。 ちなみに、twofish-xts-plain結構遅い

これをしただけだとBtrfsの存在はblkidでは残っているように見えてしまう。 そこでrebootするのだが、その前に/etc/fstabを編集してbtrfsをマウントしないようにしておかないと痛い目を見る。

Btrfs

リブートしたら暗号化を行うのと同じ手順でデバイスをオープンする。 Btrfsはそのオープンしたデバイスに対して行う。

# mkfs.btrfs -m raid1 -d raid1 -l WorldBtrfs /dev/mapper/world_enc_*

ちなみに、ストライプで高速化するのではなく、ミラーで分散してもらうことにした。 ミラーするかどうかは悩んだのだが、やはり安全をとってミラー。つまり、2ミラー、1バックアップになる。

サブボリュームを作ることを忘れていて若干ハマった。

# mount /dev/mapper/world_enc_1 /mnt/1
# cd /mnt/1
# btrfs subvolume create shared-world

設定ファイルにblkidでdm-cryptのPARTUUIDを送り込んでおく。 また、btrfsのUUIDを/etc/fstabに送り込んでおく。

# blkid | grep -F /dev/mapper/world_enc_* | tail -n 1 >> /etc/fstab

そしてvim

# vim /etc/fstab

コピペしてUUIDを書き換え、ゴミを消去し、コメントアウトを外し、noautoオプションを追加する。

修正

そういえばこの方法はdiskのパーティションを暗号化している。 これは推奨される方法だが、やはりできればwhole diskの暗号化をしたい。 LUKSも含め3TB以上のwhole diskの暗号化には対応している。

だが、そうするとかなり重大な問題が生じる。 PARTUUIDやPARTLABELが使えなくなるのだ。

これは結構困ったことで、どうしたものかと思ったが、/dev/disk/by-idを使うことにした。 これはWWIDと呼ばれる、システムに依存しないデバイスに対して永続的なIDであるという。 若干手間だが、確実だ。

ちなみにこの修正、同期がかなり進んだ段階でリセットする形で行った。 この修正に30分くらいかかってしまった。

セットアップスクリプト

#!/usr/bin/zsh

#read config file.
. /etc/worldencmount/worldrc || exit 1

#decryption
for disk in "${disks[@]}"
do
  cryptsetup --hash=sha512 --key-size=512 --cipher="twofish-xts-plain" --offset=0 --key-file=${keyfile:-/etc/worldencmount/seedfile} --type=plain open "$disk" dmcw_${disk:t}
done

.zshrc

#Check shared zshrc file.

if [[ -e $HOME/share/.zshrc ]]
then
  # Enable shared zshrc
  . $HOME/share/.zshrc
else
  # Disabled shared zshrc.
  
  print ~/share/.zshrc "is not exist." >&2
  print "Probably encrypted filesystem don't mount yet." >&2
  print "Let's mount it!" >&2
  print >&2
  
  ## Decryption ##
  if sudo btrfs filesystem show | grep -F -q HydrangeaMasterBtr
  then
    print "Btrfs filesystem already active." >&2
  else
    if sudo /usr/local/sbin/worldencmount
    then
      print "Okay, succeed to decrypt." >&2
    else
      print "Oh, failed to decrypt disks." >&2
      print "Aborting..." >&2
      exit 2
    fi
  fi
  
  ## Mount ##
  if sudo mount ~/share
  then
    print "Okay, succeed to mount the filesystem." >&2
    print "You can use the World." >&2
    exit 0
  else
    print "Gosh! failed to mount. I don't know why." >&2
    print "Please fix it." >&2
    exit 3
  fi
fi

いままでよりだいぶ複雑になった。 結局、自動マウントではなく.zshrcで処理するようにした。 このほうが合理的だ。

slave側の自動マウント

逆にslave側は常にマウントされているべきなので、systemdユニットを書く。

[Unit]
Description=Mount World filesystem.
After=network.service

[Service]
ExecStart=/usr/local/sbin/worldmount

[Install]
WantedBy=multi-user.target

そして自動起動

# systemctl enable worldmount

ものすごく手抜きで、実はまだテストしていないが、動くはずだ。 ちなみにCentOS7である。

同期

rsyncで新たに作られた~/shareに同期する。 これは復旧作業なので通常とは逆方向のsyncとなる。 結果的には約14時間かかった。gzipのためか時間はかかったが、容量は2.02TiBと約2/3に抑えられた。

鍵のバックアップ

鍵ファイルがなくなると困るのでバックアップ。 しかしそれだけでは暗号化している意味がないので、鍵ファイルも暗号化した上でCD-Rに焼く。 暗号化はopensslのencを使うのが手っ取り早く、確実。 復号化用のスクリプトも同梱した。

総論

今回の作業はそれなりにしんどかったが、それでも同期を別とすれば1時間半ほどで終了した。 (ただしそれは最初にしたようにパーティションベースで行った場合のみの話)

元々EncFSにしたのは暫定的な措置だったし、スレーブサーバー設置に伴ってこういった作業をすることは想定していた。

VHS変換用スクリプト

結局Windowsで記録するようにしたため、MPEGファイルをH.265形式に変換するためのスクリプト。

INDIR/SUBDIR/FILE.mpgファイルをOUTDIR/SUBDIR/SUBDIDR-NN.mkvに変換する。

ちょっと工夫をいれることでだいぶ楽にした。

#!/usr/bin/zsh

# Put your videos on a subdirectory under $WORKER_DIR.
WORKER_DIR=$HOME/mov/usr/vhs_converted/worker
# Videos will be put on a subdirectory under $DEST_DIR.
DEST_DIR=$HOME/mov/usr/vhs_converted/recorded/${dir:t}
# if $1 given, use as bitrate value.
bitrate={$1:512k}

for dir in $WORKER_DIR/*
do

typeset -i index=1

if [[ ! -e $DEST_DIR/${dir:t} ]]
then
mkdir $DEST_DIR/${dir:t}
fi

for i in $dir/*
do
ffmpeg -i "$i" -vcodec hevc -b:v $bitrate -aspect 720:480 $DEST_DIR/${dir:t}/${dir:t}-$(printf "%02d" $index).mkv
(( index++ ))
done
done

サイトの大幅手直し

ウェブサイトに直すべき点が大量にあったためにかなり手をいれることになった。

CSSグラデーション、デザインの修正

まずベンダープレフィクスを用いたCSS3のグラデーションを入れた。

/* headline level2 decoration */
h2 {
width: 100%;
background: -moz-linear-gradient(left -65deg, #fff, #acf);
background: -webkit-gradient(linear, left top, right bottom, from(#fff), to(#acf));
}

これはAki SI&Eのほうのもの。これを使い、サイトロゴは透過画像として(新規に作成した)、背景にグラデーションを入れた。画像はフォアグラウンドのイメージ

/* Header container (Top position of Main container) */
#Header {
background: #fff;
padding: 0 1.3em;
max-height: 125px;
}

#SITE_REASONSET #Header img.sitebanner {
margin-right : 40%;
width: 50%;
}

#SITE_REASONSET #Header {
background: -moz-linear-gradient(left -65deg, #f0f8ff, #acf);
background: -webkit-gradient(linear, left top, right bottom, from(#f0f8ff), to(#acf));
}

SI&Eのほうはメインコンテナが950pxとっているのに切り替えを750pxにしていたのでこれも修正。

Aki SI&Eのほうはキャプチャのスタイルを変更。

h1 {
border-top : double 5px;
border-bottom: double 5px;
color: #39f;
margin-left: -1em;
margin-right: -1em;
margin-top: -0.8em;
text-align: center;

}

マイナスのmarginはあまり見ない気がする。このようなデザインはほとんど見ることはないが、なかなかパッとみてすっきりと見えて目を引く良いデザインだと思う。このために、テキストインデントのマイナスは消した。テキストインデントのマイナスは一行目にのみ適用されるらしい(brで改行すればその都度適用されるのだろうが、折り返しには適用されない)。

サイトバーウィジットの変更

AKi SI&Eが先行で変更され、それがReasonSetに反映され、さらにReasonSetで追加された機能がAki SI&Eのほうに入った。

<!-- Sub column -->

<!-- Menu -->

-*- Menu -*-

<%= ENV["SUBCOLUMN_CONTENT"] || "" %>

<!-- Notes if any -->
% unless DOC.notes.empty?

-*- Notes -*-
    % DOC.notes.each do |note|

  1. <%= note %>
  2. %end

% end

<!-- Navigation links with PureDoc and PureBuilder template -->
% if DOC.meta["link"] && ( DOC.meta["link"]["next"] || DOC.meta["link"]["content"] || DOC.meta["link"]["prev"] || DOC.meta["link"]["start"] )|| ENV["reasonset_link_content"] || ENV["reasonset_link_start"]

-*- Referrances -*-

% end

<!-- / Sub column -->

このために、これまでSubCloumnコンテナ直下にメニューがあったが、それとcontent_boxクラスを分離して、同クラスのdiv要素を追加している。

/* White background */
.content_box {
background-color: #fff;
}

/* Padding for continuous content box. */
.content_box + .content_box {
margin-top: 11px;
}

Aki SI&Eのほうはもう少し手の込んだものになっている。

#SideBar .container {
padding : 1.28em;
}

#SideBar .container:last-child {
margin: 0;
}

CSS3の:last-child擬似プロパティを使うことで最後が間延びしてしまうことを防ぎながら、要素を分離してチェックをみせるようにしている。

注釈をサイドバー内にも表示するようにした他、内容的にはこれまでコンテンツ側にあったACCSのserial articleのreferranceをサイドバーに表示するように変更した。

レイアウト方法の変更

これでサイドバーが長くなったのだが、なぜかこれではメインコンテナが短縮されてしまう。あくまでメインカラムの高さに合わせるのだ。構造としては

なのでこの挙動は謎だが、MozillaでもBlinkでも同様の挙動を示す。

floatすると一見うまくいくようだが、逆にサイドカラムの高さに合わせてしまい、メインカラムが突き抜けてしまう。また、幅もきちんと規定しなくてはいけない。

結局、3者全てinline-blockにしてしまえば高さは正しく確保される。ただし、これでは2つのカラムはmarginがとられないためぴったりくっついてしまう。

そこでこれをmargin, padding, border-widthが全て0のコンテナとし、paddingは別のブロックで確保する。paddingは設定してもよかったのかもしれないが、このほうが管理しやすい。

エッセイ用のほうが先行して開発されたので、構造が甘かった。ReasonSetはもう少し安定しているが、やはり作りなおしたほうが良いと思われる。ちなみに、ReasonSetが安定しているのは、高さが規定されているためでもある。

ここまでしたのだが、inline-blockで幅を%指定だとGeckoでは見られるが、Blinkでは上下にレイアウトされてしまう。ピクセル指定にすれば大丈夫だが、relativeにした上でレイアウトし、さらにfloatすれば%指定でもBlinkで正常に表示される。

ACCSの修正

まず、TOC機能がエラーになるようになっていた。以前はRuby2.0でやっていたが、2.1になったからだろうか?どうも、クラスが違うオブジェクトを持つArrayを比較しようとしている、ということらしい。文字列しか格納しないように思うのだが、一応to_s

essaies.push [ sortitem.to_s, fp.to_s, meta["title"] ]

TOCに戻るリンクが間違っていた。これは、設定ファイルがreasonset_link_contentsとしているのに、テンプレートはreasonset_link_contentとしているという単純な理由だった。これは、HTMLのlink要素に対応するのでcontentsが正しい。ちなみに、Essayにはその機能がそもそも含まれていなかった。

そして、ACCS記事間のリンクが機能しない。Talkin’ About(Aki’s Palace)との違いが見つからず、一体…と思ったのだが、grepしてみるとその設定はrbutilに含まれており、chienomiが専用で使っている。chienomiのリンクが機能していたので、当然Talkin’ Aboutでも機能しているものと思い込んでいたが、実際はTalkin’ Aboutでは機能しないわけだ。単に記事がひとつしかないので気づかなかっただけだ。

基本的にはACCS記事内でそのスクリプトをロードすれば機能するのだが、ロードすべきスクリプトが存在しない。chienomiのものはパスがハードコーディングされている。そのため、それを修正したバージョンを用意した。

また、「最初の記事」だが、

export reasonset_link_start="si/$wd_element/${artdir:+${$(print $artdir/*.pdoc):r}.html}"

となっていた。Talkin’ Aboutでは機能していたが、これは記事がひとつしかないからだ。つまり、複数のファイルがあると、それが連なった文字列(スペース区切り)の最後だけpdocをhtmlに変えることになってしまう。

当然、先頭のファイル名のみをとり出さなくてはいけない。(#q[1])で機能しなかったので、

export reasonset_link_start="si/$wd_element/${artdir:+${$(print -l $artdir/*.pdoc | head -n 1):r}.html}"

と変更した。単純な方法だが、そこまで繰り返し呼び出すわけではないので、head(1)を呼び出す程度どうということはないだろう。

ノートインデックスの作成

ノートのインデックスを作るため、まずPureDoc側を修正。その機能をスーパークラスに追加。

### Notes ###
# Add note text to an array
def addnote(note)
@notes.push note
end

attr :notes

サブクラスで呼び出し

# Note
def n(text, ¬e)
puredoc_element(:n) do
note = note.call
addnote note
'%s</pure:note>' % [esc!(note), esc(text) ]
end
end

ちなみに、HTMLクラスタイプだけノートとテキストが逆になっていた。どちらが正しいのかよくわからなくなっている。仕様を規定すべきだろう。

そして今修正した。

def n(note, &text)
puredoc_element(:n) do
addnote esc!(note)
'%s</pure:note>' % [esc!(note), esc(text.call) ]
end
end

これでノートインデックスの作成が可能になった。だが、これに対応した表示が本文に必要だ。これはCSSに任せる。

body {
counter-reset : notes;
}

/* Note element */
.puredoc_note:after /*, puredoc|note:after */ {
vertical-align: super;
font-size: 80%;
content : "\002020" counter(notes);
counter-increment: notes;
text-decoration: none;
}

content中の文字参照はどうするのだろう、と思ったら、16進数にした上でクォート内で

FontConfig, Zsh compilation, Zsh prompt

FontConfig

うまく動かないといっていたlocal.conf及びUser configだが、どうも/etc/fonts/conf.d/のリンクをmvして戻すと有効になるようだ。理由はわからず、競合するリンクを削除しても変わらない。

また、それでもなぜかUserに対してのserifだけが適用されない。謎だ。

Zsh Compilation

以下の設定を追加してみた。

# Complation options
setopt auto_menu
setopt glob_complete
setopt auto_list
setopt list_beep
setopt list_types
setopt rec_exact
menu_complete

menu_completeは使いにくかったのでやめた。

Zsh Prompt

Momonga Linuxのzshrcサンプルを流用していて、これが結構いかしたプロンプトなのだが、Manjaroのetc版と比べてGitが有効になっていないので、有効にすると同時にちょっといじってみた。

zshのプロンプトにgitのステータスをシンプル可愛くを参考にしたのだが、これをそのまま追加するとRPROMPTと競合してしまう。どうやら、プロンプトの設定自体を動的に行うとRPROMPTの文字数が計算できなくなるようだ。

RPROMPT込みで流用できるのはきたけーさんのブログだが、これだと情報量が少ない。

そこでこれを合体させ、さらに色使いを変更し、終了ステータスで変更するのをプロンプト全体ではなくプロンプト($)のみにした。

## prompt
# USER –>
# For git. I referred http://kitak.hatenablog.jp/entry/2013/05/25/103059 and http://yoshiko.hatenablog.jp/entry/2014/04/02/zshのプロンプトにgitのステータスをシンプル可愛く. Thank you.
# VCSの情報を取得するzshの便利関数 vcs_infoを使う
autoload -Uz vcs_info

# 表示フォーマットの指定
# %b ブランチ情報
# %a アクション名(mergeなど)
zstyle ‘:vcs_info:*’ formats ‘[%b]’
zstyle ‘:vcs_info:*’ actionformats ‘[%b|%a]’
precmd () {
psvar=()
LANG=en_US.UTF-8 vcs_info
if [[ -n “$vcs_info_msg_0_” ]]
then
psvar[1]=”$vcs_info_msg_0_”
if [[ -n “$vcs_info_msg_1_” ]]
then
psvar[2]=”$vcs_info_msg_1_”
elif [[ -n “$vcs_info_msg_2_” ]]
then
psvar[2]=”$vcs_info_msg_2_”
elif [[ -n `echo “$st” | grep “^Untracked”` ]]
then
psvar[2]=”Untracked”
else
psvar[2]=”Clean”
fi
fi
}

# バージョン管理されているディレクトリにいれば表示,そうでなければ非表示
#RPROMPT=”%1(v|%F{green}%1v%f|)”
# <– USER
PROMPT=’%F{cyan}[%n@%m${WINDOW:+[$WINDOW]} %.] %1(v|%F{yellow}%1v|)%2(v|(%2v) |)%{%(?.$fg[green].$fg[red])%}%(!.#.$)%{$reset_color%} ‘
RPROMPT=[%~]
SPROMPT=’zsh: replace ‘\”%R’\” to ‘\”%r’\” ? [Yes/No/Abort/Edit] ‘

結構イケる。

ReasonSet サイト構築システム PureBuilder

PureBuilderは以前から何度か登場し、完全にやり直し、と繰り返している。今回は、「本体スクリプト」というものがない状態で登場し、ついに実用となった。

まず下地となるPureBuilderだが、かなり更新され、GitHubにも既に反映されている。まずこれがベースになる。

そして、「PureDocを使った翻訳を自動化する」というのがPureBuilderの基本的なところだ。PureBuilderのGitHubにあるpurebuild-puredoc.zshがその根幹となるスクリプトである。

はっきり言ってGitHubにある設定ファイルのサンプルとREADMEを読むのが一番早いと思うのだが、つまりこのスクリプトは.purebuild-puredoc.rc, .up_*, .rebuild-rulesファイルに従って実行するための補助ユーティリティでしかない。だが、設定ファイルを適切に書くことでほぼ全面的な自動化を実現している。サンプルをみれば分かるように、メニューを動的生成していたりする(eRubyを使っている)ため、サイトでユーザーからのアクションが自動的に反映されるような仕組みを持たないドキュメントに関してはこの方法で動的に静的ドキュメントを生成することができる。これは、著しいオーバーヘッド削減につながる。

参考までに、ReasonSetのサイトのメニューの構築を行うテンプレートは次のようなものだ。

<% menu = [
[ ["ReasonSet / Help", "//reasonset.net/", "ReasonSet全体の情報"], (ENV["SITETYPE"] == "REASONSET"), [["Profile", "//reasonset.net/profile.html", "正木はるか(柊美亜紀)のプロフィール"]] ],
[ ["HARUKA Sound", nil, "音楽プロダクション/プロジェクト HARUKA Soundの公式ページ(お仕事)"], nil, [] ],
[ ["Aki's palace", nil, "正木はるか(柊美亜紀)に関するウェブサイト。本人について、意見など"], (ENV["SITETYPE"] == "AKI"), [ ["Talkin' about", nil, "様々なテーマに対しそのフィルタを通して語る" ] ] ],
[ ["ちぇのみ-Chienomi-", nil, "コンピュータ"] , (ENV["SITETYPE"] == "CHIENOMI"), ["COLUMN", nil, "コラム" ], ["+Play programming with JavaScript", nil, "JavaScriptでプログラミングを遊ぶ"] ["+Easy step programming with Perl", nil, "Perlではじめるプログラミング入門"], ["+Easy step object oriented programming with Ruby", nil, "Rubyではじめるオブジェクト指向入門"] , ["Live with Linux", "http://reasonset.net/journal/archives/category/livewithlinux", "Linux日記" ] ],
[ ["Feel the Earth", nil, "バイク"], (ENV["SITETYPE"] == "MOTO"), [["Impression",nil,"所有車両のインプレッション"], ["Impression:HONDA VT250J SPADA", nil], ["Impression: SUZUKI SV400S", nil], ["Impression:YAMAHA MT-09", nil], ["Column", nil, "コラム"] ] ],
[ ["Blogs", "//reasonset.net/blog.html", "ブログ"], (ENV["SITETYPE"] == "BLOG") , [["journal de Aki", "http://reasonset.net/journal/", "本来のブログ"], ["Ameblo", "http://ameblo.jp/reasonset", "くだらないブログ"]] ],
[ ["SI Service", "http://reasonset.net/si/", "コンピュータの技術を供与するサービスについて(お仕事)"], nil, [] ]
]
%>

<div id="ReasonsetMenu">
% menu.each do | name, cond, items |
% if name[1]
<div class="menu_title"><a href="<%= name[1] %>" title="<%= name[2] %>"><%= name[0] %></a></div>
% else
<div class="menu_title"><span title="<%= name[2] %>"><%= name[0] %></span></div>
% end
% items.each do |i|
% if i[1]
<div class="menu_item"><a href="<%= i[1] %>" title="<%= i[2] %>"><%= i[0] %></a></div>
% else
<div class="menu_item"><span title="<%= i[2] %>"><%= i[0] %></span></div>
% end
% end if cond
% end
</div>

さらに、よりプロフィールに関してはプロフィール自体がRuby scriptとなっており、それを実行することでPureDocを拡張した形式で書かれたプロフィールを展開する。

もっとも、プロフィールを含むスクリプトはあくまで出力するだけであり、実際にアップロードする処理は.up_profileというスクリプトを書いて実行している。

全体には伝播する環境変数をうまくつかった仕組みになっていると思う。