Gauche:CGI:スケジュール予定表:Shiro版:ver1
何はともあれカレンダー
まずは、カレンダー機能を実現してみましょうか。
カレンダーの基本関数
カレンダーを作るのに必要なのは、与えられた月の最初の日の曜日とその月の 日数を知ることだ。基本的な材料(閏年の公式、曜日を求める公式など) から組み立ててもいいのだが、めんどくさい。せっかくSRFI-19で 基本的な日付を扱う関数が定義されているので、それを使ってしまおう。 (参考: GaucheRefj:srfi-19)。 また、次の月、前の月を求める関数も必要なので作っておく。
(define (make-month m y) (make-date 0 0 0 0 1 m y (date-zone-offset (current-date)))) (define (first-day-of-month date) (make-month (date-month date) (date-year date))) (define (next-month date) (if (= (date-month date) 12) (make-month 1 (+ (date-year date) 1)) (make-month (+ (date-month date) 1) (date-year date)))) (define (prev-month date) (if (= (date-month date) 1) (make-month 12 (- (date-year date) 1)) (make-month (- (date-month date) 1) (date-year date)))) (define (days-of-month date) (inexact->exact (- (date->modified-julian-day (next-month date)) (date->modified-julian-day (first-day-of-month date)))))
make-monthというのはちょっとミスリーディングな名前だが、 yy年mm月1日の日付を返す関数だ。make-dateが似たようなパターンで 多く使われるので括り出しといた。
<date>は構造体なので演算がやりにくい。日付の差などを取るには ある基準日からの日数に変換するのが楽だ。srfi-19ではjulian-dayおよび modified-julian-dayがその用途に用意されている (両者は基準日が 異なるだけ)。
さて、カレンダーは数字を一週間ごとに区切って並べるので、 表示形式(HTML)のことはとりあえず置いといて、リストでカレンダーを 表現してみよう。例えば31日あって、1日が火曜である月のカレンダーなら、 次のようなリストにする。
((#f #f 1 2 3 4 5) (6 7 8 9 10 11 12) (13 14 15 16 17 18 19) (20 21 22 23 24 25 26) (27 28 29 30 31 #f #f))
リストを指定の長さに切りそろえてゆくslices (GaucheRefj:slices)という 関数がutil.listモジュールにある。やりたい操作は、 「開始日までの曜日の数だけ#fを並べたリストと、1〜31までの数字を 並べたリストをつなげて、それを7つづつに切りそろえる。 最後の足りないぶぶんには#fを補う」、ということになる。
(define (date-slices-of-month date) (slices (append (make-list (date-week-day (first-day-of-month date)) #f) (iota (days-of-month date) 1)) 7 #t #f))
ここまでで基本的なカレンダー関数は揃った。 これらの式を評価すれば、インタラクティブに動作を確認することが出来る。 (srfi-1, srfi-19, util.listをuseしておくのを忘れずに)。
gosh> (first-day-of-month (current-date)) #<date 2004/08/01 00:00:00.000000000 (-36000)> gosh> (next-month (current-date)) #<date 2004/09/01 00:00:00.000000000 (-36000)> gosh> (prev-month (current-date)) #<date 2004/07/01 00:00:00.000000000 (-36000)> gosh> (next-month (next-month (current-date))) #<date 2004/10/01 00:00:00.000000000 (-36000)> gosh> (date-slices-of-month (current-date)) ((1 2 3 4 5 6 7) (8 9 10 11 12 13 14) (15 16 17 18 19 20 21) (22 23 24 25 26 27 28) (29 30 31 #f #f #f #f)) gosh> (date-slices-of-month (next-month (current-date))) ((#f #f #f 1 2 3 4) (5 6 7 8 9 10 11) (12 13 14 15 16 17 18) (19 20 21 22 23 24 25) (26 27 28 29 30 #f #f))
カレンダーのtable表示
まず、作成するテーブルのイメージを持っておこう。こんなかんじかな。
← 2004/8 → 日 月 火 水 木 金 土 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ...
左上、右上の矢印はそれぞれ前月、次月へのリンク、また各日付は、 その日の予定メモへのリンクと。
リンクの仕様をどうするか、なんてのを深く考えるのは後回し。 リンクを貼る部分だけを関数で括り出しておけば、あとからどうにでも 変えられる。今はとりあえず必要な情報だけをそれらしくqueryパラメータに 埋め込んどこう。
HTMLを生成するにもいくつか方法があるが、簡単なcgiスクリプトだったら text.html-liteモジュールを使ってしまうのが楽だ (GaucheRefj:text.html-lite)。 もひとつ、以下のコードでは文字列に対するmapを使っているので、 gauche.sequenceもuseしておく。(リストを使ってもいいけど、 らくちんでしょ)。
(define (month->link date content) (html:a :href #`"?y=,(date-year date)&m=,(date-month date)" content )) (define (date-cell year month date) (if date (html:a :href #`"?y=,|year|&m=,|month|&d=,|date|" date) "")) (define (calendar date) (html:table (html:tr (html:td (month->link (prev-month date) "←")) (html:td :colspan 5 :align "center" #`",(date-year date)/,(date-month date)") (html:td (month->link (next-month date) "→"))) (html:tr (map html:td "日月火水木金土")) (map (lambda (w) (html:tr (map (lambda (d) (html:td (date-cell (date-year date) (date-month date) d))) w))) (date-slices-of-month date))))
動作の確認をするには、text.treeモジュールもuseして、 calendar関数の結果をwrite-treeで表示してみせるのが良いだろう。
gosh> (write-tree (calendar (current-date))) <table><tr><td><a href="?y=2004&m=7">←</a ></td ><td colspan="5" align="center">2004/8</td ><td><a href="?y=2004&m=9">→</a ></td ></tr ><tr><td>日</td ><td>月</td ><td>火</td ... (省略) ...
cgiにする
HTMLが出てるみたいなんで、cgiにして動かしてみようか。 最初はとにかく現在の月のカレンダーを表示する、というところに とどめておく。
www.cgiモジュールを使ってみる。 www.cgiモジュールでは色々な機能が提供されているけど、 とりあえず何かを表示するだけなら、 次のように簡単なコードで済む。
(define (main args) (cgi-main (lambda (params) (list (cgi-header) (html:html (html:head (html:title "簡易スケジュール表")) (html:body (calendar (current-date))))) )))
今回のまとめ
これまでのコードをまとめるとこんな感じになる。 適当なところにcal1.cgiみたいな名前で置いて、実行パーミッションをつけて、 webブラウザからアクセスしてみよう。 パラメータの解析はしてないので、どのリンクをつついても 現在の月しか表示されないけど。
もしInternal Server Errorなどが出てしまった場合は、 cal1.cgiを直接コマンドラインから起動して問題が出ないかどうかを 調べることができる。 コマンドラインから起動した場合、次のような表示が出て入力待ちに なる(これはcgi-mainが出している)。 ここで y=2004 などのパラメータを渡すことができる。 何も渡すものが無ければEOT (通常は^D)をタイプする。
[shiro@scherzo shiro.dreamhost.com]$ ./cal1.cgi Enter parameters (name=value). ^D to stop.
正常なら、Content-Type: text/htmlに続いてHTMLが標準出力に 出力されるはず。エラーになった場合はエラーメッセージを 手がかりに問題を解決する。
コマンドラインからだと正常に動くのに、webサーバ経由だと 動かない、という場合は、パーミッション (webサーバのuser idで cgiスクリプトが実行可能か)、cgi実行環境からgoshは見えているか、 またhttpdが正しく設定されているか、等を調べることになる。
#!/usr/bin/env gosh (use srfi-1) (use srfi-19) (use util.list) (use text.html-lite) (use gauche.sequence) (use www.cgi) ;;; ;;; Calendar Logic ;;; (define (make-month m y) (make-date 0 0 0 0 1 m y (date-zone-offset (current-date)))) (define (first-day-of-month date) (make-month (date-month date) (date-year date))) (define (days-of-month date) (inexact->exact (- (date->modified-julian-day (next-month date)) (date->modified-julian-day (first-day-of-month date))))) (define (next-month date) (if (= (date-month date) 12) (make-month 1 (+ (date-year date) 1)) (make-month (+ (date-month date) 1) (date-year date)))) (define (prev-month date) (if (= (date-month date) 1) (make-month 12 (- (date-year date) 1)) (make-month (- (date-month date) 1) (date-year date)))) ;; returns list slices for calendar. (define (date-slices-of-month date) (slices (append (make-list (date-week-day (first-day-of-month date)) #f) (iota (days-of-month date) 1)) 7 #t #f)) ;;; ;;; Display ;;; (define (month->link date content) (html:a :href #`"?y=,(date-year date)&m=,(date-month date)" content )) (define (date-cell year month date) (if date (html:a :href #`"?y=,|year|&m=,|month|&d=,|date|" date) "")) (define (calendar date) (html:table (html:tr (html:td (month->link (prev-month date) "←")) (html:td :colspan 5 :align "center" #`",(date-year date)/,(date-month date)") (html:td (month->link (next-month date) "→"))) (html:tr (map html:td "日月火水木金土")) (map (lambda (w) (html:tr (map (lambda (d) (html:td (date-cell (date-year date) (date-month date) d))) w))) (date-slices-of-month date)))) ;;; ;;; Main entry ;;; (define (main args) (cgi-main (lambda (params) (list (cgi-header) (html:html (html:head (html:title "簡易スケジュール表")) (html:body (calendar (current-date))))) ))) ;; Local variables: ;; mode: scheme ;; end: