ontopia-tldr 0.0.1-java
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/COPYING +663 -0
- data/ChangeLog +7 -0
- data/README +112 -0
- data/Rakefile +22 -0
- data/config.ru.sample +13 -0
- data/lib/ontopia-tldr.rb +1 -0
- data/lib/ontopia/tldr.rb +378 -0
- data/lib/ontopia/tldr/public/site.css +61 -0
- data/lib/ontopia/tldr/public/site.js +24 -0
- data/lib/ontopia/tldr/version.rb +52 -0
- data/lib/ontopia/tldr/views/document.erb +14 -0
- data/lib/ontopia/tldr/views/documents.erb +1 -0
- data/lib/ontopia/tldr/views/index.erb +52 -0
- data/lib/ontopia/tldr/views/layout.erb +38 -0
- data/lib/ontopia/tldr/views/topic.erb +15 -0
- data/lib/ontopia/tldr/views/topics.erb +1 -0
- metadata +136 -0
data/ChangeLog
ADDED
data/README
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
= ontopia-tldr - Tolog Document Retrieval with Ontopia
|
2
|
+
|
3
|
+
== VERSION
|
4
|
+
|
5
|
+
This documentation refers to ontopia-tldr version 0.0.1
|
6
|
+
|
7
|
+
|
8
|
+
== DESCRIPTION
|
9
|
+
|
10
|
+
Ontopia::TLDR is an attempt at bridging the gap between the worlds of formal
|
11
|
+
knowledge representation (ontologies, topic maps, etc.) and bibliographic document
|
12
|
+
retrieval (bibliographic databases). It allows for retrieving documents from
|
13
|
+
bibliographic databases (currently, only Midos[http://progris.de] databases are
|
14
|
+
supported) by means of tolog[http://ontopia.net/omnigator/docs/query/tutorial.html],
|
15
|
+
Ontopia's[http://ontopia.net] topic map query language.
|
16
|
+
|
17
|
+
=== Deployment
|
18
|
+
|
19
|
+
Ontopia::TLDR comes as a Sinatra application, so all the standard deployment
|
20
|
+
options apply (rackup, Passenger[https://www.phusionpassenger.com/], etc.).
|
21
|
+
However, in order to allow for maximum flexibility, you need to supply your
|
22
|
+
own <tt>config.ru</tt> file; e.g. (see Ontopia::TLDR for available options):
|
23
|
+
|
24
|
+
require 'ontopia/tldr'
|
25
|
+
|
26
|
+
Ontopia::TLDR.set(
|
27
|
+
dbm_file: File.expand_path('../tldr.dbm', __FILE__),
|
28
|
+
xtm_file: File.expand_path('../tldr.xtm', __FILE__),
|
29
|
+
|
30
|
+
document_keys: %w[YOUR DOCUMENT KEYS],
|
31
|
+
topic_keys: %w[YOUR TOPIC KEYS],
|
32
|
+
|
33
|
+
title: 'YOUR TITLE'
|
34
|
+
)
|
35
|
+
|
36
|
+
run Ontopia::TLDR
|
37
|
+
|
38
|
+
Assuming the following directory layout:
|
39
|
+
|
40
|
+
/srv/tldr
|
41
|
+
|
|
42
|
+
+-- config.ru
|
43
|
+
|
|
44
|
+
+-- tldr.dbm
|
45
|
+
|
|
46
|
+
+-- tldr.xtm
|
47
|
+
|
|
48
|
+
+-- tmp/
|
49
|
+
|
50
|
+
Place your database and topic map files there and adjust their paths in the
|
51
|
+
<tt>config.ru</tt> file. The <tt>tmp/</tt> directory is used by Passenger
|
52
|
+
for the <tt>restart.txt</tt> file.
|
53
|
+
|
54
|
+
To deploy Ontopia::TLDR with Passenger on Apache, create a symlink in the
|
55
|
+
DocumentRoot pointing to the app's <tt>public/</tt> directory (this example
|
56
|
+
makes use of current_gem[http://blackwinter.github.com/current_gem]; adjust
|
57
|
+
the paths according to your environment):
|
58
|
+
|
59
|
+
/var/www
|
60
|
+
|
|
61
|
+
+-- tldr -> /usr/local/jruby/lib/ruby/gems/shared/current/ontopia-tldr/lib/ontopia/tldr/public
|
62
|
+
|
63
|
+
Then put the following snippet in Apache's VirtualHost configuration:
|
64
|
+
|
65
|
+
<VirtualHost *:80>
|
66
|
+
...
|
67
|
+
|
68
|
+
RackBaseURI /tldr
|
69
|
+
<Directory /var/www/tldr>
|
70
|
+
Options -MultiViews
|
71
|
+
PassengerAppRoot /srv/tldr # <-- This (non-standard) line is important
|
72
|
+
</Directory>
|
73
|
+
</VirtualHost>
|
74
|
+
|
75
|
+
|
76
|
+
== SUPPORTED PLATFORMS
|
77
|
+
|
78
|
+
Ontopia::TLDR requires JRuby[http://jruby.org]. It has been tested with jruby
|
79
|
+
1.7.4 (1.9.3p392) on OpenJDK 64-Bit Server VM 1.6.0_27-b27 [linux-amd64].
|
80
|
+
|
81
|
+
|
82
|
+
== LINKS
|
83
|
+
|
84
|
+
<b></b>
|
85
|
+
Documentation:: http://blackwinter.github.com/ontopia-tldr
|
86
|
+
Source code:: http://github.com/blackwinter/ontopia-tldr
|
87
|
+
RubyGem:: http://rubygems.org/gems/ontopia-tldr
|
88
|
+
Ontopia:: http://ontopia.net/
|
89
|
+
Demo:: http://ixtrieve.fh-koeln.de/ghn
|
90
|
+
|
91
|
+
|
92
|
+
== AUTHORS
|
93
|
+
|
94
|
+
* Jens Wille <mailto:jens.wille@gmail.com>
|
95
|
+
|
96
|
+
|
97
|
+
== LICENSE AND COPYRIGHT
|
98
|
+
|
99
|
+
Copyright (C) 2013 Jens Wille
|
100
|
+
|
101
|
+
ontopia-tldr is free software: you can redistribute it and/or modify it
|
102
|
+
under the terms of the GNU Affero General Public License as published by
|
103
|
+
the Free Software Foundation, either version 3 of the License, or (at your
|
104
|
+
option) any later version.
|
105
|
+
|
106
|
+
ontopia-tldr is distributed in the hope that it will be useful, but
|
107
|
+
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
108
|
+
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
109
|
+
License for more details.
|
110
|
+
|
111
|
+
You should have received a copy of the GNU Affero General Public License
|
112
|
+
along with ontopia-tldr. If not, see <http://www.gnu.org/licenses/>.
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require File.expand_path(%q{../lib/ontopia/tldr/version}, __FILE__)
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'hen'
|
5
|
+
|
6
|
+
Hen.lay! {{
|
7
|
+
:gem => {
|
8
|
+
:name => %q{ontopia-tldr},
|
9
|
+
:version => Ontopia::TLDR::VERSION,
|
10
|
+
:summary => %q{Tolog Document Retrieval with Ontopia.},
|
11
|
+
:author => %q{Jens Wille},
|
12
|
+
:email => %q{jens.wille@gmail.com},
|
13
|
+
:license => %q{AGPL},
|
14
|
+
:homepage => :blackwinter,
|
15
|
+
:platform => 'java',
|
16
|
+
:dependencies => %w[json ontopia-topicmaps ruby-nuggets sinatra],
|
17
|
+
:extra_files => FileList['*.sample', 'lib/ontopia/tldr/{public,views}/*'].to_a
|
18
|
+
}
|
19
|
+
}}
|
20
|
+
rescue LoadError => err
|
21
|
+
warn "Please install the `hen' gem. (#{err})"
|
22
|
+
end
|
data/config.ru.sample
ADDED
data/lib/ontopia-tldr.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'ontopia/tldr'
|
data/lib/ontopia/tldr.rb
ADDED
@@ -0,0 +1,378 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
#--
|
4
|
+
###############################################################################
|
5
|
+
# #
|
6
|
+
# ontopia-tldr -- Tolog Document Retrieval with Ontopia. #
|
7
|
+
# #
|
8
|
+
# Copyright (C) 2013 Jens Wille #
|
9
|
+
# #
|
10
|
+
# ontopia-tldr is free software: you can redistribute it and/or modify it #
|
11
|
+
# under the terms of the GNU Affero General Public License as published by #
|
12
|
+
# the Free Software Foundation, either version 3 of the License, or (at your #
|
13
|
+
# option) any later version. #
|
14
|
+
# #
|
15
|
+
# ontopia-tldr is distributed in the hope that it will be useful, but WITHOUT #
|
16
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License #
|
18
|
+
# for more details. #
|
19
|
+
# #
|
20
|
+
# You should have received a copy of the GNU Affero General Public License #
|
21
|
+
# along with ontopia-tldr. If not, see <http://www.gnu.org/licenses/>. #
|
22
|
+
# #
|
23
|
+
###############################################################################
|
24
|
+
#++
|
25
|
+
|
26
|
+
require 'json'
|
27
|
+
require 'sinatra/base'
|
28
|
+
require 'nuggets/midos'
|
29
|
+
require 'ontopia/topicmaps'
|
30
|
+
|
31
|
+
module Ontopia
|
32
|
+
class TLDR < Sinatra::Base
|
33
|
+
|
34
|
+
class << self
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def jget(*a, &b)
|
39
|
+
jroute(:get, *a, &b)
|
40
|
+
end
|
41
|
+
|
42
|
+
def jpost(*a, &b)
|
43
|
+
jroute(:post, *a, &b)
|
44
|
+
end
|
45
|
+
|
46
|
+
def jroute(m, r, t, &b)
|
47
|
+
e = 'json'; e.prepend('.') unless r.end_with?('/')
|
48
|
+
send(m, "#{r}#{e}") { instance_eval(&b); do_json }
|
49
|
+
send(m, r, provides: :html) { instance_eval(&b); erb(t) }
|
50
|
+
send(m, r, provides: :json) { instance_eval(&b); do_json }
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
set :root, __FILE__.chomp('.rb')
|
56
|
+
|
57
|
+
set :otm do
|
58
|
+
@__otm__ ||= Ontopia::Topicmaps::Topicmap.new(settings.xtm_file).tap {
|
59
|
+
Ontopia::Topicmaps.default_stringifier = :id
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
set :dbm do
|
64
|
+
@__dbm__ ||= begin
|
65
|
+
dbm, topic_keys = {}, settings.topic_keys
|
66
|
+
|
67
|
+
topic_index = Hash.new { |h, k| h[k] = {} }
|
68
|
+
dbm.define_singleton_method(:topic_index) { topic_index }
|
69
|
+
|
70
|
+
Nuggets::Midos::Parser.parse_file(settings.dbm_file, settings.dbm_opts) { |id, doc|
|
71
|
+
unless (topics = doc.values_at(*topic_keys).compact).empty?
|
72
|
+
dbm[id] = doc
|
73
|
+
topics.flatten.each { |topic| topic_index[topic][id] = doc }
|
74
|
+
end
|
75
|
+
}
|
76
|
+
|
77
|
+
dbm
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
set :topics do
|
82
|
+
@__topics__ ||= settings.otm.topics(:name)
|
83
|
+
end
|
84
|
+
|
85
|
+
set :topic_index do
|
86
|
+
@__topic_index__ ||= settings.dbm.topic_index
|
87
|
+
end
|
88
|
+
|
89
|
+
set :document_keys, %w[]
|
90
|
+
set :topic_keys, %w[]
|
91
|
+
|
92
|
+
set :title, 'TLDR'
|
93
|
+
|
94
|
+
set :xtm_file, File.expand_path('../tldr.xtm', __FILE__)
|
95
|
+
set :dbm_file, File.expand_path('../tldr.dbm', __FILE__)
|
96
|
+
set :dbm_opts, { encoding: 'utf-8', vs: '|' }
|
97
|
+
|
98
|
+
set :tolog, <<-EOT
|
99
|
+
import "http://psi.ontopia.net/tolog/string/" as s
|
100
|
+
%s
|
101
|
+
select %s from %s?
|
102
|
+
EOT
|
103
|
+
|
104
|
+
set :title_topic, 'TOPIC'
|
105
|
+
set :title_query, 'YOUR QUERY'
|
106
|
+
set :title_rules, 'CUSTOM INFERENCE RULES (optional)'
|
107
|
+
|
108
|
+
set :tolog_sample, {
|
109
|
+
q: <<-EOT,
|
110
|
+
Production($_ : isProducing, $TOPIC : isProductOf),
|
111
|
+
topic-name($TOPIC, $PRODUCTNAME),
|
112
|
+
value($PRODUCTNAME, $PRODUCTSTRING),
|
113
|
+
s:contains($PRODUCTSTRING, "service")
|
114
|
+
EOT
|
115
|
+
r: <<-EOT
|
116
|
+
direct-narrower-term($A, $B) :-
|
117
|
+
HierarchicalRelation($A : broaderTermMember,
|
118
|
+
$B : narrowerTermMember).
|
119
|
+
|
120
|
+
strictly-narrower-term($A, $B) :- {
|
121
|
+
direct-narrower-term($A, $B) |
|
122
|
+
direct-narrower-term($A, $C), strictly-narrower-term($C, $B)
|
123
|
+
}.
|
124
|
+
|
125
|
+
narrower-term($A, $B) :- {
|
126
|
+
$A = $B | strictly-narrower-term($A, $B)
|
127
|
+
}.
|
128
|
+
|
129
|
+
narrower-term-1($A, $B) :- {
|
130
|
+
$A = $B | direct-narrower-term($A, $B)
|
131
|
+
}.
|
132
|
+
|
133
|
+
narrower-term-2($A, $B) :- {
|
134
|
+
narrower-term-1($A, $B) |
|
135
|
+
narrower-term-1($A, $C), narrower-term-1($C, $B)
|
136
|
+
}.
|
137
|
+
|
138
|
+
narrower-term-3($A, $B) :- {
|
139
|
+
narrower-term-2($A, $B) |
|
140
|
+
narrower-term-2($A, $C), narrower-term-1($C, $B)
|
141
|
+
}.
|
142
|
+
|
143
|
+
direct-broader-term($A, $B) :-
|
144
|
+
direct-narrower-term($B, $A).
|
145
|
+
|
146
|
+
strictly-broader-term($A, $B) :-
|
147
|
+
strictly-narrower-term($B, $A).
|
148
|
+
|
149
|
+
broader-term($A, $B) :-
|
150
|
+
narrower-term($B, $A).
|
151
|
+
|
152
|
+
broader-term-1($A, $B) :-
|
153
|
+
narrower-term-1($B, $A).
|
154
|
+
|
155
|
+
broader-term-2($A, $B) :-
|
156
|
+
narrower-term-2($B, $A).
|
157
|
+
|
158
|
+
broader-term-3($A, $B) :-
|
159
|
+
narrower-term-3($B, $A).
|
160
|
+
EOT
|
161
|
+
}
|
162
|
+
|
163
|
+
tolog_maps_base = <<-EOT
|
164
|
+
association-role($ASSOC, $ROLE),
|
165
|
+
association-role($ASSOC, $ROLE2),
|
166
|
+
role-player($ROLE, $TOPIC2),
|
167
|
+
role-player($ROLE2, $TOPIC),
|
168
|
+
type($ASSOC, $TYPE),
|
169
|
+
$TOPIC /= $TOPIC2
|
170
|
+
EOT
|
171
|
+
|
172
|
+
set :tolog_maps, {
|
173
|
+
relations: <<-EOT,
|
174
|
+
select $TYPE, $ROLE, $TOPIC from
|
175
|
+
#{tolog_maps_base}, $TOPIC2 = %s?
|
176
|
+
EOT
|
177
|
+
roles: <<-EOT,
|
178
|
+
select $ROLE, $TOPIC from
|
179
|
+
#{tolog_maps_base}, type($ASSOC, %s)?
|
180
|
+
EOT
|
181
|
+
types: <<-EOT
|
182
|
+
select $TYPE, $TOPIC from
|
183
|
+
#{tolog_maps_base}, type($ROLE, %s)?
|
184
|
+
EOT
|
185
|
+
}
|
186
|
+
|
187
|
+
helpers ERB::Util
|
188
|
+
|
189
|
+
not_found do
|
190
|
+
@error = 'Not found!'
|
191
|
+
erb ''
|
192
|
+
end
|
193
|
+
|
194
|
+
get '/' do
|
195
|
+
erb :index
|
196
|
+
end
|
197
|
+
|
198
|
+
jpost '/', :index do
|
199
|
+
@query = params[:q]
|
200
|
+
@rules = params[:r] || ''
|
201
|
+
@param = params[:p] || TITLE_TOPIC
|
202
|
+
|
203
|
+
if !@query || @query.strip.empty?
|
204
|
+
@error = 'Query missing!'
|
205
|
+
elsif !@param || @param.strip.empty? || @param =~ /\W/
|
206
|
+
@error = 'Invalid projection!'
|
207
|
+
elsif (@topics = get_topics).empty?
|
208
|
+
@topics = nil
|
209
|
+
elsif (@documents = get_documents).empty?
|
210
|
+
@documents = nil
|
211
|
+
else
|
212
|
+
@filter = :documents
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
get '/xtm' do
|
217
|
+
do_file(settings.xtm_file, 'xml')
|
218
|
+
end
|
219
|
+
|
220
|
+
jget '/topics', :topics do
|
221
|
+
@title = "Topics (#{settings.topics.size})"
|
222
|
+
@topics, @filter = settings.topics.keys, :topics
|
223
|
+
end
|
224
|
+
|
225
|
+
jget '/topic/:i', :topic do
|
226
|
+
@title = topic_to_s(@topic = params[:i])
|
227
|
+
not_found unless settings.topics.include?(@topic)
|
228
|
+
|
229
|
+
@documents, @filter = settings.topic_index[@topic], :documents
|
230
|
+
@relations, @roles, @types = {}, {}, {}
|
231
|
+
|
232
|
+
get_topic_maps(:relations)
|
233
|
+
get_topic_maps(:roles) if @relations.empty?
|
234
|
+
get_topic_maps(:types) if @roles.empty?
|
235
|
+
end
|
236
|
+
|
237
|
+
get '/dbm' do
|
238
|
+
do_file(settings.dbm_file, 'txt')
|
239
|
+
end
|
240
|
+
|
241
|
+
jget '/documents', :documents do
|
242
|
+
@title = "Documents (#{settings.dbm.size})"
|
243
|
+
@documents, @filter = settings.dbm, :documents
|
244
|
+
end
|
245
|
+
|
246
|
+
jget '/document/:i', :document do
|
247
|
+
@title = "##{@id = params[:i].to_i}"
|
248
|
+
not_found unless @document = settings.dbm[@id]
|
249
|
+
end
|
250
|
+
|
251
|
+
private
|
252
|
+
|
253
|
+
def do_json
|
254
|
+
content_type :json
|
255
|
+
|
256
|
+
JSON.fast_generate({
|
257
|
+
d: @document || @documents,
|
258
|
+
e: @error,
|
259
|
+
i: @id,
|
260
|
+
l: @relations,
|
261
|
+
o: @roles,
|
262
|
+
p: @param,
|
263
|
+
q: @query,
|
264
|
+
r: @rules,
|
265
|
+
t: @topic || @topics,
|
266
|
+
y: @types
|
267
|
+
}.delete_if { |_, v| !v })
|
268
|
+
end
|
269
|
+
|
270
|
+
def do_file(file, type)
|
271
|
+
File.readable?(file) ? send_file(file, type: type) : not_found
|
272
|
+
end
|
273
|
+
|
274
|
+
def get_topics(query = @query, param = @param, rules = @rules)
|
275
|
+
settings.otm.query(settings.tolog % [rules, "$#{param}", query])
|
276
|
+
rescue => err
|
277
|
+
@error = err.to_s
|
278
|
+
[]
|
279
|
+
end
|
280
|
+
|
281
|
+
def get_topic_maps(name, topic = @topic, hash = nil, str = nil)
|
282
|
+
hash ||= instance_variable_get("@#{name}")
|
283
|
+
str ||= Ontopia::Topicmaps.id_stringifier
|
284
|
+
|
285
|
+
query = settings.tolog_maps[name] % topic
|
286
|
+
keys = settings.otm.extract_query_projection(query)
|
287
|
+
|
288
|
+
settings.otm.query_maps(query).each { |map|
|
289
|
+
values = map.values_at(*keys).map!(&str)
|
290
|
+
topic, key = values.pop, values.pop
|
291
|
+
|
292
|
+
(values.inject(hash) { |h, k| h[k] ||= {} }[key] ||= []) << topic
|
293
|
+
}
|
294
|
+
rescue => err
|
295
|
+
@error = err.to_s
|
296
|
+
end
|
297
|
+
|
298
|
+
def get_documents(topics = @topics)
|
299
|
+
settings.topic_index.values_at(*topics).compact.inject(&:merge)
|
300
|
+
end
|
301
|
+
|
302
|
+
def render_topics(topics = @topics)
|
303
|
+
_ul(topics.sort, id: :topics) { |topic|
|
304
|
+
_li(link_to_topic(topic), ' (', settings.topic_index[topic].size, ')')
|
305
|
+
}
|
306
|
+
end
|
307
|
+
|
308
|
+
def render_documents(documents = @documents)
|
309
|
+
_ul(documents.sort_by { |k,| k }, id: :documents) { |id, doc|
|
310
|
+
_li(link_to_document(id, doc))
|
311
|
+
}
|
312
|
+
end
|
313
|
+
|
314
|
+
def render_topics_hash(hash)
|
315
|
+
_ul(hash.map { |k, v| [topic_to_s(k), k, v] }.sort) { |name, key, value|
|
316
|
+
_li(link_to_topic(key, name),
|
317
|
+
value.is_a?(Hash) ? render_topics_hash(value) :
|
318
|
+
_ul(value.sort.uniq) { |topic| _li(link_to_topic(topic)) })
|
319
|
+
}
|
320
|
+
end
|
321
|
+
|
322
|
+
def topic_to_s(topic)
|
323
|
+
h(settings.topics[topic] || topic.tr('_', ' '))
|
324
|
+
end
|
325
|
+
|
326
|
+
def doc_to_s(id, doc)
|
327
|
+
a, t, u, y = doc.values_at(*settings.document_keys)
|
328
|
+
|
329
|
+
a = a.join('; ') if a.is_a?(Array)
|
330
|
+
s = [a, t].compact.join(': ')
|
331
|
+
|
332
|
+
if s.empty?
|
333
|
+
s = "##{id}"
|
334
|
+
elsif u
|
335
|
+
s << " – #{u}"
|
336
|
+
end
|
337
|
+
|
338
|
+
s << " (#{y})" if y
|
339
|
+
|
340
|
+
h(s)
|
341
|
+
end
|
342
|
+
|
343
|
+
def link_to_topic(topic, text = topic_to_s(topic))
|
344
|
+
_a(text, href: url("/topic/#{h(topic)}"))
|
345
|
+
end
|
346
|
+
|
347
|
+
def link_to_document(id, doc)
|
348
|
+
_a(doc_to_s(id, doc), href: url("/document/#{h(id)}"))
|
349
|
+
end
|
350
|
+
|
351
|
+
def _a(*args)
|
352
|
+
_tag(:a, *args)
|
353
|
+
end
|
354
|
+
|
355
|
+
def _ul(list, *args)
|
356
|
+
_tag(:ul, *args) { |t| list.each { |*i| t << yield(*i) } }
|
357
|
+
end
|
358
|
+
|
359
|
+
def _li(*args)
|
360
|
+
_tag(:li, *args)
|
361
|
+
end
|
362
|
+
|
363
|
+
def _tag(name, *args)
|
364
|
+
a = args.pop.map { |k, v| %Q{#{h(k)}="#{h(v)}"} } if args.last.is_a?(Hash)
|
365
|
+
|
366
|
+
t = ["<#{name}#{a.unshift(nil).join(' ') if a}>"]
|
367
|
+
|
368
|
+
args.each { |s| t << s }
|
369
|
+
yield t if block_given?
|
370
|
+
|
371
|
+
t << "</#{name}>"
|
372
|
+
t.join
|
373
|
+
end
|
374
|
+
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
require_relative 'tldr/version'
|