infod 0.0.3.1 → 0.0.3.2
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.
- 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
|
|