benschwarz-smoke 0.2.4 → 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -56,8 +56,8 @@ Integrity [is running for smoke](http://integrity.ffolio.net/smoke)
56
56
 
57
57
  ### TODO (working on, just mental notes)
58
58
 
59
- * Clean up / simplify source activation process
60
- * Consider variable injection (eg, allow to query sending a username within some aspect of the request)
59
+ * Joined sources require a call to dispatch when their parent calls output
60
+ * Sources that are renamed also need their @name's renaming
61
61
  * Implement a disaptch / request method that will actually thread property (event-machine)
62
62
  * Checkout experimental fakeweb version to stub out the yql specs
63
63
 
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :major: 0
3
- :minor: 2
3
+ :minor: 3
4
4
  :patch: 4
data/lib/smoke/origin.rb CHANGED
@@ -5,7 +5,7 @@ module Smoke
5
5
  def initialize(name, &block)
6
6
  raise StandardError, "Sources must have a name" unless name
7
7
  @name = name
8
- @items, @transformation = [], []
8
+ @items, @prepare, @transformation = [], [], []
9
9
 
10
10
  activate!
11
11
  instance_eval(&block) if block_given?
@@ -19,22 +19,23 @@ module Smoke
19
19
  # output(:json)
20
20
  # => "[{title: \"Ray\"}, {title: \"Peace\"}]"
21
21
  def output(type = :ruby)
22
+ prepare!
22
23
  dispatch if respond_to? :dispatch
23
24
  output = (@items.length == 1) ? @items.first : @items
24
25
 
25
26
  case type
26
- when :ruby
27
- return output
28
27
  when :json
29
28
  return ::JSON.generate(output)
30
29
  when :yaml
31
30
  return YAML.dump(output)
31
+ else
32
+ return output
32
33
  end
33
34
  end
34
35
 
35
36
  def items=(items) # :nodoc:
36
37
  @items = items.flatten.map{|i| i.symbolize_keys! }
37
- invoke_transformation
38
+ transform!
38
39
  end
39
40
 
40
41
  # Path allows you to traverse the tree of a the items returned to
@@ -68,7 +69,39 @@ module Smoke
68
69
  # rename(:href => :link)
69
70
  # end
70
71
  def emit(&block)
71
- @transformation << DelayedBlock.new(&block)
72
+ @transformation << block
73
+ end
74
+
75
+ # Prepare is used when you'd like to provision for
76
+ # arguments / variables to be set after the source definition.
77
+ # Eg, create a source definition for twitter, omitting the "username".
78
+ # Set the username using chaining later.
79
+ #
80
+ # Usage:
81
+ # # Definition
82
+ # Smoke.feed :twitter do
83
+ # prepare do
84
+ # url "http://twitter.com/#{username}/rss"
85
+ # end
86
+ # end
87
+ #
88
+ # # End use
89
+ # Smoke[:twitter].username(:benschwarz).output
90
+ def prepare(&block)
91
+ raise ArgumentError, "requires a block" unless block_given?
92
+ @prepare << block
93
+ end
94
+
95
+ def method_missing(symbol, *args, &block)
96
+ ivar = "@#{symbol}"
97
+
98
+ if args.empty?
99
+ return instance_variable_get(ivar) || super
100
+ else
101
+ instance_variable_set(ivar, args.pop)
102
+ end
103
+
104
+ return self
72
105
  end
73
106
 
74
107
  # Re-sort items by a particular key
@@ -134,8 +167,12 @@ module Smoke
134
167
  end
135
168
 
136
169
  private
137
- def invoke_transformation
138
- @transformation.each{|t| t.execute! } unless @transformation.nil?
170
+ def prepare!
171
+ @prepare.each{|p| p.call } unless @prepare.nil?
172
+ end
173
+
174
+ def transform!
175
+ @transformation.each{|t| t.call } unless @transformation.nil?
139
176
  end
140
177
 
141
178
  def drill(*path)
@@ -1,42 +1,34 @@
1
1
  module Smoke
2
- module Source # :nodoc:
3
- module Data # :nodoc:
4
- Smoke.register(Smoke::Source::Data)
5
-
6
- def data(name, &block)
7
- Data.new(name, &block)
2
+ module Source # :nodoc:
3
+ # The "Data" source allows you to query
4
+ # datasources that are "complete" urls
5
+ # and rely on the automagic object parsing
6
+ # that smoke provides.
7
+ #
8
+ # For example, you may use this source
9
+ # to query a complete restful api call
10
+ # unpackage the xml response and get a
11
+ # clean ruby object.
12
+ #
13
+ # Data can take as many urls as you'd like
14
+ # to throw at it.
15
+ #
16
+ # Usage:
17
+ # Smoke.data(:ruby) do
18
+ # url "http://api.flickr.com/services/rest/?user_id=36821533%40N00&tags=benschwarz-site&nojsoncallback=1&method=flickr.photos.search&format=json&api_key=your_api_key_here
19
+ # path :photos, :photo
20
+ # end
21
+ class Data < Origin
22
+ attr_reader :request
23
+
24
+ def url(source_url)
25
+ @url = source_url
8
26
  end
9
27
 
10
- # The "Data" source allows you to query
11
- # datasources that are "complete" urls
12
- # and rely on the automagic object parsing
13
- # that smoke provides.
14
- #
15
- # For example, you may use this source
16
- # to query a complete restful api call
17
- # unpackage the xml response and get a
18
- # clean ruby object.
19
- #
20
- # Data can take as many urls as you'd like
21
- # to throw at it.
22
- #
23
- # Usage:
24
- # Smoke.data(:ruby) do
25
- # url "http://api.flickr.com/services/rest/?user_id=36821533%40N00&tags=benschwarz-site&nojsoncallback=1&method=flickr.photos.search&format=json&api_key=your_api_key_here
26
- # path :photos, :photo
27
- # end
28
- class Data < Origin
29
- attr_reader :request
30
-
31
- def url(source_url)
32
- @url = source_url
33
- end
34
-
35
- protected
36
- def dispatch
37
- @request = Smoke::Request.new(@url)
38
- self.items = (@path.nil?) ? @request.body : drill(@request.body, *@path)
39
- end
28
+ protected
29
+ def dispatch
30
+ @request = Smoke::Request.new(@url)
31
+ self.items = (@path.nil?) ? @request.body : drill(@request.body, *@path)
40
32
  end
41
33
  end
42
34
  end
@@ -1,31 +1,23 @@
1
1
  module Smoke
2
2
  module Source # :nodoc:
3
- module Feed # :nodoc:
4
- Smoke.register(Smoke::Source::Feed)
3
+ # Feed can take multiple rss or atom feeds and munge them up together.
4
+ #
5
+ # Usage:
6
+ # Smoke.feed(:ruby) do
7
+ # url "domain.tld/rss"
8
+ # url "site.tld/atom"
9
+ # end
10
+ class Feed < Origin
11
+ attr_reader :requests
5
12
 
6
- def feed(name, &block)
7
- Feed.new(name, &block)
13
+ def url(feed_uri)
14
+ (@feeds ||= [] ) << feed_uri
8
15
  end
9
16
 
10
- # Feed can take multiple rss or atom feeds and munge them up together.
11
- #
12
- # Usage:
13
- # Smoke.feed(:ruby) do
14
- # url "domain.tld/rss"
15
- # url "site.tld/atom"
16
- # end
17
- class Feed < Origin
18
- attr_reader :requests
19
-
20
- def url(feed_uri)
21
- (@feeds ||= [] ) << feed_uri
22
- end
23
-
24
- protected
25
- def dispatch
26
- @requests = @feeds.map{|f| Smoke::Request.new(f, :raw_response) }
27
- self.items = @requests.map{|r| ::SimpleRSS.parse(r.body).items }.flatten
28
- end
17
+ protected
18
+ def dispatch
19
+ @requests = @feeds.map{|f| Smoke::Request.new(f, :raw_response) }
20
+ self.items = @requests.map{|r| ::SimpleRSS.parse(r.body).items }.flatten
29
21
  end
30
22
  end
31
23
  end
@@ -1,92 +1,84 @@
1
1
  module Smoke
2
- module Source # :nodoc:
3
- module YQL # :nodoc:
4
- Smoke.register(Smoke::Source::YQL)
2
+ module Source # :nodoc:
3
+ # YQL will call to Yahoo YQL services
4
+ #
5
+ # Usage:
6
+ # Smoke.yql(:ruby) do
7
+ # select :all
8
+ # from "search.web"
9
+ # where :query, "ruby"
10
+ # end
11
+ class YQL < Origin
12
+ API_BASE = "http://query.yahooapis.com/v1/public/yql"
13
+ attr_reader :request
5
14
 
6
- def yql(name, &block)
7
- YQL.new(name, &block)
15
+ # Select indicates what YQL will be selecting
16
+ # Usage:
17
+ # select :all
18
+ # => "SELECT *"
19
+ # select :title
20
+ # => "SELECT title"
21
+ # select :title, :description
22
+ # => "SELECT title, description"
23
+ def select(what = :all)
24
+ @select = what.join(",") and return if what.is_a? Array
25
+ @select = "*" and return if what == :all
26
+ @select = what.to_s
27
+ end
28
+
29
+ # from corresponds to the from fragment of the YQL query
30
+ # Usage:
31
+ # from "search.web"
32
+ # or
33
+ # from :html
34
+ def from(source)
35
+ @from = source.join(',') and return if source.is_a? Array
36
+ @from = source.to_s
37
+ end
38
+
39
+ # where is a straight up match, no fancy matchers
40
+ # are currently supported
41
+ # Usage:
42
+ # where :xpath, "//div/div/a"
43
+ # or
44
+ # where :query, "python"
45
+ def where(column, value)
46
+ @where = @where || []
47
+ @where << "#{column.to_s} = '#{value}'"
8
48
  end
9
49
 
10
- # YQL will call to Yahoo YQL services
50
+ # `use` can be used to set the url location of the data-table
51
+ # that you want YQL to search upon
11
52
  #
12
53
  # Usage:
13
- # Smoke.yql(:ruby) do
14
- # select :all
15
- # from "search.web"
16
- # where :query, "ruby"
17
- # end
18
- class YQL < Origin
19
- API_BASE = "http://query.yahooapis.com/v1/public/yql"
20
- attr_reader :request
21
-
22
- # Select indicates what YQL will be selecting
23
- # Usage:
24
- # select :all
25
- # => "SELECT *"
26
- # select :title
27
- # => "SELECT title"
28
- # select :title, :description
29
- # => "SELECT title, description"
30
- def select(what = :all)
31
- @select = what.join(",") and return if what.is_a? Array
32
- @select = "*" and return if what == :all
33
- @select = what.to_s
34
- end
35
-
36
- # from corresponds to the from fragment of the YQL query
37
- # Usage:
38
- # from "search.web"
39
- # or
40
- # from :html
41
- def from(source)
42
- @from = source.join(',') and return if source.is_a? Array
43
- @from = source.to_s
44
- end
45
-
46
- # where is a straight up match, no fancy matchers
47
- # are currently supported
48
- # Usage:
49
- # where :xpath, "//div/div/a"
50
- # or
51
- # where :query, "python"
52
- def where(column, value)
53
- @where = @where || []
54
- @where << "#{column.to_s} = '#{value}'"
55
- end
56
-
57
- # `use` can be used to set the url location of the data-table
58
- # that you want YQL to search upon
59
- #
60
- # Usage:
61
- # use "http://datatables.org/alltables.env"
62
- def use(url)
63
- params.merge!({:env => url})
64
- end
65
-
66
- protected
67
- def params
68
- @params || @params = {}
69
- end
70
-
71
- def dispatch
72
- @request = Smoke::Request.new(build_uri)
73
- self.items = [(@path.nil?) ? @request.body : drill(@request.body, *@path)]
74
- end
75
-
76
- private
77
- def build_uri
78
- chunks = []
79
- default_opts = {
80
- :q => build_query,
81
- :format => "json"
82
- }.merge(params).each {|k,v| chunks << "#{k}=#{v}" }
83
-
84
- return URI.encode(API_BASE + "?" + chunks.join("&"))
85
- end
54
+ # use "http://datatables.org/alltables.env"
55
+ def use(url)
56
+ params.merge!({:env => url})
57
+ end
58
+
59
+ protected
60
+ def params
61
+ @params || @params = {}
62
+ end
63
+
64
+ def dispatch
65
+ @request = Smoke::Request.new(build_uri)
66
+ self.items = [(@path.nil?) ? @request.body : drill(@request.body, *@path)]
67
+ end
68
+
69
+ private
70
+ def build_uri
71
+ chunks = []
72
+ default_opts = {
73
+ :q => build_query,
74
+ :format => "json"
75
+ }.merge(params).each {|k,v| chunks << "#{k}=#{v}" }
86
76
 
87
- def build_query
88
- "SELECT #{@select} FROM #{@from} WHERE #{@where.join(" AND ")}"
89
- end
77
+ return URI.encode(API_BASE + "?" + chunks.join("&"))
78
+ end
79
+
80
+ def build_query
81
+ "SELECT #{@select} FROM #{@from} WHERE #{@where.join(" AND ")}"
90
82
  end
91
83
  end
92
84
  end
data/lib/smoke.rb CHANGED
@@ -5,28 +5,16 @@ require 'json'
5
5
  require 'crack'
6
6
  require 'simple-rss'
7
7
 
8
-
9
8
  $:<< File.join(File.dirname(__FILE__))
10
9
 
11
10
  # Core ext
12
11
  require 'core_ext/hash.rb'
13
12
 
14
13
  module Smoke
15
- class << self
16
-
14
+ class << self
17
15
  @@active_sources = {}
18
16
  attr_reader :active_sources
19
17
 
20
- # Smoke sources can invoke access to themselves
21
- # via the register method:
22
- #
23
- # Smoke.register(Smoke::Source::YQL)
24
- #
25
- # Check the supplied sources for usage
26
- def register(mod)
27
- class_eval { include mod }
28
- end
29
-
30
18
  # Access registered smoke source instances
31
19
  #
32
20
  # Usage:
@@ -34,7 +22,7 @@ module Smoke
34
22
  # Smoke.yql(:ruby) do ....
35
23
  #
36
24
  # Smoke[:ruby]
37
- # => #<Smoke::Source::YQL::YQL:0x18428d4...
25
+ # => #<Smoke::Source::YQL::0x18428d4...
38
26
  def [](source)
39
27
  active_sources[source]
40
28
  end
@@ -49,11 +37,13 @@ module Smoke
49
37
  return source
50
38
  end
51
39
 
40
+ # Activates new instances of sources
41
+ # Source instances are stored within the
42
+ # @@active_sources class variable for later use
52
43
  def activate(name, source)
53
44
  if active_sources.has_key?(name)
54
45
  Smoke.log.warn "Smoke source activation: Source with idential name already initialized"
55
46
  end
56
-
57
47
  active_sources.update({ name => source })
58
48
  end
59
49
 
@@ -77,9 +67,13 @@ module Smoke
77
67
  # Smoke.log.error "message"
78
68
  # Smoke.log.warn "message"
79
69
  def log
80
- @logger ||= Logger.new($stdout)
70
+ @@log ||= Logger.new($stdout)
81
71
  end
82
72
 
73
+ def yql(name, &block); Smoke::Source::YQL.new(name, &block); end
74
+ def data(name, &block); Smoke::Source::Data.new(name, &block); end
75
+ def feed(name, &block); Smoke::Source::Feed.new(name, &block); end
76
+
83
77
  private
84
78
  def get_sources(sources = [])
85
79
  active_sources.find_all{|k, v| sources.include?(k) }
@@ -89,7 +83,6 @@ end
89
83
 
90
84
  require 'smoke/request'
91
85
  require 'smoke/origin'
92
- require 'smoke/delayed_block'
93
86
 
94
87
  class Object # :nodoc:
95
88
  include Smoke
@@ -97,9 +97,64 @@ describe Smoke::Origin do
97
97
  TestSource.source(:chain).sort(:header).should_not raise_error("NoMethodError")
98
98
  end
99
99
 
100
- describe "variable injection" do
101
- it "should allow variables to be left happlessly in your source definitions"
102
- it "should allow setting of variables"
103
- it "should error if output is called before a variable has been set"
100
+ describe "preperation" do
101
+ before :all do
102
+ @source = TestSource.source(:preperation)
103
+ end
104
+
105
+ it "should respond to prepare" do
106
+ @source.should respond_to(:prepare)
107
+ end
108
+
109
+ it "should require a block" do
110
+ lambda { @source.prepare }.should raise_error
111
+ lambda { @source.prepare {} }.should_not raise_error
112
+ end
113
+
114
+ describe "call order" do
115
+ before :all do
116
+ @url = "http://domain.tld/benschwarz/feed"
117
+ FakeWeb.register_uri(@url, :response => File.join(SPEC_DIR, 'supports', 'flickr-photo.json'))
118
+
119
+ Smoke.data :feed_preperation_call_order do
120
+ prepare do
121
+ url "http://domain.tld/#{username}/feed"
122
+ end
123
+
124
+ path :photos, :photo
125
+ end
126
+ end
127
+
128
+ describe "before setting variables" do
129
+ it "should fail" do
130
+ lambda { Smoke[:feed_preperation_call_order].output }.should raise_error(NameError)
131
+ end
132
+ end
133
+
134
+ describe "setting abstract properties" do
135
+ it "should not respond to a property that hasn't been set" do
136
+ lambda { Smoke[:feed_preperation_call_order].abstract }.should raise_error(NoMethodError)
137
+ end
138
+
139
+ it "should allow setting a property" do
140
+ lambda { Smoke[:feed_preperation_call_order].abstract(:value) }.should_not raise_error(NoMethodError)
141
+ Smoke[:feed_preperation_call_order].abstract.should == :value
142
+ end
143
+
144
+ it "should chain the source when setting a property" do
145
+ Smoke[:feed_preperation_call_order].abstract(:value).should be_an_instance_of(Smoke::Source::Data)
146
+ end
147
+ end
148
+
149
+ describe "after setting variables" do
150
+ it "should output successfully" do
151
+ lambda { Smoke[:feed_preperation_call_order].username("benschwarz").output }.should_not raise_error
152
+ end
153
+
154
+ it "should have used the correct url" do
155
+ Smoke[:feed_preperation_call_order].request.uri.should == @url
156
+ end
157
+ end
158
+ end
104
159
  end
105
160
  end
@@ -12,7 +12,7 @@ describe "'Data' source" do
12
12
  end
13
13
 
14
14
  it "should have been activated" do
15
- Smoke[:photos].should(be_an_instance_of(Smoke::Source::Data::Data))
15
+ Smoke[:photos].should(be_an_instance_of(Smoke::Source::Data))
16
16
  end
17
17
 
18
18
  it "should be a list of things" do
@@ -16,7 +16,7 @@ describe "Feed" do
16
16
 
17
17
 
18
18
  it "should have been activated" do
19
- Smoke[:slashdot].should(be_an_instance_of(Smoke::Source::Feed::Feed))
19
+ Smoke[:slashdot].should(be_an_instance_of(Smoke::Source::Feed))
20
20
  end
21
21
 
22
22
  it "should be a list of things" do
@@ -19,7 +19,7 @@ describe "YQL" do
19
19
  end
20
20
 
21
21
  it "should have been activated" do
22
- Smoke[:search].should(be_an_instance_of(Smoke::Source::YQL::YQL))
22
+ Smoke[:search].should(be_an_instance_of(Smoke::Source::YQL))
23
23
  end
24
24
 
25
25
  it "should be a list of things" do
@@ -31,8 +31,18 @@ describe "YQL" do
31
31
  Smoke[:search].output
32
32
  end
33
33
 
34
- it "should hold the url used to query" do
35
- Smoke[:search].request.uri.should == "http://query.yahooapis.com/v1/public/yql?q=SELECT%20*%20FROM%20search.web%20WHERE%20query%20=%20'ruby'&format=json"
34
+ describe "url" do
35
+ it "should contain the base uri for yql" do
36
+ Smoke[:search].request.uri.should =~ /^http:\/\/query.yahooapis.com\/v1\/public\/yql?/
37
+ end
38
+
39
+ it "should be format=json" do
40
+ Smoke[:search].request.uri.should include("format=json")
41
+ end
42
+
43
+ it "should contain the query" do
44
+ Smoke[:search].request.uri.should include("SELECT%20*%20FROM%20search.web%20WHERE%20query%20=%20'ruby'")
45
+ end
36
46
  end
37
47
 
38
48
  it "should have renamed url to link" do
data/spec/smoke_spec.rb CHANGED
@@ -7,20 +7,6 @@ describe Smoke do
7
7
  @source_c = TestSource.source :c
8
8
  end
9
9
 
10
- describe "registration" do
11
- before do
12
- Smoke.register(Smoke::SecretSauce) # defined in supports/mayo.rb
13
- end
14
-
15
- it "should allow sources to register themselves" do
16
- Smoke.included_modules.should include(SecretSauce)
17
- end
18
-
19
- it "should have an instance method of 'mayo'" do
20
- Smoke.instance_methods.should include("mayo")
21
- end
22
- end
23
-
24
10
  describe "joining" do
25
11
  before do
26
12
  @joined = Smoke.join(:a, :b)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: benschwarz-smoke
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.3.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Schwarz
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-05-31 00:00:00 -07:00
12
+ date: 2009-06-08 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -47,7 +47,6 @@ files:
47
47
  - lib/core_ext
48
48
  - lib/core_ext/hash.rb
49
49
  - lib/smoke
50
- - lib/smoke/delayed_block.rb
51
50
  - lib/smoke/origin.rb
52
51
  - lib/smoke/request.rb
53
52
  - lib/smoke/source
@@ -1,9 +0,0 @@
1
- class DelayedBlock # :nodoc:
2
- def initialize(&block)
3
- @block = block
4
- end
5
-
6
- def execute!
7
- @block.call
8
- end
9
- end