fap 0.0.1

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.
@@ -0,0 +1,4 @@
1
+ === 0.0.1 2010-03-30
2
+
3
+ * 1 major enhancement:
4
+ * Initial release, yay.
@@ -0,0 +1,22 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.rdoc
4
+ Rakefile
5
+ lib/fap.rb
6
+ lib/fap/relation.rb
7
+ lib/fap/support
8
+ lib/fap/support/class.rb
9
+ lib/fap/fap.rb
10
+ lib/fap/mixins
11
+ lib/fap/mixins/properties.rb
12
+ lib/fap/mixins/relations.rb
13
+ lib/fap/mixins.rb
14
+ lib/fap/collection.rb
15
+ lib/fap/property.rb
16
+ script/console
17
+ script/destroy
18
+ script/generate
19
+ spec/fap_spec.rb
20
+ spec/spec.opts
21
+ spec/spec_helper.rb
22
+ tasks/rspec.rake
@@ -0,0 +1,74 @@
1
+ = fap
2
+
3
+ * http://github.com/oz/fap
4
+
5
+ == DESCRIPTION:
6
+
7
+ FAP is a ruby gem build on top of the excellent Nokogiri, to turn boring XML,
8
+ or HTML documents into yummy ruby objects. Right now, it only support
9
+ using Nokogiri's XPath selectors, and simple "relations" between a document
10
+ nodes, though this will hopefully get better.
11
+
12
+ FAP's ideas are loosely connected to tools built by some adventurous fellas at
13
+ AF83, who still do PHP things to their brains. Some credits should go to them,
14
+ and to the horrid weather that kept me locked inside last week-end.
15
+
16
+ And yes, I know it's a stupid name. But I'm sure you can come up with a decent
17
+ acronym. :)
18
+
19
+ == SYNOPSIS:
20
+
21
+ class Atom < FAP::FAP
22
+ string :title, :xpath => '//feed/title'
23
+ uri :url, :xpath => '//feed/link[@rel="self"]', :get => :href
24
+ date :updated, :xpath => '//feed/updated'
25
+
26
+ has_many :articles, :class => 'Article', :xpath => '//feed/entry'
27
+ end
28
+
29
+ class Article < FAP::FAP
30
+ string :title # It is assumed here, that you want a string mapped on a <title> element
31
+ uri :url, :xpath => 'link[@rel="alternate"]', :get => :href # Fetch the :href attribute
32
+ string :content
33
+ date :updated_at, :xpath => 'updated'
34
+
35
+ belongs_to :atom, :class => 'Atom'
36
+ end
37
+
38
+ atom = Atom.new open("http://search.twitter.com/search.atom?q=ruby")
39
+ atom.articles.each do |article|
40
+ puts "#{article.title}: #{article.url}"
41
+ end
42
+
43
+ == REQUIREMENTS:
44
+
45
+ * nokogiri
46
+
47
+ == INSTALL:
48
+
49
+ * gem install fap
50
+
51
+ == LICENSE:
52
+
53
+ (The MIT License)
54
+
55
+ Copyright (c) 2010 Arnaud Berthomier
56
+
57
+ Permission is hereby granted, free of charge, to any person obtaining
58
+ a copy of this software and associated documentation files (the
59
+ 'Software'), to deal in the Software without restriction, including
60
+ without limitation the rights to use, copy, modify, merge, publish,
61
+ distribute, sublicense, and/or sell copies of the Software, and to
62
+ permit persons to whom the Software is furnished to do so, subject to
63
+ the following conditions:
64
+
65
+ The above copyright notice and this permission notice shall be
66
+ included in all copies or substantial portions of the Software.
67
+
68
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
69
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
70
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
71
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
72
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
73
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
74
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ gem 'hoe', '>= 2.1.0'
3
+ require 'hoe'
4
+ require 'fileutils'
5
+ require './lib/fap'
6
+
7
+ Hoe.plugin :newgem
8
+ $hoe = Hoe.spec 'fap' do
9
+ self.developer 'Arnaud Berthomier', 'oz@cyprio.net'
10
+ self.rubyforge_name = self.name # TODO this is default value
11
+ # self.extra_deps = [['activesupport','>= 2.0.2']]
12
+
13
+ end
14
+
15
+ require 'newgem/tasks'
16
+ Dir['tasks/**/*.rake'].each { |t| load t }
@@ -0,0 +1,32 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'nokogiri'
5
+
6
+ module FAP
7
+ VERSION = '0.0.1'
8
+
9
+ autoload :FAP, 'fap/fap'
10
+ autoload :Property, 'fap/property'
11
+ autoload :Relation, 'fap/relation'
12
+ autoload :Mixins, 'fap/mixins'
13
+ autoload :Collection, 'fap/collection'
14
+
15
+ # extracted from Extlib
16
+ #
17
+ # Constantize tries to find a declared constant with the name specified
18
+ # in the string. It raises a NameError when the name is not in CamelCase
19
+ # or is not initialized.
20
+ #
21
+ # @example
22
+ # "Module".constantize #=> Module
23
+ # "Class".constantize #=> Class
24
+ def self.constantize(camel_cased_word)
25
+ unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word
26
+ raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!"
27
+ end
28
+ Object.module_eval "::#{$1}", __FILE__, __LINE__
29
+ end
30
+ end
31
+
32
+ # *fap* *fap* *fap*
@@ -0,0 +1,29 @@
1
+ module FAP
2
+ class Collection
3
+ include Enumerable
4
+
5
+ def initialize relation, node
6
+ @nodes = []
7
+ @fetched = false
8
+ @node = node
9
+ @relation = relation
10
+ @klass = ::FAP.constantize relation.klass
11
+ end
12
+
13
+ def each
14
+ fetch_nodes unless @fetched
15
+ @nodes.map do |node|
16
+ obj = @klass.new nil
17
+ obj.from_relation @relation, node
18
+ yield obj
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def fetch_nodes
25
+ @nodes = @node.xpath @relation.xpath
26
+ @fetched = true
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module FAP
4
+ class FAP
5
+
6
+ def self.inherited subclass
7
+ subclass.send :include, ::FAP::Mixins::Properties
8
+ subclass.send :include, ::FAP::Mixins::Relations
9
+ subclass.class_eval <<-EOS, __FILE__, __LINE__ + 1
10
+ def self.inherited subclass
11
+ super
12
+ subclass.properties = self.properties.dup
13
+ subclass.relations = self.relations.dup
14
+ end
15
+ EOS
16
+ end
17
+
18
+ def initialize stream, opts={}
19
+ data = stream.respond_to?(:read) ? stream.read : stream.to_s
20
+ @_node = Nokogiri data
21
+ @_cache = {}
22
+ end
23
+
24
+ ##
25
+ # Build object starting from a relation's node
26
+ #
27
+ # @see FAP::Collection
28
+ # @param [FAP::Relation] Object relation
29
+ # @param [Nokogiri::XML::Element] starting node
30
+ # @retun [FAP::FAP]
31
+ def from_relation relation, node
32
+ relation.from.class
33
+ owners = self.relations.select { |rel| rel.type == :belongs_to && rel.klass == relation.from.class.to_s }
34
+ @_cache ||= {}
35
+ @_cache[owners.first.name] = relation.from if owners.size == 1
36
+ @_node = node
37
+ self
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,6 @@
1
+ module FAP
2
+ module Mixins
3
+ autoload :Properties, 'fap/mixins/properties'
4
+ autoload :Relations, 'fap/mixins/relations'
5
+ end
6
+ end
@@ -0,0 +1,77 @@
1
+ # For extlib_inheritable_accessor
2
+ require File.join File.dirname(__FILE__), '..', 'support', 'class'
3
+
4
+ module FAP
5
+ module Mixins
6
+ module Properties
7
+
8
+ def self.included base
9
+ base.class_eval <<-EOS, __FILE__, __LINE__ + 1
10
+ extlib_inheritable_accessor(:properties) unless self.respond_to?(:properties)
11
+ self.properties ||= []
12
+ EOS
13
+ base.extend ClassMethods
14
+ end
15
+
16
+ def _search_property name
17
+ property = properties.select { |prop| prop.name == name }.first
18
+ node = @_node.at_xpath property.xpath
19
+ raise StandardError, "Could not find #{self.class}.#{name} (#{property.xpath}) in stream." unless node
20
+ property.cast node
21
+ end
22
+
23
+ module ClassMethods
24
+
25
+ ##
26
+ # Define a new property.
27
+ #
28
+ # property :foo, "String", :some => "options"
29
+ # property :foo
30
+ # property :foo, :some => "options"
31
+ #
32
+ # @param [Symbol] property name
33
+ # @param [Array] splat args
34
+ def property name, *args
35
+ opts = {}
36
+ opts.merge!(:type => args.shift) if args[0].class == String
37
+ opts.merge!(*args) unless args.empty?
38
+ property = ::FAP::Property.new name, opts
39
+ self.properties << property
40
+ define_property_getter property
41
+ end
42
+
43
+ def string name, *args
44
+ property name, *args
45
+ end
46
+
47
+ def number name, *args
48
+ property name, 'Fixnum', *args
49
+ end
50
+ alias :integer :number
51
+
52
+ def date name, *args
53
+ property name, 'DateTime', *args
54
+ end
55
+ alias :time :date
56
+
57
+ def uri name, *args
58
+ property name, 'URI', *args
59
+ end
60
+
61
+ protected
62
+
63
+ def define_property_getter property
64
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
65
+ def #{property.name}
66
+ if @_cache[:#{property.name}]
67
+ @_cache[:#{property.name}]
68
+ else
69
+ @_cache[:#{property.name}] = _search_property :#{property.name}
70
+ end
71
+ end
72
+ EOS
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,85 @@
1
+ # For extlib_inheritable_accessor
2
+ require File.join File.dirname(__FILE__), '..', 'support', 'class'
3
+
4
+ module FAP
5
+ module Mixins
6
+ module Relations
7
+
8
+ def self.included base
9
+ base.class_eval <<-EOS, __FILE__, __LINE__ + 1
10
+ extlib_inheritable_accessor(:relations) unless self.respond_to?(:relations)
11
+ self.relations ||= []
12
+ EOS
13
+ base.extend ClassMethods
14
+ end
15
+
16
+ ##
17
+ # Load a relation by its nane
18
+ #
19
+ # @return [FAP::Collection] for "has_many" relations
20
+ # @return [FAP::FAP] "father" object for "belongs_to" relations
21
+ def _load_relation name
22
+ relation = self.relations.select { |rel| rel.name == name }.first
23
+ if relation.type == :has_many
24
+ _load_has_many relation
25
+ elsif relation.type == :belongs_to
26
+ _load_belongs_to relation
27
+ else
28
+ raise "Unkown relation type"
29
+ end
30
+ end
31
+
32
+ def _load_has_many relation
33
+ relation.from = self
34
+ ::FAP::Collection.new relation, @_node
35
+ end
36
+
37
+ def _load_belongs_to relation
38
+ nil
39
+ end
40
+
41
+ module ClassMethods
42
+
43
+ ##
44
+ # Define a "many" relation
45
+ #
46
+ def has_many name, opts={}
47
+ opts.merge! :type => :has_many, :from => self
48
+ relation = ::FAP::Relation.new name, opts
49
+ self.relations << relation
50
+ define_relation_getter relation
51
+ end
52
+
53
+ ##
54
+ # Define a "belongs to" relation
55
+ #
56
+ def belongs_to name, opts={}
57
+ opts.merge! :type => :belongs_to, :from => self
58
+ relation = ::FAP::Relation.new name, opts
59
+ self.relations << relation
60
+ define_relation_getter relation
61
+ end
62
+
63
+ protected
64
+
65
+ ##
66
+ # Add getter for a relation.
67
+ #
68
+ # @param [FAP::Relation]
69
+ # @return TBD
70
+ def define_relation_getter relation
71
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
72
+ def #{relation.name}
73
+ if @_cache[:#{relation.name}]
74
+ @_cache[:#{relation.name}]
75
+ else
76
+ @_cache[:#{relation.name}] = _load_relation :#{relation.name}
77
+ end
78
+ end
79
+ EOS
80
+ end
81
+ end
82
+
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,33 @@
1
+ module FAP
2
+ class Property
3
+ attr_reader :name, :type, :xpath, :attribute
4
+
5
+ def initialize name, opt={}
6
+ @name = name
7
+ @type = opt[:type] || 'String'
8
+ @xpath = opt[:xpath] || name.to_s
9
+ @attribute = opt[:get] || nil
10
+ end
11
+
12
+ ##
13
+ # Cast a Nokogiri node value to @type
14
+ # @param [Nokogiri::XML::Element] a node
15
+ # @return value of type @type
16
+ def cast node
17
+ raise "Invalid XML node" if node.nil?
18
+ what = @attribute ? node[@attribute.to_s] : node.text
19
+ case @type
20
+ when 'Fixnum'
21
+ what.to_i 10
22
+ when 'DateTime'
23
+ DateTime.parse what
24
+ when 'URI'
25
+ URI.parse what
26
+ when 'String'
27
+ what.to_s
28
+ else
29
+ ::FAP.constantize(@type).new(what)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,15 @@
1
+ module FAP
2
+ class Relation
3
+ attr_reader :name, :type, :xpath, :klass
4
+ attr_accessor :from
5
+
6
+ def initialize name, opt={}
7
+ @name = name
8
+ @type = opt[:type]
9
+ @xpath = opt[:xpath] || name
10
+ @klass = opt[:class] || nil
11
+ @from = opt[:from] || nil
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,157 @@
1
+ # Copyright (c) 2006-2009 David Heinemeier Hansson
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.
21
+ #
22
+ # Extracted From
23
+ # http://github.com/rails/rails/commit/971e2438d98326c994ec6d3ef8e37b7e868ed6e2
24
+
25
+ # Extends the class object with class and instance accessors for class attributes,
26
+ # just like the native attr* accessors for instance attributes.
27
+ #
28
+ # class Person
29
+ # cattr_accessor :hair_colors
30
+ # end
31
+ #
32
+ # Person.hair_colors = [:brown, :black, :blonde, :red]
33
+ class Class
34
+ def cattr_reader(*syms)
35
+ syms.flatten.each do |sym|
36
+ next if sym.is_a?(Hash)
37
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
38
+ unless defined? @@#{sym} # unless defined? @@hair_colors
39
+ @@#{sym} = nil # @@hair_colors = nil
40
+ end # end
41
+ #
42
+ def self.#{sym} # def self.hair_colors
43
+ @@#{sym} # @@hair_colors
44
+ end # end
45
+ #
46
+ def #{sym} # def hair_colors
47
+ @@#{sym} # @@hair_colors
48
+ end # end
49
+ EOS
50
+ end
51
+ end unless Class.respond_to?(:cattr_reader)
52
+
53
+ def cattr_writer(*syms)
54
+ options = syms.extract_options!
55
+ syms.flatten.each do |sym|
56
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
57
+ unless defined? @@#{sym} # unless defined? @@hair_colors
58
+ @@#{sym} = nil # @@hair_colors = nil
59
+ end # end
60
+ #
61
+ def self.#{sym}=(obj) # def self.hair_colors=(obj)
62
+ @@#{sym} = obj # @@hair_colors = obj
63
+ end # end
64
+ #
65
+ #{" #
66
+ def #{sym}=(obj) # def hair_colors=(obj)
67
+ @@#{sym} = obj # @@hair_colors = obj
68
+ end # end
69
+ " unless options[:instance_writer] == false } # # instance writer above is generated unless options[:instance_writer] == false
70
+ EOS
71
+ end
72
+ end unless Class.respond_to?(:cattr_writer)
73
+
74
+ def cattr_accessor(*syms)
75
+ cattr_reader(*syms)
76
+ cattr_writer(*syms)
77
+ end unless Class.respond_to?(:cattr_accessor)
78
+
79
+ # Defines class-level inheritable attribute reader. Attributes are available to subclasses,
80
+ # each subclass has a copy of parent's attribute.
81
+ #
82
+ # @param *syms<Array[#to_s]> Array of attributes to define inheritable reader for.
83
+ # @return <Array[#to_s]> Array of attributes converted into inheritable_readers.
84
+ #
85
+ # @api public
86
+ #
87
+ # @todo Do we want to block instance_reader via :instance_reader => false
88
+ # @todo It would be preferable that we do something with a Hash passed in
89
+ # (error out or do the same as other methods above) instead of silently
90
+ # moving on). In particular, this makes the return value of this function
91
+ # less useful.
92
+ def extlib_inheritable_reader(*ivars)
93
+ instance_reader = ivars.pop[:reader] if ivars.last.is_a?(Hash)
94
+
95
+ ivars.each do |ivar|
96
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
97
+ def self.#{ivar}
98
+ return @#{ivar} if self.object_id == #{self.object_id} || defined?(@#{ivar})
99
+ ivar = superclass.#{ivar}
100
+ return nil if ivar.nil? && !#{self}.instance_variable_defined?("@#{ivar}")
101
+ @#{ivar} = ivar && !ivar.is_a?(Module) && !ivar.is_a?(Numeric) && !ivar.is_a?(TrueClass) && !ivar.is_a?(FalseClass) ? ivar.dup : ivar
102
+ end
103
+ RUBY
104
+ unless instance_reader == false
105
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
106
+ def #{ivar}
107
+ self.class.#{ivar}
108
+ end
109
+ RUBY
110
+ end
111
+ end
112
+ end unless Class.respond_to?(:extlib_inheritable_reader)
113
+
114
+ # Defines class-level inheritable attribute writer. Attributes are available to subclasses,
115
+ # each subclass has a copy of parent's attribute.
116
+ #
117
+ # @param *syms<Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to
118
+ # define inheritable writer for.
119
+ # @option syms :instance_writer<Boolean> if true, instance-level inheritable attribute writer is defined.
120
+ # @return <Array[#to_s]> An Array of the attributes that were made into inheritable writers.
121
+ #
122
+ # @api public
123
+ #
124
+ # @todo We need a style for class_eval <<-HEREDOC. I'd like to make it
125
+ # class_eval(<<-RUBY, __FILE__, __LINE__), but we should codify it somewhere.
126
+ def extlib_inheritable_writer(*ivars)
127
+ instance_writer = ivars.pop[:writer] if ivars.last.is_a?(Hash)
128
+ ivars.each do |ivar|
129
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
130
+ def self.#{ivar}=(obj)
131
+ @#{ivar} = obj
132
+ end
133
+ RUBY
134
+ unless instance_writer == false
135
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
136
+ def #{ivar}=(obj) self.class.#{ivar} = obj end
137
+ RUBY
138
+ end
139
+
140
+ self.send("#{ivar}=", yield) if block_given?
141
+ end
142
+ end unless Class.respond_to?(:extlib_inheritable_writer)
143
+
144
+ # Defines class-level inheritable attribute accessor. Attributes are available to subclasses,
145
+ # each subclass has a copy of parent's attribute.
146
+ #
147
+ # @param *syms<Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to
148
+ # define inheritable accessor for.
149
+ # @option syms :instance_writer<Boolean> if true, instance-level inheritable attribute writer is defined.
150
+ # @return <Array[#to_s]> An Array of attributes turned into inheritable accessors.
151
+ #
152
+ # @api public
153
+ def extlib_inheritable_accessor(*syms, &block)
154
+ extlib_inheritable_reader(*syms)
155
+ extlib_inheritable_writer(*syms, &block)
156
+ end unless Class.respond_to?(:extlib_inheritable_accessor)
157
+ end
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # File: script/console
3
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
4
+
5
+ libs = " -r irb/completion"
6
+ libs << " -r rubygems" unless ENV['NO_RUBYGEMS']
7
+ # Perhaps use a console_lib to store any extra methods I may want available in the cosole
8
+ # libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
9
+ libs << " -r #{File.dirname(__FILE__) + '/../lib/fap.rb'}"
10
+ puts "Loading fap gem"
11
+ exec "#{irb} #{libs} --simple-prompt"
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)
@@ -0,0 +1,157 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+ describe "a very light FAP" do
4
+ before :all do
5
+ class Foo < FAP::FAP ; end
6
+ end
7
+
8
+ it "should define #properties #accessor" do
9
+ Foo.properties.should be_an_instance_of(Array)
10
+ Foo.properties.should be_empty
11
+ end
12
+
13
+ it "should define #relations read accessor" do
14
+ Foo.relations.should be_an_instance_of(Array)
15
+ Foo.relations.should be_empty
16
+ end
17
+ end
18
+
19
+ describe "a very light FAP with properties" do
20
+ before :all do
21
+ class Foo < FAP::FAP
22
+ property :str_name
23
+ property :str_prop, 'String'
24
+ property :xpath_prop, 'Fixnum', :xpath => '//foobar'
25
+ end
26
+ end
27
+
28
+ it "should have 3 properties" do
29
+ Foo.properties.should have(3).items
30
+ end
31
+
32
+ it "should have 2 'String' properties" do
33
+ props = Foo.properties.select { |prop| prop.name != :xpath_prop }
34
+ props.each do |prop|
35
+ prop.type.should == 'String'
36
+ end
37
+ end
38
+
39
+ it "should have 1 'Fixnum' property" do
40
+ props = Foo.properties.select { |prop| prop.name == :xpath_prop }
41
+ props.each do |prop|
42
+ prop.type.should == 'Fixnum'
43
+ end
44
+ end
45
+
46
+ it "should define a property's xpath with the :xpath option" do
47
+ props = Foo.properties.select { |prop| prop.name == :xpath_prop }
48
+ props.each do |prop|
49
+ prop.xpath.should == '//foobar'
50
+ end
51
+ end
52
+
53
+ it "should define property accessors" do
54
+ x = Foo.new nil
55
+ x.should respond_to(:str_name)
56
+ x.should respond_to(:str_prop)
57
+ x.should respond_to(:xpath_prop)
58
+ end
59
+ end
60
+
61
+ describe "a FAP with some relations" do
62
+ before :all do
63
+ class Foo < FAP::FAP
64
+ has_many :bars, :class => 'Bar', :xpath => '//foo/bars'
65
+ end
66
+
67
+ class Bar < FAP::FAP
68
+ belongs_to :foo, :class => 'Foo'
69
+ end
70
+ end
71
+
72
+ it "should define some relations" do
73
+ Foo.relations.should have(1).item
74
+ Bar.relations.should have(1).item
75
+ end
76
+
77
+ it "should define has_many relations" do
78
+ x = Foo.new nil
79
+ x.should respond_to(:bars)
80
+ x.bars.should be_an_instance_of(FAP::Collection)
81
+ end
82
+
83
+ it "should define belongs_to relations" do
84
+ x = Bar.new nil
85
+ x.should respond_to(:foo)
86
+ x.foo.should be_an_instance_of(NilClass)
87
+ end
88
+ end
89
+
90
+ describe "a simple FAP to read a stream" do
91
+ before :all do
92
+ class Foo < FAP::FAP ; end
93
+ end
94
+
95
+ it "should load a File object" do
96
+ foo = Foo.new open('spec/fixtures/twitter-search.atom')
97
+ end
98
+ end
99
+
100
+ describe "a simple FAP to parse an atom feed" do
101
+ before :all do
102
+ # Define a simple Atom parser for twitter searches.
103
+ class Atom < FAP::FAP
104
+ string :title, :xpath => '//feed/title'
105
+ uri :url, :xpath => '//feed/link[@rel="self"]', :get => :href
106
+ date :updated, :xpath => '//feed/updated'
107
+
108
+ has_many :articles, :class => 'Article', :xpath => '//feed/entry'
109
+ end
110
+
111
+ class Article < FAP::FAP
112
+ string :title
113
+ uri :url, :xpath => 'link[@rel="alternate"]', :get => :href
114
+ string :content
115
+ date :updated_at, :xpath => 'updated'
116
+
117
+ belongs_to :atom, :class => 'Atom'
118
+ end
119
+ end
120
+
121
+ before :each do
122
+ @atom = Atom.new open('spec/fixtures/twitter-search.atom')
123
+ end
124
+
125
+ it "should find a title" do
126
+ @atom.title.should == "ruby - Twitter Search"
127
+ end
128
+
129
+ it "should find a URI" do
130
+ @atom.url.should be_an_instance_of(URI::HTTP)
131
+ end
132
+
133
+ it "should find a date" do
134
+ @atom.updated.should be_an_instance_of(DateTime)
135
+ end
136
+
137
+ it "should define a collection of :articles" do
138
+ @atom.articles.should be_an_instance_of(FAP::Collection)
139
+ @atom.articles.should respond_to(:each)
140
+ end
141
+
142
+ it "should map relations to their required class" do
143
+ @atom.articles.first.should be_an_instance_of(Article)
144
+ end
145
+
146
+ it "should map relations to their required class instances" do
147
+ @atom.articles.first.title.should be_an_instance_of(String)
148
+ @atom.articles.first.url.should be_an_instance_of(URI::HTTP)
149
+ @atom.articles.first.content.should be_an_instance_of(String)
150
+ @atom.articles.first.updated_at.should be_an_instance_of(DateTime)
151
+ @atom.articles.first.atom.should be_an_instance_of(Atom)
152
+ end
153
+
154
+ it "should preserve relations' objects" do
155
+ @atom.articles.first.atom.object_id.should == @atom.object_id
156
+ end
157
+ end
@@ -0,0 +1 @@
1
+ --colour
@@ -0,0 +1,11 @@
1
+ begin
2
+ require 'rubygems' unless ENV['NO_RUBYGEMS']
3
+ require 'spec'
4
+ rescue LoadError
5
+ require 'rubygems' unless ENV['NO_RUBYGEMS']
6
+ gem 'rspec'
7
+ require 'spec'
8
+ end
9
+
10
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
11
+ require 'fap'
@@ -0,0 +1,21 @@
1
+ begin
2
+ require 'spec'
3
+ rescue LoadError
4
+ require 'rubygems' unless ENV['NO_RUBYGEMS']
5
+ require 'spec'
6
+ end
7
+ begin
8
+ require 'spec/rake/spectask'
9
+ rescue LoadError
10
+ puts <<-EOS
11
+ To use rspec for testing you must install rspec gem:
12
+ gem install rspec
13
+ EOS
14
+ exit(0)
15
+ end
16
+
17
+ desc "Run the specs under spec/models"
18
+ Spec::Rake::SpecTask.new do |t|
19
+ t.spec_opts = ['--options', "spec/spec.opts"]
20
+ t.spec_files = FileList['spec/**/*_spec.rb']
21
+ end
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fap
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Arnaud Berthomier
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-03-31 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rubyforge
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 2.0.4
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: hoe
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 2.6.0
34
+ version:
35
+ description: |-
36
+ FAP is a ruby gem build on top of the excellent Nokogiri, to turn boring XML,
37
+ or HTML documents into yummy ruby objects. Right now, it only support
38
+ using Nokogiri's XPath selectors, and simple "relations" between a document
39
+ nodes, though this will hopefully get better.
40
+
41
+ FAP's ideas are loosely connected to tools built by some adventurous fellas at
42
+ AF83, who still do PHP things to their brains. Some credits should go to them,
43
+ and to the horrid weather that kept me locked inside last week-end.
44
+
45
+ And yes, I know it's a stupid name. But I'm sure you can come up with a decent
46
+ acronym. :)
47
+ email:
48
+ - oz@cyprio.net
49
+ executables: []
50
+
51
+ extensions: []
52
+
53
+ extra_rdoc_files:
54
+ - History.txt
55
+ - Manifest.txt
56
+ files:
57
+ - History.txt
58
+ - Manifest.txt
59
+ - README.rdoc
60
+ - Rakefile
61
+ - lib/fap.rb
62
+ - lib/fap/relation.rb
63
+ - lib/fap/support/class.rb
64
+ - lib/fap/fap.rb
65
+ - lib/fap/mixins/properties.rb
66
+ - lib/fap/mixins/relations.rb
67
+ - lib/fap/mixins.rb
68
+ - lib/fap/collection.rb
69
+ - lib/fap/property.rb
70
+ - script/console
71
+ - script/destroy
72
+ - script/generate
73
+ - spec/fap_spec.rb
74
+ - spec/spec.opts
75
+ - spec/spec_helper.rb
76
+ - tasks/rspec.rake
77
+ has_rdoc: true
78
+ homepage: http://github.com/oz/fap
79
+ licenses: []
80
+
81
+ post_install_message:
82
+ rdoc_options:
83
+ - --main
84
+ - README.rdoc
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: "0"
92
+ version:
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: "0"
98
+ version:
99
+ requirements: []
100
+
101
+ rubyforge_project: fap
102
+ rubygems_version: 1.3.5
103
+ signing_key:
104
+ specification_version: 3
105
+ summary: FAP is a ruby gem build on top of the excellent Nokogiri, to turn boring XML, or HTML documents into yummy ruby objects
106
+ test_files: []
107
+