milkode 0.7.1 → 0.8.0

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.
Files changed (53) hide show
  1. data/Gemfile +17 -4
  2. data/HISTORY.ja.rdoc +27 -0
  3. data/HISTORY.rdoc +27 -0
  4. data/LICENSE.txt +23 -1
  5. data/Rakefile +1 -16
  6. data/VERSION +1 -1
  7. data/bin/gmilk +1 -1
  8. data/bin/milk +1 -1
  9. data/lib/milkode/cdstk/cdstk.rb +171 -168
  10. data/lib/milkode/cdstk/milkode_yaml.rb +1 -1
  11. data/lib/milkode/cdweb/app.rb +11 -3
  12. data/lib/milkode/cdweb/lib/command.rb +43 -0
  13. data/lib/milkode/cdweb/lib/database.rb +58 -210
  14. data/lib/milkode/cdweb/lib/mkurl.rb +10 -1
  15. data/lib/milkode/cdweb/lib/package_list.rb +61 -0
  16. data/lib/milkode/cdweb/lib/search_files.rb +2 -1
  17. data/lib/milkode/cdweb/public/css/bootstrap-responsive.min.css +9 -0
  18. data/lib/milkode/cdweb/public/css/bootstrap.min.css +9 -0
  19. data/lib/milkode/cdweb/public/css/milkode.css +44 -9
  20. data/lib/milkode/cdweb/public/img/glyphicons-halflings-white.png +0 -0
  21. data/lib/milkode/cdweb/public/img/glyphicons-halflings.png +0 -0
  22. data/lib/milkode/cdweb/public/js/bootstrap.min.js +6 -0
  23. data/lib/milkode/cdweb/views/index.haml +52 -16
  24. data/lib/milkode/cdweb/views/packages.haml +11 -0
  25. data/lib/milkode/cli.rb +8 -0
  26. data/lib/milkode/common/array_diff.rb +18 -0
  27. data/lib/milkode/common/util.rb +16 -0
  28. data/lib/milkode/database/document_record.rb +64 -0
  29. data/lib/milkode/database/document_table.rb +341 -0
  30. data/lib/milkode/database/groonga_database.rb +105 -0
  31. data/lib/milkode/database/package_table.rb +98 -0
  32. data/lib/milkode/findgrep/findgrep.rb +20 -124
  33. data/lib/milkode/grep/cli_grep.rb +3 -1
  34. data/milkode.gemspec +32 -2
  35. data/test/data/.gitignore +4 -0
  36. data/test/data/c_project/a.txt +1 -0
  37. data/test/data/c_project/abc.c +2 -0
  38. data/test/data/c_project/abc.h +3 -0
  39. data/test/data/c_project/b.txt +1 -0
  40. data/test/data/c_project/c.txt +1 -0
  41. data/test/data/c_project/cc.txt +1 -0
  42. data/test/data/c_project/to/file.rb +1 -0
  43. data/test/test_cdstk.rb +4 -4
  44. data/test/test_cli.rb +9 -2
  45. data/test/test_database.rb +5 -14
  46. data/test/test_document_record.rb +77 -0
  47. data/test/test_document_table.rb +317 -0
  48. data/test/test_groonga_database.rb +58 -0
  49. data/test/test_mkurl.rb +2 -0
  50. data/test/test_package_list.rb +21 -0
  51. data/test/test_package_table.rb +215 -0
  52. data/test/test_util.rb +8 -0
  53. metadata +57 -5
@@ -74,7 +74,7 @@ EOF
74
74
  def package_root(dir)
75
75
  nd = Util::normalize_filename dir
76
76
  @contents.find do |v|
77
- v if nd =~ /^#{v.directory}/
77
+ v if nd =~ /^#{Regexp.escape(v.directory)}/
78
78
  end
79
79
  end
80
80
 
@@ -15,15 +15,17 @@ require 'milkode/cdweb/lib/database'
15
15
  require 'milkode/cdweb/lib/command'
16
16
  require 'milkode/cdweb/lib/mkurl'
17
17
  require 'milkode/cdweb/lib/web_setting'
18
+ require 'milkode/cdweb/lib/package_list'
18
19
 
19
20
  set :haml, :format => :html5
20
21
 
21
22
  get '/' do
22
23
  @setting = WebSetting.new
23
- @version = "0.7.0"
24
+ @version = "0.8.0"
24
25
  @package_num = Database.instance.yaml_package_num
25
26
  @file_num = Database.instance.totalRecords
26
- haml :index
27
+ @package_list = PackageList.new(Database.instance.grndb)
28
+ haml :index, :layout => false
27
29
  end
28
30
 
29
31
  def package_path(path)
@@ -56,7 +58,13 @@ get '/home*' do |path|
56
58
  path = path.sub(/^\//, "")
57
59
  record = Database.instance.record(path)
58
60
 
59
- if (record)
61
+ if path.empty?
62
+ if (params[:query] and !params[:query].empty?)
63
+ search(path, params, before)
64
+ else
65
+ packages(params, before)
66
+ end
67
+ elsif (record)
60
68
  view(record, params, before)
61
69
  else
62
70
  if (params[:query] and !params[:query].empty?)
@@ -28,6 +28,7 @@ module Milkode
28
28
  @record_content = CodeRayWrapper.new(record.content, record.shortpath).to_html
29
29
  end
30
30
 
31
+ Database.instance.touch_viewtime(@path)
31
32
  @elapsed = Time.now - before
32
33
  haml :view
33
34
  end
@@ -61,12 +62,54 @@ module Milkode
61
62
  @record_content = fileList.map do |v|
62
63
  "<dt class='result-file'>#{file_or_dirimg(v[1])}<a href='#{Mkurl.new('/home/' + v[0], params).inherit_query_shead}'>#{File.basename v[0]}</a></dt>"
63
64
  end.join
65
+ Database.instance.touch_viewtime(path)
64
66
  @elapsed = Time.now - before
65
67
  haml :filelist
66
68
  end
67
69
 
70
+ def packages(params, before)
71
+ @setting = WebSetting.new
72
+ @title = "Package List"
73
+ @path = ""
74
+ packages = Database.instance.packages(params["sort"])
75
+ @total_records = packages.size
76
+
77
+ @sort_change_content =
78
+ [
79
+ sort_change_content(params["sort"], '名前'),
80
+ '|',
81
+ sort_change_content(params["sort"], '最近使った', 'viewtime'),
82
+ '|',
83
+ sort_change_content(params["sort"], '追加順' , 'addtime'),
84
+ '|',
85
+ sort_change_content(params["sort"], '更新順' , 'updatetime'),
86
+ '|',
87
+ sort_change_content(params["sort"], 'お気に入り', 'favtime'),
88
+ ].join("\n")
89
+
90
+ @record_content = packages.map do |v|
91
+ "<dt class='result-file'>#{file_or_dirimg(false)}<a href='#{Mkurl.new('/home/' + v, params).inherit_query_shead}'>#{File.basename v}</a></dt>"
92
+ end.join
93
+ @elapsed = Time.now - before
94
+ haml :packages
95
+ end
96
+
97
+ private
98
+
68
99
  def file_or_dirimg(is_file)
69
100
  src = (is_file) ? '/images/file.png' : '/images/directory.png'
70
101
  "<img alt='' style='vertical-align:bottom; border: 0; margin: 1px;' src='#{src}'>"
71
102
  end
103
+
104
+ def sort_change_content(current_value, text, sort_kind = nil)
105
+ if (current_value != sort_kind)
106
+ if (sort_kind)
107
+ "<a href='#{Mkurl.new('/home', params).inherit_query_shead_set_sort(sort_kind)}'>#{text}</a>"
108
+ else
109
+ "<a href='#{Mkurl.new('/home', params).inherit_query_shead}'>#{text}</a>"
110
+ end
111
+ else
112
+ text
113
+ end
114
+ end
72
115
  end
@@ -11,9 +11,11 @@ require 'singleton'
11
11
  require 'groonga'
12
12
  require 'milkode/common/dbdir'
13
13
  require 'milkode/cdstk/yaml_file_wrapper'
14
+ require 'milkode/database/groonga_database'
14
15
  include Milkode
15
16
 
16
17
  module Milkode
18
+ # @todo データベースアクセスは将来的にはGroongaDatabaseに収束させる
17
19
  class Database
18
20
  include Singleton
19
21
 
@@ -28,93 +30,73 @@ module Milkode
28
30
  end
29
31
 
30
32
  attr_reader :yaml
33
+ attr_reader :grndb
31
34
 
32
35
  def initialize
33
- open(Database.dbdir)
36
+ open
34
37
  end
35
38
 
36
39
  def yaml_reload
37
40
  # @yaml = YamlFileWrapper.load_if(@@db_dir || Dbdir.default_dir)
38
41
  end
39
42
 
40
- def open(db_dir)
41
- dbfile = Dbdir.expand_groonga_path(db_dir)
42
-
43
- if File.exist? dbfile
44
- Groonga::Database.open(dbfile)
45
- else
46
- raise "error : #{dbfile} not found!!"
43
+ def open
44
+ if !@grndb || @grndb.closed?
45
+ @grndb = GroongaDatabase.new
46
+ @grndb.open(Database.dbdir)
47
+ @grndb.yaml_sync(yaml_load.contents)
48
+ @documents = @grndb.documents
47
49
  end
48
-
49
- @documents = Groonga::Context.default["documents"]
50
50
  end
51
51
 
52
52
  def record(shortpath)
53
- reopen_patch
54
- table = @documents.select { |record| record.shortpath == shortpath }
55
- return table.records[0]
53
+ DocumentRecord.create @documents.find_shortpath(shortpath)
56
54
  end
57
55
 
58
56
  def search(patterns, packages, current_path, fpaths, suffixs, offset = 0, limit = -1)
59
- # パッケージ名から絶対パスに変換
60
- unless packages.empty?
61
- packages = convert_packages(packages)
62
-
63
- # キーワードがパッケージ名にマッチしなければ検索しない
64
- return [], 0 if packages.empty?
65
- else
66
- # パッケージ名未指定の時は現在位置もfpathsに含める
67
- if current_path != ""
68
- fpaths << path2fpath(current_path)
69
- end
70
- end
71
-
72
- # @todo fpathを厳密に検索するには、検索結果からさらに先頭からのパスではないものを除外する
73
- records, total_records = searchMain(patterns, packages, fpaths, suffixs, offset, limit)
74
- end
75
-
76
- def path2fpath(path)
77
- pa = path.split("/")
78
- File.join(convert_packages([pa[0]])[0], pa[1..-1].join('/'))
79
- end
80
-
81
- def selectAll(offset = 0, limit = -1)
82
- table = @documents.select
57
+ paths = []
83
58
 
84
- # マッチ数
85
- total_records = table.size
86
-
87
- # @todo ここが速度低下の原因?と思ったけど、ここは全て選択の部分だけか・・・
59
+ # パッケージ名未指定の時は現在位置を検索条件に追加
60
+ if packages.empty? && current_path != ''
61
+ package, restpath = Util::divide_shortpath(current_path)
88
62
 
89
- # 2010/10/29 ongaeshi
90
- # 本当はこのようにgroongaAPIでソートしたいのだが上手くいかなかった
91
- # # ファイル名順にソート
92
- # records = table.sort([{:key => "shortpath", :order => "descending"}],
93
- # :offset => offset,
94
- # :limit => limit)
63
+ # PackageRecord#directory をキーに検索する
64
+ directory = @grndb.packages[package].directory
95
65
 
96
- # ソート
97
- if (limit != -1)
98
- records = table.records.sort_by{|record| record.shortpath.downcase }[offset, limit]
99
- else
100
- records = table.records.sort_by{|record| record.shortpath.downcase }[offset..limit]
66
+ if restpath
67
+ paths << File.join(directory, restpath)
68
+ else
69
+ paths << directory
70
+ end
101
71
  end
102
72
 
103
- return records, total_records
73
+ # 検索
74
+ result = @documents.search(
75
+ :patterns => patterns,
76
+ # :keywords => ,
77
+ :paths => paths,
78
+ :packages => packages,
79
+ :restpaths => fpaths,
80
+ :suffixs => suffixs,
81
+ :offset => offset,
82
+ :limit => limit
83
+ )
84
+
85
+ # 結果
86
+ return result.map{|r| DocumentRecord.new(r)}, result.size
104
87
  end
105
88
 
106
- def selectAll2(offset = 0, limit = -1)
107
- records, total_records = selectAll(offset, limit)
108
- records
89
+ def selectAll(offset, limit)
90
+ @documents.select_all_sort_by_shortpath(offset, limit)
109
91
  end
110
-
92
+
111
93
  # レコード数を得る
112
94
  def totalRecords
113
- reopen_patch
114
- @documents.select.size
95
+ @documents.size
115
96
  end
116
97
 
117
98
  # yamlからパッケージの総数を得る
99
+ # @todo PackageTableから取得するように変更する
118
100
  def yaml_package_num
119
101
  yaml_load.contents.size
120
102
  end
@@ -128,17 +110,13 @@ module Milkode
128
110
  if (base_depth == 0)
129
111
  return yaml_load.contents.sort_by{|v| v.name}.map{|v| [v.name, false] }
130
112
  end
131
-
132
- # shortpathにマッチするものだけに絞り込む
133
- if (base == "")
134
- records = @documents.select.records
135
- else
136
- records = @documents.select {|record| record.shortpath =~ base }.to_a
137
- end
113
+
114
+ # base/以下のファイルを全て取得
115
+ records = @documents.find_shortpath_below(base)
138
116
 
139
117
  # ファイルリストの生成
140
118
  paths = records.map {|record|
141
- record.shortpath.split("/")
119
+ DocumentRecord.new(record).shortpath.split("/")
142
120
  }.find_all {|parts|
143
121
  parts.length > base_depth and parts[0, base_depth] == base_parts
144
122
  }.map {|parts|
@@ -151,161 +129,31 @@ module Milkode
151
129
 
152
130
  paths
153
131
  end
154
-
155
- # 指定したfpathにマッチするレコードを削除する
156
- def remove_fpath(fpath)
157
- # データベースを開き直す
158
- reopen_patch
159
-
160
- # 削除したいコンテンツを検索
161
- records, total_records = searchMain([], [], [fpath], [], 0, -1)
162
-
163
- # 検索結果はHashのレコードなので、これを直接deleteしても駄目
164
- # 1. Record#record_idを使って主キー(Groonga#Arrayのレコード)を取り出し
165
- # 2. Record#delete で削除
166
- records.each do |r|
167
- yield r if block_given?
168
- r.record_id.delete
169
- end
170
- end
171
132
 
172
- # 実体の存在しないデータを削除
173
- def cleanup
174
- # データベースを開き直す
175
- reopen_patch
133
+ def packages(sort_kind)
134
+ sorted = nil
176
135
 
177
- # クリーンアップ
178
- records = selectAll2
179
-
180
- records.each do |r|
181
- unless File.exist? r.path
182
- yield r if block_given?
183
- r.record_id.delete
184
- end
185
- end
186
- end
187
-
188
- private
189
-
190
- def reopen_patch
191
- # 削除系のコマンドが上手く動作しないためのパッチ
192
- # 本質的な解決にはなっていないと思う
193
- open(Database.dbdir)
194
- end
195
-
196
- def searchMain(patterns, packages, fpaths, suffixs, offset, limit)
197
- table = @documents.select do |record|
198
- expression = nil
199
-
200
- # キーワード
201
- patterns.each do |word|
202
- sub_expression = record.content =~ word
203
- if expression.nil?
204
- expression = sub_expression
205
- else
206
- expression &= sub_expression
207
- end
208
- end
209
-
210
- # パッケージ(OR)
211
- pe = package_expression(record, packages)
212
- if (pe)
213
- if expression.nil?
214
- expression = pe
215
- else
216
- expression &= pe
217
- end
218
- end
219
-
220
- # ファイルパス
221
- fpaths.each do |word|
222
- sub_expression = record.path =~ word
223
- if expression.nil?
224
- expression = sub_expression
225
- else
226
- expression &= sub_expression
227
- end
228
- end
229
-
230
- # 拡張子(OR)
231
- se = suffix_expression(record, suffixs)
232
- if (se)
233
- if expression.nil?
234
- expression = se
235
- else
236
- expression &= se
237
- end
238
- end
239
-
240
- # 検索式
241
- expression
242
- end
243
-
244
- # スコアとタイムスタンプでソート
245
- # records = table.sort([{:key => "_score", :order => "descending"},
246
- # {:key => "timestamp", :order => "descending"}],
247
- # :offset => offset,
248
- # :limit => limit)
249
-
250
- # ファイル名でソート
251
- records = table.sort([{:key => "shortpath", :order => "ascending"}],
252
- :offset => offset,
253
- :limit => limit)
254
-
255
- # マッチ数
256
- total_records = table.size
257
-
258
- return records, total_records
259
- end
260
- private :searchMain
261
-
262
- def package_expression(record, packages)
263
- sub = nil
264
-
265
- # @todo 専用カラム package が欲しいところ
266
- # でも今でもpackageはORとして機能してるからいいっちゃいい
267
- packages.each do |word|
268
- e = record.path =~ word
269
- if sub.nil?
270
- sub = e
271
- else
272
- sub |= e
273
- end
136
+ if sort_kind == "favtime"
137
+ sorted = @grndb.packages.favs
138
+ elsif (sort_kind)
139
+ sorted = @grndb.packages.sort(sort_kind)
140
+ else
141
+ sorted = @grndb.packages.sort("name", "ascending")
274
142
  end
275
143
 
276
- sub
144
+ sorted.map {|r| r.name}
277
145
  end
278
- private :package_expression
279
146
 
280
- def suffix_expression(record, suffixs)
281
- sub = nil
282
-
283
- suffixs.each do |word|
284
- e = record.suffix =~ word
285
- if sub.nil?
286
- sub = e
287
- else
288
- sub |= e
289
- end
290
- end
291
-
292
- sub
147
+ def touch_viewtime(path)
148
+ package, restpath = Util::divide_shortpath(path)
149
+ @grndb.packages.touch(package, :viewtime) if package
293
150
  end
294
- private :suffix_expression
295
151
 
296
- def convert_packages(packages)
297
- packages.inject([]) {|r, p| r += expand_packages(p)}
298
- end
299
-
300
- def expand_packages(keyword)
301
- yaml_load.match_all(keyword).map{|p| p.directory}
302
- end
152
+ private
303
153
 
304
154
  def yaml_load
305
155
  YamlFileWrapper.load_if(Database.dbdir)
306
156
  end
307
157
 
308
- # --- error ---
309
- class NotFoundPackage < RuntimeError ; end
310
158
  end
311
159
  end
@@ -24,6 +24,14 @@ module Milkode
24
24
  create_url(query_param(true, true, false))
25
25
  end
26
26
 
27
+ def inherit_query_shead_set_sort(sort_kind)
28
+ create_url(query_param(true, true, false, sort_kind))
29
+ end
30
+
31
+ def inherit_query_shead
32
+ create_url(query_param(true, true, false))
33
+ end
34
+
27
35
  def inherit_shead
28
36
  create_url(query_param(false, true, false))
29
37
  end
@@ -42,13 +50,14 @@ module Milkode
42
50
  end
43
51
  end
44
52
 
45
- def query_param(query_inherit, shead_inherit, offset_inherit)
53
+ def query_param(query_inherit, shead_inherit, offset_inherit, sort_kind = nil)
46
54
  qparam = []
47
55
  qparam << "query=#{escape(@params[:query])}" if (query_inherit and @params[:query])
48
56
  qparam << "shead=#{escape(@params[:shead])}" if (shead_inherit and @params[:shead])
49
57
  qparam << "onematch=#{escape(@params[:onematch])}" if (shead_inherit and @params[:onematch])
50
58
  qparam << "offset=#{escape(@params[:offset])}" if (offset_inherit and @params[:offset])
51
59
  qparam << "line=#{escape(@params[:line])}" if (offset_inherit and @params[:line])
60
+ qparam << "sort=#{sort_kind}" if sort_kind
52
61
  qparam.join('&')
53
62
  end
54
63
  end