OSDN Git Service

set version as 1.4.4-snapshot
[howm/howm.git] / ext / howmkara
1 #!/usr/bin/ruby -s
2 # -*- coding: euc-jp -*-
3 # -*- Ruby -*-
4 # $Id: howmkara,v 1.7 2010-12-31 12:12:47 hira Exp $
5
6 # Convert ~/howm/ to HTML or other formats.
7 # Only RD format is supported unless you will give me patches. :p
8
9 #############################################################
10
11 require 'cgi'
12
13 def usage
14   name = File::basename $0
15   print <<EOU
16 #{name}: howm ¥á¥â¤ò HTML ²½
17 ¡¦¤ä¤Ð¤¤Ê¸»ú¤ò¥¨¥¹¥±¡¼¥×
18 ¡¦¥­¡¼¥ï¡¼¥É¤ò¥ê¥ó¥¯¤ËÊÑ´¹
19 ¡¦¥Ø¥Ã¥À¤È¥Õ¥Ã¥¿¤ò¤Ä¤±¤ë
20 (Îã)
21   #{name} ~/howm/ ~/converted/
22   ls ~/howm/*/*/*7-*.txt | #{name} -list ~/converted/
23   grep -rl '¤Û¤²' ~/howm/ | #{name} -list ~/converted/
24 (¥ª¥×¥·¥ç¥óÎã)
25   -list                     ¢ª ¥á¥â¥Õ¥¡¥¤¥ë¤Î¥ê¥¹¥È¤òɸ½àÆþÎϤ«¤éÆɤà
26   -exclude='^[.]|CVS'       ¢ª Âоݳ°¤Î¥Õ¥¡¥¤¥ë¤òÀµµ¬É½¸½¤Ç»ØÄê
27   -home='index.html'        ¢ª ¡ÖHome¡×¤Î¥ê¥ó¥¯Àè
28   -silent ¤Þ¤¿¤Ï -s         ¢ª ¿ÊĽɽ¼¨¤ò¤·¤Ê¤¤
29   -help ¤Þ¤¿¤Ï -h           ¢ª ¤³¤Î¥á¥Ã¥»¡¼¥¸¤òɽ¼¨
30   (-debug                   ¢ª ¥Ç¥Ð¥Ã¥°ÍѽÐÎÏ)
31 EOU
32 end
33
34 argv_len = $list ? 1 : 2
35 if ($help || $h || ARGV.length != argv_len)
36   usage
37   exit 0
38 end
39
40 #############################################################
41
42 $exclude ||= "^[.\#]|CVS|~$"
43 $silent ||= $s
44 $summary_length ||= 70
45 $come_from ||= "<<< *(.+)$"
46 $come_from_pos ||= 1
47
48 #############################################################
49
50 def notice(str)
51   STDERR.print str if !$silent
52 end
53
54 def ls_R(dir)
55   a = Array::new
56   Dir::open(dir){|d| d.each{|f| a.push f}}  # map doesn't work??
57   b = Array::new
58   a.each{|f|
59     next if f =~ /#$exclude/
60     path = File::expand_path f, dir
61     b.push f if FileTest::file? path
62     b += ls_R(path).map{|g| "#{f}/#{g}"} if FileTest::directory? path
63   }
64   return b
65 end
66
67 # FixMe :-(
68 def mkdir_p(path)
69   parent = File::dirname path
70   return true if parent == path  # root dir
71   mkdir_p parent
72   if !FileTest::exist? path
73     Dir::mkdir path
74   end
75 end
76
77 # FixMe :-(
78 def split_base(file_list)
79   fs = file_list.map{|f| File::expand_path f}
80   ds = fs.map{|f| File::dirname f}
81   common = ds[0] || ''
82   ds.each{|d|
83     while common != d
84       if common.length <= d.length
85         d = File::dirname d
86       else
87         common = File::dirname common
88       end
89     end
90   }
91   rs = fs.map{|f| f[(common.length + 1)..-1]}  # +1 for '/'
92   return [common, rs]
93 end
94
95 class HashList < Hash
96   def cons(key, val)
97     self[key] ||= Array::new
98     self[key].push val
99   end
100 end
101
102 # class Array
103 #   def flatten
104 #     ret = []
105 #     self.each{|x| ret += x}
106 #     ret
107 #   end
108 # end
109
110 class String
111   def offsets(pattern)
112     a = Array::new
113     pos = 0
114     # necessary for use of last_match. sigh...
115     pattern = Regexp::new(Regexp::quote(pattern)) if pattern.is_a? String
116     while (i = index pattern, pos)
117       a.push Regexp.last_match.offset(0)
118       pos = i + 1
119     end
120     return a
121   end
122 end
123
124 module Bundle
125   def expand_readlines(f)
126     open(File::expand_path(f, base_dir), 'r'){|io| io.readlines.join}
127 #     open(File::expand_path(f, base_dir), 'r'){|io| io.read}  # for ruby-1.7?
128   end
129   def first_line(f)
130     open(File::expand_path(f, base_dir), 'r'){|io| io.gets.chop}
131   end
132   def link_tag(f)  # Clean me. ²¿ÅÙ¤â¸Æ¤Ö¤Î¤Ï¤«¤Ã¤³°­¤¤.
133     fline = first_line f
134     [:link, f + '.b', f + ': ' + fline[0, $summary_length]]
135   end
136 end
137
138 #############################################################
139
140 class Formatter
141   attr_accessor :home
142   def initialize(home = nil)
143     @home = home
144   end
145   def newpage
146     @result = ''
147   end
148   def put(*com)
149     com.each{|c| put_one c}
150   end
151   def put_one(command)
152     type = command.shift
153     case type
154     when :pre
155       items = command.shift
156       @result += "<pre>\n"
157       put *items
158       @result += "</pre>\n"
159     when :as_is
160       @result += CGI::escapeHTML(command.shift)
161     when :link
162       link, str = command
163       url = link.is_a?(String) ? link + '.html' : link[1]
164       @result += %!<a href="#{url}">#{CGI::escapeHTML str}</a>!
165     when :list
166       items = command.shift
167       @result += "<ol>\n"
168       items.each{|i| @result += ' <li>'; put_one i; @result += "\n"}
169       @result += "</ol>\n"
170     end
171   end
172   def wrapped_result(title)
173     etitle = CGI::escapeHTML title
174     <<_EOS_
175 <html>
176 <head><title>#{etitle}</title></head>
177 <body>
178 <h1>#{etitle}</h1>
179 <hr>
180 #{@result}
181 <hr>
182 <a href="#{@home}">Home</a>
183 <a href="book.h.html">Files</a>
184 <a href="index.h.html">Keywords</a>
185 </body>
186 </html>
187 _EOS_
188   end
189   def write(title, file, dir)
190     f = File::expand_path(file, dir) + '.html'
191     mkdir_p File::dirname(f)
192     open(f, 'w'){|io| io.puts wrapped_result(title)}
193   end
194 end
195
196 class Book
197   include Bundle
198   attr_accessor :files, :base_dir
199   def initialize(files, base_dir)
200     @files    = files
201     @base_dir = base_dir
202   end
203   def first_page
204     link_tag(@files[0])
205   end
206   def write(dest_dir, formatter)
207     index = Index::new @files, @base_dir
208     write_each  dest_dir, formatter, index
209     write_list  dest_dir, formatter
210     index.write dest_dir, formatter
211   end
212   def write_list(dest_dir, formatter)
213     formatter.newpage
214     formatter.put [:list, @files.sort.map{|f|
215       link_tag f
216 #       first_line = open(File::expand_path f, @base_dir){|io| io.gets.chop}
217 #       [:link, f + '.b', f + ': ' + first_line[0, $summary_length]]
218     }]
219     formatter.write 'Files', 'book.h', dest_dir
220     notice ".\n"
221   end
222   def write_each(dest_dir, formatter, index)
223     @files.each{|f|
224       formatter.newpage
225       formatter.put [:pre, interpret(expand_readlines(f), index, f)]
226       formatter.write first_line(f), f + '.b', dest_dir
227 #       formatter.write f, f + '.b', dest_dir
228       notice '.'
229     }
230     notice "\n"
231   end
232   def interpret(src, index, f)
233     hit = search src, index, f
234     cursor = 0
235     ret = []
236     while !hit.empty?
237       h = hit.shift
238       b, e, key = h
239       case cursor <=> b
240       when -1  # eat until beginning of this hit, and retry
241         ret.push [:as_is, src[cursor...b]]
242         hit.unshift h
243         cursor = b
244       when 0  # expand this hit
245         s = src[b...e]
246         if key == :url
247           link = [:url, s]
248         elsif key == :decl
249           s =~ /#$come_from/
250           w = Regexp::last_match[$come_from_pos]
251           link = CGI::escape(CGI::escape(w)) + '.i'
252         else
253           decl = index.decl[key]
254           link = decl.member?(f) ? nil : decl[0] + '.b'
255         end
256         ret.push(link ? [:link, link, s] : [:as_is, s])
257         cursor = e
258       when 1  # discard this hit
259       end
260     end
261     ret.push [:as_is, src[cursor..-1]]
262     ret
263   end
264   def search(src, index, f)
265     hit = []
266     index.decl.each_key{|k|
267       offsets = src.offsets k
268       index.used.cons k, f if !offsets.empty? && !index.decl[k].member?(f)
269       hit += offsets.map{|o| o.push k}
270     }
271     hit += src.offsets(%r{http://[-!@#\$%^&*()_+|=:~/?a-zA-Z0-9.,;]*[-!@#\$%^&*()_+|=:~/?a-zA-Z0-9]+}).map{|o| o.push :url}
272     hit += src.offsets($come_from).map{|o| o.push :decl}
273     hit.sort{|h1, h2| earlier_longer h1, h2}
274   end
275   def earlier_longer(h1, h2)
276     [h1[0], - h1[1]] <=> [h2[0], - h2[1]]
277   end
278 end
279
280 class Index
281   include Bundle
282   attr_accessor :files, :base_dir
283   attr_reader :decl, :used
284   def initialize(files, base_dir)
285     @files    = files
286     @base_dir = base_dir
287     @decl = HashList::new
288     @used = HashList::new
289     search_decl
290   end
291   def search_decl
292     @files.each{|f|
293       expand_readlines(f).scan($come_from){|hit| @decl.cons hit[0], f}
294     }
295   end
296   def write(dest_dir, formatter)
297     write_each dest_dir, formatter
298     write_list dest_dir, formatter
299   end
300   def write_list(dest_dir, formatter)
301     formatter.newpage
302     formatter.put [
303       :list,
304       @decl.keys.sort.map{|key|
305         [:link, CGI::escape(CGI::escape(key)) + '.i', key + " (#{(@used[key]||[]).length})"]
306       }
307     ]
308     formatter.write 'Keywords', 'index.h', dest_dir
309     notice ".\n"
310   end
311   def write_each(dest_dir, formatter)
312     @decl.each_key{|key|
313       f = CGI::escape(key) + '.i'
314       to_decl = @decl[key].map{|g| link_tag g}
315       to_used = (@used[key] || []).map{|g| link_tag g}
316       to_rel  = related_keys(key).map{|g| [:link, @decl[g][0] + '.b', g]}
317 #       to_decl = @decl[key].map{|g| [:link, g + '.b', g]}
318 #       to_used = (@used[key] || []).map{|g| [:link, g + '.b', g]}
319 #       to_rel  = related_keys(key).map{|g| [:link, @decl[g][0] + '.b', g]}
320       formatter.newpage
321       c = [
322         [:as_is, "Declared:\n"],
323         [:list, to_decl],
324         [:as_is, "Linked:\n"],
325         [:list, to_used],
326         [:as_is, "Related:\n"],
327         [:list, to_rel],
328       ]
329       formatter.put *c
330       formatter.write key, f, dest_dir
331       notice '.'
332     }
333     notice "\n"
334   end
335   def related_keys(key)
336     sub = included_keys key
337     sub.map{|k| @decl.keys.select{|x| x.include? k and x != key}}.flatten.uniq.sort
338   end
339   def included_keys(key)
340     @decl.keys.select{|k| key.include? k}
341   end
342 end
343
344 #############################################################
345
346 if $list
347   dest_dir = ARGV.shift
348   src_dir, files = split_base(STDIN.readlines.map{|s| s.chomp})
349 else
350   src_dir, dest_dir = ARGV
351   files = ls_R src_dir
352 end
353 notice "#{files.length} files "
354
355 b = Book::new files, src_dir
356 i = Index::new files, src_dir
357 notice "(#{i.decl.length} entries)\n"
358
359 $home ||= b.first_page[1] + '.html'
360 fmt = Formatter::new $home
361 b.write dest_dir, fmt