2 # -*- coding: euc-jp -*-
4 # $Id: howmkara,v 1.7 2010-12-31 12:12:47 hira Exp $
6 # Convert ~/howm/ to HTML or other formats.
7 # Only RD format is supported unless you will give me patches. :p
9 #############################################################
14 name = File::basename $0
16 #{name}: howm ¥á¥â¤ò HTML ²½
17 ¡¦¤ä¤Ð¤¤Ê¸»ú¤ò¥¨¥¹¥±¡¼¥×
18 ¡¦¥¡¼¥ï¡¼¥É¤ò¥ê¥ó¥¯¤ËÊÑ´¹
19 ¡¦¥Ø¥Ã¥À¤È¥Õ¥Ã¥¿¤ò¤Ä¤±¤ë
21 #{name} ~/howm/ ~/converted/
22 ls ~/howm/*/*/*7-*.txt | #{name} -list ~/converted/
23 grep -rl '¤Û¤²' ~/howm/ | #{name} -list ~/converted/
25 -list ¢ª ¥á¥â¥Õ¥¡¥¤¥ë¤Î¥ê¥¹¥È¤òɸ½àÆþÎϤ«¤éÆɤà
26 -exclude='^[.]|CVS' ¢ª Âоݳ°¤Î¥Õ¥¡¥¤¥ë¤òÀµµ¬É½¸½¤Ç»ØÄê
27 -home='index.html' ¢ª ¡ÖHome¡×¤Î¥ê¥ó¥¯Àè
28 -silent ¤Þ¤¿¤Ï -s ¢ª ¿ÊĽɽ¼¨¤ò¤·¤Ê¤¤
29 -help ¤Þ¤¿¤Ï -h ¢ª ¤³¤Î¥á¥Ã¥»¡¼¥¸¤òɽ¼¨
30 (-debug ¢ª ¥Ç¥Ð¥Ã¥°ÍѽÐÎÏ)
34 argv_len = $list ? 1 : 2
35 if ($help || $h || ARGV.length != argv_len)
40 #############################################################
42 $exclude ||= "^[.\#]|CVS|~$"
44 $summary_length ||= 70
45 $come_from ||= "<<< *(.+)$"
48 #############################################################
51 STDERR.print str if !$silent
56 Dir::open(dir){|d| d.each{|f| a.push f}} # map doesn't work??
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
69 parent = File::dirname path
70 return true if parent == path # root dir
72 if !FileTest::exist? path
78 def split_base(file_list)
79 fs = file_list.map{|f| File::expand_path f}
80 ds = fs.map{|f| File::dirname f}
84 if common.length <= d.length
87 common = File::dirname common
91 rs = fs.map{|f| f[(common.length + 1)..-1]} # +1 for '/'
97 self[key] ||= Array::new
105 # self.each{|x| ret += x}
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)
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?
130 open(File::expand_path(f, base_dir), 'r'){|io| io.gets.chop}
132 def link_tag(f) # Clean me. ²¿ÅÙ¤â¸Æ¤Ö¤Î¤Ï¤«¤Ã¤³°¤¤.
134 [:link, f + '.b', f + ': ' + fline[0, $summary_length]]
138 #############################################################
142 def initialize(home = nil)
149 com.each{|c| put_one c}
155 items = command.shift
158 @result += "</pre>\n"
160 @result += CGI::escapeHTML(command.shift)
163 url = link.is_a?(String) ? link + '.html' : link[1]
164 @result += %!<a href="#{url}">#{CGI::escapeHTML str}</a>!
166 items = command.shift
168 items.each{|i| @result += ' <li>'; put_one i; @result += "\n"}
172 def wrapped_result(title)
173 etitle = CGI::escapeHTML title
176 <head><title>#{etitle}</title></head>
182 <a href="#{@home}">Home</a>
183 <a href="book.h.html">Files</a>
184 <a href="index.h.html">Keywords</a>
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)}
198 attr_accessor :files, :base_dir
199 def initialize(files, base_dir)
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
212 def write_list(dest_dir, formatter)
214 formatter.put [:list, @files.sort.map{|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]]
219 formatter.write 'Files', 'book.h', dest_dir
222 def write_each(dest_dir, formatter, index)
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
232 def interpret(src, index, f)
233 hit = search src, index, f
240 when -1 # eat until beginning of this hit, and retry
241 ret.push [:as_is, src[cursor...b]]
244 when 0 # expand this hit
250 w = Regexp::last_match[$come_from_pos]
251 link = CGI::escape(CGI::escape(w)) + '.i'
253 decl = index.decl[key]
254 link = decl.member?(f) ? nil : decl[0] + '.b'
256 ret.push(link ? [:link, link, s] : [:as_is, s])
258 when 1 # discard this hit
261 ret.push [:as_is, src[cursor..-1]]
264 def search(src, index, f)
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}
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}
275 def earlier_longer(h1, h2)
276 [h1[0], - h1[1]] <=> [h2[0], - h2[1]]
282 attr_accessor :files, :base_dir
283 attr_reader :decl, :used
284 def initialize(files, base_dir)
287 @decl = HashList::new
288 @used = HashList::new
293 expand_readlines(f).scan($come_from){|hit| @decl.cons hit[0], f}
296 def write(dest_dir, formatter)
297 write_each dest_dir, formatter
298 write_list dest_dir, formatter
300 def write_list(dest_dir, formatter)
304 @decl.keys.sort.map{|key|
305 [:link, CGI::escape(CGI::escape(key)) + '.i', key + " (#{(@used[key]||[]).length})"]
308 formatter.write 'Keywords', 'index.h', dest_dir
311 def write_each(dest_dir, formatter)
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]}
322 [:as_is, "Declared:\n"],
324 [:as_is, "Linked:\n"],
326 [:as_is, "Related:\n"],
330 formatter.write key, f, dest_dir
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
339 def included_keys(key)
340 @decl.keys.select{|k| key.include? k}
344 #############################################################
347 dest_dir = ARGV.shift
348 src_dir, files = split_base(STDIN.readlines.map{|s| s.chomp})
350 src_dir, dest_dir = ARGV
353 notice "#{files.length} files "
355 b = Book::new files, src_dir
356 i = Index::new files, src_dir
357 notice "(#{i.decl.length} entries)\n"
359 $home ||= b.first_page[1] + '.html'
360 fmt = Formatter::new $home
361 b.write dest_dir, fmt