rtriplify 0.0.0

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/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 [name of plugin creator]
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,18 @@
1
+ Triplify
2
+ ========
3
+
4
+ Introduction goes here.
5
+
6
+
7
+ Example
8
+ =======
9
+
10
+ gem install rtriplify
11
+
12
+
13
+
14
+
15
+ Example goes here.
16
+
17
+
18
+ Copyright (c) 2010 Nico Patitz, released under the MIT license
data/install.rb ADDED
@@ -0,0 +1 @@
1
+ # Install hook code here
@@ -0,0 +1,95 @@
1
+ require 'configatron'
2
+
3
+ class TriplifyController < ActionController::Base
4
+ def all
5
+ #get all models
6
+ #render all models
7
+ ret=""
8
+ t = Tripleizer.new
9
+ $base_uri= t.uri request.env['REQUEST_URI'].to_s
10
+
11
+ filename = 'test.rdf'
12
+ headers.merge!(
13
+ 'Content-Type' => 'text/rdf+n3',
14
+ 'Content-Disposition' => "attachment; filename=\"#{filename}\"",
15
+ 'Content-Transfer-Encoding' => 'binary'
16
+ )
17
+
18
+ render :content_type => 'text/plain', :text => proc { |response, output|
19
+ t.write_rdf_head(output)
20
+ model_groups = eval("configatron.query").to_hash
21
+ model_groups.each do |model_group_name,model_group|
22
+ model_group.each do |model_name,model_attributes|
23
+ if model_name.to_s =="sql_query"
24
+ t.write_sql(model_group_name,model_attributes,output)
25
+ else
26
+ t.write_model(model_name, model_attributes, output)
27
+ end
28
+ end
29
+ end
30
+ t_metadata = TriplifyMetadata.new
31
+ t.output = output
32
+ t_metadata.write_metadata(t, output)
33
+ }
34
+ end
35
+
36
+ def model
37
+ ret=""
38
+ t = Tripleizer.new
39
+ $base_uri= t.uri request.env['REQUEST_URI'].to_s.split(params[:model])[0]
40
+ render :content_type => 'text/plain', :text => proc {|response, output|
41
+ t.write_rdf_head(output)
42
+ model_group= params[:model].to_s #TODO:groß/kleinschreibung.capitalize
43
+ model_group.downcase!
44
+ params[:id] ? entries= [params[:id]] : entries = :all
45
+ #TODO: multiple models
46
+ models = t.search_models model_group
47
+ models.each do |model_name, model_attributes|
48
+ if model_name.to_s =="sql_query"
49
+ t.write_sql(model_group_name,model_attributes,output)
50
+ else
51
+ t.write_model(model_name, model_attributes, output)
52
+ end
53
+ end
54
+ #write metadata
55
+ #t.write_rdf_metadata(t,output)
56
+ }
57
+ end
58
+
59
+ def index
60
+ ret=""
61
+ t = Tripleizer.new
62
+ $base_uri= t.uri request.env['REQUEST_URI'].to_s
63
+ render :content_type => 'text/plain', :text => proc { |response, output|
64
+ t.write_rdf_head(output)
65
+ subclass = $base_uri.split("triplify/")[1].split("/")
66
+
67
+ group = eval("configatron.query."+controller).to_hash;
68
+ group.each do |key,name|
69
+ model = key.to_s.capitalize
70
+ model = eval(model)
71
+ dtypes =t.dbd_types model
72
+
73
+ model= model.find(:all)
74
+ mapping =eval("configatron.query."+controller+"."+key.to_s).to_hash
75
+
76
+ model.each do |item|
77
+ #t.write_triple(item.id, object, is_literal, dtype, lang)
78
+ c1 = Hash.new
79
+ #add first the id
80
+ c1[:id] = item.id
81
+ #und jetzt das mapping
82
+ mapping.each do |k,v|
83
+ c1[k]=eval("item."+v.to_s)
84
+ end
85
+
86
+ output.write t.make_triples(c1, controller , "", dtypes)
87
+ #render :text => t.make_triples(c1, controller , "", t.dbd_types)
88
+ end
89
+ end
90
+ meta = TriplifyMetadata.new
91
+ t.output=output
92
+ meta.write_metadata t,output
93
+ }
94
+ end
95
+ end
@@ -0,0 +1,8 @@
1
+ require 'configatron'
2
+
3
+ module TriplifyHelper
4
+
5
+ def tweet(text)
6
+ "Tweet! #{text}"
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ class Triplify < ActiveRecord::Base
2
+
3
+ end
@@ -0,0 +1,263 @@
1
+ namespaces:
2
+ vocabulary: http://your-webapp.com/vocabulary/
3
+ rdf: http://www.w3.org/1999/02/22-rdf-syntax-ns#
4
+ rdfs: http://www.w3.org/2000/01/rdf-schema#
5
+ owl: http://www.w3.org/2002/07/owl#
6
+ foaf: http://xmlns.com/foaf/0.1/
7
+ sioc: http://rdfs.org/sioc/ns#
8
+ sioctypes: http://rdfs.org/sioc/types#
9
+ dc: http://purl.org/dc/elements/1.1/
10
+ dcterms: http://purl.org/dc/terms/
11
+ skos: http://www.w3.org/2004/02/skos/core#
12
+ tag: http://www.holygoat.co.uk/owl/redwood/0.1/tags/
13
+ xsd: http://www.w3.org/2001/XMLSchema#
14
+ update: http://triplify.org/vocabulary/update#
15
+ gr: http://purl.org/goodrelations/v1#
16
+ v: http://www.w3.org/2006/vcard/ns#
17
+ daml: http://www.daml.org/2001/09/countries/iso-3166-ont#
18
+
19
+ #queries:
20
+ # project: SELECT p.id AS id,p.title AS 'dc:title',p.description AS 'sioc:content',u.nickname AS 'sioc:has_creator',p.changed AS 'dcterms:modified',p.created AS 'dcterms:created',p.status FROM project p INNER JOIN user u ON(user_id=u.id)
21
+ # user: SELECT nickname id,SHA(CONCAT('mailto:',email)) AS 'foaf:mbox_sha1sum',profile AS 'profile@en' FROM user",
22
+ # update: SELECT p.changed AS id,p.id AS 'update:updatedResource->project' FROM project p
23
+
24
+ query:
25
+ location: #the group write it lowercase
26
+ Country: #the model name...write it capitalized
27
+ ID: iso
28
+ daml:name: iso_name
29
+ iso: iso
30
+ v:name: name
31
+ daml:code: iso3
32
+ numcode: numcode
33
+ state->State: states
34
+ # filter:
35
+ # ID: ">213 and id <224"
36
+ #iso_name: ='VENEZUELA'
37
+ #id must be written upcase, otherwis some trouble with standard yaml language
38
+ #["created_at > ? AND updated_at > ?"]: ["hello","hello]
39
+ #TODO: do it more the ror way
40
+
41
+ State:
42
+ ID: abbr
43
+ name: name
44
+ abrr: abbr
45
+ Country->Country: country
46
+
47
+ # Zone:
48
+ # ID: name
49
+ # description: description
50
+ # ZoneMembers->ZoneMembers: zone_member
51
+
52
+ # ZoneMembers:
53
+ # Zone->Zone: zone
54
+ # type: zoneable_type
55
+ # Country->Country: zoneable
56
+
57
+ product: #the group write it lowercase
58
+ Product: #the model name...write it capitalized
59
+ name: name
60
+ description: description
61
+ available_on: available_on
62
+ permalink: permalink
63
+ Property->Propery: properties
64
+ PropertyValue->ProductProperty: product_properties
65
+ OptionType->OptionType: option_types
66
+ CategoryName: tax_category.name
67
+ CategoryDescription: tax_category.description
68
+
69
+ Property: #the model name...write it capitalized
70
+ ID: name
71
+ presentation: presentation
72
+ Prototype->Prototpye: prototypes
73
+ Product->Product: products
74
+ ProductProperty:
75
+ value: value
76
+
77
+
78
+ # TaxCategory:
79
+ # ID: name
80
+ # description: description
81
+
82
+ ProductGroup:
83
+ name: name
84
+ permalink: permalink
85
+ Product->Product: cached_products
86
+
87
+ Prototype:
88
+ name: name
89
+ Property->Property: properties
90
+ OptionType->OptionType: option_types
91
+
92
+ OptionType:
93
+ ID: name
94
+ presentation: presentation
95
+ Prototype->Prototype: prototypes
96
+
97
+
98
+ Variant:
99
+ Product->Product: product
100
+ sku: sku
101
+ price: price
102
+ weight: weight
103
+ height: height
104
+ width: width
105
+ depth: depth
106
+ count_on_hand: count_on_hand
107
+ OptionValue->OptionValue: option_values
108
+
109
+ Taxon:
110
+ ID: name
111
+ name: taxonomy.name
112
+ permalink: permalink
113
+ Parent->Taxon: parent
114
+
115
+
116
+
117
+ OptionValue:
118
+ ID: name
119
+ position: position
120
+ presentation: presentation
121
+ Variant->Variant: variants
122
+ OptionType->OptionType: option_type
123
+
124
+
125
+
126
+ user:
127
+ User:
128
+ firstname: ship_address.firstname
129
+ lastname: ship_address.lastname
130
+ role->Role: roles
131
+
132
+ Role:
133
+ name: name
134
+ ID: name
135
+
136
+ paymentmethod: #the group write it lowercase
137
+ PaymentMethod: #the model name...write it capitalized
138
+ gr:PaymentMethod: name
139
+ description: description
140
+ filter:
141
+ active: = 't'
142
+ #["created_at > ? AND updated_at > ?"]: ["hello","hello]
143
+ #TODO: do it more the ror way
144
+ # orders:
145
+ # sql_query:
146
+ # query1: select * from products where id > 706676762
147
+
148
+
149
+ # Some of the columns of the Triplify queries will contain references to other
150
+ # objects rather than literal values. The following configuration array
151
+ # specifies, which columns are references to objects of which type.
152
+
153
+ objectProperties:
154
+ has_creator: movie
155
+
156
+ # Objects are classified according to their type. However, you can specify
157
+ # a mapping here, if objects of a certain type should be associated with a
158
+ # different class (e.g. classify all users as 'foaf:person'). If you are
159
+ # unsure it is safe to leave this configuration array empty.
160
+ #
161
+ classMap:
162
+ movie: foaf:person
163
+
164
+ license: http://creativecommons.org/licenses/by/3.0/us/
165
+
166
+ # Additional metadata
167
+ # You can add arbitrary metadata. The keys of the following array are
168
+ # properties, the values will be represented as respective property values.
169
+
170
+ metadata:
171
+ dc:title: test
172
+ dc:publisher: test
173
+
174
+ # Set this to true in order to register your linked data endpoint with the
175
+ # Triplify registry (http://triplify.org/Registry).
176
+ # Registering is absolutely recommended, since that allows other Web sites
177
+ # (e.g. peer Web applications, search engines and mashups) to easily find your
178
+ # content. Requires PHP ini variable allow_url_fopen set to true.
179
+ # You can also register your data source manually by accessing register.php in
180
+ # the triplify folder, or at: http://triplify.org/Registry
181
+
182
+ register: true
183
+ ttl: 0
184
+ cachedir: cache
185
+
186
+ # Linked Data Depth
187
+ #
188
+ # Specify on which URI level to expose the data - possible values are:
189
+ # - Use 0 or ommit to expose all available content on the highest level
190
+ # all content will be exposed when /triplify/ is accessed on your server
191
+ # this configuration is recommended for small to medium websites.
192
+ # - Use 1 to publish only links to the classes on the highest level and all
193
+ # content will be exposed when for example /triplify/user/ is accessed.
194
+ # - Use 2 to publish only links on highest and classes level and all
195
+ # content will be exposed on the instance level, e.g. when /triplify/user/1/
196
+ # is accessed.
197
+ #
198
+ LinkedDataDepth: 2
199
+
200
+ # Callback Functions
201
+ #
202
+ # Some of the columns of the Triplify queries will contain data, which has to
203
+ # be processed before exposed as RDF (literals). This configuration array maps
204
+ # column names to respective functions, which have to take the data value as a
205
+ # parameter and return it processed.
206
+
207
+ CallbackFunctions:
208
+
209
+
210
+ # Semantic Pingback
211
+ #
212
+ # This section contains the Semantic Pingback configuration.
213
+ #
214
+ pingback:
215
+ # Whether X-Pingback header should be exposed and XML-RPC is active.
216
+ enabled: true
217
+ #Whether to write Pingbacks with the instance data.
218
+ write: true
219
+ #Whether to use mod_rewrite, if it is available...
220
+ use_mod_rewrite: true
221
+
222
+
223
+ # metadata
224
+
225
+ ##
226
+ ## BEGIN OF CONFIGURATION
227
+ ##
228
+
229
+ # You have to specify the operator of this Triplify service. The operator is
230
+ # usually you or your group or organization.
231
+ # There are two options to specify the operator. The first option is an HTTP
232
+ # URI that identifies the operator. This is the preferred option.
233
+ #
234
+ operator_uri:
235
+ #/* The second option is the specification by providing a name, a homepage
236
+ # * address, and a URI that identifies the type of the operator.
237
+ # */
238
+ operator_name:
239
+ operator_homepage:
240
+ operator_type:
241
+ #// $provenance['OperatorType'] = 'http://xmlns.com/foaf/0.1/Organization';
242
+ #// $provenance['OperatorType'] = 'http://swrc.ontoware.org/ontology#ResearchGroup';
243
+
244
+
245
+ #/* If you have an HTTP URI that identifies your triplified dataset (and that
246
+ # * links to a voiD description of your dataset) specify it here:
247
+ # */
248
+ dataset:
249
+
250
+ #/* If you have an HTTP URI that identifies the accessed database server specify
251
+ # * it here:
252
+ # */
253
+ database_server:
254
+
255
+ #/* If you have an HTTP URI that identifies the configuration file used by your
256
+ # * Triplify installation specify it here:
257
+ # */
258
+ mapping:
259
+
260
+ #
261
+ #//
262
+ #// END OF CONFIGURATION
263
+ #//
data/lib/metadata.rb ADDED
@@ -0,0 +1,120 @@
1
+ require 'tripleizer'
2
+ require 'configatron'
3
+
4
+ class TriplifyMetadata
5
+ def write_metadata tripleizer,output
6
+ p = ProvenanceWriter.new
7
+ p.describe_provenance "",tripleizer,output
8
+ end
9
+ end
10
+
11
+ class ProvenanceWriter
12
+ def initialize
13
+
14
+ end
15
+
16
+ def describe_provenance( subject, tripleizer,output)
17
+ @now = Time.new
18
+ @triplify_instance = get_new_bnode_id
19
+ tripleizer.write_triple(subject, tripleizer.uri("rdf:type" ), tripleizer.uri("prv:DataItem" ))
20
+
21
+ if configatron.data_set
22
+ tripleizer.write_triple( subject, tripleizer.uri("prv:containedBy"),tripleizer.uri(configatron.data_set ))
23
+ tripleizer.write_triple( tripleizer.uri(configatron.data_set), tripleizer.uri('rdf:type'), tripleizer.uri('void:Dataset') )
24
+ end
25
+
26
+ creation = get_new_bnode_id
27
+ tripleizer.write_triple( subject, tripleizer.uri("prv:createdBy"), creation )
28
+ describe_creation( creation, tripleizer )
29
+ describe_triplify_instance( @triplify_instance, tripleizer );
30
+ end
31
+
32
+ def describe_triplify_instance(subject, tripleizer)
33
+ tripleizer.write_triple(subject,tripleizer.uri("rdf:type"),tripleizer.uri("prvTypes:DataCreatingService"))
34
+ tripleizer.write_triple(subject,tripleizer.uri("rdfs:comment"), "Triplify #{tripleizer.version} (http://Triplify.org)", "isliteral")
35
+
36
+ operator=""
37
+ if configatron.operator_uri
38
+ operator = tripleizer.uri(configatron.operator_name)
39
+ else
40
+ if configatron.operator_type || configatron.operator_homepage
41
+ operator= get_new_bnode_id
42
+ end
43
+ end
44
+
45
+ unless operator.blank?
46
+ tripleizer.write_triple( subject, tripleizer.uri('prv:operatedBy'), operator )
47
+ if configatron.operator_type
48
+ if ( tripleizer.uri(configatron.operator_type) != tripleizer.uri('prv:HumanActor') )
49
+ tripleizer.write_triple(operator, tripleizer.uri('rdf:type'),tripleizer.uri(configatron.operator_type) )
50
+ end
51
+ end
52
+ tripleizer.write_triple(operator, tripleizer.uri('rdf:type'),tripleizer.uri('prv:HumanActor') )
53
+ if configatron.operator_name
54
+ tripleizer.write_triple( operator, tripleizer.uri('foaf:name'), configatron.operator_name, "true" )
55
+ end
56
+ if configatron.operator_homepage
57
+ tripleizer.write_triple( operator, tripleizer.uri('foaf:homepage'), configatron.operator_homepage )
58
+ end
59
+ end
60
+ end
61
+
62
+ def describe_creation(subject, tripleizer)
63
+ tripleizer.write_triple( subject, tripleizer.uri('rdf:type'), tripleizer.uri('prv:DataCreation') )
64
+ #TODO: @now in iso time format date("c",...
65
+ tripleizer.write_triple( subject, tripleizer.uri('prv:performedAt'),@now , "true", tripleizer.uri('xsd:dateTime') )
66
+
67
+ creator = @triplify_instance
68
+ source_data = get_new_bnode_id
69
+
70
+ mapping = configatron.mapping ? tripleizer.uri(configatron.mapping) : get_new_bnode_id
71
+
72
+ tripleizer.write_triple( subject, tripleizer.uri('prv:performedBy'), creator );
73
+ tripleizer.write_triple( subject, tripleizer.uri('prv:usedData'), source_data );
74
+ tripleizer.write_triple( subject, tripleizer.uri('prv:usedGuideline'), mapping );
75
+
76
+ describe_source_data( source_data, tripleizer );
77
+ describe_mapping( mapping, tripleizer );
78
+ end
79
+
80
+ def describe_source_data ( subject, tripleizer )
81
+ doc = get_new_bnode_id
82
+ tripleizer.write_triple( subject, tripleizer.uri('prv:containedBy'), doc )
83
+ tripleizer.write_triple( doc, tripleizer.uri('rdf:type'), tripleizer.uri('prv:Representation') )
84
+
85
+ access = get_new_bnode_id
86
+ tripleizer.write_triple( doc, tripleizer.uri('prv:retrievedBy'), access )
87
+ describe_source_data_access access, tripleizer
88
+ end
89
+
90
+ def describe_source_data_access ( subject, tripleizer )
91
+ tripleizer.write_triple( subject,
92
+ tripleizer.uri('rdf:type'),
93
+ tripleizer.uri('prv:DataAccess') )
94
+ tripleizer.write_triple( subject,
95
+ tripleizer.uri('prv:performedAt'),
96
+ @now,
97
+ "true",
98
+ tripleizer.uri('xsd:dateTime') )
99
+
100
+ if configatron.database_server
101
+ tripleizer.write_triple( subject,
102
+ tripleizer.uri('prv:accessedServer'),
103
+ tripleizer.uri(configatron.database_server) )
104
+ end
105
+ accessor = @triplify_instance
106
+ tripleizer.write_triple( subject, tripleizer.uri('prv:performedBy'), accessor )
107
+ end
108
+
109
+
110
+ def describe_mapping ( subject, tripleizer )
111
+ tripleizer.write_triple( subject, tripleizer.uri('rdf:type'), tripleizer.uri('prvTypes:TriplifyMapping') )
112
+ end
113
+
114
+ def get_new_bnode_id ()
115
+ @bnode_counter ||=0
116
+ @bnode_counter+=1
117
+ "_:x#{@bnode_counter}"
118
+ end
119
+
120
+ end
data/lib/model.rb ADDED
@@ -0,0 +1,138 @@
1
+ # To change this template, choose Tools | Templates
2
+ # and open the template in the editor.
3
+ require 'configatron'
4
+
5
+ class Model
6
+ attr_accessor :model
7
+ attr_accessor :model_name
8
+ attr_accessor :model_attributes
9
+
10
+ def initialize model_name
11
+ @model_name= model_name
12
+
13
+ #read the model attributes
14
+ @model_attributes = get_model_attributes model_name
15
+ unless @model_attributes
16
+ h="hello"
17
+ end
18
+ @model = get_model
19
+
20
+ end
21
+
22
+ def get_model
23
+ eval(@model_name.to_s)
24
+ end
25
+
26
+ ###
27
+ # *returns the filter of the model
28
+ #*@remove if filter should be removed from model_attributes list
29
+ ##*/
30
+ def get_filter remove=false
31
+ filter=nil
32
+ if model_attributes.has_key? :filter
33
+ filter=""
34
+ model_attributes[:filter].to_hash.each do |a,b|
35
+ filter += (filter.blank? ? "" : " and ") + a.to_s.downcase+b.to_s
36
+ end
37
+ model_attributes.delete :filter if remove
38
+ end
39
+ filter
40
+ end
41
+
42
+ def get_include
43
+ ret = Array.new
44
+ model_attributes.select {|k,v| k.to_s.include?("->")}.each do |attribute|
45
+ ref_model = attribute[1]
46
+ #get model
47
+ ret.push(ref_model.downcase.to_sym)
48
+ end
49
+ model_attributes.select {|k,v| v.to_s.include?(".")}.each do |attribute|
50
+ ref_model = attribute[1].split(".")[0]
51
+ #get model
52
+ ret.push(ref_model.downcase.to_sym)
53
+ end
54
+ ret
55
+ end
56
+
57
+ def get_model_attributes key
58
+ model_groups = eval("configatron.query").to_hash
59
+ model_groups.each do |model_group_name,model_group|
60
+ model_group.each do |model_name_query,attributes|
61
+ if model_name_query.to_s.downcase == key.to_s.downcase
62
+ return attributes
63
+ end
64
+ end
65
+ end
66
+ nil
67
+ end
68
+
69
+ def read_attributes
70
+ get_model_attributes @model_name
71
+ end
72
+
73
+ def get_datatypes
74
+ #hard coded mapping look mapping table
75
+ mapping = Hash[ "binary"=>"base64Binary","boolean"=>"boolean","date"=>"date","datetime"=>"dateTime","decimal"=>"decimal","float"=>"float","integer"=>"integer","string"=>"string","text"=>"string","time"=>"time","timestamp"=>"dateTime",]
76
+ dtypes = Hash.new
77
+ @model.columns_hash.each_key do |m|
78
+ #make xsd datatye
79
+ dtypes[m.to_s] ="xsd:#{mapping[@model.columns_hash[m].type.to_s] }"
80
+ end
81
+ #replace mapping
82
+ @model_attributes.each do |k,v|
83
+ dtypes[k.to_s]=dtypes[v.to_s]
84
+ end
85
+ dtypes
86
+ end
87
+
88
+ def get_rows
89
+ #make filter
90
+ t = @model
91
+ filter = get_filter(true)
92
+ include = get_include
93
+
94
+ if include.empty?
95
+ if filter
96
+ t = t.find(:all, :conditions =>filter)
97
+ else
98
+ t = t.find(:all)
99
+ end
100
+ else
101
+ if filter
102
+ t = t.find(:all,:include => include, :conditions => filter)
103
+ else
104
+ t = t.find(:all,:include => include)
105
+ end
106
+ end
107
+ t
108
+ end
109
+
110
+ def get_key
111
+ id_keys = @model_attributes.to_hash.keys
112
+ #hotfix..bad performance
113
+ id_keys.map!{|k| k.to_s }
114
+ id_key= id_keys.select{|k| k =~/^(ID|id|iD|Id)$/ }
115
+ return :id if id_key.empty?
116
+ return @model_attributes[id_key[0].to_sym]
117
+ end
118
+
119
+ # def extract_id_line model_attributes, line,item,dtypes
120
+ # #look if id is mapped to another field
121
+ # id_keys = model_attributes.to_hash.keys
122
+ # #hotfix..bad performance
123
+ # id_keys.map!{|k| k.to_s }
124
+ # id_key= id_keys.select{|k| k =~/^(ID|id|iD|Id)$/ }
125
+ # if id_key.empty?
126
+ # line[:id] = item.id
127
+ # else
128
+ # line[:id] = eval("item.#{model_attributes[id_key[0].to_sym]}")
129
+ # #set the correct datatype for it
130
+ # dtypes["id"]= line[:id].class.to_s.downcase
131
+ # #remove the id line
132
+ # line.delete id_key[0].to_sym
133
+ # end
134
+ # end
135
+
136
+
137
+
138
+ end
data/lib/rtriplify.rb ADDED
@@ -0,0 +1,17 @@
1
+ require 'configatron'
2
+
3
+ #loading helpers and controllers
4
+
5
+ %w{ models controllers helpers}.each do |dir|
6
+ path = File.join(File.dirname(__FILE__), 'app', dir)
7
+ $LOAD_PATH << path
8
+ ActiveSupport::Dependencies.load_paths << path
9
+ ActiveSupport::Dependencies.load_once_paths.delete(path)
10
+ end
11
+
12
+ # load settings
13
+ configatron.configure_from_yaml('vendor/plugins/rtriplify/lib/config/database.yml')
14
+
15
+
16
+
17
+
data/lib/tripleizer.rb ADDED
@@ -0,0 +1,302 @@
1
+ require 'configatron'
2
+
3
+ class Tripleizer
4
+ def initialize output=nil
5
+ @object_properties = configatron.objectProperties.to_hash
6
+ @object_namespaches = configatron.namespaces.to_hash
7
+ @class_map = configatron.classMap.to_hash
8
+ @output=output
9
+ @version="0.0.1"
10
+
11
+ end
12
+
13
+ attr_accessor :output
14
+ attr_reader :version
15
+
16
+ def tripleize (tr_self,c =nil,id=nil)
17
+ #$this->writeTriple($self,$this->uri('rdfs:comment'),'Generated by Triplify '.$this->version.' (http://Triplify.org)',true);
18
+
19
+ #if($this->config['license'])
20
+ # $this->writeTriple($self,'http://creativecommons.org/ns#license',$this->config['license']);
21
+
22
+
23
+ # configatron.queries.to_hash.each do |_class,q|
24
+ # puts k.to_s+ " \n"
25
+ # cols,group=nil,nil
26
+ #
27
+ # #TODO:mehrere Unterabfragen
28
+ #
29
+ # case configatron.LinkedDataDepth
30
+ # when 3
31
+ # if !id
32
+ # return #datentiefe von 3 gibts laut config nicht TODO:abfangen
33
+ # end
34
+ # when 2
35
+ # if !c
36
+ # write_triple uri(_class),uri('rdf:type'), uri('owl:Class')
37
+ # end
38
+ # #TODO continue 2
39
+ # return
40
+ # when 1
41
+ #
42
+ # else
43
+ # #TODO:DataDepthERROR
44
+ # end
45
+ # #TODO:some magic sql-query building
46
+ #
47
+ # #query sql
48
+ # #for each result make_triples
49
+ #
50
+ # end
51
+ end
52
+
53
+ def search_models key
54
+ model_groups = eval("configatron.query").to_hash
55
+ model_groups.each do |model_group_name,model_group|
56
+ if model_group_name.to_s.downcase == key.downcase
57
+ return model_group
58
+ end
59
+ end
60
+ end
61
+
62
+ #
63
+ def write_model model_name,model_attributes, output
64
+ puts model_name
65
+
66
+ if model_name.to_s == "OptionValue"
67
+ hello = "Product"
68
+ end
69
+ model = Model.new(model_name)
70
+ #dtypes =dbd_types model,model.model_attributes
71
+ #
72
+
73
+ data_types= model.get_datatypes
74
+ #build hash
75
+ model.get_rows.each do |item|
76
+ line = Hash.new
77
+ subline = Hash.new
78
+ #and now add all mapping-attributes
79
+
80
+ model.model_attributes.each do |k,v|
81
+
82
+ if k.to_s.include?("->")
83
+ #find all subelements
84
+
85
+ submodel = eval("item."+v.downcase)
86
+
87
+ if submodel.class != (Array)
88
+ if submodel
89
+ sub_m = Model.new(submodel.class.to_s)
90
+ subline[:id] = item.id
91
+ subline[k] = eval("submodel."+sub_m.get_key.to_s)
92
+ #subline[k.split"->"[0]] = eval("item."+k.split"->"[1]+".id")
93
+ sub_data_types = model.get_datatypes
94
+ extract_id_line(model.model_attributes, subline, item, sub_data_types)
95
+ output.write make_triples(subline, model.model_name , "", sub_data_types,false)
96
+ end
97
+ else
98
+ unless submodel.empty?
99
+ sub_m = Model.new(submodel[0].class.to_s)
100
+ submodel.each do |submodel_item|
101
+ subline[:id] = item.id
102
+ subline[k] = eval("submodel_item."+sub_m.get_key.to_s)
103
+ #subline[k.split"->"[0]] = eval("item."+k.split"->"[1]+".id")
104
+ sub_data_types = model.get_datatypes
105
+ extract_id_line(model.model_attributes, subline, item,sub_data_types)
106
+ output.write make_triples(subline, model.model_name , "", sub_data_types,false)
107
+ end
108
+ end
109
+ end
110
+ else
111
+ write_line = true;
112
+ if v.to_s.include? "."
113
+ write_line = nil unless eval("item."+v.to_s.split(".")[0])
114
+ end
115
+ line[k.to_s]=eval("item."+v.to_s) if write_line
116
+ end
117
+ end
118
+ extract_id_line(model.model_attributes, line, item,data_types)
119
+
120
+ #watch if there is a mapping to another object with "->"
121
+ #get triples of row
122
+ output.write make_triples(line, model.model_name , "", data_types)
123
+ #render :text => t.make_triples(c1, controller , "", t.dbd_types)
124
+ end
125
+ end
126
+
127
+ def extract_id_line model_attributes, line,item,dtypes
128
+ #look if id is mapped to another field
129
+ id_keys = model_attributes.to_hash.keys
130
+ #hotfix..bad performance
131
+ id_keys.map!{|k| k.to_s }
132
+ id_key= id_keys.select{|k| k =~/^(ID|id|iD|Id)$/ }
133
+ if id_key.empty?
134
+ line[:id] = item.id
135
+ else
136
+ line[:id] = eval("item.#{model_attributes[id_key[0].to_sym]}")
137
+ #set the correct datatype for it
138
+ dtypes["id"]= dtypes[id_key[0]]
139
+ #remove the id line
140
+ line.delete id_key[0]
141
+ end
142
+ end
143
+
144
+ def write_sql model_name, model_attributes,output
145
+ model_attributes.each do|key,query|
146
+ sql= ActiveRecord::Base.connection();
147
+ (sql.select_all query).each do |row|
148
+ output.write make_triples(row,model_name,"")
149
+ end
150
+ end
151
+ end
152
+
153
+ def write_rdf_head output
154
+ # $this->writeTriple($self,$this->uri('rdfs:comment'),'Generated by Triplify '.$this->version.' (http://Triplify.org)',true);
155
+ output.write write_triple($base_uri, uri("rdfs:comment"), 'Generated by Triplify '+'version'+ " (http://Triplify.org) ", "literal")
156
+ unless configatron.license.blank?
157
+ #$this->writeTriple($self,'http://creativecommons.org/ns#license',$this->config['license']);
158
+ output.write write_triple($base_uri,'http://creativecommons.org/ns#license',configatron.license)
159
+ end
160
+ end
161
+
162
+
163
+
164
+ def make_triples (c1,rdf_class,maketype,dtypes=Array.new,rdf_type=true)
165
+ rdf_ns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
166
+ ipref= uri rdf_class.to_s+'/'
167
+ is_literal=false
168
+ dtype,lang,ret="","",""
169
+ object_property = nil
170
+
171
+ unless c1[:id]
172
+ id_row = c1.select {|k,v| k =~ /^(id|ID|iD|Id)$/}
173
+ c1[:id] = id_row[0][1]
174
+ c1.delete id_row[0][0]
175
+
176
+ #set datatype for id
177
+ #dtypes[:id] = uri( (id_row[0][1]).type)
178
+ end
179
+
180
+ subject = uri c1[:id].to_s,ipref
181
+ #write model :-)
182
+
183
+ c= @class_map[rdf_class.to_sym] ?@class_map[rdf_class.to_sym]:rdf_class
184
+ ret+= write_triple subject, rdf_ns+"type", uri(c, @object_namespaches[:vocabulary]) if rdf_type
185
+
186
+ c1.delete :id unless rdf_type
187
+
188
+ c1.each do |k,v|
189
+ k=k.to_s
190
+ if v.to_s.empty?
191
+ next
192
+ end
193
+ # beinhaltet key ^^ dann type oder sprache richtig setzen
194
+ if k.index("^^")
195
+ #TODO: k,dtype = k.split("^^")
196
+ dtype= uri k.split("^^")[1]
197
+ k= k.split("^^")[0]
198
+ else if dtypes.include? k
199
+ dtype= uri dtypes[k]
200
+ else if k.index("@")
201
+ lang=k.split("@")[1]
202
+ k = k.split("@")[0]
203
+ end
204
+ end
205
+ end
206
+ # beinhaltet key -> dann muss objekt richtig gesetzt werden
207
+ if k.index("->")
208
+ k, object_property = k.split("->")
209
+ else
210
+ object_property = @object_properties[k]
211
+ end
212
+
213
+ #TODO
214
+ #callbackfunktion
215
+ #
216
+
217
+ prop= self.uri(k,@object_namespaches[:vocabulary])
218
+
219
+ unless object_property
220
+ is_literal= true
221
+ object= v.to_s
222
+ else
223
+ is_literal= false
224
+ #uri($objectProperty.($objectProperty&&substr($objectProperty,-1,1)!='/'?'/':'').$val);
225
+ #TODO: fixme "/" in the middle
226
+ #object= uri "#{object_property}/#{object_property && object_property[-1,1].to_s !="/" ?"/":":"}#{v}"
227
+ object= uri "#{object_property}#{object_property[-1,1].to_s !="/" ? "/":":"}#{v}"
228
+ end
229
+ ret +=write_triple(subject,prop,object,is_literal,dtype,lang).to_s
230
+ end
231
+ ret
232
+ end
233
+
234
+ def write_triple(subject,predicate,object,is_literal=false,dtype="",lang="")
235
+ #if json -
236
+ #end
237
+ #else
238
+ #(lang?"@#{lang}":'')
239
+
240
+ #define the object
241
+ if(is_literal)
242
+ object = "\"#{object.to_s.gsub('"','%22')}\""+ (dtype.empty? ? (lang.empty? ? "": "@#{lang}" ):"^^<#{dtype}>" )
243
+ else
244
+ object = ( object[0..1] == "_:" ) ? object : "<#{object}>"
245
+ end
246
+ #object="\"#{object[1..-2].gsub('"','\"')}\""
247
+ #define the subject
248
+ subject = ( subject[0..1] == "_:" ) ? subject : "<#{subject}>"
249
+ if @output
250
+ @output.write "#{subject} <#{predicate}> #{object} .\n"
251
+ end
252
+ "#{subject} <#{predicate}> #{object} .\n"
253
+ end
254
+
255
+ #generates an uri with the given name
256
+ def uri (name,default="")
257
+ name=name.to_s
258
+
259
+ if name.try(:include?, "://")
260
+ return name[0..name.length-2] if name[-1,1]=="/"
261
+ return name[0..name.length]
262
+ else
263
+ name +="/" unless (name[name.length-1..name.length-1] == ("/" || "#")) || name.try(:include?, ":")
264
+
265
+ if name.index(":")
266
+ #$this->ns[substr($name,0,strpos($name,':'))].$this->normalizeLocalName(substr($name,strpos($name,':')+1))
267
+ t= @object_namespaches[name.split(":")[0].to_sym]
268
+ t ||=""
269
+ t += "/" unless (t[t.length-1..t.length-1] == ("/" || "#") || t.blank?) || name.try(:include?, ":")
270
+ return uri( t+ normalize_local_name(name.split(":")[1])+"/")
271
+ else
272
+ ##($default?$default:$GLOBALS['baseURI']).$this->normalizeLocalName($name));
273
+ #falls bei default hinten ein "/" ist abschneiden
274
+ #default= default[0.. default.length-2] unless default[default.length-1..default.length-1] == "/"
275
+ t= default.blank? ? $base_uri : default
276
+ t += "/" unless t[t.length-1..t.length-1]=="/"
277
+ return uri( t + normalize_local_name(name))
278
+ end
279
+ end
280
+ end
281
+
282
+ ### returns an hash with columnname as key and datatype as value
283
+ def dbd_types (model,model_attributes)
284
+ #hard coded mapping look mapping table
285
+ mapping = Hash[ "binary"=>"base64Binary","boolean"=>"boolean","date"=>"date","datetime"=>"dateTime","decimal"=>"decimal","float"=>"float","integer"=>"integer","string"=>"string","text"=>"string","time"=>"time","timestamp"=>"dateTime",]
286
+ dtypes = Hash.new
287
+ model.columns_hash.each_key do |m|
288
+ #make xsd datatye
289
+ dtypes[m.to_s] ="xsd:#{mapping[model.columns_hash[m].type.to_s] }"
290
+ end
291
+ #replace mapping
292
+ model_attributes.each do |k,v|
293
+ dtypes[k.to_s]=dtypes[v.to_s]
294
+ end
295
+ dtypes
296
+ end
297
+ #some encoding stuff
298
+ def normalize_local_name name
299
+ CGI::escape(name).gsub(/%2F/,'/').gsub(/%23/,'#')
300
+ end
301
+
302
+ end
File without changes
@@ -0,0 +1,4 @@
1
+ # To change this template, choose Tools | Templates
2
+ # and open the template in the editor.
3
+
4
+ puts "Hello World"
File without changes
data/lib/triplify.rb ADDED
@@ -0,0 +1,11 @@
1
+ #loading helpers and controllers
2
+
3
+ %w{ models controllers helpers}.each do |dir|
4
+ path = File.join(File.dirname(__FILE__), 'app', dir)
5
+ $LOAD_PATH << path
6
+ ActiveSupport::Dependencies.load_paths << path
7
+ ActiveSupport::Dependencies.load_once_paths.delete(path)
8
+ end
9
+
10
+
11
+
@@ -0,0 +1,121 @@
1
+ require 'configatron'
2
+
3
+ class TriplifyMetadata
4
+ def write_metadata tripleizer,output
5
+ p = ProvenanceWriter.new
6
+ p.describe_provenance "http://triplify.org",tripleizer,output
7
+ end
8
+ end
9
+
10
+ class ProvenanceWriter
11
+ def initialize
12
+
13
+ end
14
+
15
+ def describe_provenance( subject, tripleizer,output)
16
+ @now = Time.new
17
+ @triplify_instance = get_new_bnode_id
18
+ tripleizer.write_triple(subject, tripleizer.uri("rdf:type" ), tripleizer.uri("prv:DataItem" ))
19
+
20
+ if configatron.data_set
21
+ tripleizer.write_triple( subject, tripleizer.uri("prv:containedBy"),tripleizer.uri(configatron.data_set ))
22
+ tripleizer.write_triple( tripleizer.uri(configatron.data_set), tripleizer.uri('rdf:type'), tripleizer.uri('void:Dataset') )
23
+ end
24
+
25
+ creation = get_new_bnode_id
26
+ tripleizer.write_triple( subject, tripleizer.uri("prv:createdBy"), creation )
27
+ describe_creation( creation, tripleizer )
28
+ describe_triplify_instance( @triplify_instance, tripleizer );
29
+ end
30
+
31
+ def describe_triplify_instance(subject, tripleizer)
32
+ tripleizer.write_triple(subject,tripleizer.uri("rdf:type"),tripleizer.uri("prvTypes:DataCreatingService"))
33
+ tripleizer.write_triple(subject,tripleizer.uri("rdfs:comment"), "Triplify #{tripleizer.version} (http://Triplify.org)", "isliteral")
34
+
35
+ operator=""
36
+ if configatron.operator_uri
37
+ operator = tripleizer.uri(configatron.operator_name)
38
+ else
39
+ if configatron.operator_type || configatron.operator_homepage
40
+ operator= get_new_bnode_id
41
+ end
42
+ end
43
+
44
+ unless operator.blank?
45
+ tripleizer.write_triple( subject, tripleizer.uri('prv:operatedBy'), operator )
46
+ if configatron.operator_type
47
+ if ( tripleizer.uri(configatron.operator_type) != tripleizer.uri('prv:HumanActor') )
48
+ tripleizer.write_triple(operator, tripleizer.uri('rdf:type'),tripleizer.uri(configatron.operator_type) )
49
+ end
50
+ end
51
+ tripleizer.write_triple(operator, tripleizer.uri('rdf:type'),tripleizer.uri('prv:HumanActor') )
52
+ if configatron.operator_name
53
+ tripleizer.write_triple( operator, tripleizer.uri('foaf:name'), configatron.operator_name, "true" )
54
+ end
55
+ if configatron.operator_homepage
56
+ tripleizer.write_triple( operator, tripleizer.uri('foaf:homepage'), configatron.operator_homepage )
57
+ end
58
+ end
59
+ end
60
+
61
+ def describe_creation(subject, tripleizer)
62
+ tripleizer.write_triple( subject, tripleizer.uri('rdf:type'), tripleizer.uri('prv:DataCreation') )
63
+ #TODO: @now in iso time format date("c",...
64
+ tripleizer.write_triple( subject, tripleizer.uri('prv:performedAt'),@now , "true", tripleizer.uri('xsd:dateTime') )
65
+
66
+ creator = @triplify_instance
67
+ source_data = get_new_bnode_id
68
+
69
+ mapping = configatron.mapping ? tripleizer.uri(configatron.mapping) : get_new_bnode_id
70
+
71
+ tripleizer.write_triple( subject, tripleizer.uri('prv:performedBy'), creator );
72
+ tripleizer.write_triple( subject, tripleizer.uri('prv:usedData'), source_data );
73
+ tripleizer.write_triple( subject, tripleizer.uri('prv:usedGuideline'), mapping );
74
+
75
+ describe_source_data( source_data, tripleizer );
76
+ describe_mapping( mapping, tripleizer );
77
+ end
78
+
79
+ def describe_source_data ( subject, tripleizer )
80
+ doc = get_new_bnode_id
81
+ tripleizer.write_triple( subject, tripleizer.uri('prv:containedBy'), doc )
82
+ tripleizer.write_triple( doc, tripleizer.uri('rdf:type'), tripleizer.uri('prv:Representation') )
83
+
84
+ access = get_new_bnode_id
85
+ tripleizer.write_triple( doc, tripleizer.uri('prv:retrievedBy'), access )
86
+ describe_source_data_access access, tripleizer
87
+ end
88
+
89
+ def describe_source_data_access ( subject, tripleizer )
90
+ tripleizer.write_triple( subject,
91
+ tripleizer.uri('rdf:type'),
92
+ tripleizer.uri('prv:DataAccess') )
93
+ tripleizer.write_triple( subject,
94
+ tripleizer.uri('prv:performedAt'),
95
+ @now,
96
+ "true",
97
+ tripleizer.uri('xsd:dateTime') )
98
+
99
+ if configatron.database_server
100
+ tripleizer.write_triple( subject,
101
+ tripleizer.uri('prv:accessedServer'),
102
+ tripleizer.uri(configatron.database_server) )
103
+ end
104
+ accessor = @triplify_instance
105
+ tripleizer.write_triple( subject, tripleizer.uri('prv:performedBy'), accessor )
106
+ end
107
+
108
+
109
+ def describe_mapping ( subject, tripleizer )
110
+ tripleizer.write_triple( subject, tripleizer.uri('rdf:type'), tripleizer.uri('prvTypes:TriplifyMapping') )
111
+ end
112
+
113
+ def get_new_bnode_id ()
114
+ @bnode_counter ||=0
115
+ @bnode_counter+=1
116
+ "_:x#{@bnode_counter}"
117
+ end
118
+
119
+ end
120
+
121
+
data/rails/init.rb ADDED
@@ -0,0 +1,30 @@
1
+ # Include hook code here
2
+
3
+ require_dependency 'rtriplify'
4
+
5
+ require 'configatron'
6
+
7
+ Hash.class_eval do
8
+ def is_a_special_hash?
9
+ true
10
+ end
11
+ end
12
+ #set the routes for triplify
13
+ ActionController::Routing::Routes.draw do |map|
14
+
15
+ # map.connect 'triplify/:action', :controller => 'triplify', :action => 'model'
16
+ map.connect 'triplify', :controller => 'triplify', :action => 'all'
17
+ map.connect 'triplify/:model/:id', :controller => 'triplify', :action => 'model', :id => /\d*/
18
+ map.connect 'triplify/*specs', :controller => 'triplify', :action => "index"
19
+ #map.connect '/rtriplify/*', :controller => 'rtriplify', :action => 'index'
20
+ end
21
+
22
+ #["rtriplify"].each do |plugin_name|
23
+ reloadable_path = RAILS_ROOT + "/vendor/plugins/rtriplify/lib"
24
+
25
+ #ActiveSupport::Dependencies.load_once_paths.delete(reloadable_path)
26
+
27
+
28
+ #configatron.configure_from_yaml('config/database_1.yml')
29
+ configatron.configure_from_yaml('vendor/plugins/rtriplify/lib/config/database.yml')
30
+
data/uninstall.rb ADDED
@@ -0,0 +1 @@
1
+ # Uninstall hook code here
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rtriplify
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 0
9
+ version: 0.0.0
10
+ platform: ruby
11
+ authors:
12
+ - Nico Patitz
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2007-09-03 00:00:00 +02:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: a ruby clone of triplify. it can provide rdf data out of your existing database
22
+ email: nico.patitz@gmx.de
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files: []
28
+
29
+ files:
30
+ - install.rb
31
+ - uninstall.rb
32
+ - README
33
+ - MIT-LICENSE
34
+ - rails/init.rb
35
+ - lib/config/database.yml
36
+ - lib/app/controllers/triplify_controller.rb
37
+ - lib/app/helpers/triplify_helper.rb
38
+ - lib/app/models/triplify.rb
39
+ - lib/metadata.rb
40
+ - lib/model.rb
41
+ - lib/rtriplify.rb
42
+ - lib/tripleizer.rb
43
+ - lib/triplify/acts_ass_yaffle.rb
44
+ - lib/triplify/commands.rb
45
+ - lib/triplify/core_ext.rb
46
+ - lib/triplify.rb
47
+ - lib/triplify_metadata.rb
48
+ has_rdoc: true
49
+ homepage: http://www.triplify.org/
50
+ licenses: []
51
+
52
+ post_install_message:
53
+ rdoc_options: []
54
+
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ segments:
63
+ - 0
64
+ version: "0"
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ segments:
71
+ - 0
72
+ version: "0"
73
+ requirements: []
74
+
75
+ rubyforge_project:
76
+ rubygems_version: 1.3.7
77
+ signing_key:
78
+ specification_version: 3
79
+ summary: a ruby clone of triplify
80
+ test_files: []
81
+