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.
- data/infod.rb +1 -6
- data/infod/404.rb +3 -4
- data/infod/500.rb +3 -2
- data/infod/GET.rb +23 -14
- data/infod/HEAD.rb +1 -1
- data/infod/HTTP.rb +24 -40
- data/infod/POST.rb +18 -16
- data/infod/audio.rb +12 -4
- data/infod/blog.rb +1 -1
- data/infod/code.rb +2 -2
- data/infod/constants.rb +6 -6
- data/infod/csv.rb +27 -4
- data/infod/edit.rb +58 -42
- data/infod/feed.rb +55 -27
- data/infod/fs.rb +6 -4
- data/infod/glob.rb +3 -4
- data/infod/graph.rb +66 -50
- data/infod/grep.rb +12 -9
- data/infod/groonga.rb +35 -50
- data/infod/html.rb +82 -55
- data/infod/image.rb +1 -1
- data/infod/index.rb +23 -73
- data/infod/infod.rb +1 -6
- data/infod/kv.rb +8 -6
- data/infod/lambda.rb +2 -0
- data/infod/ls.rb +21 -15
- data/infod/mail.rb +36 -27
- data/infod/man.rb +3 -3
- data/infod/microblog.rb +3 -3
- data/infod/mime.rb +1 -0
- data/infod/names.rb +28 -92
- data/infod/page.rb +10 -4
- data/infod/rdf.rb +11 -4
- data/infod/ruby.rb +2 -1
- data/infod/schema.rb +4 -20
- data/infod/sh.rb +3 -5
- data/infod/text.rb +5 -3
- data/infod/threads.rb +12 -13
- data/infod/time.rb +16 -14
- data/infod/webid.rb +0 -0
- metadata +3 -7
- data/infod/PATCH.rb +0 -5
- data/infod/css.rb +0 -21
- data/infod/du.rb +0 -16
- data/infod/json.rb +0 -38
- data/infod/search.rb +0 -16
data/infod/groonga.rb
CHANGED
@@ -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
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
-
#
|
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
|
-
|
29
|
-
|
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
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
41
|
-
|
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['
|
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 ||=
|
59
|
-
|
60
|
-
|
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
|
-
#
|
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
|
-
#
|
67
|
+
# add
|
82
68
|
def roonga graph="global", m = self.graph
|
83
|
-
|
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
|
|
data/infod/html.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#watch __FILE__
|
2
2
|
|
3
|
-
#
|
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
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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.
|
111
|
-
|
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 =
|
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
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
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.
|
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',->
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
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
|
data/infod/image.rb
CHANGED
@@ -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
|
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]] &&
|
data/infod/index.rb
CHANGED
@@ -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
|
-
#
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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' =>
|
50
|
-
u[Next] = {'uri' =>
|
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
|
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
|
-
#
|
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
|
-
#
|
158
|
-
|
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
|
-
|
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
|
119
|
+
count = count - 1 # decrement nodes-left count
|
169
120
|
set.push n # add node to result-set
|
170
|
-
ok = true # iterator
|
121
|
+
ok = true # mark iterator as within range
|
171
122
|
end )}}
|
172
123
|
|
173
|
-
visit.(c)
|
174
|
-
# result set
|
124
|
+
visit.(c)
|
175
125
|
set
|
176
126
|
end
|
177
127
|
|