oaipmh 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -1,3 +1,129 @@
1
- README for oaipmh
2
- =================
3
-
1
+ # = README
2
+ #
3
+ # Copyright (C) 2006 William Groppe
4
+ #
5
+ # Will Groppe mailto:wfg@artstor.org
6
+ #
7
+ # Open Archives Initiative - Protocol for Metadata Harvesting see
8
+ # http://www.openarchives.org/
9
+ #
10
+ # === Features
11
+ # * Easily setup a simple repository
12
+ # * Simple integration with ActiveRecord
13
+ # * Dublin Core metadata format included
14
+ # * Easily add addition metadata formats
15
+ # * Adaptable to any data source
16
+ #
17
+ #
18
+ # === Current shortcomings
19
+ # * No resumption tokens
20
+ # * Doesn't validate metadata
21
+ # * No deletion support
22
+ # * Many others I can't think of right now. :-)
23
+ #
24
+ #
25
+ # === ActiveRecord integration
26
+ #
27
+ # To successfully use ActiveRecord as a OAI PMH datasource the database table
28
+ # should include an updated_at column so that updates to the table are
29
+ # tracked by ActiveRecord. This provides much of the base functionality for
30
+ # selecting update periods.
31
+ #
32
+ # To understand how the data is extracted from the AR model it's best to just
33
+ # go thru the logic:
34
+ #
35
+ # Does the model respond to 'to_{prefix}'? Where prefix is the
36
+ # metadata prefix. If it does then just include the response from
37
+ # the model. So if you want to provide custom or complex metadata you can
38
+ # simply define a 'to_{prefix}' method on your model.
39
+ #
40
+ # Example:
41
+ #
42
+ # class Record < ActiveRecord::Base
43
+ #
44
+ # def to_oai_dc
45
+ # xml = Builder::XmlMarkup.new
46
+ # xml.tag!('oai_dc:dc',
47
+ # 'xmlns:oai_dc' => "http://www.openarchives.org/OAI/2.0/oai_dc/",
48
+ # 'xmlns:dc' => "http://purl.org/dc/elements/1.1/",
49
+ # 'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance",
50
+ # 'xsi:schemaLocation' =>
51
+ # %{http://www.openarchives.org/OAI/2.0/oai_dc/
52
+ # http://www.openarchives.org/OAI/2.0/oai_dc.xsd}) do
53
+ #
54
+ # xml.oai_dc :title, title
55
+ # xml.oai_dc :subject, subject
56
+ # end
57
+ # xml.to_s
58
+ # end
59
+ #
60
+ # end
61
+ #
62
+ # If the model doesn't define a 'to_{prefix}' then start iterating thru
63
+ # the defined metadata fields.
64
+ #
65
+ # Grab a mapping if one exists by trying to call 'map_{prefix}'.
66
+ #
67
+ # Now do the iteration and try calling methods on the model that match
68
+ # the field names, or the mapped field names.
69
+ #
70
+ # So with Dublin Core we end up with the following:
71
+ #
72
+ # 1. Check for 'title' mapped to a different method.
73
+ # 2. Call model.titles - try plural
74
+ # 3. Call model.title - try singular last
75
+ #
76
+ # Extremely contrived Blog example:
77
+ #
78
+ # class Post < ActiveRecord::Base
79
+ # def map_oai_dc
80
+ # {:subject => :tags,
81
+ # :description => :text,
82
+ # :creator => :user,
83
+ # :contibutor => :comments}
84
+ # end
85
+ # end
86
+ #
87
+ # === Supporting custom metadata
88
+ #
89
+ # See OaiPmh::Metadata for details.
90
+ #
91
+ # == Examples
92
+ #
93
+ # === Sub classing a provider
94
+ #
95
+ # class MyProvider < OaiPmh::Provider
96
+ # name 'My little OAI provider'
97
+ # url 'http://localhost/provider'
98
+ # prefix 'oai:localhost'
99
+ # email 'root@localhost' # String or Array
100
+ # deletes 'no' # future versions will support deletes
101
+ # granularity 'YYYY-MM-DDThh:mm:ssZ' # update resolution
102
+ # model MyModel # Class to get data from
103
+ # end
104
+ #
105
+ # # Now use it
106
+ #
107
+ # provider = MyProvider.new
108
+ # provider.identify
109
+ # provider.list_sets
110
+ # provider.list_metadata_formats
111
+ # # these verbs require a working model
112
+ # provider.list_identifiers
113
+ # provider.list_records
114
+ # provider.get_record('oai:localhost/1')
115
+ #
116
+ #
117
+ # === Configuring the default provider
118
+ #
119
+ # class OaiPmh::Provider
120
+ # name 'My little OAI Provider'
121
+ # url 'http://localhost/provider'
122
+ # prefix 'oai:localhost'
123
+ # email 'root@localhost' # String or Array
124
+ # deletes 'no' # future versions will support deletes
125
+ # granularity 'YYYY-MM-DDThh:mm:ssZ' # update resolution
126
+ # model MyModel # Class to get data from
127
+ # end
128
+ #
129
+ #
data/Rakefile CHANGED
@@ -10,9 +10,9 @@ require 'fileutils'
10
10
  include FileUtils
11
11
  require File.join(File.dirname(__FILE__), 'lib', 'oaipmh', 'version')
12
12
 
13
- AUTHOR = "will"
14
- EMAIL = "your contact email for bug fixes and info"
15
- DESCRIPTION = "description of gem"
13
+ AUTHOR = "Will Groppe"
14
+ EMAIL = "will.groppe@gmail.com"
15
+ DESCRIPTION = "OAI-PMH Provider"
16
16
  RUBYFORGE_PROJECT = "oaipmh"
17
17
  HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
18
18
  BIN_FILES = %w( )
@@ -57,8 +57,10 @@ spec =
57
57
  s.require_path = "lib"
58
58
  s.autorequire = "oaipmh"
59
59
 
60
- #s.add_dependency('activesupport', '>=1.3.1')
61
- #s.required_ruby_version = '>= 1.8.2'
60
+ s.add_dependency('activesupport', '>=1.3.1')
61
+ s.add_dependency('chronic', '>=0.1.4')
62
+ s.add_dependency('builder', '>=2.0.0')
63
+ s.required_ruby_version = '>= 1.8.2'
62
64
 
63
65
  s.files = %w(README CHANGELOG Rakefile) +
64
66
  Dir.glob("{bin,doc,test,lib,templates,generator,extras,website,script}/**/*") +
@@ -1,9 +1,8 @@
1
- # Copyright (C) 2006 William Groppe
2
- #
3
- # Will Groppe mailto:wfg@artstor.org
4
- #
5
- #
1
+ require 'rubygems'
2
+ require 'active_support'
3
+ require 'builder'
4
+ require 'chronic'
6
5
 
7
- Dir[File.join(File.dirname(__FILE__), 'oaipmh/**/*.rb')].sort.each do |lib|
6
+ Dir[File.join(File.dirname(__FILE__), 'oaipmh/*.rb')].sort.each do |lib|
8
7
  require lib
9
8
  end
@@ -1,5 +1,7 @@
1
1
  module OaiPmh
2
2
 
3
+ METADATA = {}
4
+
3
5
  module Const
4
6
 
5
7
  # OAI defines six verbs with various allowable options.
@@ -1,11 +1,11 @@
1
1
  require 'oaipmh'
2
2
 
3
3
  module OaiPmh
4
- module Extensions
5
- module Camped
4
+ module Goes
5
+ module Camping
6
6
 
7
7
  def self.included(mod)
8
- instance_eval(%{module #{mod}::Controllers
8
+ instance_eval(%{module ::#{mod}::Controllers
9
9
  class Oai
10
10
  def get
11
11
  @headers['Content-Type'] = 'text/xml'
@@ -1,7 +1,5 @@
1
1
  require 'oaipmh/metadata/oai_dc'
2
2
  module OaiPmh
3
-
4
- METADATA = {}
5
3
 
6
4
  def OaiPmh.register_metadata_class(metadata_class)
7
5
  METADATA[metadata_class.to_s] = metadata_class
@@ -132,28 +132,31 @@ module OaiPmh
132
132
  class Provider
133
133
  include Helpers
134
134
 
135
- @@options = {}
136
-
137
135
  AVAILABLE_FORMATS = {}
138
136
 
139
137
  class << self
140
-
141
- OaiPmh::Const::DEFAULTS.keys.each do |field|
142
- class_eval %{
143
- def #{field}(value)
144
- @@options[:#{field}] = value
145
- end
146
- }
147
- end
148
-
138
+ attr_accessor :options
139
+
149
140
  def model(value)
150
- @@options[:model] = value
141
+ self.options ||={}
142
+ self.options[:model] = value
151
143
  end
152
-
144
+
153
145
  end
154
146
 
147
+ OaiPmh::Const::DEFAULTS.keys.each do |field|
148
+ class_eval %{
149
+ def self.#{field}(value)
150
+ self.options ||={}
151
+ self.options[:#{field}] = value
152
+ end
153
+ }
154
+ end
155
+
155
156
  def initialize
156
- @config = OaiPmh::Const::DEFAULTS.merge(@@options)
157
+ @config = self.class.options ?
158
+ OaiPmh::Const::DEFAULTS.merge(self.class.options) :
159
+ OaiPmh::Const::DEFAULTS
157
160
  end
158
161
 
159
162
  def identify
@@ -235,12 +238,13 @@ module OaiPmh
235
238
  end
236
239
 
237
240
  def list_sets_response
238
- raise SetException.new unless sets
241
+ raise SetException.new unless @model && @model.oai_sets
239
242
  @xml.ListSets do |ls|
240
- oai_sets.each do |ms|
243
+ @model.oai_sets.each do |ms|
241
244
  @xml.set do |set|
242
245
  @xml.setSpec ms.spec
243
246
  @xml.setName ms.name
247
+ @xml.setDescription(ms.description) if ms.respond_to?(:description)
244
248
  end
245
249
  end
246
250
  end
@@ -250,19 +254,19 @@ module OaiPmh
250
254
  @xml.ListMetadataFormats do
251
255
  @config[:formats].each_pair do |key, format|
252
256
  @xml.metadataFormat do
253
- @xml.metadataPrefix instance_eval("#{format}.prefix")
254
- @xml.schema instance_eval("#{format}.schema")
255
- @xml.metadataNamespace instance_eval("#{format}.namespace")
257
+ @xml.metadataPrefix format.send(:prefix)
258
+ @xml.schema format.send(:schema)
259
+ @xml.metadataNamespace format.send(:namespace)
256
260
  end
257
261
  end
258
262
  end
259
263
  end
260
264
 
261
265
  def list_identifiers_response
262
- raise FORMAT_ERROR unless @config[:formats].include? @format
266
+ raise FormatException.new unless @config[:formats].include? @format
263
267
  records = find :all
264
268
 
265
- raise RECORDS_ERROR if records.nil? || records.empty?
269
+ raise NoMatchException.new if records.nil? || records.empty?
266
270
 
267
271
  @xml.ListIdentifiers do
268
272
  records.each do |record|
@@ -350,10 +354,12 @@ module OaiPmh
350
354
 
351
355
  # emit record header
352
356
  def metadata_header(record)
353
- @xml.header do |h|
354
- h.identifier "#{@config[:prefix]}/#{record.id}"
355
- h.datestamp record.updated_at.utc.xmlschema
356
- h.set @opts[:set] if @opts[:set]
357
+ @xml.header do
358
+ @xml.identifier "#{@config[:prefix]}/#{record.id}"
359
+ @xml.datestamp record.updated_at.utc.xmlschema
360
+ record.sets.each do |set|
361
+ @xml.setSpec set.spec
362
+ end if record.respond_to?(:sets)
357
363
  end
358
364
  end
359
365
 
@@ -369,8 +375,8 @@ module OaiPmh
369
375
  @xml << str
370
376
  end
371
377
  else
372
- map = record.respond_to?("map_#{@format}") ?
373
- instance_eval("record.map_#{@format}") : {}
378
+ map = @model.respond_to?("map_#{@format}") ?
379
+ @model.send("map_#{@format}") : {}
374
380
 
375
381
  mdformat = @config[:formats][@format]
376
382
  @xml.metadata do
@@ -378,7 +384,7 @@ module OaiPmh
378
384
  mdformat.fields.each do |field|
379
385
  set = value_for(field, record, map)
380
386
  set.each do |mdv|
381
- instance_eval("@xml.#{mdformat.element_ns} :#{field}, %{#{mdv}}")
387
+ @xml.tag! "#{mdformat.element_ns}:#{field}", mdv
382
388
  end
383
389
  end
384
390
  end
@@ -397,17 +403,20 @@ module OaiPmh
397
403
  # 4) Try calling the singular name method on the model, if it's not a
398
404
  # reserved word.
399
405
  def value_for(field, record, map)
400
- if map.keys.include?(field)
401
- return map[field].nil? ? [] :
402
- record.send("#{map[field]}").to_a
406
+ if map.keys.include?(field.intern)
407
+ value = record.send(map[field.intern])
408
+ if value.kind_of?(String)
409
+ return [value]
410
+ end
411
+ return value.to_a
403
412
  end
404
413
 
405
414
  begin
406
- return record.send("#{field.pluralize}").to_a
407
- rescue
415
+ return record.send(field.pluralize).to_a
416
+ rescue
408
417
  unless OaiPmh::Const::RESERVED_WORDS.include?(field)
409
418
  begin
410
- return record.send("#{field}").to_a
419
+ return [record.send(field)]
411
420
  rescue
412
421
  return []
413
422
  end
@@ -0,0 +1,30 @@
1
+ # = set.rb
2
+ #
3
+ # Copyright (C) 2006 William Groppe
4
+ #
5
+ # Will Groppe mailto: wfg@artstor.org
6
+ #
7
+ #
8
+ # Implementing a set from scratch requires overridding two methods from
9
+ # OaiPmh::Set
10
+ #
11
+ # * name - descriptive name of this set
12
+ # * spec - short name of set
13
+ #
14
+ # and optionally
15
+ #
16
+ # * description - long description of the set
17
+ #
18
+ module OaiPmh
19
+ class Set
20
+
21
+ def name
22
+ "unknown"
23
+ end
24
+
25
+ def spec
26
+ "set"
27
+ end
28
+
29
+ end
30
+ end
@@ -2,7 +2,7 @@ module Oaipmh #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
4
  MINOR = 0
5
- TINY = 1
5
+ TINY = 2
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -1,11 +1,81 @@
1
+ require 'rexml/document'
1
2
  require File.dirname(__FILE__) + '/test_helper.rb'
2
3
 
4
+ class MappedProvider < OaiPmh::Provider
5
+ name 'Mapped Provider'
6
+ prefix 'oai:test'
7
+ model MappedModel
8
+ end
9
+
10
+ class SimpleProvider < OaiPmh::Provider
11
+ name 'Test Provider'
12
+ prefix 'oai:test'
13
+ model SimpleModel
14
+ end
15
+
3
16
  class OaipmhTest < Test::Unit::TestCase
4
17
 
5
18
  def setup
19
+ @simple_provider = SimpleProvider.new
20
+ @mapped_provider = MappedProvider.new
21
+ end
22
+
23
+ def test_indentify
24
+ doc = REXML::Document.new(@simple_provider.identify)
25
+ assert doc.elements["/OAI-PMH/Identify/repositoryName"].text == 'Test Provider'
26
+ assert doc.elements["/OAI-PMH/Identify/earliestDatestamp"].text == SimpleModel.oai_earliest.to_s
27
+ end
28
+
29
+ def test_list_sets
30
+ doc = REXML::Document.new(@simple_provider.list_sets)
31
+ sets = doc.elements["/OAI-PMH/ListSets"]
32
+ assert sets.size == 2
33
+ assert sets[0].elements["//setName"].text == "Test Set"
34
+ end
35
+
36
+ def test_metadata_formats
37
+ assert_nothing_raised { REXML::Document.new(@simple_provider.list_metadata_formats) }
38
+ doc = REXML::Document.new(@simple_provider.list_metadata_formats)
39
+ assert doc.elements['/OAI-PMH/ListMetadataFormats/metadataFormat/metadataPrefix'].text == 'oai_dc'
6
40
  end
7
41
 
8
- def test_truth
9
- assert true
42
+ def test_list_records
43
+ assert_nothing_raised { REXML::Document.new(@simple_provider.list_records) }
44
+ doc = REXML::Document.new(@simple_provider.list_records)
45
+ assert_equal 5, doc.elements['OAI-PMH/ListRecords'].to_a.size
46
+ doc = REXML::Document.new(@simple_provider.list_records(:set => 'A'))
47
+ assert_equal 5, doc.elements['OAI-PMH/ListRecords'].to_a.size
48
+ doc = REXML::Document.new(@simple_provider.list_records(:set => 'A:B'))
49
+ assert_equal 2, doc.elements['OAI-PMH/ListRecords'].to_a.size
10
50
  end
51
+
52
+ def test_list_identifiers
53
+ assert_nothing_raised { REXML::Document.new(@simple_provider.list_identifiers) }
54
+ doc = REXML::Document.new(@simple_provider.list_identifiers)
55
+ assert_equal 5, doc.elements['OAI-PMH/ListIdentifiers'].to_a.size
56
+ doc = REXML::Document.new(@simple_provider.list_identifiers(:set => 'A'))
57
+ assert_equal 5, doc.elements['OAI-PMH/ListIdentifiers'].to_a.size
58
+ doc = REXML::Document.new(@simple_provider.list_identifiers(:set => 'A:B'))
59
+ assert_equal 2, doc.elements['OAI-PMH/ListIdentifiers'].to_a.size
60
+ end
61
+
62
+ def test_get_record
63
+ assert_nothing_raised { REXML::Document.new(@simple_provider.get_record('oai:test/1')) }
64
+ doc = REXML::Document.new(@simple_provider.get_record('oai:test/1'))
65
+ assert_equal 'oai:test/1', doc.elements['OAI-PMH/GetRecord/record/header/identifier'].text
66
+ end
67
+
68
+ def test_mapped_source
69
+ assert_nothing_raised { REXML::Document.new(@mapped_provider.list_records) }
70
+ doc = REXML::Document.new(@mapped_provider.list_records)
71
+ assert_equal "title 1", doc.elements['OAI-PMH/ListRecords/record/metadata/oai_dc:dc/dc:creator'].text
72
+ assert_equal "creator", doc.elements['OAI-PMH/ListRecords/record/metadata/oai_dc:dc/dc:title'].text
73
+ assert_equal "tag 1", doc.elements['OAI-PMH/ListRecords/record/metadata/oai_dc:dc/dc:subject'].text
74
+ end
75
+
76
+ def test_verb_exception
77
+ doc = REXML::Document.new(@simple_provider.process_verb('NoVerb'))
78
+ assert doc.elements["/OAI-PMH/error"].attributes["code"] == 'badVerb'
79
+ end
80
+
11
81
  end
@@ -1,2 +1,106 @@
1
1
  require 'test/unit'
2
2
  require File.dirname(__FILE__) + '/../lib/oaipmh'
3
+
4
+ class Record
5
+ attr_accessor :id, :titles, :creator, :tags, :sets, :updated_at
6
+
7
+ def initialize(id, titles, creator, tags, sets)
8
+ @id = id;
9
+ @titles = titles
10
+ @creator = creator
11
+ @tags = tags
12
+ @sets = sets
13
+ @updated_at = Time.new.utc
14
+ end
15
+
16
+ # Override Object.id
17
+ def id
18
+ @id
19
+ end
20
+
21
+ def in_set(spec)
22
+ @sets.each { |set| return true if set.spec == spec }
23
+ false
24
+ end
25
+
26
+ end
27
+
28
+ class OneSet < OaiPmh::Set
29
+
30
+ def name
31
+ "Test Set"
32
+ end
33
+
34
+ def spec
35
+ "A"
36
+ end
37
+
38
+ def description
39
+ "A long winded description of this set."
40
+ end
41
+
42
+ end
43
+
44
+ class TwoSet < OaiPmh::Set
45
+
46
+ def name
47
+ "Not so test Set"
48
+ end
49
+
50
+ def spec
51
+ "A:B"
52
+ end
53
+
54
+ def description
55
+ "A short winded description of this set."
56
+ end
57
+
58
+ end
59
+
60
+
61
+
62
+
63
+ class SimpleModel
64
+ include OaiPmh::Model
65
+
66
+ RECORDS = [
67
+ Record.new(1, ['title 1', 'title 2'], 'creator', ['tag 1', 'tag 2'], [OneSet.new]),
68
+ Record.new(2, ['title 3', 'title 4'], 'creator', ['tag 3', 'tag 4'], [OneSet.new]),
69
+ Record.new(3, ['title 5', 'title 6'], 'creator', ['tag 5', 'tag 6'], [OneSet.new]),
70
+ Record.new(4, ['title 7', 'title 8'], 'creator', ['tag 9', 'tag 8'], [OneSet.new, TwoSet.new]),
71
+ Record.new(5, ['title 9', 'title 10'], 'creator', ['tag 9', 'tag 10'], [OneSet.new, TwoSet.new]),
72
+ ]
73
+
74
+ class << self
75
+ def oai_earliest
76
+ Time.parse("2006-10-31T00:00:00Z")
77
+ end
78
+
79
+ def oai_sets
80
+ [OneSet.new, TwoSet.new]
81
+ end
82
+
83
+ def oai_find(selector, opts = {})
84
+ if selector == :all
85
+ if opts[:set]
86
+ return RECORDS.select { |rec| rec.in_set(opts[:set]) }
87
+ else
88
+ return RECORDS
89
+ end
90
+ else
91
+ RECORDS.each do |record|
92
+ return record if record.id.to_s == selector
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ class MappedModel < SimpleModel
100
+
101
+ def self.map_oai_dc
102
+ {:title => :creator, :creator => :titles, :subject => :tags}
103
+ end
104
+
105
+ end
106
+
metadata CHANGED
@@ -3,31 +3,31 @@ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: oaipmh
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.0.1
7
- date: 2006-11-02 00:00:00 -05:00
8
- summary: description of gem
6
+ version: 0.0.2
7
+ date: 2006-11-07 00:00:00 -05:00
8
+ summary: OAI-PMH Provider
9
9
  require_paths:
10
10
  - lib
11
- email: your contact email for bug fixes and info
11
+ email: will.groppe@gmail.com
12
12
  homepage: http://oaipmh.rubyforge.org
13
13
  rubyforge_project: oaipmh
14
- description: description of gem
14
+ description: OAI-PMH Provider
15
15
  autorequire: oaipmh
16
16
  default_executable:
17
17
  bindir: bin
18
18
  has_rdoc: true
19
19
  required_ruby_version: !ruby/object:Gem::Version::Requirement
20
20
  requirements:
21
- - - ">"
21
+ - - ">="
22
22
  - !ruby/object:Gem::Version
23
- version: 0.0.0
23
+ version: 1.8.2
24
24
  version:
25
25
  platform: ruby
26
26
  signing_key:
27
27
  cert_chain:
28
28
  post_install_message:
29
29
  authors:
30
- - will
30
+ - Will Groppe
31
31
  files:
32
32
  - README
33
33
  - CHANGELOG
@@ -44,6 +44,7 @@ files:
44
44
  - lib/oaipmh/metadata.rb
45
45
  - lib/oaipmh/model.rb
46
46
  - lib/oaipmh/provider.rb
47
+ - lib/oaipmh/set.rb
47
48
  - lib/oaipmh/version.rb
48
49
  - lib/oaipmh/extensions/camping.rb
49
50
  - lib/oaipmh/metadata/oai_dc.rb
@@ -70,5 +71,31 @@ extensions: []
70
71
 
71
72
  requirements: []
72
73
 
73
- dependencies: []
74
-
74
+ dependencies:
75
+ - !ruby/object:Gem::Dependency
76
+ name: activesupport
77
+ version_requirement:
78
+ version_requirements: !ruby/object:Gem::Version::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 1.3.1
83
+ version:
84
+ - !ruby/object:Gem::Dependency
85
+ name: chronic
86
+ version_requirement:
87
+ version_requirements: !ruby/object:Gem::Version::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: 0.1.4
92
+ version:
93
+ - !ruby/object:Gem::Dependency
94
+ name: builder
95
+ version_requirement:
96
+ version_requirements: !ruby/object:Gem::Version::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: 2.0.0
101
+ version: