ontopia-tldr 0.0.1-java
Sign up to get free protection for your applications and to get access to all the features.
- 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'
|