craft 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0d73f28ad0726d5645b313a8692748e7513f0b8e
4
+ data.tar.gz: 63a7eda56f737e4e52e281390117f5dda143a0b4
5
+ SHA512:
6
+ metadata.gz: 336a3002c19325e9079b56be7dd69d60cd02e332c1004592fe7aafd7d2b2b74385cf5bef45d3c221f6e12fde68b0798ff04f1fc5962057e47af2f408a5c6905a
7
+ data.tar.gz: 7070a01973af959e33fe286d9b68e4f36c89d930c65774916e4eca99136520f50311b0e4b618ccf96dc2e299d3f674c4ce6056f7bc70dd7f54b1c4678575131d
data/Gemfile CHANGED
@@ -1,13 +1,2 @@
1
1
  source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in craft.gemspec
4
2
  gemspec
5
-
6
- group :ci do
7
- gem 'rake'
8
- end
9
-
10
- platforms :jruby do
11
- gem 'minitest'
12
- end
13
- gem 'pry'
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012 Hakan Ensari
1
+ Copyright (c) 2013 Hakan Ensari
2
2
 
3
3
  MIT License
4
4
 
@@ -19,4 +19,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
19
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
20
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
21
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,42 +1,6 @@
1
- # Craft [![Build Status](https://secure.travis-ci.org/papercavalier/craft.png)](http://travis-ci.org/papercavalier/craft)
1
+ # Craft
2
2
 
3
- Craft XML and HTML into objects.
3
+ Build [page objects][po] in [Capybara][ca].
4
4
 
5
- ## Examples
6
- ```ruby
7
- require 'craft'
8
- require 'open-uri'
9
-
10
- class Page < Craft
11
- # Use CSS selectors
12
- one :title, 'title'
13
-
14
- # Use XPath
15
- many :links, 'a/@href'
16
-
17
- # Perform transforms on returned nodes
18
- many :images, 'img', lambda { |img| img.attr('src').upcase }
19
-
20
- # Stub attributes that don't need to be parsed
21
- stub :spidered_at, lambda { Time.now }
22
- end
23
-
24
- page = Page.parse open('http://www.google.com')
25
-
26
- page.title #=> 'Google'
27
- page.links #=> ['http://www.google.com/imghp?hl=en&tab=wi', ...]
28
- page.images #=> ['/LOGOS/2012/MOBY_DICK12-HP.JPG']
29
-
30
- page.attributes #=> { :title => 'Google', :links => ... }
31
-
32
- class Script < Craft
33
- one :body, 'text()'
34
- end
35
-
36
- class Page < Craft
37
- many :scripts, 'script', Script
38
- end
39
-
40
- page = Page.parse open('http://www.google.com')
41
- page.scripts[0].body #=> 'window.google=...'
42
- ```
5
+ [po]: https://code.google.com/p/selenium/wiki/PageObjects
6
+ [ca]: https://github.com/jnicklas/capybara
data/Rakefile CHANGED
@@ -2,7 +2,5 @@ require 'bundler/gem_tasks'
2
2
  require 'rake/testtask'
3
3
 
4
4
  Rake::TestTask.new do |t|
5
- t.pattern = "spec/*_spec.rb"
5
+ t.pattern = 'test/*_test.rb'
6
6
  end
7
-
8
- task :default => :test
@@ -6,17 +6,17 @@ require 'craft/version'
6
6
  Gem::Specification.new do |gem|
7
7
  gem.name = 'craft'
8
8
  gem.version = Craft::VERSION
9
- gem.authors = ['Ezekiel Templin', 'Hakan Ensari']
10
- gem.email = ['code@papercavalier.com']
11
- gem.description = %q{Craft XML into objects}
12
- gem.summary = %q{Craft is a data extraction tool that crafts objects
13
- out of HTML and XML.}
14
- gem.homepage = 'http://papercavalier.com/craft/'
9
+ gem.authors = ['Hakan Ensari']
10
+ gem.email = ['hakan.ensari@papercavalier.com']
11
+ gem.summary = 'Build page objects in Capybara'
12
+ gem.homepage = 'https://github.com/hakanensari/craft'
15
13
 
16
14
  gem.files = `git ls-files`.split($/)
17
- gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
18
- gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
15
+ gem.test_files = gem.files.grep(%r{^test/})
19
16
  gem.require_paths = ['lib']
20
17
 
21
- gem.add_dependency 'nokogiri', '~> 1.5'
18
+ gem.add_dependency 'capybara', '~> 2.1'
19
+ gem.add_development_dependency 'minitest'
20
+ gem.add_development_dependency 'poltergeist'
21
+ gem.add_development_dependency 'rake'
22
22
  end
@@ -1,127 +1,17 @@
1
- require 'craft/version'
2
- require 'nokogiri'
1
+ require 'capybara'
3
2
 
4
- # Craft objects out of HTML and XML.
5
- #
6
- # Examples
7
- #
8
- # module Transformations
9
- # IntegerTransform = lambda { |n| Integer n.text }
10
- # Timestamp = lambda { Time.now }
11
- # end
12
- #
13
- # class Person < Craft
14
- # include Transformations
15
- #
16
- # one :name, 'div.name'
17
- # one :age, 'div.age', IntegerTransform
18
- # many :friends, 'li.friend', Person
19
- # stub :created_at, Timestamp
20
- # end
21
- #
22
- class Craft
23
- class << self
24
- # Returns an Array of names for the attributes defined in the class.
25
- attr :attribute_names
26
-
27
- # Define an attribute that extracts a collection of values from a parsed
28
- # document.
29
- #
30
- # name - The Symbol name of the attribute.
31
- # paths - One or more String XPath of CSS queries. An optional Proc
32
- # transformation on the extracted value may be appended. If none is
33
- # appended, the default transformation returns the stripped String
34
- # value of the node.
35
- #
36
- # Returns nothing.
37
- def many(name, *paths)
38
- transform = pop_transform_from_paths paths
39
- @attribute_names << name
40
-
41
- define_method name do
42
- @node.search(*paths).map { |node| instance_exec node, &transform }
43
- end
44
- end
45
-
46
- # Define an attribute that extracts a single value from a parsed document.
47
- #
48
- # name - The Symbol name of the attribute.
49
- # paths - One or more String XPath of CSS queries. An optional Proc
50
- # transformation on the extracted value may be appended. If none is
51
- # appended, the default transformation returns the stripped String
52
- # value of the node.
53
- #
54
- # Returns nothing.
55
- def one(name, *paths)
56
- transform = pop_transform_from_paths paths
57
- @attribute_names << name
58
-
59
- define_method name do
60
- instance_exec @node.at(*paths), &transform
61
- end
62
- end
63
-
64
- # Parse a document.
65
- #
66
- # body - A String HTML or XML document.
67
- #
68
- # Returns an instance of its self.
69
- def parse(body)
70
- new Nokogiri body
71
- end
72
-
73
- # Define an attribute that returns a value without parsing the document.
74
- #
75
- # name - The Symbol name of the attribute.
76
- # value - Some value the attribute should return. If given a Proc, the
77
- # value will be generated dynamically (default: nil).
78
- #
79
- # Returns nothing.
80
- def stub(name, value = nil)
81
- @attribute_names << name
82
-
83
- define_method name do
84
- value.respond_to?(:call) ? instance_exec(&value) : value
85
- end
86
- end
87
-
88
- def to_proc
89
- klass = self
90
- ->(node) { klass.new node, self }
91
- end
3
+ module Craft
4
+ include Capybara::DSL
92
5
 
93
- private
94
-
95
- def inherited(subclass)
96
- subclass.instance_variable_set :@attribute_names, []
97
- end
6
+ class << self
7
+ attr_accessor :driver
98
8
 
99
- def pop_transform_from_paths(array)
100
- if array.last.respond_to? :to_proc
101
- array.pop
102
- else
103
- ->(node) { node.text.strip if node }
104
- end
9
+ def session
10
+ Thread.current[:session] ||= Capybara::Session.new(driver)
105
11
  end
106
12
  end
107
13
 
108
- attr :parent
109
-
110
- # Craft a new object.
111
- #
112
- # node - A Nokogiri::XML::Node.
113
- def initialize(node, parent = nil)
114
- @node = node
115
- @parent = parent
116
- end
117
-
118
- # Returns the Hash attributes.
119
- def attributes
120
- Hash[attribute_names.map { |key| [key, self.send(key)] }]
121
- end
122
-
123
- # Returns an Array of names for the attributes on this object.
124
- def attribute_names
125
- self.class.attribute_names
14
+ def page
15
+ Craft.session
126
16
  end
127
17
  end
@@ -1,3 +1,3 @@
1
- class Craft
2
- VERSION = '0.1.0'
1
+ module Craft
2
+ VERSION = '0.2.0'
3
3
  end
@@ -0,0 +1,50 @@
1
+ require 'minitest/autorun'
2
+ require 'capybara/poltergeist'
3
+ require 'craft'
4
+
5
+ Craft.driver = :poltergeist
6
+
7
+ class Home
8
+ include Craft
9
+
10
+ def initialize
11
+ visit('http://www.duckduckgo.com')
12
+ end
13
+
14
+ def search(query)
15
+ fill_in('q', with: query)
16
+ click_on('search_button_homepage')
17
+
18
+ Search.new
19
+ end
20
+ end
21
+
22
+ class Search
23
+ include Craft
24
+
25
+ def results
26
+ all('.results_links_deep')
27
+ end
28
+ end
29
+
30
+ class TestCraft < Minitest::Test
31
+ def test_browser
32
+ duck = Home.new
33
+ search = duck.search('ruby')
34
+
35
+ refute_empty search.results
36
+ end
37
+
38
+ def test_session
39
+ thrs = %w(ruby python)
40
+ .map { |query|
41
+ Thread.new {
42
+ Thread.current[:current_url] = Home.new.search(query).current_url
43
+ }
44
+ }
45
+ .map(&:join)
46
+
47
+ refute_equal thrs.first[:current_url], thrs.last[:current_url]
48
+ end
49
+ end
50
+
metadata CHANGED
@@ -1,43 +1,80 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: craft
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
5
- prerelease:
4
+ version: 0.2.0
6
5
  platform: ruby
7
6
  authors:
8
- - Ezekiel Templin
9
7
  - Hakan Ensari
10
8
  autorequire:
11
9
  bindir: bin
12
10
  cert_chain: []
13
- date: 2012-10-25 00:00:00.000000000 Z
11
+ date: 2013-07-21 00:00:00.000000000 Z
14
12
  dependencies:
15
13
  - !ruby/object:Gem::Dependency
16
- name: nokogiri
14
+ name: capybara
17
15
  requirement: !ruby/object:Gem::Requirement
18
- none: false
19
16
  requirements:
20
17
  - - ~>
21
18
  - !ruby/object:Gem::Version
22
- version: '1.5'
19
+ version: '2.1'
23
20
  type: :runtime
24
21
  prerelease: false
25
22
  version_requirements: !ruby/object:Gem::Requirement
26
- none: false
27
23
  requirements:
28
24
  - - ~>
29
25
  - !ruby/object:Gem::Version
30
- version: '1.5'
31
- description: Craft XML into objects
26
+ version: '2.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: poltergeist
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description:
32
70
  email:
33
- - code@papercavalier.com
71
+ - hakan.ensari@papercavalier.com
34
72
  executables: []
35
73
  extensions: []
36
74
  extra_rdoc_files: []
37
75
  files:
38
76
  - .gitignore
39
77
  - .travis.yml
40
- - CHANGES.md
41
78
  - Gemfile
42
79
  - LICENSE.txt
43
80
  - README.md
@@ -45,36 +82,30 @@ files:
45
82
  - craft.gemspec
46
83
  - lib/craft.rb
47
84
  - lib/craft/version.rb
48
- - spec/craft_spec.rb
49
- homepage: http://papercavalier.com/craft/
85
+ - test/craft_test.rb
86
+ homepage: https://github.com/hakanensari/craft
50
87
  licenses: []
88
+ metadata: {}
51
89
  post_install_message:
52
90
  rdoc_options: []
53
91
  require_paths:
54
92
  - lib
55
93
  required_ruby_version: !ruby/object:Gem::Requirement
56
- none: false
57
94
  requirements:
58
- - - ! '>='
95
+ - - '>='
59
96
  - !ruby/object:Gem::Version
60
97
  version: '0'
61
- segments:
62
- - 0
63
- hash: 2821744877133646519
64
98
  required_rubygems_version: !ruby/object:Gem::Requirement
65
- none: false
66
99
  requirements:
67
- - - ! '>='
100
+ - - '>='
68
101
  - !ruby/object:Gem::Version
69
102
  version: '0'
70
- segments:
71
- - 0
72
- hash: 2821744877133646519
73
103
  requirements: []
74
104
  rubyforge_project:
75
- rubygems_version: 1.8.23
105
+ rubygems_version: 2.0.5
76
106
  signing_key:
77
- specification_version: 3
78
- summary: Craft is a data extraction tool that crafts objects out of HTML and XML.
107
+ specification_version: 4
108
+ summary: Build page objects in Capybara
79
109
  test_files:
80
- - spec/craft_spec.rb
110
+ - test/craft_test.rb
111
+ has_rdoc:
data/CHANGES.md DELETED
@@ -1,8 +0,0 @@
1
- v0.1.0
2
- -----------
3
-
4
- - When nesting, make parent accessible to child.
5
- - Craft#attributes returns the attributes.
6
- - Craft.stub stubs a static or dynamic value that doesn't need to be parsed in
7
- the document.
8
- - Transforms are executed in the context of the crafted object.
@@ -1,131 +0,0 @@
1
- require 'bundler/setup'
2
- require 'minitest/autorun'
3
- require 'craft'
4
-
5
- describe Craft do
6
- let(:html) { '<html><ul><li>1</li><li>2</li>' }
7
- let(:klass) { Class.new Craft }
8
- let(:instance) { klass.parse html }
9
-
10
- describe '.attribute_names' do
11
- it 'is empty by default' do
12
- klass.attribute_names.must_equal []
13
- end
14
-
15
- it 'does not reference other attribute names' do
16
- klass.stub :foo
17
- other = Class.new(Craft) { stub :bar }
18
- klass.attribute_names.wont_equal other.attribute_names
19
- end
20
- end
21
-
22
- describe '.many' do
23
- it 'extracts nodes' do
24
- klass.many :foo, 'li'
25
- instance.foo.must_equal %w(1 2)
26
- end
27
-
28
- it 'transforms' do
29
- klass.many :foo, 'li', ->(node) { node.text.to_i }
30
- instance.foo.must_equal [1, 2]
31
- end
32
-
33
- it 'transforms in scope' do
34
- klass.many :foo, 'li', ->(node) { bar }
35
- klass.send(:define_method, :bar) { 'bar' }
36
- instance.foo.must_equal ['bar', 'bar']
37
- end
38
-
39
- it 'stores attribute name' do
40
- klass.many :foo, 'li'
41
- klass.attribute_names.must_include :foo
42
- end
43
-
44
- it 'nests' do
45
- klass.many :foo, 'ul', Class.new(Craft)
46
- instance.foo.each { |attr| attr.must_be_kind_of Craft }
47
- end
48
-
49
- it 'has a parent when nested' do
50
- klass.many :foo, 'li', Class.new(Craft)
51
- instance.foo.each { |attr| attr.parent.must_equal instance }
52
- end
53
- end
54
-
55
- describe '.one' do
56
- it 'extracts a node' do
57
- klass.one :foo, 'li'
58
- instance.foo.must_equal '1'
59
- end
60
-
61
- it 'transforms' do
62
- klass.one :foo, 'li', ->(node) { node.text.to_i }
63
- instance.foo.must_equal 1
64
- end
65
-
66
- it 'transforms in scope' do
67
- klass.one :foo, 'li', ->(node) { bar }
68
- klass.send(:define_method, :bar) { 'bar' }
69
- instance.foo.must_equal 'bar'
70
- end
71
-
72
- it 'stores attribute name' do
73
- klass.one :foo, 'li'
74
- klass.attribute_names.must_include :foo
75
- end
76
-
77
- it 'nests' do
78
- klass.one :foo, 'ul', Class.new(Craft)
79
- instance.foo.must_be_kind_of Craft
80
- end
81
-
82
- it 'has a parent when nested' do
83
- klass.one :foo, 'li', Class.new(Craft)
84
- instance.foo.parent.must_equal instance
85
- end
86
-
87
- describe 'given no matches' do
88
- before { klass.one :foo, 'bar' }
89
-
90
- it 'returns nil' do
91
- instance.foo.must_be_nil
92
- end
93
- end
94
- end
95
-
96
- describe '.stub' do
97
- it 'returns nil by default' do
98
- klass.stub :foo
99
- instance.foo.must_be_nil
100
- end
101
-
102
- it 'returns a static value' do
103
- klass.stub :foo, 1
104
- instance.foo.must_equal 1
105
- end
106
-
107
- it 'returns a dynamic value' do
108
- klass.stub :foo, -> { Time.now }
109
- instance.foo.must_be_instance_of Time
110
- end
111
-
112
- it 'transforms in scope' do
113
- klass.stub :foo, -> { bar }
114
- klass.send(:define_method, :bar) { 'bar' }
115
- instance.foo.must_equal 'bar'
116
- end
117
-
118
- it 'stores attribute name' do
119
- klass.stub :foo
120
- klass.attribute_names.must_include :foo
121
- end
122
- end
123
-
124
- describe '#attributes' do
125
- it 'returns attributes' do
126
- klass.stub :foo
127
- klass.one :bar, 'li'
128
- instance.attributes.must_equal({ foo: nil, bar: '1' })
129
- end
130
- end
131
- end