OSDN Git Service

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