benschwarz-smoke 0.2.3 → 0.2.4
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.
- data/README.markdown +16 -6
- data/VERSION.yml +1 -1
- data/lib/smoke/origin.rb +62 -29
- data/lib/smoke/request.rb +1 -1
- data/lib/smoke/source/data.rb +0 -14
- data/lib/smoke/source/yql.rb +24 -5
- data/lib/smoke.rb +1 -1
- data/spec/smoke/origin_spec.rb +37 -16
- data/spec/smoke/source/feed_spec.rb +17 -11
- data/spec/smoke/source/yql_spec.rb +45 -13
- data/spec/spec.opts +1 -1
- data/spec/supports/test_source.rb +1 -3
- metadata +2 -2
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
|
-
|
|
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
|
-
*
|
|
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
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
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
136
|
+
private
|
|
137
|
+
def invoke_transformation
|
|
138
|
+
@transformation.each{|t| t.execute! } unless @transformation.nil?
|
|
107
139
|
end
|
|
108
140
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
data/lib/smoke/source/data.rb
CHANGED
|
@@ -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
|
data/lib/smoke/source/yql.rb
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
88
|
+
"SELECT #{@select} FROM #{@from} WHERE #{@where.join(" AND ")}"
|
|
70
89
|
end
|
|
71
90
|
end
|
|
72
91
|
end
|
data/lib/smoke.rb
CHANGED
data/spec/smoke/origin_spec.rb
CHANGED
|
@@ -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 ==
|
|
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 ==
|
|
59
|
+
Smoke[:discard].discard(:head, /^K/).output.should == {:head => "Platypus"}
|
|
56
60
|
end
|
|
57
61
|
end
|
|
58
62
|
end
|
|
59
63
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
41
|
-
|
|
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
|
|
1
|
+
-c
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
module TestSource
|
|
2
2
|
def self.source(name, &block)
|
|
3
|
-
source = Smoke::Origin.
|
|
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.
|
|
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-
|
|
12
|
+
date: 2009-05-31 00:00:00 -07:00
|
|
13
13
|
default_executable:
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|