infod 0.0.3.1 → 0.0.3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,66 +1,52 @@
1
1
  #watch __FILE__
2
2
  class E
3
+ =begin
4
+ gem install rroonga
5
+ a ruby full-text searcher & column-store
6
+ http://groonga.org/
7
+ =end
8
+
9
+ fn 'view/'+Search+'Groonga',-> d,e {{_: :form, action: '/', c: [{_: :input, name: :q, style: 'font-size:2em'},{_: :input, type: :hidden, name: :graph, value: :groonga}]}}
3
10
 
4
- # adaptor for ruby text-search-engine & column-store
5
- # http://groonga.org/ http://ranguba.org/
6
-
7
- # query
8
- fn 'protograph/roonga',->d,e,m{
9
-
10
- # groonga engine
11
- ga = E.groonga
12
-
13
- # search expression
14
- q = e['q']
15
-
16
- # context
17
- g = e["context"] || d.env['SERVER_NAME']
11
+ fn 'protograph/groonga',->d,e,m{
12
+ E.groonga.do{|ga|
13
+ q = e['q'] # search expression
14
+ g = e["context"] || d.env['SERVER_NAME'] # context
18
15
 
19
16
  begin
20
- # execute
21
- r = (q && !q.empty?) ? ga.select{|r|(r['graph'] == g) & r["content"].match(q)} : # expression if exists
22
- ga.select{|r| r['graph'] == g} # ordered set (index date-range)
23
-
24
- # offset, size
25
- start = e['start'].do{|c| c.to_i.max(r.size - 1).min 0 } || 0
26
- c = (e['c']||e['count']).do{|c|c.to_i.max(10000).min(0)} || 8
17
+ m['/'] = {Type => E[Search+'Groonga']} # add a groonga resource to the graph
27
18
 
28
- # are further results traversible?
29
- down = r.size > start+c
30
- up = !(start<=0)
31
-
32
- # sort results
33
- r = r.sort(e.has_key?('score') ? [["_score"]] : [["time", "descending"]],:offset => start,:limit => c)
19
+ r = (q && !q.empty?) ? ga.select{|r|(r['graph'] == g) & r["content"].match(q)} : # expression if exists
20
+ ga.select{|r| r['graph'] == g} # or just an ordered set
34
21
 
35
- # results -> graph
36
- r = r.map{|r| r['.uri'].E }
37
- (r.map &:docs).flatten.uniq.map{|r| m[r.uri] = r.env e}
22
+ start = e['start'].do{|c| c.to_i.max(r.size - 1).min 0 } || 0 # offset
23
+ c = (e['c']||e['count']).do{|c|c.to_i.max(10000).min(0)} || 16 # count
24
+ down = r.size > start+c # prev
25
+ up = !(start<=0) # next
26
+ r = r.sort(e.has_key?('best') ? [["_score"]]:[["time","descending"]],:offset =>start,:limit =>c) # sort
27
+ r = r.map{|r| r['.uri'].E } # URI
28
+ (r.map &:docs).flatten.uniq.map{|r|m[r.uri] = r.env e} # set resource thunks
38
29
 
39
- m['#'] = {'uri' => '#',
40
- RDFs+'member' => r,
41
- Type=>E[HTTP+'Response']}
42
- m['#'][Prev]={'uri' => '/search' + {'q' => q, 'start' => start + c, 'c' => c}.qs} if down
43
- m['#'][Next]={'uri' => '/search' + {'q' => q, 'start' => start - c, 'c' => c}.qs} if up
44
- m['/search'] = {Type => E[Search]}
30
+ m['#'] = {'uri' => '#', RDFs+'member' => r, Type=>E[HTTP+'Response']} # add pagination data to request-graph
31
+ m['#'][Prev]={'uri' => '/' + {'graph' => 'groonga', 'q' => q, 'start' => start + c, 'c' => c}.qs} if down
32
+ m['#'][Next]={'uri' => '/' + {'graph' => 'groonga', 'q' => q, 'start' => start - c, 'c' => c}.qs} if up
45
33
 
46
34
  rescue Groonga::SyntaxError => x
47
- m['/search'] = {Type => E[Search]}
48
- m['#'] = {
49
- Type => E[COGS+'Exception'],
50
- Title => "bad expression",
51
- Content => CGI.escapeHTML(x.message)}
35
+ m['#'] = {Type => E[COGS+'Exception'], Title => "invalid expr", Content => CGI.escapeHTML(x.message)}
52
36
  e['nocache']=true
53
37
  end
54
38
 
55
- F['docsID'][m,e]}
39
+ F['docsID'][m,e]}}
56
40
 
57
41
  def E.groonga
58
- @groonga ||= (require 'groonga'
59
- E['/E/groonga'].groonga
60
- Groonga["E"] )
42
+ @groonga ||=
43
+ (begin require 'groonga'
44
+ E['/E/groonga'].groonga
45
+ Groonga["E"]
46
+ rescue LoadError => e; end)
61
47
  end
62
48
 
63
- # load or create groongaDB at URI
49
+ # init groongaDB
64
50
  def groonga
65
51
  return Groonga::Database.open d if e # open db
66
52
  dirname.mk # create containing dir
@@ -78,17 +64,16 @@ class E
78
64
  %w{graph content}.map{|c| t.index("E." + c) }}}
79
65
  end
80
66
 
81
- # index resource
67
+ # add
82
68
  def roonga graph="global", m = self.graph
83
- # puts "text #{graph} < #{uri} #{m.keys.join ' '}"
84
- g = E.groonga # db
69
+ E.groonga.do{|g|
85
70
  m.map{|u,i|
86
71
  r = g[u] || g.add(u) # create or load entry
87
72
  r.uri = u # update data
88
73
  r.graph = graph.to_s
89
74
  r.content = i.to_s
90
75
  r.time = i[E::Date].do{|t|t[0].to_time}
91
- }
76
+ }}
92
77
  self
93
78
  end
94
79
 
@@ -1,6 +1,6 @@
1
1
  #watch __FILE__
2
2
 
3
- # Ruby to HTML
3
+ # (H) templates
4
4
  def H _
5
5
  case _
6
6
  when Hash
@@ -20,11 +20,17 @@ class H
20
20
  def H.[] h; H h end
21
21
 
22
22
  def H.js a,inline=false
23
- p=a+'.js'
23
+ p = a + '.js'
24
24
  inline ? {_: :script, c: p.E.r} :
25
25
  {_: :script, type: "text/javascript", src: p}
26
26
  end
27
27
 
28
+ def H.css a,inline=false
29
+ p = a + '.css'
30
+ inline ? {_: :style, c: p.E.r} :
31
+ {_: :link, href: p, rel: :stylesheet, type: E::MIME[:css]}
32
+ end
33
+
28
34
  def H.once e,n,*h
29
35
  return if e[n]
30
36
  e[n]=true
@@ -33,8 +39,8 @@ class H
33
39
  end
34
40
 
35
41
  class Array
36
- def html v=nil
37
- map{|e|e.html v}.join ' '
42
+ def html v=nil,g=nil
43
+ map{|e|e.html v,g}.join ' '
38
44
  end
39
45
  end
40
46
 
@@ -54,49 +60,52 @@ class String
54
60
  '<a href="'+self+'">' + (name||abbrURI) + '</a>'
55
61
  end
56
62
  def abbrURI
57
- sub /(?<scheme>[a-z]+:\/\/)?(?<abbr>.*?)(?<frag>[^#\/]*)$/,
58
- '<span class="abbr"><span class="scheme">\k<scheme></span>\k<abbr></span><span class="frag">\k<frag></span>'
63
+ sub /(?<scheme>[a-z]+:\/\/)?(?<abbr>.*?)(?<frag>[^#\/]+)\/?$/,'<span class="abbr"><span class="scheme">\k<scheme></span>\k<abbr></span><span class="frag">\k<frag></span>'
59
64
  end
60
- def html e=nil
61
- # CGI.escapeHTML self
65
+ def html e=nil,g=nil
62
66
  self
63
67
  end
64
68
  end
65
69
 
66
70
  class Fixnum
67
- def html e=nil; to_s end
71
+ def html e=nil,g=nil; to_s end
68
72
  end
69
73
 
70
74
  class Float
71
- def html e=nil; to_s end
75
+ def html e=nil,g=nil; to_s end
72
76
  end
73
77
 
74
78
  class TrueClass
75
- def html e=nil; H({_: :input, type: :checkbox, title: :True, checked: :checked}) end
79
+ def html e=nil,g=nil; H({_: :input, type: :checkbox, title: :True, checked: :checked}) end
76
80
  end
77
81
 
78
82
  class FalseClass
79
- def html e=nil; H({_: :input, type: :checkbox, title: :False}) end
83
+ def html e=nil,g=nil; H({_: :input, type: :checkbox, title: :False}) end
80
84
  end
81
85
 
86
+ IsBnode = /^_:/
87
+
82
88
  class Hash
83
- def html e={'SERVER_NAME'=>'localhost'}, key=true
84
- (keys.size == 1 && has_key?('uri')) ? url.href :
85
- H({_: :table, class: :html, c:
86
- map{|k,v|
87
- {_: :tr, property: k, c:
88
- [({_: :td,
89
- c: [{_: :a, name: k, href: (k == 'uri' ? v : k), c: k.to_s.abbrURI}], class: :key} if key),
90
- {_: :td,
91
- c: (case k
92
- when E::Content
93
- v
94
- when 'uri'
95
- u = v.E
96
- {_: :a, id: u, href: u.url, c: v}
97
- else
98
- v.html e
99
- end), class: :val}]}}})
89
+ def html e={'SERVER_NAME'=>'localhost'}, g={}, key=true
90
+ if keys.size == 1 && has_key?('uri')
91
+ if uri.match IsBnode
92
+ g[uri].do{|r|
93
+ r.html e,g,key } || uri.href
94
+ else
95
+ uri.href
96
+ end
97
+ else
98
+ H({_: :table, class: :html, c: map{|k,v|
99
+ unless k == 'uri' && (v.match IsBnode)
100
+ {_: :tr, property: k, c:
101
+ [k == E::Content ? {_: :td, class: :val, colspan: 2, c: v} :
102
+ [
103
+ ({_: :td, c: [{_: :a, name: k, href: (k == 'uri' ? v : k), c: k.to_s.abbrURI}], class: :key} if key),
104
+ {_: :td, c: k == 'uri' ? v.E.do{|u| {_: :a, id: u, href: u.url, c: v}} : v.html(e,g), class: :val},
105
+ ]]}
106
+ end
107
+ }})
108
+ end
100
109
  end
101
110
  end
102
111
 
@@ -107,17 +116,21 @@ class E
107
116
  end
108
117
 
109
118
  fn 'view',->d,e{
110
- d.values.sort_by{|r| r[Date].do{|d| d[0].to_s} || ''}.reverse.
111
- map{|r| Fn 'view/select',r,e }}
119
+ d.values.select{|r|
120
+ !r.has_key?('uri') || # URI field missing
121
+ !r.uri.match(IsBnode) # blank node
122
+ true
123
+ }.
124
+ sort_by{|r| r[Date].do{|d| d[0].to_s} || ''}.reverse.
125
+ map{|r| Fn 'view/select',r,e,d}}
112
126
 
113
- fn 'view/base',->d,e,k=true{
127
+ fn 'view/base',->d,e,k=true,graph=nil{
114
128
  [H.once(e,'base',H.css('/css/html')),
115
- d.values.map{|v|v.html e,k}]}
129
+ d.values.map{|v|v.html e,graph,k}]}
116
130
 
117
- fn 'view/select',->r,e{
131
+ fn 'view/select',->r,e,d{
118
132
  graph = {r.uri => r}
119
- view = F['view/base']
120
- # find types, skipping malformed/missing info
133
+ view = nil
121
134
  if r.class == Hash
122
135
  (r[Type].class==Array ? r[Type] : [r[Type]]).do{|types|
123
136
  views = types.map{|t|
@@ -131,13 +144,11 @@ class E
131
144
  flatten.compact
132
145
  view = views[0] unless views.empty?}
133
146
  end
134
- view[graph,e]}
135
-
136
- # multiple views (comma-separated)
137
- fn 'view/multi',->d,e{
138
- e.q['views'].do{|vs|
139
- vs.split(',').map{|v|
140
- F['view/'+v].do{|f|f[d,e]}}}}
147
+ if !view # default view
148
+ F['view/base'][graph,e,true,d]
149
+ else
150
+ view[graph,e]
151
+ end}
141
152
 
142
153
  # enumerate available views
143
154
  fn 'view/?',->d,e{
@@ -154,10 +165,24 @@ class E
154
165
  yield uri,Content,(f && read).do{|r|enc ? r.force_encoding(enc).to_utf8 : r}.hrefs
155
166
  end
156
167
 
168
+ require 'nokogiri'
169
+ def nokogiri; Nokogiri::HTML.parse read end
170
+
171
+ F['HTMLbody'] = -> b {
172
+ b.to_s.split(/<body[^>]*>/)[-1].to_s.split(/<\/body>/)[0] }
173
+
174
+ F['cleanHTML'] = -> b {
175
+ h = Nokogiri::HTML.fragment F['HTMLbody'][b]
176
+ h.css('iframe').remove
177
+ h.css('script').remove
178
+ h.xpath("//@*[starts-with(name(),'on')]").remove
179
+ h.to_s
180
+ }
181
+
157
182
  def contentURIresolve *f
158
183
  send(*f){|s,p,o|
159
184
  yield s, p, p == Content ?
160
- (Nokogiri::HTML.parse o).do{|o|
185
+ (Nokogiri::HTML.fragment o).do{|o|
161
186
  o.css('a').map{|a|
162
187
  if a.has_attribute? 'href'
163
188
  (a.set_attribute 'href', (URI.join s, (a.attr 'href'))) rescue nil
@@ -216,16 +241,18 @@ class E
216
241
  "table= layout arg required"
217
242
  end}
218
243
 
219
- fn 'view/table',->i,e{[H.css('/css/table'),(Fn 'table',i.values,e)]}
220
-
221
- fn 'table',->es,q=nil{ p = {}
222
- es.map{|e|e.respond_to?(:keys) &&
223
- e.keys.map{|k|p[k]=true}}
224
- keys = p.keys
225
- keys.empty? ? es.html :
226
- H({_: :table,:class => :tab,
227
- c: [{_: :tr, c: keys.map{|k|{_: :th, class: :label, property: k, c: k.abbrURI}}},
228
- *es.map{|e|
229
- {_: :tr, about: e.uri, c: keys.map{|k| {_: :td, property: k, c: k=='uri' ? e.E.html : e[k].html}}}}]})}
244
+ fn 'view/table',->g,e{
245
+ keys = E.graphProperties g
246
+ v = g.values
247
+ e.q['sort'].do{|p|
248
+ p = p.expand
249
+ v = v.sort_by{|s|
250
+ s[p].do{|o|
251
+ o[0].to_s}||''}} # cast to a single type (String) so sort will work. every class seems to have a #to_s
252
+ v = v.reverse if e.q['reverse']
253
+ [H.css('/css/table'),
254
+ {_: :table,:class => :tab,
255
+ c: [{_: :tr, c: keys.map{|k|{_: :th, class: :label, property: k, c: k.abbrURI}}},
256
+ v.map{|e|{_: :tr, about: e.uri, c: keys.map{|k| {_: :td, property: k, c: k=='uri' ? e.E.html : e[k].html}}}}]}]}
230
257
 
231
258
  end
@@ -78,7 +78,7 @@ class E
78
78
  # view
79
79
  {uri: s,
80
80
  # img and link to containing resource
81
- c: ->{"<a href='#{v.uri.to_s.do{|u|u.path? ? u : u.E.url}}'><img style='float:left;#{h}' src='#{s}'></a>"}}}}.flatten.map{|i|
81
+ c: ->{"<a href='#{v.uri}'><img style='float:left;#{h}' src='#{s}'></a>"}}}}.flatten.map{|i|
82
82
 
83
83
  # show and mark as seen
84
84
  !seen[i[:uri]] &&
@@ -1,4 +1,4 @@
1
- watch __FILE__
1
+ #watch __FILE__
2
2
  class E
3
3
 
4
4
  # POSIX-fs based index of triples
@@ -11,9 +11,9 @@ class E
11
11
  end
12
12
 
13
13
  # index a triple - no type-normalization
14
+ # we jsut rotate them, use existing k/v store and set @noIndex to stop looping infinitely to index the index..
14
15
  def indexEdit p,o,a
15
16
  return if @noIndex
16
- # puts "index #{p} #{o} #{a}"
17
17
  p.pIndex.noIndex[o,self,a]
18
18
  end
19
19
  def noIndex
@@ -36,79 +36,46 @@ class E
36
36
  g
37
37
  end
38
38
 
39
- # subtree traverse
40
- fn 'set/subtree',->d,r,m{
41
- c =(r['c'].do{|c|c.to_i + 1} || 3).max(100) # one extra for start of next-page
42
- o = r['d'] =~ /^a/ ? :asc : :desc # direction
43
-
44
- ('/'.E.take c, o, d.uri).do{|s| # take subtree
45
- desc, asc = o == :desc ? # orient pagination hints
46
- [s.pop, s[0]] : [s[0], s.pop]
39
+ fn 'set/depth',->d,r,m{ # depth-first
40
+ global = !r.has_key?('local')
41
+ p = global ? d.pathSegment : d
42
+ loc = global ? '' : '&local'
43
+ c = ((r['c'].do{|c|c.to_i} || 12) + 1).max(128) # an extra for next-page pointer
44
+ o = r['d'] =~ /^a/ ? :asc : :desc # direction
45
+ (p.take c, o, r['offset'].do{|o|o.E}).do{|s| # take subtree
46
+ first, last = s[0], s.size > 1 && s.pop
47
+ desc, asc = o == :asc ? [first,last] : [last,first]
47
48
  u = m['#']
48
49
  u[Type] = E[HTTP+'Response']
49
- u[Prev] = {'uri' => desc.url + {'d' => 'desc'}.qs} if desc
50
- u[Next] = {'uri' => asc.url + {'d' => 'asc'}.qs} if asc
50
+ u[Prev] = {'uri' => d.uri + "?set=depth&c=#{c-1}&d=desc#{loc}&offset=" + (URI.escape desc.uri)} if desc
51
+ u[Next] = {'uri' => d.uri + "?set=depth&c=#{c-1}&d=asc#{loc}&offset=" + (URI.escape asc.uri)} if asc
51
52
  s }}
52
53
 
53
- # subtree traverse index on p+o cursor
54
- fn 'set/index',->d,r,m{
55
- top = r['p'].expand.E
56
- count = r['c'] &&
57
- r['c'].to_i.max(1000) || 8
58
- dir = r['d'] && r['d'].match(/^(a|de)sc$/) &&
59
- r['d'].to_sym || :desc
60
-
61
- (top.rangePO count+1, dir, r['offset'], r['o']).do{|s|
62
- # orient pagination pointers
63
- ascending = r['d'].do{|d| d == 'asc' }
64
- first, last = s[0], s.size > 1 && s.pop
65
- desc, asc = ascending && [first,last] || [last,first]
66
- # response description
67
- u = m['#']
68
- u[RDFs+'member'] = s
69
- u[Prev] = {'uri' => '/index/' + r['p'] + '/' + CGI.escape(r['o']) + {'offset' => desc.uri, 'c' => count}.qs } if desc
70
- u[Next] = {'uri' => '/index/' + r['p'] + '/' + CGI.escape(r['o']) + {'offset' => asc.uri, 'c' => count}.qs } if asc
71
-
72
- s.map(&:docs).flatten.uniq }}
73
-
74
- fn '/index/GET',->e,r{
75
- a = e.pathSegment.uri[7..-1].split '/',2
76
- r.q['set'] = 'index'
77
- r.q['p'] = a[0]
78
- r.q['o'] = CGI.unescape a[1]
79
- e.response}
80
-
81
- # predicate index
82
54
  def pIndex
83
55
  shorten.prependURI '/index/'
84
56
  end
85
57
 
86
- # predicate-object index
87
58
  def poIndex o
88
59
  pIndex.concatURI o
89
60
  end
90
61
 
91
- # predicate-object index lookup
62
+ # predicate+object pair lookup
92
63
  def po o
93
64
  pIndex[o.class == E ? o : literal(o)]
94
65
  end
95
66
 
96
- # range query - predicate
97
67
  def rangeP size=8, dir=:desc, offset=nil, object=nil
98
68
  pIndex.subtree(size,dir,offset).map &:ro
99
69
  end
100
70
 
101
- # range query - predicate-object
102
71
  def rangePO n=8,d=:desc,s=nil,o
103
72
  poIndex(o).subtree(n,d,s).map &:ro
104
73
  end
105
74
 
106
- # E -> [node]
107
75
  def subtree *a
108
76
  u.take *a
109
77
  end
110
78
 
111
- # E -> [E]
112
79
  def take *a
113
80
  no.take(*a).map &:E
114
81
  end
@@ -129,49 +96,32 @@ end
129
96
 
130
97
  class Pathname
131
98
 
132
- # take N els from fs tree in sorted, depth-first order
133
99
  def take count=1000, direction=:desc, offset=nil
100
+ offset = offset.d if offset
134
101
 
135
- # construct offset-path
136
- offset = (to_s + offset).gsub(/\/+/,'/').E.path if offset
137
-
138
- # in-range indicator
139
- ok = false
140
-
141
- # result set
102
+ ok = false # in-range mark
142
103
  set=[]
143
-
144
- # asc/desc operators
145
104
  v,m={asc: [:id,:>=],
146
105
  desc: [:reverse,:<=]}[direction]
147
106
 
148
- # visitation function
149
107
  visit=->nodes{
150
-
151
- # sort nodes in asc or desc order
152
108
  nodes.sort_by(&:to_s).send(v).each{|n|
153
109
  ns = n.to_s
154
- # have we got enough nodes?
155
110
  return if 0 >= count
156
111
 
157
- # continue if
158
- (# already in-range
159
- ok ||
160
- # no offset specified
161
- !offset ||
162
- # offset satisfies in-range operator
112
+ (ok || # already in-range
113
+ !offset || # no offset required
163
114
  (sz = [ns,offset].map(&:size).min
164
- ns[0..sz-1].send(m,offset[0..sz-1]))) && (
165
- if !(c = n.c).empty? # has children?
115
+ ns[0..sz-1].send(m,offset[0..sz-1]))) &&
116
+ (if !(c = n.c).empty? # has children?
166
117
  visit.(c) # visit children
167
118
  else
168
- count = count - 1 # decrement wanted-nodes count
119
+ count = count - 1 # decrement nodes-left count
169
120
  set.push n # add node to result-set
170
- ok = true # iterator is now within range
121
+ ok = true # mark iterator as within range
171
122
  end )}}
172
123
 
173
- visit.(c) # start
174
- # result set
124
+ visit.(c)
175
125
  set
176
126
  end
177
127