tdiary-contrib 4.0.2.1 → 4.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,53 +0,0 @@
1
- ### This tool is obsolete,
2
- ### use alternative plugin: plugin/search-default.rb.
3
- ### This tool will be deleted in the near future.
4
-
5
-
6
- tdiarysearch README
7
- ===================
8
-
9
- This is tdiarysearch, simple tDiary search interface.
10
-
11
-
12
- Installation and setup
13
- ----------------------
14
-
15
- 1. Copy search.rb aside of tDiary's index.rb.
16
-
17
- $ ls
18
- index.rb*
19
- search.rb*
20
- tdiary.conf
21
- update.rb*
22
-
23
- 2. Make search.rb executable.
24
-
25
- $ chmod a+x search.rb
26
-
27
- 3. (Optional) Adjust shebang (#!) line of search.rb.
28
-
29
- 4. (Optional) If you'd like, edit embedded paramter in
30
- search.rb and turn on query logging.
31
-
32
- 5. Add following HTML form in your tDiary header:
33
-
34
- <form method="post" action="search.rb" class="searchform">
35
- <input type="text" name="q" size="20" value="">
36
- <input type="submit" value="Search">
37
- </form>
38
-
39
- 6. Done.
40
-
41
-
42
- License
43
- -------
44
-
45
- GNU GPL, General Public License version 2.
46
-
47
-
48
- Contact
49
- -------
50
-
51
- Minero Aoki <aamine@loveruby.net>
52
- http://i.loveruby.net/en/
53
-
@@ -1,99 +0,0 @@
1
- ■■このツールは最新のtDiaryに追従していません。
2
- ■■検索機能はプラグインとして実装されるようになったため、
3
- ■■同等の機能はplugin/search-default.rbで実現しています。
4
- ■■このツールは近々削除する予定です。
5
-
6
-
7
-
8
- tdiarysearch README
9
- ===================
10
-
11
- tDiary簡単検索CGIです。
12
- あらかじめインデックスを作る必要がないので、
13
- 一般的な検索エンジンに比べてインストールが
14
- 圧倒的に簡単です。また、書いた直後の日記も
15
- 検索対象になるという長所があります。
16
-
17
-
18
- 必要環境
19
- --------
20
-
21
- * テキスト形式のデータベースを使っていること (tDiary 1.5 以降)
22
- * tDiary が動いているサーバに独自の CGI を追加する権限があること
23
-
24
-
25
- セットアップ
26
- ------------
27
-
28
- 1. search.rb を tDiary の index.rb の隣にコピーします。
29
- 必要なら index.rb と同じようにシンボリックリンクを
30
- 張ったり名前を変えたりしてください。
31
-
32
- 2. CGI として実行可能にします。
33
-
34
- $ chmod a+x search.rb
35
-
36
- 3. 必要なら #! のパスを合わせます。
37
-
38
- 4. (オプション) search.rb 先頭のパラメータを変更して
39
- クエリーログを有効にしてください。
40
- ※ クエリーログの詳細については後述
41
-
42
- 5. 自分の tDiary の好きな場所 (例えばヘッダ) に以下の
43
- フォームを加えてください。
44
-
45
- <form method="post" action="search.rb" class="searchform">
46
- <input type="text" name="q" size="20" value="">
47
- <input type="submit" value="Search">
48
- </form>
49
-
50
- 以上です。
51
-
52
-
53
- 検索のしかた
54
- ------------
55
-
56
- tdiarysearch はトピックごとに AND 検索を行います。
57
- 指定した語は空白で分割され、全単語がトピック内のテキストに
58
- 全て含まれる場合にそのトピックを検索結果にリストします。
59
-
60
- AND 以外の演算はサポートしません。
61
-
62
- また rev 1.8 の段階ではまだツッコミが検索できません。
63
- 近いうちに追加します。
64
-
65
-
66
- クエリーログ
67
- ------------
68
-
69
- クエリーログ (query log) というのは、検索語のログの
70
- ことです。このログが存在すると他の人が何を検索したのか
71
- 見られるようになります。クエリーログを有効にするには、
72
- search.rb 内の定数 LOGGING を true にしてください。
73
-
74
- CGI からログを見られるようにするには、search.rb に
75
- パラメータ history=on を付けて呼びます。例えば
76
- 次のリンクを日記に入れておくと、リンクをたどるだけで
77
- 日記から最近の検索ログが見られます。
78
-
79
- <a href="search.rb?history=on">[検索ログ]</a>
80
-
81
- なお、クエリーログは tDiary のデータディレクトリに search.log
82
- という名前で作成されます。特にローテーション処理はしていないので、
83
- 適当な間隔でローテートしてください。
84
-
85
-
86
- 連絡先
87
- ------
88
-
89
- 青木峰郎 (あおき・みねろう)
90
- Minero Aoki <aamine@loveruby.net>
91
- http://i.loveruby.net/w/tdiarysearch.html
92
-
93
- バグ報告は以下のどこかにお願いします。
94
-
95
- * Wiki 上のバグ報告ページ
96
- http://i.loveruby.net/w/tdiarysearchBugs.html
97
- * tdiary-devel ML
98
- * 直接メール
99
-
@@ -1,504 +0,0 @@
1
- #!/usr/bin/env ruby
2
- #
3
- # tdiarysearch
4
- #
5
- # Copyright (C) 2003-2005 Minero Aoki
6
- #
7
- # This program is free software.
8
- # You can distribute/modify this program under the terms of
9
- # the GNU GPL, General Public License version 2.
10
- #
11
- # $originalId: search.rb,v 1.14 2005/07/27 07:16:07 aamine Exp $
12
- #
13
- # Project home page: http://i.loveruby.net/w/tdiarysearch.html
14
- #
15
-
16
- #
17
- # Static Configurations
18
- #
19
-
20
- LOGGING = false
21
- LOGFILE_NAME = 'search.log'
22
- DEBUG = $DEBUG
23
-
24
- #
25
- # HTML Templates
26
- #
27
-
28
- def unindent(str)
29
- str.gsub(/^#{str[/\A(?:\t+| +)/]}/, '')
30
- end
31
-
32
- HEADER = unindent <<'EOS'
33
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
34
- <html lang="ja">
35
- <head>
36
- <meta http-equiv="Content-Type" content="text/html; charset=<%= TDIARY_ENCODING %>">
37
- <meta http-equiv="Content-Language" content="ja">
38
- <meta name="robots" content="noindex">
39
- <link rel="stylesheet" href="theme/base.css" type="text/css" media="all">
40
- <link rel="stylesheet" href="theme/<%= theme %>/<%= theme %>.css" title="<%= theme %>" type="text/css" media="all">
41
- <title>tDiary Search</title>
42
- </head>
43
- <body>
44
- <div class="whole-content">
45
- EOS
46
-
47
- FOOTER = unindent <<'EOS'
48
- </div>
49
- </body>
50
- </html>
51
- EOS
52
-
53
- SEARCH_FORM = unindent <<"EOS"
54
- <form method="post" action="#{File.basename(__FILE__)}">
55
- <input type="text" name="q" size="20" value="<%= patterns.map {|re| escape(re.source) }.join(' ') %>">
56
- <input type="submit" value="Search">
57
- <%
58
- if theme
59
- %><input type="hidden" name="theme" value="on"><%
60
- end
61
- %>
62
- </form>
63
- EOS
64
-
65
- SEARCH_PAGE = unindent <<"EOS"
66
- <h1>tDiary Search</h1>
67
- #{SEARCH_FORM}
68
- EOS
69
-
70
- TOO_MANY_HITS = 50
71
-
72
- SEARCH_RESULT = unindent <<"EOS"
73
- <h1>tDiary Search: Search Result</h1>
74
- #{SEARCH_FORM}
75
- <%
76
- nhits = 0
77
- toomanyhits = false
78
- match_components(patterns) {|diary, fragment, component|
79
- nhits += 1
80
- if nhits > TOO_MANY_HITS
81
- toomanyhits = true
82
- break
83
- end
84
- %>
85
- <div class="day">
86
- <h2><a href="<%= url(diary, fragment) %>"><%= diary.y_m_d %></a></h2>
87
- <div class="body">
88
- <div class="section">
89
- <p><%= short_html(component) %></p>
90
- </div>
91
- </div>
92
- </div><%
93
- }
94
- %>
95
- <p><%= toomanyhits ? 'too many hits.' : nhits.to_s+' hits.' %></p>
96
- #{SEARCH_FORM}
97
- EOS
98
-
99
- SEARCH_ERROR = unindent <<"EOS"
100
- #{SEARCH_FORM}
101
- <%= escape(reason) %>.
102
- EOS
103
-
104
- HISTORY = unindent <<"EOS"
105
- <h1>tDiary Search: Search History</h1>
106
- #{SEARCH_FORM}
107
- <ul>
108
- <%
109
- recent_queries.sort_by {|t,q| -t.to_i }.each do |time, query|
110
- %><li><%= time.strftime('%Y-%m-%d %H:%M:%S') %> | <a href="#{File.basename(__FILE__)}?q=<%= escape_url(query) %>"><%= escape(query) %></a></li>
111
- <%
112
- end
113
- %></ul>
114
- #{SEARCH_FORM}
115
- EOS
116
-
117
- #
118
- # Main
119
- #
120
-
121
- if File.symlink?(__FILE__)
122
- tdiarylib = File.dirname(File.readlink(__FILE__))
123
- else
124
- tdiarylib = File.dirname(__FILE__)
125
- end
126
- $:.unshift tdiarylib
127
- require 'tdiary'
128
- require 'tdiary/io/default'
129
- require 'erb'
130
-
131
- TDIARY_ENCODING = (TDIARY_VERSION >= '2.3.0') ? 'utf-8' : 'euc-jp'
132
-
133
- class WrongQuery < StandardError; end
134
-
135
- Z_SPACE = "\241\241" # zen-kaku space
136
-
137
- BEGIN { $stdout.binmode }
138
-
139
- def main
140
- $KCODE = TDIARY_ENCODING
141
- cgi = CGI.new
142
- @config = TDiary::Config.new(cgi)
143
- @config.options['apply_plugin'] = true
144
- html = '<html><head><title></title></head><body><p>error</p></body></html>'
145
- begin
146
- html = generate_page(cgi)
147
- ensure
148
- send_html cgi, html
149
- end
150
- exit 0
151
- end
152
-
153
- def generate_page(cgi)
154
- query = nil
155
- begin
156
- theme = @config.theme
157
- if LOGGING and File.file?(query_log()) and cgi.valid?('history')
158
- return history_page(theme)
159
- end
160
- begin
161
- return search_form_page(theme) unless cgi.valid?('q')
162
- initialize_tdiary_plugins cgi
163
- query = @config.to_native([cgi.params['q']].flatten.compact.join(' '))
164
- patterns = setup_patterns(query)
165
- html = search_result_page(theme, patterns)
166
- save_query(query, query_log()) if LOGGING
167
- return html
168
- rescue WrongQuery => err
169
- return search_error_page(theme, (patterns || []), err.message)
170
- end
171
- rescue Exception => err
172
- html = ''
173
- html << HEADER
174
- html << "<pre>\n"
175
- html << 'q=' << escape(query) << "\n" if query
176
- html << escape(err.class.name) << "\n" if DEBUG
177
- html << escape(err.message) << "\n"
178
- html << err.backtrace.map {|i| escape(i) }.join("\n") if DEBUG
179
- html << "</pre>\n"
180
- html << FOOTER
181
- return html
182
- end
183
- end
184
-
185
- def send_html(cgi, html)
186
- print cgi.header('status' => '200 OK',
187
- 'type' => 'text/html',
188
- 'charset' => TDIARY_ENCODING,
189
- 'Content-Length' => html.length.to_s,
190
- 'Cache-Control' => 'no-cache',
191
- 'Pragma' => 'no-cache')
192
- print html unless cgi.request_method == 'HEAD'
193
- end
194
-
195
- def setup_patterns(query)
196
- patterns = split_string(query).map {|pat|
197
- check_pattern pat
198
- Regexp.new( Regexp.quote(pat), Regexp::IGNORECASE, TDIARY_ENCODING )
199
- }
200
- raise WrongQuery, 'no pattern' if patterns.empty?
201
- raise WrongQuery, 'too many sub patterns' if patterns.length > 8
202
- patterns
203
- end
204
-
205
- def check_pattern(pat)
206
- raise WrongQuery, 'no pattern' unless pat
207
- raise WrongQuery, 'empty pattern' if pat.empty?
208
- raise WrongQuery, "pattern too short: #{pat}" if pat.length < 2
209
- raise WrongQuery, 'pattern too long' if pat.length > 128
210
- end
211
-
212
- def split_string(str)
213
- str.split(/[\s#{Z_SPACE}]+/ou).reject {|w| w.empty? }
214
- end
215
-
216
- def save_query(query, file)
217
- File.open(file, 'a') {|f|
218
- begin
219
- f.flock(File::LOCK_EX)
220
- f.puts "#{Time.now.to_i}: #{query.dump}"
221
- ensure
222
- f.flock(File::LOCK_UN)
223
- end
224
- }
225
- end
226
-
227
- #
228
- # eRuby Dispatchers and Helper Routines
229
- #
230
-
231
- def search_form_page(theme)
232
- patterns = []
233
- ERB.new(HEADER + SEARCH_FORM + FOOTER).result(binding())
234
- end
235
-
236
- def search_result_page(theme, patterns)
237
- ERB.new(HEADER + SEARCH_RESULT + FOOTER).result(binding())
238
- end
239
-
240
- def search_error_page(theme, patterns, reason)
241
- ERB.new(HEADER + SEARCH_ERROR + FOOTER).result(binding())
242
- end
243
-
244
- def history_page(theme)
245
- patterns = []
246
- ERB.new(HEADER + HISTORY + FOOTER).result(binding())
247
- end
248
-
249
- def query_log
250
- "#{@config.data_path}#{LOGFILE_NAME}"
251
- end
252
-
253
- N_SHOW_QUERY_MAX = 20
254
-
255
- def recent_queries
256
- return unless File.file?(query_log())
257
- File.readlines(query_log()).reverse[0, N_SHOW_QUERY_MAX].map {|line|
258
- time, q = *line.split(/:/, 2)
259
- [Time.at(time.to_i), eval(q)]
260
- }
261
- end
262
-
263
- INF = 1 / 0.0
264
-
265
- def match_components(patterns)
266
- foreach_diary_from_latest do |diary|
267
- next unless diary.visible?
268
- num = 1
269
- diary.each_section do |sec|
270
- if patterns.all? {|re| re =~ sec.to_src }
271
- yield diary, fragment('p', num), sec
272
- end
273
- num += 1
274
- end
275
- diary.each_visible_comment(INF) do |cmt, num|
276
- if patterns.all? {|re| re =~ cmt.body }
277
- yield diary, fragment('c', num), cmt
278
- end
279
- end
280
- end
281
- end
282
-
283
- def fragment(type, num)
284
- sprintf('%s%02d', type, num)
285
- end
286
-
287
- #
288
- # tDiary Implementation Dependent
289
- #
290
-
291
- def foreach_diary_from_latest(&block)
292
- foreach_data_file(@config.data_path.sub(%r</+\z>, '')) do |path|
293
- read_diaries(path).sort_by {|diary| diary.date }.reverse_each(&block)
294
- end
295
- end
296
-
297
- def foreach_data_file(data_path, &block)
298
- Dir.glob("#{data_path}/[0-9]*/*.td2").sort.reverse_each do |path|
299
- yield path.untaint
300
- end
301
- end
302
-
303
- def read_diaries(path)
304
- d = nil
305
- diaries = {}
306
- load_tdiary_textdb(path) do |header, body|
307
- d = diary_class(header['Format']).new(header['Date'], '', body)
308
- d.show(header['Visible'] != 'false')
309
- diaries[d.ymd] = d
310
- end
311
- (Years[d.y] ||= []).push(d.m) if d
312
- load_comments diaries, path
313
- diaries.values
314
- end
315
-
316
- DIARY_CLASS_CACHE = {}
317
-
318
- def diary_class(style)
319
- c = DIARY_CLASS_CACHE[style]
320
- return c if c
321
- require "tdiary/style/#{style.downcase}_style.rb"
322
- c = eval("TDiary::#{style.capitalize}Diary")
323
- c.__send__(:include, DiaryClassDelta)
324
- DIARY_CLASS_CACHE[style] = c
325
- c
326
- end
327
-
328
- module DiaryClassDelta
329
- def ymd
330
- date().strftime('%Y%m%d')
331
- end
332
-
333
- def y_m_d
334
- date().strftime('%Y-%m-%d')
335
- end
336
-
337
- def y
338
- '%04d' % date().year
339
- end
340
-
341
- def m
342
- '%02d' % date().month
343
- end
344
- end
345
-
346
- def load_comments(diaries, path)
347
- cmtfile = path.sub(/2\z/, 'c')
348
- return unless File.file?(cmtfile)
349
- load_tdiary_textdb(cmtfile) do |header, body|
350
- c = TDiary::Comment.new(header['Name'], header['Mail'], body,
351
- Time.at(header['Last-Modified'].to_i))
352
- c.show = (header['Visible'] != 'false')
353
- d = diaries[header['Date']]
354
- d.add_comment c if d
355
- end
356
- end
357
-
358
- def load_tdiary_textdb(path)
359
- File.open(path) {|f|
360
- ver = f.gets.strip
361
- raise "unkwnown format: #{ver}" unless ver == 'TDIARY2.00.00' or ver == 'TDIARY2.01.00'
362
- f.each('') do |header|
363
- h = {}
364
- header.untaint.strip.each do |line|
365
- n, v = *line.split(':', 2)
366
- h[n.strip] = v.strip
367
- end
368
- yield h, f.gets("\n.\n").chomp(".\n").untaint
369
- end
370
- }
371
- end
372
-
373
- def short_html(component)
374
- # Section classes do not have common superclass, we can't use class here.
375
- case component.class.name
376
- when /Section/
377
- section = component
378
- if section.subtitle
379
- sprintf('%s<br>%s',
380
- tdiary2text(section.subtitle_to_html),
381
- tdiary2text(section.body_to_html))
382
- else
383
- tdiary2text(section.body_to_html)
384
- end
385
- when /Comment/
386
- cmt = component
387
- shorten(escape((cmt.name + ': ' + cmt.body)))
388
- else
389
- raise "must not happen: #{component.class}"
390
- end
391
- end
392
-
393
- def tdiary2text(html)
394
- re = Regexp.new('<[^>]*>', Regexp::EXTENDED, TDIARY_ENCODING)
395
- shorten(apply_tdiary_plugins(html).gsub(re, ''))
396
- end
397
-
398
- Years = {}
399
-
400
- TDiary::Plugin.__send__(:public, :apply_plugin)
401
- def apply_tdiary_plugins(html)
402
- @plugin.apply_plugin(html, false)
403
- end
404
-
405
- @plugin = nil
406
-
407
- def initialize_tdiary_plugins(cgi)
408
- @plugin = TDiary::Plugin.new('conf' => @config,
409
- 'secure' => false,
410
- 'diaries' => {},
411
- 'cgi' => cgi,
412
- 'index' => @config.index,
413
- 'years' => Years,
414
- 'cache_path' => @config.cache_path ||
415
- @config.data_path)
416
- end
417
-
418
- #
419
- # Utils
420
- #
421
-
422
- HTML_ESCAPE_TABLE = {
423
- '&' => '&amp;',
424
- '<' => '&lt;',
425
- '>' => '&gt;',
426
- '"' => '&quot;'
427
- }
428
-
429
- def escape(str)
430
- tbl = HTML_ESCAPE_TABLE
431
- str.gsub(/[&"<>]/) {|ch| tbl[ch] }
432
- end
433
-
434
- def escape_url(u)
435
- escape(urlencode(u))
436
- end
437
-
438
- def urlencode(str)
439
- str.gsub(/[^\w-]/n) {|ch| sprintf('%%%02x', ch[0]) }
440
- end
441
-
442
- def shorten(str)
443
- re = Regexp.new('\A.{0,120}', Regexp::MULTILINE, TDIARY_ENCODING)
444
- str.slice(re)
445
- end
446
-
447
- def url(diary, fragment)
448
- if ( html_anchor_enabled? )
449
- "#{@config.index}#{diary.ymd}.html\##{fragment}"
450
- else
451
- "#{@config.index}?date=#{diary.ymd}\##{fragment}"
452
- end
453
- end
454
-
455
- def html_anchor_enabled?
456
- if ( @html_anchor.nil? )
457
- @html_anchor = @config.options2['sp.selected'].include?( 'html_anchor.rb' )
458
- end
459
-
460
- return @html_anchor
461
- end
462
-
463
- #
464
- # Old Ruby Compatibility
465
- #
466
-
467
- if RUBY_VERSION < '1.8.0'
468
- class String
469
- remove_method :slice
470
- def slice(re, n = 0)
471
- m = re.match(self) or return nil
472
- m[n]
473
- end
474
- end
475
- end
476
-
477
- unless Array.method_defined?(:all?)
478
- module Enumerable
479
- def all?
480
- each do |i|
481
- return false unless yield(i)
482
- end
483
- true
484
- end
485
- end
486
- end
487
-
488
- unless Array.method_defined?(:sort_by)
489
- module Enumerable
490
- def sort_by
491
- map {|i| [yield(i), i] }.sort.map {|val, i| i }
492
- end
493
- end
494
- end
495
-
496
- unless MatchData.method_defined?(:captures)
497
- class MatchData
498
- def captures
499
- to_a()[1..-1]
500
- end
501
- end
502
- end
503
-
504
- main