benschwarz-smoke 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -4,7 +4,11 @@ smoke is a Ruby based DSL that allows you to take data from YQL, RSS / Atom (and
4
4
  This "data" can then be re-represented, sorted and filtered. You can collect data from a multiude of sources, sort them on a common property
5
5
  and return a plain old ruby object or json (You could add in something to output XML too)
6
6
 
7
- In an early attempt to get feedback I created [this screencast](http://vimeo.com/4272804). I will do another once the library becomes closer to 1.0.0.
7
+ ## Media
8
+
9
+ * [Presentation from Melbourne #roro](http://www.slideshare.net/benschwarz/smoke-1371124)
10
+ * Early [screencast](http://vimeo.com/4272804) to get developer / peer feedback
11
+
8
12
 
9
13
  ## The concept
10
14
 
@@ -13,9 +17,8 @@ The concept comes from using [Yahoo Pipes](http://pipes.yahoo.com) to make littl
13
17
  ## How or what to contribute
14
18
 
15
19
  * Test everything you do
16
- * Add a source (I was thinking a source that took simply took a url and parsed whatever it got)
17
20
  * Add a way to output (XML, anyone?)
18
- * Examples of queries you'd like to be able to do
21
+ * Examples of queries you'd like to be able to do (email / github message them to me)
19
22
 
20
23
  ## API Examples
21
24
  ### YQL
@@ -53,13 +56,20 @@ Integrity [is running for smoke](http://integrity.ffolio.net/smoke)
53
56
 
54
57
  ### TODO (working on, just mental notes)
55
58
 
56
- * Look at moving object transformations into the origin class
59
+ * Clean up / simplify source activation process
60
+ * Consider variable injection (eg, allow to query sending a username within some aspect of the request)
61
+ * Implement a disaptch / request method that will actually thread property (event-machine)
62
+ * Checkout experimental fakeweb version to stub out the yql specs
63
+
64
+ #### Later / maybe
57
65
  * Allow for sources to explicitly set the content type being returned for those stupid content providers
58
- * Consider invokation methods (registering of sources, namespacing et al)
59
- * YQL Definitions
60
66
  * YQL w/oAuth
61
67
  * YQL Subqueries?
62
68
 
69
+ #### For wiki pages (docs, later)
70
+ * How to use `path`
71
+ * YQL Definitions
72
+
63
73
  ### Copyright
64
74
 
65
75
  Copyright (c) 2009 Ben Schwarz. See LICENSE for details.
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :major: 0
3
3
  :minor: 2
4
- :patch: 3
4
+ :patch: 4
data/lib/smoke/origin.rb CHANGED
@@ -5,10 +5,60 @@ 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 = []
8
+ @items, @transformation = [], []
9
+
9
10
  activate!
10
11
  instance_eval(&block) if block_given?
12
+ end
13
+
14
+ # Output your items in a range of formats (:ruby, :json and :yaml currently)
15
+ # Ruby is the default format and will automagically yielded from your source
16
+ #
17
+ # Usage
18
+ #
19
+ # output(:json)
20
+ # => "[{title: \"Ray\"}, {title: \"Peace\"}]"
21
+ def output(type = :ruby)
11
22
  dispatch if respond_to? :dispatch
23
+ output = (@items.length == 1) ? @items.first : @items
24
+
25
+ case type
26
+ when :ruby
27
+ return output
28
+ when :json
29
+ return ::JSON.generate(output)
30
+ when :yaml
31
+ return YAML.dump(output)
32
+ end
33
+ end
34
+
35
+ def items=(items) # :nodoc:
36
+ @items = items.flatten.map{|i| i.symbolize_keys! }
37
+ invoke_transformation
38
+ end
39
+
40
+ # Path allows you to traverse the tree of a the items returned to
41
+ # only give you access to what you're interested in.
42
+ #
43
+ # Usage:
44
+ # path :down, :to, :the, :data
45
+ #
46
+ # Will traverse through a tree as follows:
47
+ #
48
+ # {
49
+ # :down => {
50
+ # :to => {
51
+ # :the => {
52
+ # :data => []
53
+ # }
54
+ # }
55
+ # }
56
+ # }
57
+ #
58
+ # You will need to help smoke find an array of
59
+ # items that you're interested in.
60
+ def path(*path)
61
+ @path = path
12
62
  end
13
63
 
14
64
  # Transform each item
@@ -18,7 +68,7 @@ module Smoke
18
68
  # rename(:href => :link)
19
69
  # end
20
70
  def emit(&block)
21
- (@transformations ||= []) << DelayedBlock.new(&block)
71
+ @transformation << DelayedBlock.new(&block)
22
72
  end
23
73
 
24
74
  # Re-sort items by a particular key
@@ -43,7 +93,7 @@ module Smoke
43
93
  @items.reject! {|i| (i[key] =~ matcher) ? false : true }
44
94
  return self
45
95
  end
46
-
96
+
47
97
  # Discard items that do not match the regex
48
98
  #
49
99
  # Usage (chained):
@@ -57,7 +107,7 @@ module Smoke
57
107
  @items.reject! {|i| (i[key] =~ matcher) ? true : false }
58
108
  return self
59
109
  end
60
-
110
+
61
111
  # Rename one or many keys at a time
62
112
  # Suggested that you run it within an each block, but it makes no difference
63
113
  # other than readability
@@ -71,25 +121,7 @@ module Smoke
71
121
  @items.each {|item| item.rename(*args) }
72
122
  return self
73
123
  end
74
-
75
- # Output your items in a range of formats (:ruby, :json and :yaml currently)
76
- # Ruby is the default format and will automagically yielded from your source
77
- #
78
- # Usage
79
- #
80
- # output(:json)
81
- # => "[{title: \"Ray\"}, {title: \"Peace\"}]"
82
- def output(type = :ruby)
83
- case type
84
- when :ruby
85
- return @items
86
- when :json
87
- return ::JSON.generate(@items)
88
- when :yaml
89
- return YAML.dump(@items)
90
- end
91
- end
92
-
124
+
93
125
  # Truncate your result set to this many objects
94
126
  #
95
127
  # Usage
@@ -101,14 +133,15 @@ module Smoke
101
133
  return self
102
134
  end
103
135
 
104
- def items=(items)
105
- @items = items.map{|i| i.symbolize_keys! }
106
- invoke_transformations
136
+ private
137
+ def invoke_transformation
138
+ @transformation.each{|t| t.execute! } unless @transformation.nil?
107
139
  end
108
140
 
109
- private
110
- def invoke_transformations
111
- @transformations.each{|t| t.execute! } unless @transformations.nil?
141
+ def drill(*path)
142
+ path.inject(nil) do |obj, pointer|
143
+ obj = obj.nil? ? pointer : obj[pointer]
144
+ end
112
145
  end
113
146
 
114
147
  def activate!
data/lib/smoke/request.rb CHANGED
@@ -5,7 +5,7 @@ module Smoke
5
5
 
6
6
  def initialize(uri, msg)
7
7
  @uri = URI.parse(uri)
8
- Smoke.log.error "Smoke Request: Failed to get from #{@uri.host} via #{@uri.scheme}\n#{msg}"
8
+ Smoke.log.error "Smoke Request: Failed to get from #{@uri} (#{msg})"
9
9
  end
10
10
  end
11
11
 
@@ -32,25 +32,11 @@ module Smoke
32
32
  @url = source_url
33
33
  end
34
34
 
35
- # Path allows you to traverse the tree of a the items returned to
36
- # only give you access to what you're interested in. In the case
37
- # of the comments in this document I traverse to the photos returned.
38
- def path(*path)
39
- @path = path
40
- end
41
-
42
35
  protected
43
36
  def dispatch
44
37
  @request = Smoke::Request.new(@url)
45
38
  self.items = (@path.nil?) ? @request.body : drill(@request.body, *@path)
46
39
  end
47
-
48
- private
49
- def drill(*path)
50
- path.inject(nil) do |obj, pointer|
51
- obj = obj.nil? ? pointer : obj[pointer]
52
- end
53
- end
54
40
  end
55
41
  end
56
42
  end
@@ -16,6 +16,7 @@ module Smoke
16
16
  # where :query, "ruby"
17
17
  # end
18
18
  class YQL < Origin
19
+ API_BASE = "http://query.yahooapis.com/v1/public/yql"
19
20
  attr_reader :request
20
21
 
21
22
  # Select indicates what YQL will be selecting
@@ -26,7 +27,7 @@ module Smoke
26
27
  # => "SELECT title"
27
28
  # select :title, :description
28
29
  # => "SELECT title, description"
29
- def select(what)
30
+ def select(what = :all)
30
31
  @select = what.join(",") and return if what.is_a? Array
31
32
  @select = "*" and return if what == :all
32
33
  @select = what.to_s
@@ -53,20 +54,38 @@ module Smoke
53
54
  @where << "#{column.to_s} = '#{value}'"
54
55
  end
55
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
+
56
66
  protected
67
+ def params
68
+ @params || @params = {}
69
+ end
70
+
57
71
  def dispatch
58
72
  @request = Smoke::Request.new(build_uri)
59
- self.items = @request.body[:query][:results][:result]
73
+ self.items = [(@path.nil?) ? @request.body : drill(@request.body, *@path)]
60
74
  end
61
75
 
62
76
  private
63
-
64
77
  def build_uri
65
- "http://query.yahooapis.com/v1/public/yql?q=#{build_query}&format=json"
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("&"))
66
85
  end
67
86
 
68
87
  def build_query
69
- URI.encode("SELECT #{@select} FROM #{@from} WHERE #{@where.join(" AND ")}")
88
+ "SELECT #{@select} FROM #{@from} WHERE #{@where.join(" AND ")}"
70
89
  end
71
90
  end
72
91
  end
data/lib/smoke.rb CHANGED
@@ -12,7 +12,7 @@ $:<< File.join(File.dirname(__FILE__))
12
12
  require 'core_ext/hash.rb'
13
13
 
14
14
  module Smoke
15
- class << self
15
+ class << self
16
16
 
17
17
  @@active_sources = {}
18
18
  attr_reader :active_sources
@@ -18,6 +18,10 @@ describe Smoke::Origin do
18
18
  it "should be ordered by title" do
19
19
  @origin.output.first[:title].should == "Kangaroo"
20
20
  end
21
+
22
+ it "should output a single hash rather than a hash in an array when there is one item" do
23
+ Smoke[:test].truncate(1).output.should == {:title => "Kangaroo"}
24
+ end
21
25
  end
22
26
 
23
27
  describe "transformations" do
@@ -44,7 +48,7 @@ describe Smoke::Origin do
44
48
  end
45
49
 
46
50
  it "should only contain items that match" do
47
- Smoke[:keep].keep(:head, /^K/).output.should == [{:head => "Kangaroo"}]
51
+ Smoke[:keep].keep(:head, /^K/).output.should == {:head => "Kangaroo"}
48
52
  end
49
53
 
50
54
  it "should discard items" do
@@ -52,25 +56,36 @@ describe Smoke::Origin do
52
56
  end
53
57
 
54
58
  it "should not contain items that match" do
55
- Smoke[:discard].discard(:head, /^K/).output.should == [{:head => "Platypus"}]
59
+ Smoke[:discard].discard(:head, /^K/).output.should == {:head => "Platypus"}
56
60
  end
57
61
  end
58
62
  end
59
63
 
60
- it "should output" do
61
- @origin.output.should be_an_instance_of(Array)
62
- end
63
-
64
- it "should output two items" do
65
- @origin.output.size.should == 2
66
- end
67
-
68
- it "should output json" do
69
- @origin.output(:json).should =~ /^\[\{/
70
- end
71
-
72
- it "should output yml" do
73
- @origin.output(:yaml).should =~ /--- \n- :title:/
64
+ describe "output" do
65
+ it "should output" do
66
+ @origin.output.should be_an_instance_of(Array)
67
+ end
68
+
69
+ it "should output two items" do
70
+ @origin.output.size.should == 2
71
+ end
72
+
73
+ it "should output json" do
74
+ @origin.output(:json).should =~ /^\[\{/
75
+ end
76
+
77
+ it "should output yml" do
78
+ @origin.output(:yaml).should =~ /--- \n- :title:/
79
+ end
80
+
81
+ it "should dispatch when output is called" do
82
+ TestSource.source(:no_dispatch)
83
+ Smoke[:no_dispatch].should_not_receive(:dispatch)
84
+
85
+ TestSource.source(:dispatch)
86
+ Smoke[:dispatch].should_receive(:dispatch)
87
+ Smoke[:dispatch].output
88
+ end
74
89
  end
75
90
 
76
91
  it "method chaining" do
@@ -81,4 +96,10 @@ describe Smoke::Origin do
81
96
  it "should softly error when attempting to sort on a key that doesn't exist" do
82
97
  TestSource.source(:chain).sort(:header).should_not raise_error("NoMethodError")
83
98
  end
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"
104
+ end
84
105
  end
@@ -27,16 +27,22 @@ describe "Feed" do
27
27
  Smoke[:slashdot].should respond_to(:url)
28
28
  end
29
29
 
30
- it "should accept multiple urls" do
31
- Smoke[:slashdot].requests.should be_an_instance_of(Array)
32
- end
33
-
34
- it "should hold the url used to query" do
35
- Smoke[:slashdot].requests.collect{|r| r.uri }.should include("http://slashdot.org/index.rdf")
36
- end
37
-
38
- it "should have renamed url to link" do
39
- Smoke[:slashdot].items.first.should have_key(:url)
40
- Smoke[:slashdot].items.first.should_not have_key(:link)
30
+ describe "after dispatch / query" do
31
+ before do
32
+ Smoke[:slashdot].output
33
+ end
34
+
35
+ it "should accept multiple urls" do
36
+ Smoke[:slashdot].requests.should be_an_instance_of(Array)
37
+ end
38
+
39
+ it "should hold the url used to query" do
40
+ Smoke[:slashdot].requests.collect{|r| r.uri }.should include("http://slashdot.org/index.rdf")
41
+ end
42
+
43
+ it "should have renamed url to link" do
44
+ Smoke[:slashdot].output.first.should have_key(:url)
45
+ Smoke[:slashdot].output.first.should_not have_key(:link)
46
+ end
41
47
  end
42
48
  end
@@ -4,15 +4,13 @@ describe "YQL" do
4
4
  before :all do
5
5
  # Fake web does not yet support regex matched uris
6
6
 
7
- #FakeWeb.register_uri("query.yahooapis.com/*") do |response|
8
- # response.body = File.read(File.join(SPEC_DIR, 'supports', 'search-web.yql'))
9
- # response.content_type "text/json"
10
- #end
7
+ FakeWeb.register_uri("query.yahooapis.com/*", :response => File.join(SPEC_DIR, 'supports', 'search-web.yql'))
11
8
 
12
9
  Smoke.yql(:search) do
13
10
  select :all
14
11
  from "search.web"
15
12
  where :query, "ruby"
13
+ path :query, :results, :result
16
14
 
17
15
  emit do
18
16
  rename(:url => :link)
@@ -28,16 +26,50 @@ describe "YQL" do
28
26
  Smoke[:search].items.should be_an_instance_of(Array)
29
27
  end
30
28
 
31
- it "should hold the url used to query" do
32
- 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"
33
- end
34
-
35
- it "should have renamed url to link" do
36
- Smoke[:search].items.first.should have_key(:link)
37
- Smoke[:search].items.first.should_not have_key(:href)
29
+ describe "after dispatch" do
30
+ before do
31
+ Smoke[:search].output
32
+ end
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"
36
+ end
37
+
38
+ it "should have renamed url to link" do
39
+ Smoke[:search].output.first.should have_key(:link)
40
+ Smoke[:search].output.first.should_not have_key(:url)
41
+ end
42
+
43
+ it "should output a ruby object" do
44
+ Smoke[:search].output.should be_an_instance_of(Array)
45
+ end
38
46
  end
39
47
 
40
- it "should output a ruby object" do
41
- Smoke[:search].output.should be_an_instance_of(Array)
48
+ describe "yql definitions" do
49
+ before do
50
+ Smoke.yql(:smoke) do
51
+ use "http://datatables.org/alltables.env"
52
+
53
+ select :all
54
+ from "github.repo"
55
+ where :id, "benschwarz"
56
+ where :repo, "smoke"
57
+ path :query, :results
58
+ end
59
+
60
+ Smoke[:smoke].output # Force execution
61
+ end
62
+
63
+ it "should be a respository" do
64
+ Smoke[:smoke].output.should have_key(:repository)
65
+ end
66
+
67
+ it "should respond to use" do
68
+ Smoke[:smoke].should respond_to(:use)
69
+ end
70
+
71
+ it "should contain 'env' within the query string" do
72
+ Smoke[:smoke].request.uri.should =~ /env=/
73
+ end
42
74
  end
43
75
  end
data/spec/spec.opts CHANGED
@@ -1 +1 @@
1
- -c --format specdoc
1
+ -c
@@ -1,8 +1,6 @@
1
1
  module TestSource
2
2
  def self.source(name, &block)
3
- source = Smoke::Origin.allocate
4
- source.stub!(:dispatch)
5
- source.send(:initialize, name, &block || Proc.new {})
3
+ source = Smoke::Origin.new(name, &block || Proc.new {})
6
4
  source.items = [
7
5
  {:head => "Platypus"},
8
6
  {:head => "Kangaroo"}
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.3
4
+ version: 0.2.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-04-29 00:00:00 -07:00
12
+ date: 2009-05-31 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency