OSDN Git Service

update URL (sj.jp ==> osdn.jp)
[howm/howm.git] / ext / hcal.rb
1 #!/usr/bin/ruby -sKe
2 # -*- Ruby -*-
3
4 # require 'jcode'
5 require 'iconv'
6 require 'date'
7
8 def usage
9   name = File::basename $0
10   print <<EOU
11 #{name}: Í½Äê¤ä todo ¤Î°ìÍ÷¤ò½ÐÎÏ
12 (Îã)
13   #{name} *.memo  ¢ª ¥«¥ì¥ó¥À¡¼(ͽÄꡦ¡ºÀÚ¡¦ºÑ¤ß¤Î°ìÍ÷)¤ò½ÐÎÏ
14   #{name} -l *.memo  ¢ª ½ÜÅÙ½ç¤Î todo °ìÍ÷¤ò½ÐÎÏ
15   #{name} -i *.memo  ¢ª iCalendar ·Á¼°¤Ç½ÐÎÏ (ºîÀ®Ãæ)
16   #{name} -h ¤Þ¤¿¤Ï #{name} -help  ¢ª ¤³¤Î¥á¥Ã¥»¡¼¥¸¤ò½ÐÎÏ
17 (¹àÌܤνñ¼°)
18   https://howm.osdn.jp/ »²¾È
19 EOU
20 end
21
22 ############################################
23 # const
24
25 $schedule_types ||= '[@!.]'
26 $type_alias = {'' => '-'}
27 $date_sep = '-'
28
29 def item_regexp(types)
30   x = $date_sep
31   if $format == 'old'
32     $todo_types ||= '[-+~!.]?'
33     %r|(@\[(\d\d\d\d)#{x}(\d\d)#{x}(\d\d)\](#{types})(\d*)\s+(.*))$|
34   else
35     $todo_types ||= '[-+~!.]'
36     %r|(\[(\d\d\d\d)#{x}(\d\d)#{x}(\d\d)\](#{types})(\d*)\s+(.*))$|
37   end
38 end
39
40 # calendar
41
42 $comment_width ||= 12
43 $comment_width = $comment_width.to_i
44
45 $schedule_mark ||= '@'
46 $deadline_mark ||= '!'
47 $done_mark ||= '.'
48 $type_display = {
49   '@' => $schedule_mark,
50   '!' => $deadline_mark,
51   '.' => $done_mark,
52   '?' => '¢¥'
53 }
54 $type_order = {'@' => 2, '!' => 1, '.' => 3, '?' => 4}
55 $today_mark ||= ' <<<<##>>>>'
56 $same_day_mark ||= ' #>>>>'
57
58 # todo
59
60 $priority_func = {
61 #  ''  => lambda{|lt, lz| pr_normal lt, lz},
62   '-' => lambda{|lt, lz| pr_normal lt, lz},
63   '+' => lambda{|lt, lz| pr_todo lt, lz},
64   '~' => lambda{|lt, lz| pr_defer lt, lz},
65   '!' => lambda{|lt, lz| pr_deadline lt, lz},
66   '@' => lambda{|lt, lz| pr_schedule lt, lz},
67   '.' => lambda{|lt, lz| pr_done lt, lz},
68   '?' => lambda{|lt, lz| 0},
69 }
70
71 # defaults
72 $lz_normal = 1
73 $lz_todo = 7
74 $lz_defer = 30
75 $lz_deadline = 7
76
77 # init
78 $pr_todo = -7
79 $pr_defer = -14
80 $pr_defer_peak = 0
81 $pr_deadline = -2
82
83 $huge = 77777
84 $huger = 88888
85 $pr_normal_bottom   = - $huge 
86 $pr_todo_bottom     = - $huge 
87 $pr_defer_bottom    = - $huge 
88 $pr_deadline_bottom = - $huge 
89 $pr_deadline_top    = $huge        
90 $pr_done_bottom     = - $huger
91
92 # misc
93
94 $now = Time::now.to_f
95 $daysec = 60 * 60 * 24
96
97 ############################################
98 # func
99
100 def late(time)
101   ($now - time.to_f) / $daysec
102 end
103
104 # def late(y, m, d, now)
105 #   ($now - Time::local(y,m,d,0,0,0).to_f) / $daysec
106 # end
107
108 def relative_late(late, laziness, default)
109   laziness = default if laziness == 0
110   late / laziness
111 end
112
113 def pr_normal(lt, lz)
114   r = relative_late lt, lz, $lz_normal
115   r >= 0 ? - r : $pr_normal_bottom + r
116 end
117
118 def pr_todo(lt, lz)
119   r = relative_late lt, lz, $lz_todo
120   c = - $pr_todo
121   r >= 0 ? c * (r - 1) : $pr_todo_bottom + r
122 end
123
124 def pr_defer(lt, lz)
125   r = relative_late lt, lz, $lz_defer
126   c = $pr_defer_peak - $pr_defer
127   v = 2 * (((r % 1) - 0.5).abs)
128   r >= 0 ? $pr_defer_peak - c * v : $pr_defer_bottom + r
129 end
130
131 def pr_deadline(lt, lz)
132   r = relative_late lt, lz, $lz_deadline
133   c = - $pr_deadline
134   if r > 0
135     $pr_deadline_top + r
136   elsif r < -1
137     $pr_deadline_bottom + r
138   else
139     c * r
140   end
141 end
142
143 # dummy
144 def pr_schedule(lt, lz)
145   0
146 end
147
148 def pr_done(lt, lz)
149   $pr_done_bottom + lt
150 end
151
152 ############################################
153 # main
154
155 if ($help || $h)
156   usage
157   exit 0
158 end
159
160 def item(types)
161   ARGF.grep(item_regexp(types)){|x|
162     h = Hash::new
163     h[:text] = $1
164     y, m, d = [$2, $3, $4].map{|s| s.to_i}
165 #     h[:y] = y = $2.to_i
166 #     h[:m] = m = $3.to_i
167 #     h[:d] = d = $4.to_i
168     h[:time] = time = Time::mktime(y, m, d)
169     h[:type] = type = $type_alias[$5] || $5
170     h[:laziness] = laziness = $6.to_i
171     h[:comment] = $7
172     h[:priority] = - $priority_func[type].call(late(time), laziness)
173     h[:file] = ARGF.filename
174     h[:line] = ARGF.file.lineno
175     h
176   }
177 end
178
179 # def select_type(item, types)
180 #   item.select{|h| types.member? h[:type]}
181 # end
182
183 ### todo
184
185 def todo()
186   item($todo_types).sort{
187     |a, b| a[:priority] <=> b[:priority]
188   }.each{|a|
189     puts "#{a[:file]}:#{a[:line]}:#{a[:text]}"
190   }
191 end
192
193 ### ical
194
195 $conv = Iconv.new("UTF-8", "EUC-JP")
196
197 def ical()
198   puts <<_EOS_
199 BEGIN:VCALENDAR
200 VERSION:2.0
201 PRODID:-//howm.sourceforge.jp//hcal.rb $Revision: 1.12 $//EN
202 CALSCALE:Gregorian
203 #{item($schedule_types).map{|h| ical_item h}.select{|z| z}.join.chomp}
204 END:VCALENDAR
205 _EOS_
206 end
207
208 def ical_item(h)
209 #    if !Date.valid_date?(h[:y], h[:m], 1)
210 #      $stderr.puts 'Invalid date:#{h[:file]}:#{h[:line]}:#{h[:text]}'
211 #      return nil
212 #    end
213 #   # convert 2005-09-31 to 2005-10-01
214 #   d = Date.new(h[:y], h[:m], 1) + (h[:d] - 1)
215 #   d = Date.new h[:y], h[:m], h[:d]
216 #   s, e = [d, d+1].map{|z| z.strftime '%Y%m%d'}
217   s, e = [h[:time], h[:time] + 86400].map{|z| z.strftime '%Y%m%d'}
218   return <<_EOS_
219 BEGIN:VEVENT
220 DTSTART:#{s}
221 DTEND:#{e}
222 SUMMARY:#{$conv.iconv(h[:type] + h[:comment])}
223 END:VEVENT
224 _EOS_
225 end
226
227 ### schedule
228
229 def schedule()
230   cal = Hash::new
231   item($schedule_types).each{|h|
232     t = h[:time]
233     cal[t] ||= Array::new
234     cal[t].push h
235     # [2004-12-25]@3 ==> [2004-12-25]@ [2004-12-26]@ [2004-12-27]@
236     if h[:type] == '@' && h[:laziness] > 1
237       (1...h[:laziness]).each{|d|
238         td = t + 60 * 60 * 24 * d
239         cal[td] ||= Array::new
240         cal[td].push h
241       }
242     end
243   }
244   min_time = cal.keys.min
245   max_time = cal.keys.max
246   t = min_time
247   while t <= max_time
248     c = cal[t] || []
249     puts if t.wday == 0
250     puts "----------------<#{t.month}>---------------- #{t.year}" if t.day == 1
251     day = t.strftime '%d %a'
252     text = c.sort{|a,b|
253       x, y = [a, b].map{|z| [$type_order[z[:type]], z[:comment]]}
254       x <=> y
255 #       $type_order[a[:type]] <=> $type_order[b[:type]]
256     }.map{|h|
257       h[:comment].sub!(%r|^(cancel)? *\[[#{$date_sep}0-9]+\][!+]?[0-9]*\s*|){|s| $1 ? 'x' : ''} if h[:type] == '.'  # adhoc!
258       $type_display[h[:type]] + h[:comment].split(//)[0, $comment_width].join
259     }.join ' '
260     mark = if t.strftime('%Y%m%d') == Time::now.strftime('%Y%m%d')
261              $today_mark
262            elsif t.strftime('%m%d') == Time::now.strftime('%m%d')
263              $same_day_mark
264            else
265              ''
266            end
267     puts "#{day} #{text}#{mark}"
268     t += 60*60*24
269   end
270 end
271
272 ### main
273
274 if $l
275   todo
276 elsif $i
277   ical
278 else
279   schedule
280 end