#!/usr/bin/ruby1.6 -Ke
# -*- ruby -*-

## today
##
## Author:  Yoshinari Nomura <nom@quickhack.net>
##
## Created: 1999/11/11
## Revised: 2004/01/12 10:24:37
##
## today was originally written in perl.
## This  is a ruby version of it. Original authors were
##
##    Yoshinari Nomura <nom@quickhack.net>
##    OHARA Shigeki <os@iij.ad.jp>
##

$DEBUG2       = false

MailServer    = 'localhost'  ## for --mail option
MyHostName    = 'localhost'  ## for --mail option

require 'mhc-schedule'
require 'mhc-kconv'
require 'net/smtp'

class String
  # digest a string upto max size.
  # when the bound is unsafe for muti-bytes, replace the character with '$'
  def digest (max, tail_adjust = '$', fill_up = nil)
    euc = (Kconv .toeuc self)[0..max-1]

    if fill_up and euc .size < max
      return euc + fill_up * (max - euc .size)
    end

    is2byte = false
    euc .each_byte do |char|
      is2byte = ! is2byte if char & 0x80 == 0x80
    end

    if is2byte
      euc[max-1] = tail_adjust
    end
    return euc
  end
end

def usage(do_exit = true)
  STDERR .print "usage: today [options]
  Show your today's schedules.
  --help               show this message.
  --format=FORMAT      change output format to one of: ps, html
  --category=CATEGORY  pick only in CATEGORY. 
                       '!' and space separated multiple values are allowed.
  --date=strig[+n]     set a period of date.
                       string is one of these:
                         today, tomorrow, sun ... sat, yyyymmdd, yyyymm
                       yyyymm lists all days in the month.
                       list n+1 days of schedules if +n is given.
                       default value is 'today+0'
  --mail=ADDRESS       send a e-mail to ADDRESS instead of listing to stdout.\n"
  exit if do_exit
end

def string_to_date(string, range)
  date_from = nil
  date_to   = nil

  case (string .downcase)
  when 'today'
    date_from = MhcDate .new

  when 'tomorrow'
    date_from = MhcDate .new .succ

  when /^(sun|mon|tue|wed|thu|fri|sat)/
    date_from = MhcDate .new .w_this(string .downcase)

  when /^\d{8}$/
    date_from = MhcDate .new(string)

  when /^\d{6}$/
    date_from = MhcDate .new(string + '01')
    if range
      date_to = date_from .succ(range .to_i)
    else
      date_to = MhcDate .new(string + format("%02d", date_from .m_days))
    end
  else
    return nil
  end

  date_to   = date_from .succ((range || '0') .to_i) if !date_to
  return [date_from, date_to]
end

def get_schedule(db, from, to, category, formatter, hook = nil)
  ret = ''

  db .search(from, to, category) .each{|date, items|
    ret += formatter .call(date, items)
  }
  if hook
    return hook .call(db, from, to, category, ret)
  else
    return ret
  end
end

################################################################
## formatter

formatter_normal = Proc .new{|date, items|
  ret = ''
  heading = format("%02d/%02d %s ", date .m, date .d, date .w_s)

  first = true
  items .each{|sch|
    heading = heading .gsub(/./, ' ') if !first
    ret += heading + format("%-11s %s%s\n",
			    sch .time_as_string,
			    sch .subject,
			    if sch .location and sch .location != ''
			      ' [' + sch .location + ']'
			    else
			      ''
			    end)
    first = false
  }
  MhcKconv::todisp(ret)
}

formatter_ps = Proc .new{|date, items|
  ret = ''
  heading = format("%d", date .d)

  items .each{|sch|
    if sch .in_category?('Holiday')
      ret += '__HOLIDAY__'
    end
    ret += heading + 
      format(" (%-5s %s)__XXX__",
	     sch .time_b .to_s,
	     sch .subject .digest(15) .gsub(/([()])/, '\\\\\1'))
  }
  MhcKconv::tops(ret)
}

formatter_html = Proc .new{|date, items|
  buffer_max, buffer = 30, ''

  week = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][date .w]

  items2 = []
  items .each do |schedule|
    time = schedule .time_b
    time &&= "#{time} "
    items2 .push Kconv .toeuc("#{time}#{schedule .subject}")
  end

  if (string = items2 .join(", ")) .size > buffer_max
    string = items2 .collect {|item| item[0,10]} .join(", ")
  end
  buffer += format("%02d/%02d(%s)", date .m, date .d, week) + " "
  buffer += string .digest(buffer_max) + "<BR>\n"
  MhcKconv::tohtml(buffer)
}

################################################################
## hook

hook_ps = Proc .new {|db, from, to, category, ret|
  ps_contents = open(__FILE__) .gets(nil) .split("__END__\n")[1]
  landscape = true # xxx
  holiday  = ''
  schedule = ''

  ret .split('__XXX__') .each{|item|
    if item =~ /^__HOLIDAY__(.*)/
      holiday += $1
    else
      schedule += item
    end
  }
  trans_table = {
    '@MONTH@'     => from .m .to_s,
    '@YEAR@'      => from .y .to_s,
    '@TFONT@'     => 'Times-Bold',
    '@DFONT@'     => 'Helvetica-Bold',
    '@EFONT@'     => 'Times-Roman',
    '@JFONT@'     => 'Ryumin-Light-EUC-H',
    '@HOLIDAYS@'  => holiday,
    '@SCHEDULES@' => schedule,
    '@BANNER@'    => '',
    '@LFOOT@'     => '',
    '@RFOOT@'     => '',
    '@CFOOT@'     => '',
    '@SCALE@'     => landscape ? '1.0 1.0' : '0.75 0.75', 
    '@ROTATE@'    => landscape ? '90'      : '0', 
    '@TRANSLATE@' => landscape ? '50 -120' : '50 900'
  }
  trans_table .each{|key,val|
    ps_contents .gsub!(key, val)
  }
  ps_contents
}

################################################################
## main

date_from   = date_to = MhcDate .new
formatter   = formatter_normal
hook_proc   = nil

while option = ARGV .shift
  case (option)
  when /^--category=(.+)/
    category = $1

  when /^--format=(.+)/
    case $1
    when 'html'
      formatter = formatter_html
    when 'ps'
      formatter = formatter_ps
      hook_proc = hook_ps
      date_from = date_from .m_first_day
      date_to   = date_from .m_last_day
    else
      formatter = formatter_normal
    end

  when /^--date=([^+]+)(\+(-?[\d]+))?/
    date_from, date_to = string_to_date($1, $3) || usage()

  when /^--mail=(.+)/
    mail_address = $1

  else
    usage()
  end
end  

user_name = ENV['USERNAME'] || ENV['USER'] || ENV['LOGNAME'] || mail_address

print "date_from: #{date_from .to_s}\n" if $DEBUG2
print "date_to:   #{date_to   .to_s}\n" if $DEBUG2
print "e-mail:    #{mail_address}\n"    if $DEBUG2

db = MhcScheduleDB .new

if mail_address
  header    = "To: #{mail_address}\n"
  header   += "From: secretary-of-#{mail_address}\n"
  header   += "Subject: Today's schedule (#{date_from .to_s})\n"
  header   += "\n"
  header   += "#{user_name}'s schedule: \n\n"
  contents  = 
    get_schedule(db, date_from, date_to, category, formatter, hook_proc)

  if contents && contents != ''
    message = MhcKconv::tomail(header + contents)
    Net::SMTPSession .start(MailServer, 25, MyHostName) {|server|
      server .sendmail(message, mail_address, [mail_address])
    }
  end
else
  print get_schedule(db, date_from, date_to, category, formatter, hook_proc)
end

################################################################
# postscript code from pscalj.sh
################################################################
__END__
%!
% PostScript program to draw calendar
% Copyright (C) 1987 by Pipeline Associates, Inc.
% Permission is granted to modify and distribute this free of charge.

% The number after /month should be set to a number from 1 to 12.
% The number after /year should be set to the year you want.
% You can change the title and date fonts, if you want.
% We figure out the rest.
% This program won't produce valid calendars before 1800 due to the switch
% from Julian to Gregorian calendars in September of 1752 wherever English
% was spoken.

%% For Japanese. Added by ichimal, 2000/2/6.
%% Original code is generated by k2ps.
/copyfont {     % font-dic extra-entry-count  copyfont  font-dic
        1 index maxlength add dict begin
        {       1 index /FID ne 2 index /UniqueID ne and
                {def}{pop pop} ifelse
        } forall
        currentdict
        end
} bind def

%% For Japanese. Added by ichimal, 2000/2/6.
%% Original code is generated by k2ps.
/narrowfont {   % ASCIIFontName EUCFontName  compositefont  font'
    findfont dup /FontType get 0 eq {
        12 dict begin
            %
            % 7+8 bit EUC font
            %
            12 dict begin
                /EUCFont exch def
                /FontInfo (7+8 bit EUC font) readonly def
                /PaintType 0 def
                /FontType 0 def
                /FontMatrix matrix def
                % /FontName
                /Encoding [
                    16#00 1 16#20 { pop 0 } for
                    16#21 1 16#28 { 16#20 sub } for
                    16#29 1 16#2F { pop 0 } for
                    16#30 1 16#74 { 16#27 sub } for
                    16#75 1 16#FF { pop 0 } for
                ] def
                /FMapType 2 def
                EUCFont /WMode known
                { EUCFont /WMode get /WMode exch def }
                { /WMode 0 def } ifelse
                /FDepVector [
                    EUCFont /FDepVector get 0 get
                    [ 16#21 1 16#28 {} for 16#30 1 16#74 {} for ]
                    {
                        13 dict begin
                            /EUCFont EUCFont def
                            /UpperByte exch 16#80 add def       
                            % /FontName
                            /FontInfo (EUC lower byte font) readonly def
                            /PaintType 0 def
                            /FontType 3 def
                            /FontMatrix matrix def
                            /FontBBox {0 0 0 0} def
                            /Encoding [
                                16#00 1 16#A0 { pop /.notdef } for
                                16#A1 1 16#FE { 16#80 sub 16 2 string cvrs
                                                (cXX) dup 1 4 -1 roll
                                                putinterval cvn } for
                                /.notdef
                            ] def
                            % /UniqueID
                            % /WMode
                            /BuildChar {
                                gsave
                                exch dup /EUCFont get setfont
                                /UpperByte get
                                2 string
                                dup 0 4 -1 roll put
                                dup 1 4 -1 roll put
                                dup stringwidth setcharwidth
                                0 0 moveto show
                                grestore
                            } bind def
                            currentdict
                        end
                        /lowerbytefont exch definefont
                    } forall
                ] def
                currentdict
            end
            /eucfont exch definefont
            exch
            findfont 1 copyfont dup begin
                /FontMatrix FontMatrix [.83 0 0 1 0 0.05] matrix concatmatrix def
            end
            /asciifont exch definefont
            exch
            /FDepVector [ 4 2 roll ] def
            /FontType 0 def
            /WMode 0 def
            /FMapType 4 def
            /FontMatrix matrix def
            /Encoding [0 1] def
            /FontBBox {0 0 0 0} def
            currentdict
        end
    }{
        pop findfont 0 copyfont
    } ifelse
} def

/month @MONTH@ def
/year @YEAR@ def
/titlefont /@TFONT@ def
/dayfont /@DFONT@ def
%% For Japanese. Changed by ichimal, 2000/2/6.
%% Original code is generated by k2ps.
% /eventfont /@EFONT@ def
/Courier-Ryumin
    /Courier /@JFONT@ narrowfont definefont pop
/eventfont /Courier-Ryumin def

/holidays [ @HOLIDAYS@ ] def
/schedules [ @SCHEDULES@ ] def
/Bannerstring (@BANNER@) def
/Lfootstring (@LFOOT@) def
/Rfootstring (@RFOOT@) def
/Cfootstring (@CFOOT@) def

% calendar names - change these if you don't speak english
% "August", "April" and "February" could stand to be kerned even if you do

/month_names
[ (January) (February) (March) (April) (May) (June) (July)
(August) (September) (October) (November) (December) ]
def

/day_names
[ (Sunday) (Monday) (Tuesday) (Wednesday) (Thursday) (Friday) (Saturday) ]
def

% layout parameters - you can change these, but things may not look nice

/daywidth 100 def
/dayheight 95 def

/titlefontsize 48 def
/weekdayfontsize 10 def
/datefontsize 30 def
/footfontsize 20 def

/topgridmarg 35 def
/leftmarg 35 def
/daytopmarg 10 def
/dayleftmarg 5 def

% layout constants - don't change these, things probably won't work

/rows 5 def
/subrows 6 def

% calendar constants - change these if you want a French revolutionary calendar

/days_week 7 def

/days_month [ 31 28 31 30 31 30 31 31 30 31 30 31 ] def

/isleap {				% is this a leap year?
	year 4 mod 0 eq			% multiple of 4
	year 100 mod 0 ne 		% not century
	year 1000 mod 0 eq or and	% unless it's a millenia
} def

/ndays {				% number of days in this month
	days_month month 1 sub get
	month 2 eq			% February
	isleap and
	{
		1 add
	} if
} def

/weekday {				% weekday (range 0-6) for integer date
	days_week mod
} def

/startday {				% starting day-of-week for this month
	/off year 2032 sub def		% offset from start of "epoch"
	off
	off 4 idiv add			% number of leap years
	off 100 idiv sub		% number of centuries
	off 1000 idiv add		% number of millenia
	4 add weekday days_week add 	% offset from Jan 1 2032
	/off exch def
	1 1 month 1 sub {
		/idx exch def
		days_month idx 1 sub get
		idx 2 eq
		isleap and
		{
			1 add
		} if
		/off exch off add def
	} for
	off weekday			% 0--Sunday, 1--monday, etc.
} def

/prtevent {		% event-string day prtevent
			%  print out an event
	/start startday def
	/day 2 1 roll def
	day start add 1 sub 7 mod daywidth mul
	day start add 1 sub 7 div truncate dayheight neg mul 
	-5 
	numevents day start add get -10 mul add
	numevents
	day start add 
	numevents day start add get 1 add
	put
	add moveto
	show
} def

/drawevents {		% read in a file full of events; print
			%  the events for this month
	/numevents
	[0 0 0 0 0 0 0
	 0 0 0 0 0 0 0
	 0 0 0 0 0 0 0
	 0 0 0 0 0 0 0
	 0 0 0 0 0 0 0
	 0 0 0 0 0 0 0] def 
	 eventfont findfont 9 scalefont setfont
	 0 2 holidays length 2 sub { % for the "Holidays"
		dup
		1 add holidays 2 1 roll get
		2 1 roll holidays 2 1 roll get
		prtevent
	} for
	 0 2 schedules length 2 sub { % for the "Schedules"
		dup
		1 add schedules 2 1 roll get
		2 1 roll schedules 2 1 roll get
		prtevent
	} for
		
} def

% ------------------------------------------------------------------------

/prtnum { 3 string cvs show } def

/center {				% center string in given width
	/width exch def
	/str exch def width str 
	stringwidth pop sub 2 div 0 rmoveto str show
} def

/centernum { exch 3 string cvs exch center } def

/drawgrid {				% draw calendar boxes
	titlefont findfont weekdayfontsize scalefont setfont
	currentpoint /y0 exch def /x0 exch def
	0 1 days_week 1 sub {
		submonth 0 eq
		{
			x0 y0 moveto
			dup dup daywidth mul 40 rmoveto
			day_names exch get
			daywidth center
		} if
		x0 y0 moveto
		daywidth mul topgridmarg rmoveto
		1.0 setlinewidth
		submonth 0 eq
		{
			/rowsused rows 1 sub def
		}
		{
			/rowsused rows def
		}
		ifelse
		0 1 rowsused {
			gsave
			daywidth 0 rlineto 
			0 dayheight neg rlineto
			daywidth neg 0 rlineto
			closepath stroke
			grestore
			0 dayheight neg rmoveto
		} for
	} for
} def

/drawnums {				% place day numbers on calendar
	dayfont findfont datefontsize
	submonth 0 ne
	{
		2.5 mul
	} if scalefont setfont
	/start startday def
	/days ndays def
	start daywidth mul dayleftmarg add daytopmarg rmoveto
	submonth 0 ne
	{
		dayleftmarg neg dayheight -2 div rmoveto
	} if
	1 1 days {
		/day exch def
		gsave
		day start add weekday 0 eq
		{
			submonth 0 eq
			{
				.7 setgray
			} if
		} if
		day start add weekday 1 eq
		{
			submonth 0 eq
			{
				.7 setgray
			} if
		} if
		%% Added by ichimal, 2000.2
        submonth 0 eq {
            0 2 holidays length 2 sub {
                holidays 2 1 roll get day eq {
                    .7 setgray
                    exit
                } if
            } for
        } if
		submonth 0 eq
		{
			isdouble
			{
				day prtdouble
			}
			{
				day prtnum
			} ifelse
		}
		{
			day daywidth centernum
		} ifelse
		grestore
		day start add weekday 0 eq
		{
			currentpoint exch pop dayheight sub 0 exch moveto
			submonth 0 eq
			{
				dayleftmarg 0 rmoveto
			} if
		}
		{
			daywidth 0 rmoveto
		} ifelse
	} for
} def
/isdouble {				% overlay today with next/last week?
	days start add rows days_week mul gt
	{
		day start add rows days_week mul gt
		{
			true true
		}
		{
			day start add rows 1 sub days_week mul gt
			day days_week add days le and
			{
				false true
			}
			{
				false
			} ifelse
		} ifelse
	}
	{
		false
	} ifelse
} def

/prtdouble {
	gsave
	  dayfont findfont datefontsize 2 mul 3 div scalefont setfont
	  exch
	  {
		(23/) stringwidth pop dayheight rmoveto
		prtnum
	  }
	  {
		0 datefontsize 5 div rmoveto
		prtnum
		0 datefontsize -5 div rmoveto
		gsave
		  dayfont findfont datefontsize scalefont setfont
		  (/) show
		grestore
	  } ifelse
	grestore
} def

/drawfill {				% place fill squares on calendar
	/start startday def
	/days ndays def
	currentpoint /y0 exch def /x0 exch def
	submonth 0 eq
	{
		usefirst
		{
			/fillstart 2 def
		}
		{
			/fillstart 0 def
		}
		ifelse
	}
	{
		/fillstart 0 def
	}
	ifelse
	fillstart daywidth mul topgridmarg rmoveto
	1.0 setlinewidth
	fillstart 1 start 1 sub {
		gsave
		.9 setgray
		daywidth 0 rlineto 
		0 dayheight neg rlineto
		daywidth neg 0 rlineto
		closepath fill
		grestore
		daywidth 0 rmoveto
	} for
	x0 y0 moveto
	submonth 0 ne
	{
		/lastday rows 1 add days_week mul def
		days_week 1 sub daywidth mul -440 rmoveto
	}
	{
		/lastday rows days_week mul 2 sub fillstart add def
		days_week 3 sub fillstart add daywidth mul
		-440 dayheight add rmoveto
	} ifelse
	lastday -1 ndays start 1 add add
	{
		/day exch def
		gsave
		.9 setgray
		daywidth 0 rlineto 
		0 dayheight neg rlineto
		daywidth neg 0 rlineto
		closepath fill
		grestore
		day weekday 1 eq
		{
			x0 y0 moveto
			days_week 1 sub daywidth mul -440 dayheight add rmoveto
		}
		{
			daywidth neg 0 rmoveto
		} ifelse
	} for
} def

/usefirst {				% are last two boxes used by days?
	start ndays add rows days_week mul 3 sub gt
	start 2 ge and
	
} def

/calendar
{
	titlefont findfont titlefontsize scalefont setfont
	0 60 moveto
	/month_name month_names month 1 sub get def
	month_name show
	/yearstring year 10 string cvs def
	daywidth days_week mul yearstring stringwidth pop sub 60 moveto
	yearstring show

	eventflag {
		% Show a centered Banner if any at the Top
		daywidth days_week mul 2 div
		Bannerstring stringwidth pop 2 div sub
		60 moveto
		Bannerstring show
		% Show footnotes left-center-right
		eventfont findfont footfontsize scalefont setfont
		/bottomrow { dayheight rows mul 5 sub neg } def
		0 bottomrow moveto
		Lfootstring show
		daywidth days_week mul Rfootstring stringwidth pop sub
		bottomrow moveto
		Rfootstring show
		daywidth days_week mul Cfootstring stringwidth pop sub 2 div
		bottomrow moveto
		Cfootstring show
		
	} if

	0 -5 moveto
	drawnums

	0 -5 moveto
	drawfill

	eventflag {
		0 0 moveto
		drawevents
	} if

	0 -5 moveto
	drawgrid
} def

/eventflag true def

@SCALE@	scale
@ROTATE@ rotate
@TRANSLATE@ translate
/submonth 0 def
calendar
/eventflag false def
month 1 sub 0 eq
{
	/lmonth 12 def
	/lyear year 1 sub def
}
{
	/lmonth month 1 sub def
	/lyear year def
} ifelse
month 1 add 13 eq
{
	/nmonth 1 def
	/nyear year 1 add def
} 
{
	/nmonth month 1 add def
	/nyear year def
} ifelse
usefirst
{
	0 30 translate
}
{
	days_week 2 sub daywidth mul -350 translate
}
ifelse
/submonth 1 def
/year lyear def
/month lmonth def
gsave
.138 .138 scale
12 -120 translate
calendar
grestore
/submonth 1 def
/year nyear def
/month nmonth def
daywidth 0 translate
gsave
.138 .138 scale
12 -120 translate
calendar
grestore

showpage
__END__

### Copyright Notice:

## Copyright (C) 1999, 2000 Yoshinari Nomura. All rights reserved.
## Copyright (C) 2000 MHC developing team. All rights reserved.

## Redistribution and use in source and binary forms, with or without
## modification, are permitted provided that the following conditions
## are met:
## 
## 1. Redistributions of source code must retain the above copyright
##    notice, this list of conditions and the following disclaimer.
## 2. Redistributions in binary form must reproduce the above copyright
##    notice, this list of conditions and the following disclaimer in the
##    documentation and/or other materials provided with the distribution.
## 3. Neither the name of the team nor the names of its contributors
##    may be used to endorse or promote products derived from this software
##    without specific prior written permission.
## 
## THIS SOFTWARE IS PROVIDED BY THE TEAM AND CONTRIBUTORS ``AS IS''
## AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
## FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
## THE TEAM OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
## INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
## (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
## SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
## HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
## STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
## ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
## OF THE POSSIBILITY OF SUCH DAMAGE.

### today ends here
