benschwarz-smoke 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Ben Schwarz
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.
data/README.markdown ADDED
@@ -0,0 +1,65 @@
1
+ # smoke
2
+
3
+ smoke is a Ruby based DSL that allows you to take data from YQL, RSS / Atom (and more, if you think of a useful source).
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
+ and return a plain old ruby object or json (You could add in something to output XML too)
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.
8
+
9
+ ## The concept
10
+
11
+ The concept comes from using [Yahoo Pipes](http://pipes.yahoo.com) to make little mash ups, get a list of tv shows for my torrent client, compile a recipe book or make tools to give me a list of albums that artists in my music library are about to be release.
12
+
13
+ ## How or what to contribute
14
+
15
+ * 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
+ * Add a way to output (XML, anyone?)
18
+ * Examples of queries you'd like to be able to do
19
+
20
+ ## API Examples
21
+ ### YQL
22
+ # This will use yahoo search to get an array of search results about Ruby
23
+ Smoke.yql(:ruby) do
24
+ select :all
25
+ from "search.web"
26
+ where :query, "ruby"
27
+
28
+ discard :title, /tuesday/i
29
+ end
30
+
31
+ Smoke.yql(:python) do
32
+ select :all
33
+ from "search.web"
34
+ where :query, "python"
35
+ end
36
+
37
+ ### Join sources and use them together
38
+ Smoke.join(:ruby, :python)
39
+
40
+ or even
41
+
42
+ Smoke.join(:python, :ruby) do
43
+ emit do
44
+ sort :title
45
+ rename :shit_name => :title
46
+ end
47
+ end
48
+
49
+ ### CI
50
+
51
+ Integrity [is running for smoke](http://integrity.ffolio.net/smoke)
52
+
53
+
54
+ ### TODO (working on, just mental notes)
55
+
56
+ * Look at moving object transformations into the origin class
57
+ * 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
+ * YQL w/oAuth
61
+ * YQL Subqueries?
62
+
63
+ ### Copyright
64
+
65
+ Copyright (c) 2009 Ben Schwarz. See LICENSE for details.
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 2
4
+ :patch: 3
@@ -0,0 +1,20 @@
1
+ class Hash # :nodoc:
2
+
3
+ # Thanks merb!
4
+ def symbolize_keys!
5
+ each do |k,v|
6
+ sym = k.respond_to?(:to_sym) ? k.to_sym : k
7
+ self[sym] = Hash === v ? v.symbolize_keys! : v
8
+ delete(k) unless k == sym
9
+ end
10
+ self
11
+ end
12
+
13
+ def rename(candidates)
14
+ candidates.each do |old_key, new_key|
15
+ self[new_key] = self.delete(old_key) if self.has_key?(old_key)
16
+ end
17
+
18
+ return self
19
+ end
20
+ end
@@ -0,0 +1,9 @@
1
+ class DelayedBlock # :nodoc:
2
+ def initialize(&block)
3
+ @block = block
4
+ end
5
+
6
+ def execute!
7
+ @block.call
8
+ end
9
+ end
@@ -0,0 +1,120 @@
1
+ module Smoke
2
+ class Origin
3
+ attr_reader :items, :name
4
+
5
+ def initialize(name, &block)
6
+ raise StandardError, "Sources must have a name" unless name
7
+ @name = name
8
+ @items = []
9
+ activate!
10
+ instance_eval(&block) if block_given?
11
+ dispatch if respond_to? :dispatch
12
+ end
13
+
14
+ # Transform each item
15
+ #
16
+ # Usage:
17
+ # emit do
18
+ # rename(:href => :link)
19
+ # end
20
+ def emit(&block)
21
+ (@transformations ||= []) << DelayedBlock.new(&block)
22
+ end
23
+
24
+ # Re-sort items by a particular key
25
+ def sort(key)
26
+ @items = @items.sort_by{|i| i[key] }
27
+ rescue NoMethodError => e
28
+ Smoke.log.info "You're trying to sort by \"#{key}\" but it does not exist in your item set"
29
+ ensure
30
+ return self
31
+ end
32
+
33
+ # Keep items that match the regex
34
+ #
35
+ # Usage (chained):
36
+ # Smoke[:ruby].keep(:title, /tuesday/i)
37
+ # Usage (block, during initialization):
38
+ # Smoke.yql(:ruby) do
39
+ # ...
40
+ # keep(:title, /tuesday/i)
41
+ # end
42
+ def keep(key, matcher)
43
+ @items.reject! {|i| (i[key] =~ matcher) ? false : true }
44
+ return self
45
+ end
46
+
47
+ # Discard items that do not match the regex
48
+ #
49
+ # Usage (chained):
50
+ # Smoke[:ruby].discard(:title, /tuesday/i)
51
+ # Usage (block, during initialization):
52
+ # Smoke.yql(:ruby) do
53
+ # ...
54
+ # discard(:title, /tuesday/i)
55
+ # end
56
+ def discard(key, matcher)
57
+ @items.reject! {|i| (i[key] =~ matcher) ? true : false }
58
+ return self
59
+ end
60
+
61
+ # Rename one or many keys at a time
62
+ # Suggested that you run it within an each block, but it makes no difference
63
+ # other than readability
64
+ #
65
+ # Usage
66
+ # # Renames all items with a key of href to link
67
+ # rename(:href => :link)
68
+ # or
69
+ # rename(:href => :link, :description => :excerpt)
70
+ def rename(*args)
71
+ @items.each {|item| item.rename(*args) }
72
+ return self
73
+ 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
+
93
+ # Truncate your result set to this many objects
94
+ #
95
+ # Usage
96
+ #
97
+ # truncate(3).output
98
+ # => [{title => "Canon"}, {:title => "Nikon"}, {:title => "Pentax"}]
99
+ def truncate(length)
100
+ @items = @items[0..(length - 1)]
101
+ return self
102
+ end
103
+
104
+ def items=(items)
105
+ @items = items.map{|i| i.symbolize_keys! }
106
+ invoke_transformations
107
+ end
108
+
109
+ private
110
+ def invoke_transformations
111
+ @transformations.each{|t| t.execute! } unless @transformations.nil?
112
+ end
113
+
114
+ def activate!
115
+ Smoke.activate(name, self)
116
+ end
117
+ end
118
+ end
119
+
120
+ Dir["#{File.dirname(__FILE__)}/source/*.rb"].each &method(:require)
@@ -0,0 +1,57 @@
1
+ module Smoke
2
+ class Request # :nodoc:
3
+ class Failure < Exception # :nodoc:
4
+ attr_reader :uri
5
+
6
+ def initialize(uri, msg)
7
+ @uri = URI.parse(uri)
8
+ Smoke.log.error "Smoke Request: Failed to get from #{@uri.host} via #{@uri.scheme}\n#{msg}"
9
+ end
10
+ end
11
+
12
+ SUPPORTED_TYPES = %w(json xml javascript)
13
+ attr_reader :uri, :content_type, :body, :type
14
+
15
+ def initialize(uri, *options)
16
+ @uri = uri
17
+ @options = options
18
+ dispatch
19
+ end
20
+
21
+ private
22
+ def dispatch
23
+ Thread.new {
24
+ open(@uri, "User-Agent" => "Ruby/#{RUBY_VERSION}/Smoke") do |request|
25
+ @content_type = request.content_type
26
+ @body = request.read
27
+ end
28
+ }.join
29
+
30
+ unless @options.include?(:raw_response)
31
+ present!
32
+ end
33
+ rescue OpenURI::HTTPError => e
34
+ Failure.new(@uri, e)
35
+ end
36
+
37
+ def present!
38
+ set_type
39
+ parse!
40
+ end
41
+
42
+ def set_type
43
+ @type = (SUPPORTED_TYPES.detect{|t| @content_type =~ /#{t}/ } || "unknown").to_sym
44
+ end
45
+
46
+ def parse!
47
+ case @type
48
+ when :json, :javascript
49
+ @body = ::Crack::JSON.parse(@body).symbolize_keys!
50
+ when :xml
51
+ @body = ::Crack::XML.parse(@body).symbolize_keys!
52
+ when :unknown
53
+ Smoke.log.warn "Smoke Request: Format unknown for #{@uri} (#{@content_type})"
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,57 @@
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)
8
+ end
9
+
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
+ # 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
+ protected
43
+ def dispatch
44
+ @request = Smoke::Request.new(@url)
45
+ self.items = (@path.nil?) ? @request.body : drill(@request.body, *@path)
46
+ 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
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,32 @@
1
+ module Smoke
2
+ module Source # :nodoc:
3
+ module Feed # :nodoc:
4
+ Smoke.register(Smoke::Source::Feed)
5
+
6
+ def feed(name, &block)
7
+ Feed.new(name, &block)
8
+ end
9
+
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
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,74 @@
1
+ module Smoke
2
+ module Source # :nodoc:
3
+ module YQL # :nodoc:
4
+ Smoke.register(Smoke::Source::YQL)
5
+
6
+ def yql(name, &block)
7
+ YQL.new(name, &block)
8
+ end
9
+
10
+ # YQL will call to Yahoo YQL services
11
+ #
12
+ # Usage:
13
+ # Smoke.yql(:ruby) do
14
+ # select :all
15
+ # from "search.web"
16
+ # where :query, "ruby"
17
+ # end
18
+ class YQL < Origin
19
+ attr_reader :request
20
+
21
+ # Select indicates what YQL will be selecting
22
+ # Usage:
23
+ # select :all
24
+ # => "SELECT *"
25
+ # select :title
26
+ # => "SELECT title"
27
+ # select :title, :description
28
+ # => "SELECT title, description"
29
+ def select(what)
30
+ @select = what.join(",") and return if what.is_a? Array
31
+ @select = "*" and return if what == :all
32
+ @select = what.to_s
33
+ end
34
+
35
+ # from corresponds to the from fragment of the YQL query
36
+ # Usage:
37
+ # from "search.web"
38
+ # or
39
+ # from :html
40
+ def from(source)
41
+ @from = source.join(',') and return if source.is_a? Array
42
+ @from = source.to_s
43
+ end
44
+
45
+ # where is a straight up match, no fancy matchers
46
+ # are currently supported
47
+ # Usage:
48
+ # where :xpath, "//div/div/a"
49
+ # or
50
+ # where :query, "python"
51
+ def where(column, value)
52
+ @where = @where || []
53
+ @where << "#{column.to_s} = '#{value}'"
54
+ end
55
+
56
+ protected
57
+ def dispatch
58
+ @request = Smoke::Request.new(build_uri)
59
+ self.items = @request.body[:query][:results][:result]
60
+ end
61
+
62
+ private
63
+
64
+ def build_uri
65
+ "http://query.yahooapis.com/v1/public/yql?q=#{build_query}&format=json"
66
+ end
67
+
68
+ def build_query
69
+ URI.encode("SELECT #{@select} FROM #{@from} WHERE #{@where.join(" AND ")}")
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
data/lib/smoke.rb ADDED
@@ -0,0 +1,96 @@
1
+ require 'rubygems'
2
+ require 'open-uri'
3
+ require 'logger'
4
+ require 'json'
5
+ require 'crack'
6
+ require 'simple-rss'
7
+
8
+
9
+ $:<< File.join(File.dirname(__FILE__))
10
+
11
+ # Core ext
12
+ require 'core_ext/hash.rb'
13
+
14
+ module Smoke
15
+ class << self
16
+
17
+ @@active_sources = {}
18
+ attr_reader :active_sources
19
+
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
+ # Access registered smoke source instances
31
+ #
32
+ # Usage:
33
+ #
34
+ # Smoke.yql(:ruby) do ....
35
+ #
36
+ # Smoke[:ruby]
37
+ # => #<Smoke::Source::YQL::YQL:0x18428d4...
38
+ def [](source)
39
+ active_sources[source]
40
+ end
41
+
42
+ def join(*sources, &block)
43
+ @items = get_sources(sources).map {|source| source[1].items }.flatten
44
+
45
+ joined_name = (sources << "joined").join("_").to_sym
46
+ source = Origin.new(joined_name, &block)
47
+
48
+ source.items = @items
49
+ return source
50
+ end
51
+
52
+ def activate(name, source)
53
+ if active_sources.has_key?(name)
54
+ Smoke.log.warn "Smoke source activation: Source with idential name already initialized"
55
+ end
56
+
57
+ active_sources.update({ name => source })
58
+ end
59
+
60
+ # Returns all activated smoke sources
61
+ def active_sources
62
+ @@active_sources
63
+ end
64
+
65
+ # Rename a source
66
+ def rename(candidates)
67
+ candidates.each do |o, n|
68
+ active_sources.rename(o => n)
69
+ return active_sources[n]
70
+ end
71
+ end
72
+
73
+ # Log for info, debug, error and warn with:
74
+ #
75
+ # Smoke.log.info "message"
76
+ # Smoke.log.debug "message"
77
+ # Smoke.log.error "message"
78
+ # Smoke.log.warn "message"
79
+ def log
80
+ @logger ||= Logger.new($stdout)
81
+ end
82
+
83
+ private
84
+ def get_sources(sources = [])
85
+ active_sources.find_all{|k, v| sources.include?(k) }
86
+ end
87
+ end
88
+ end
89
+
90
+ require 'smoke/request'
91
+ require 'smoke/origin'
92
+ require 'smoke/delayed_block'
93
+
94
+ class Object # :nodoc:
95
+ include Smoke
96
+ end
@@ -0,0 +1,25 @@
1
+ require File.join(File.dirname(__FILE__), "..", "spec_helper.rb")
2
+
3
+ describe Hash do
4
+ before :all do
5
+ @item = {:a => "a key", :c => "another key"}
6
+ end
7
+
8
+ describe "transformations" do
9
+ it "should be renamable" do
10
+ @item.should respond_to(:rename)
11
+ end
12
+
13
+ it "should have a transform object" do
14
+ @item.rename(:a => :b).should == {:b => "a key", :c => "another key"}
15
+ end
16
+
17
+ it "should rename many keys" do
18
+ @item.rename(:a => :b, :c => :d).should == {:b => "a key", :d => "another key"}
19
+ end
20
+
21
+ it "should symbolise string keys" do
22
+ {"a" => {"b" => "bee"}}.symbolize_keys!.should == {:a => {:b => "bee"}}
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,84 @@
1
+ require File.join(File.dirname(__FILE__), "..", "spec_helper.rb")
2
+
3
+ describe Smoke::Origin do
4
+ before :all do
5
+ @origin = TestSource.source(:test) do
6
+ emit do
7
+ rename(:head => :title)
8
+ sort(:title)
9
+ end
10
+ end
11
+ end
12
+
13
+ describe "after emit, object output" do
14
+ it "should not have a key of head" do
15
+ @origin.output.first.should_not have_key(:head)
16
+ end
17
+
18
+ it "should be ordered by title" do
19
+ @origin.output.first[:title].should == "Kangaroo"
20
+ end
21
+ end
22
+
23
+ describe "transformations" do
24
+ it "should rename properties" do
25
+ Smoke[:test].rename(:title => :header).output.first.should have_key(:header)
26
+ end
27
+
28
+ it "should sort by a given property" do
29
+ Smoke[:test].sort(:header).output.first[:header].should == "Kangaroo"
30
+ end
31
+
32
+ it "should truncate results given a length" do
33
+ Smoke[:test].truncate(1).output.size.should == 1
34
+ end
35
+
36
+ describe "filtering" do
37
+ before do
38
+ TestSource.source(:keep)
39
+ TestSource.source(:discard)
40
+ end
41
+
42
+ it "should keep items" do
43
+ Smoke[:keep].should(respond_to(:keep))
44
+ end
45
+
46
+ it "should only contain items that match" do
47
+ Smoke[:keep].keep(:head, /^K/).output.should == [{:head => "Kangaroo"}]
48
+ end
49
+
50
+ it "should discard items" do
51
+ Smoke[:discard].should(respond_to(:discard))
52
+ end
53
+
54
+ it "should not contain items that match" do
55
+ Smoke[:discard].discard(:head, /^K/).output.should == [{:head => "Platypus"}]
56
+ end
57
+ end
58
+ end
59
+
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:/
74
+ end
75
+
76
+ it "method chaining" do
77
+ @source = TestSource.source(:chain)
78
+ @source.rename(:head => :header).sort(:header).output.should == [{:header => "Kangaroo"}, {:header => "Platypus"}]
79
+ end
80
+
81
+ it "should softly error when attempting to sort on a key that doesn't exist" do
82
+ TestSource.source(:chain).sort(:header).should_not raise_error("NoMethodError")
83
+ end
84
+ end
@@ -0,0 +1,38 @@
1
+ require File.join(File.dirname(__FILE__), "..", "spec_helper.rb")
2
+
3
+ describe Smoke::Request do
4
+ before do
5
+ @url = "http://fake.tld/canned/"
6
+ @web_search = File.join(SPEC_DIR, 'supports', 'slashdot.xml')
7
+ FakeWeb.register_uri(@url, :file => @web_search)
8
+ @request = Smoke::Request.new(@url)
9
+ end
10
+
11
+ it "should return a Request object" do
12
+ @request.should be_an_instance_of(Smoke::Request)
13
+ end
14
+
15
+ it "should have a response body" do
16
+ @request.body.should == File.read(@web_search)
17
+ end
18
+
19
+ it "should have a content type" do
20
+ @request.content_type.should == 'application/octet-stream'
21
+ end
22
+
23
+ it "should be a pure ruby array response" do
24
+ # Temporary real request, fakeweb isn't allowing content_type setting as of writing
25
+ @request = Smoke::Request.new("http://query.yahooapis.com/v1/public/yql?q=SELECT%20*%20FROM%20search.web%20WHERE%20query%20=%20'ruby'&format=xml")
26
+ @request.body.should be_an_instance_of(Hash)
27
+ end
28
+
29
+ it "should be a raw string response" do
30
+ @request = Smoke::Request.new(@url, :raw_response)
31
+ @request.body.should be_an_instance_of(String)
32
+ end
33
+
34
+ describe "http redirects" do
35
+ it "should follow a redirect to a resource"
36
+ it "should follow only one html redirect before raising an error"
37
+ end
38
+ end
@@ -0,0 +1,41 @@
1
+ require File.join(File.dirname(__FILE__), "..", "..", "spec_helper.rb")
2
+
3
+ describe "'Data' source" do
4
+ before :all do
5
+ FakeWeb.register_uri("http://photos.tld/index.json", :response => File.join(SPEC_DIR, 'supports', 'flickr-photo.json'))
6
+
7
+ Smoke.data(:photos) do
8
+ url "http://photos.tld/index.json"
9
+
10
+ path :photos, :photo
11
+ end
12
+ end
13
+
14
+ it "should have been activated" do
15
+ Smoke[:photos].should(be_an_instance_of(Smoke::Source::Data::Data))
16
+ end
17
+
18
+ it "should be a list of things" do
19
+ Smoke[:photos].output.should be_an_instance_of(Array)
20
+ end
21
+
22
+ it "should respond to url" do
23
+ Smoke[:photos].should respond_to(:url)
24
+ end
25
+
26
+ it "should hold the url used to query" do
27
+ Smoke[:photos].request.uri.should == "http://photos.tld/index.json"
28
+ end
29
+
30
+ describe "results" do
31
+ it "should have two items" do
32
+ Smoke[:photos].output.size.should == 2
33
+ end
34
+
35
+ it "should be photo objects" do
36
+ keys = [:ispublic, :title, :farm, :id, :isfamily, :server, :isfriend, :owner, :secret].each do |key|
37
+ Smoke[:photos].output.first.should(have_key(key))
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,42 @@
1
+ require File.join(File.dirname(__FILE__), "..", "..", "spec_helper.rb")
2
+
3
+ describe "Feed" do
4
+ before :all do
5
+ FakeWeb.register_uri("http://slashdot.org/index.rdf", :file => File.join(SPEC_DIR, 'supports', 'slashdot.xml'))
6
+
7
+ Smoke.feed(:slashdot) do
8
+ url "http://slashdot.org/index.rdf"
9
+ url "http://slashdot.org/index.rdf"
10
+
11
+ emit do
12
+ rename(:link => :url)
13
+ end
14
+ end
15
+ end
16
+
17
+
18
+ it "should have been activated" do
19
+ Smoke[:slashdot].should(be_an_instance_of(Smoke::Source::Feed::Feed))
20
+ end
21
+
22
+ it "should be a list of things" do
23
+ Smoke[:slashdot].items.should be_an_instance_of(Array)
24
+ end
25
+
26
+ it "should respond to url" do
27
+ Smoke[:slashdot].should respond_to(:url)
28
+ end
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)
41
+ end
42
+ end
@@ -0,0 +1,43 @@
1
+ require File.join(File.dirname(__FILE__), "..", "..", "spec_helper.rb")
2
+
3
+ describe "YQL" do
4
+ before :all do
5
+ # Fake web does not yet support regex matched uris
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
11
+
12
+ Smoke.yql(:search) do
13
+ select :all
14
+ from "search.web"
15
+ where :query, "ruby"
16
+
17
+ emit do
18
+ rename(:url => :link)
19
+ end
20
+ end
21
+ end
22
+
23
+ it "should have been activated" do
24
+ Smoke[:search].should(be_an_instance_of(Smoke::Source::YQL::YQL))
25
+ end
26
+
27
+ it "should be a list of things" do
28
+ Smoke[:search].items.should be_an_instance_of(Array)
29
+ end
30
+
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)
38
+ end
39
+
40
+ it "should output a ruby object" do
41
+ Smoke[:search].output.should be_an_instance_of(Array)
42
+ end
43
+ end
@@ -0,0 +1,61 @@
1
+ require File.join(File.dirname(__FILE__), "spec_helper.rb")
2
+
3
+ describe Smoke do
4
+ before do
5
+ @source_a = TestSource.source :a
6
+ @source_b = TestSource.source :b
7
+ @source_c = TestSource.source :c
8
+ end
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
+ describe "joining" do
25
+ before do
26
+ @joined = Smoke.join(:a, :b)
27
+ end
28
+
29
+ it "should contain items from sources a and b" do
30
+ @joined.output.size.should == (@source_a.output.size + @source_b.output.size)
31
+ end
32
+
33
+ it "should accept a block" do
34
+ lambda { Smoke.join(:a, :b, Proc.new {}) }.should_not raise_error
35
+ end
36
+
37
+ it "should allow sorting" do
38
+ Smoke.join(:a, :b).should respond_to(:sort)
39
+ end
40
+
41
+ it "should allow renaming" do
42
+ Smoke.join(:a, :b).should respond_to(:rename)
43
+ end
44
+
45
+ it "should allow changes to output" do
46
+ Smoke.join(:a, :b).should respond_to(:output)
47
+ end
48
+ end
49
+
50
+ describe "active sources" do
51
+ it "should allow access to sources via an array accessor" do
52
+ Smoke[:a].should be_an_instance_of(Smoke::Origin)
53
+ end
54
+
55
+ it "should be able to be renamed" do
56
+ Smoke.rename(:a => :b)
57
+ Smoke[:a].should be_nil
58
+ Smoke[:b].should be_an_instance_of(Smoke::Origin)
59
+ end
60
+ end
61
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ -c --format specdoc
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ gem 'rspec'
3
+
4
+ require 'spec'
5
+ require 'fake_web'
6
+
7
+ $:<< File.join(File.dirname(__FILE__), '..', 'lib')
8
+
9
+
10
+ SPEC_DIR = File.dirname(__FILE__) unless defined? SPEC_DIR
11
+ $:<< SPEC_DIR
12
+
13
+ require 'smoke'
14
+
15
+ require 'supports/mayo.rb'
16
+ require 'supports/test_source.rb'
@@ -0,0 +1,9 @@
1
+ HTTP/1.1 200 OK
2
+ Date: Mon, 27 Apr 2009 08:04:23 GMT
3
+ P3P: policyref="http://p3p.yahoo.com/w3c/p3p.xml", CP="CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE GOV"
4
+ Vary: Accept-Encoding
5
+ Content-Length: 398
6
+ Connection: close
7
+ Content-Type: text/json; charset=utf-8
8
+
9
+ {"photos":{"page":1, "pages":1, "perpage":100, "total":"2", "photo":[{"id":"3443335843", "owner":"36821533@N00", "secret":"5a15f0bfb9", "server":"3305", "farm":4, "title":"How I roll", "ispublic":1, "isfriend":0, "isfamily":0}, {"id":"3345220961", "owner":"36821533@N00", "secret":"a1dd2b9eca", "server":"3581", "farm":4, "title":"My desk", "ispublic":1, "isfriend":0, "isfamily":0}]}, "stat":"ok"}
@@ -0,0 +1,5 @@
1
+ module Smoke::SecretSauce
2
+ def mayo
3
+ puts "some mayo makes even chicken managable"
4
+ end
5
+ end
@@ -0,0 +1 @@
1
+ {"query":{"count":"10","created":"2009-04-05T07:40:33Z","lang":"en-US","updated":"2009-04-05T07:40:33Z","uri":"http://query.yahooapis.com/v1/yql?q=SELECT+*+FROM+search.web+WHERE+query+%3D+%27ruby%27","diagnostics":{"publiclyCallable":"true","url":{"execution-time":"124","content":"http://boss.yahooapis.com/ysearch/web/v1/ruby?format=xml&start=0&count=10"},"user-time":"128","service-time":"124","build-version":"911"},"results":{"result":[{"abstract":"For the programming language, see <b>Ruby<\/b> (programming language) <b>...<\/b> The <b>ruby<\/b> is considered one of the four precious stones, together with the <b>...<\/b>","clickurl":"http://lrd.yahooapis.com/_ylc=X3oDMTQ4NnBodGRzBF9TAzIwMjMxNTI3MDIEYXBwaWQDb0pfTWdwbklrWW5CMWhTZnFUZEd5TkouTXNxZlNMQmkEY2xpZW50A2Jvc3MEc2VydmljZQNCT1NTBHNsawN0aXRsZQRzcmNwdmlkAzhRYkJoVWdlQXUwRVdpS3drZ0NONFJYdDBqSE1sRW5ZWUhFQUI4RFc-/SIG=116cnla88/**http%3A//en.wikipedia.org/wiki/Ruby","date":"2009/03/28","dispurl":"<b>en.wikipedia.org<\/b>/wiki/<b>Ruby<\/b>","size":"71069","title":"<b>Ruby<\/b> - Wikipedia, the free encyclopedia","url":"http://en.wikipedia.org/wiki/Ruby"},{"abstract":"<b>Ruby<\/b> originated in Japan during the mid-1990s and was initially developed and <b>...<\/b> <b>Ruby<\/b> supports multiple programming paradigms, including functional, object <b>...<\/b>","clickurl":"http://lrd.yahooapis.com/_ylc=X3oDMTQ4NnBodGRzBF9TAzIwMjMxNTI3MDIEYXBwaWQDb0pfTWdwbklrWW5CMWhTZnFUZEd5TkouTXNxZlNMQmkEY2xpZW50A2Jvc3MEc2VydmljZQNCT1NTBHNsawN0aXRsZQRzcmNwdmlkAzhRYkJoVWdlQXUwRVdpS3drZ0NONFJYdDBqSE1sRW5ZWUhFQUI4RFc-/SIG=11tsuk1oo/**http%3A//en.wikipedia.org/wiki/Ruby_(programming_language)","date":"2009/04/03","dispurl":"<b>en.wikipedia.org<\/b>/wiki/<wbr><b>Ruby<\/b>_(programming_language)","size":"127053","title":"<b>Ruby<\/b> (programming language) - Wikipedia, the free encyclopedia","url":"http://en.wikipedia.org/wiki/Ruby_(programming_language)"},{"abstract":null,"clickurl":"http://lrd.yahooapis.com/_ylc=X3oDMTQ4NnBodGRzBF9TAzIwMjMxNTI3MDIEYXBwaWQDb0pfTWdwbklrWW5CMWhTZnFUZEd5TkouTXNxZlNMQmkEY2xpZW50A2Jvc3MEc2VydmljZQNCT1NTBHNsawN0aXRsZQRzcmNwdmlkAzhRYkJoVWdlQXUwRVdpS3drZ0NONFJYdDBqSE1sRW5ZWUhFQUI4RFc-/SIG=110v0kjjt/**http%3A//www.ruby-lang.org/en","date":"2009/04/03","dispurl":"www.<b>ruby-lang.org<\/b>/en","size":"13876","title":"<b>Ruby<\/b> Programming Language","url":"http://www.ruby-lang.org/en"},{"abstract":"\"<b>Ruby<\/b> on Rails is a breakthrough in lowering the barriers of entry to programming. <b>...<\/b> \"Before <b>Ruby<\/b> on Rails, web programming required a lot of verbiage, steps <b>...<\/b>","clickurl":"http://lrd.yahooapis.com/_ylc=X3oDMTQ4NnBodGRzBF9TAzIwMjMxNTI3MDIEYXBwaWQDb0pfTWdwbklrWW5CMWhTZnFUZEd5TkouTXNxZlNMQmkEY2xpZW50A2Jvc3MEc2VydmljZQNCT1NTBHNsawN0aXRsZQRzcmNwdmlkAzhRYkJoVWdlQXUwRVdpS3drZ0NONFJYdDBqSE1sRW5ZWUhFQUI4RFc-/SIG=10sqpm4qa/**http%3A//rubyonrails.org/","date":"2009/04/03","dispurl":"<b>rubyonrails.org<\/b>","size":"11832","title":"<b>Ruby<\/b> on Rails","url":"http://rubyonrails.org/"},{"abstract":"<b>Ruby<\/b> is a very popular open-source language which lends itself especially well <b>...<\/b> Educational sites where you can learn how to program using <b>Ruby<\/b>. <b>...<\/b>","clickurl":"http://lrd.yahooapis.com/_ylc=X3oDMTQ4NnBodGRzBF9TAzIwMjMxNTI3MDIEYXBwaWQDb0pfTWdwbklrWW5CMWhTZnFUZEd5TkouTXNxZlNMQmkEY2xpZW50A2Jvc3MEc2VydmljZQNCT1NTBHNsawN0aXRsZQRzcmNwdmlkAzhRYkJoVWdlQXUwRVdpS3drZ0NONFJYdDBqSE1sRW5ZWUhFQUI4RFc-/SIG=115aqsp4l/**http%3A//developer.yahoo.com/ruby/","date":"2009/03/26","dispurl":"<b>developer.yahoo.com<\/b>/<b>ruby<\/b>","size":"22832","title":"<b>Ruby<\/b> Developer Center - Yahoo! Developer Network","url":"http://developer.yahoo.com/ruby/"},{"abstract":"<b>...<\/b> <b>Ruby<\/b> Developer Center, where you'll find all the information and tools you need <b>...<\/b> The <b>Ruby<\/b>-on-Rails community offers a mailing list, wiki, IRC, bug <b>...<\/b>","clickurl":"http://lrd.yahooapis.com/_ylc=X3oDMTQ4NnBodGRzBF9TAzIwMjMxNTI3MDIEYXBwaWQDb0pfTWdwbklrWW5CMWhTZnFUZEd5TkouTXNxZlNMQmkEY2xpZW50A2Jvc3MEc2VydmljZQNCT1NTBHNsawN0aXRsZQRzcmNwdmlkAzhRYkJoVWdlQXUwRVdpS3drZ0NONFJYdDBqSE1sRW5ZWUhFQUI4RFc-/SIG=114fkbio4/**http%3A//developers.sun.com/ruby/","date":"2009/03/30","dispurl":"<b>developers.sun.com<\/b>/<b>ruby<\/b>","size":"23266","title":"<b>Ruby<\/b> Developer Center - Sun Developer Network (SDN)","url":"http://developers.sun.com/ruby/"},{"abstract":"<b>Ruby<\/b> Annotation. W3C Recommendation 31 May 2001 (Markup errors corrected 25 <b>...<\/b> \"<b>Ruby<\/b>\" are short runs of text alongside the base text, typically used in East <b>...<\/b>","clickurl":"http://lrd.yahooapis.com/_ylc=X3oDMTQ4NnBodGRzBF9TAzIwMjMxNTI3MDIEYXBwaWQDb0pfTWdwbklrWW5CMWhTZnFUZEd5TkouTXNxZlNMQmkEY2xpZW50A2Jvc3MEc2VydmljZQNCT1NTBHNsawN0aXRsZQRzcmNwdmlkAzhRYkJoVWdlQXUwRVdpS3drZ0NONFJYdDBqSE1sRW5ZWUhFQUI4RFc-/SIG=10udggnjn/**http%3A//www.w3.org/TR/ruby","date":"2009/03/17","dispurl":"www.<b>w3.org<\/b>/TR/<b>ruby<\/b>","size":"74715","title":"<b>Ruby<\/b> Annotation","url":"http://www.w3.org/TR/ruby"},{"abstract":"<b>ruby<\/b> n. , pl. -bies . A deep red, translucent variety of the mineral corundum, highly valued as a precious stone <b>...<\/b> red of fine-quality <b>ruby<\/b> is the result of <b>...<\/b>","clickurl":"http://lrd.yahooapis.com/_ylc=X3oDMTQ4NnBodGRzBF9TAzIwMjMxNTI3MDIEYXBwaWQDb0pfTWdwbklrWW5CMWhTZnFUZEd5TkouTXNxZlNMQmkEY2xpZW50A2Jvc3MEc2VydmljZQNCT1NTBHNsawN0aXRsZQRzcmNwdmlkAzhRYkJoVWdlQXUwRVdpS3drZ0NONFJYdDBqSE1sRW5ZWUhFQUI4RFc-/SIG=116gndgit/**http%3A//www.answers.com/topic/ruby","date":"2009/03/27","dispurl":"www.<b>answers.com<\/b>/topic/<b>ruby<\/b>","size":"121670","title":"<b>ruby<\/b>: Definition from Answers.com","url":"http://www.answers.com/topic/ruby"},{"abstract":"<b>Ruby<\/b> resource: One-click Windows installer; online copy of Programming <b>Ruby<\/b>: The <b>...<\/b> is a 501(c)(3) tax-exempt organization, dedicated to <b>Ruby<\/b> support and advocacy. <b>...<\/b>","clickurl":"http://lrd.yahooapis.com/_ylc=X3oDMTQ4NnBodGRzBF9TAzIwMjMxNTI3MDIEYXBwaWQDb0pfTWdwbklrWW5CMWhTZnFUZEd5TkouTXNxZlNMQmkEY2xpZW50A2Jvc3MEc2VydmljZQNCT1NTBHNsawN0aXRsZQRzcmNwdmlkAzhRYkJoVWdlQXUwRVdpS3drZ0NONFJYdDBqSE1sRW5ZWUhFQUI4RFc-/SIG=110s1u4un/**http%3A//www.rubycentral.com/","date":"2009/03/30","dispurl":"www.<b>rubycentral.com<\/b>","size":"2615","title":"<b>Ruby<\/b> Central","url":"http://www.rubycentral.com/"},{"abstract":"And red is also the colour of the <b>ruby<\/b>, the king of the gemstones. <b>...<\/b> For a long time India was regarded as the <b>ruby's<\/b> classical country of origin. <b>...<\/b>","clickurl":"http://lrd.yahooapis.com/_ylc=X3oDMTQ4NnBodGRzBF9TAzIwMjMxNTI3MDIEYXBwaWQDb0pfTWdwbklrWW5CMWhTZnFUZEd5TkouTXNxZlNMQmkEY2xpZW50A2Jvc3MEc2VydmljZQNCT1NTBHNsawN0aXRsZQRzcmNwdmlkAzhRYkJoVWdlQXUwRVdpS3drZ0NONFJYdDBqSE1sRW5ZWUhFQUI4RFc-/SIG=11pea0sti/**http%3A//www.gemstone.org/gem-by-gem/english/ruby.html","date":"2008/09/23","dispurl":"www.<b>gemstone.org<\/b>/gem-by-gem/<wbr>english/<b>ruby<\/b>.html","size":"52995","title":"<b>Ruby<\/b> - english","url":"http://www.gemstone.org/gem-by-gem/english/ruby.html"}]}}}
@@ -0,0 +1,98 @@
1
+ <?xml version="1.0" encoding="ISO-8859-1"?>
2
+ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://my.netscape.com/rdf/simple/0.9/" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0">
3
+
4
+ <channel>
5
+ <title>Slashdot</title>
6
+ <link>http://slashdot.org/</link>
7
+ <description>News for nerds, stuff that matters</description>
8
+ </channel>
9
+
10
+ <image>
11
+ <title>Slashdot</title>
12
+ <url>http://s.fsdn.com/sd/topics/topicslashdot.gif</url>
13
+ <link>http://slashdot.org/</link>
14
+ </image>
15
+
16
+ <item>
17
+ <title>North Korea Launches "Communication Satellite" Rocket</title>
18
+ <link>http://rss.slashdot.org/~r/Slashdot/slashdot/to/~3/Z3_sEVEWN44/article.pl</link>
19
+ <description>&lt;p&gt;&lt;a href="http://feedads.googleadservices.com/~at/ugeT-w-dcRANVSAFjeOyE1xWxLI/a"&gt;&lt;img src="http://feedads.googleadservices.com/~at/ugeT-w-dcRANVSAFjeOyE1xWxLI/i" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/Slashdot/slashdot/to/~4/Z3_sEVEWN44" height="1" width="1"/&gt;</description><feedburner:origLink>http://news.slashdot.org/article.pl?sid=09/04/05/0412228&amp;from=rss</feedburner:origLink></item>
20
+
21
+ <item>
22
+ <title>ARM &amp;mdash; Heretic In the Church of Intel, Moore's Law</title>
23
+ <link>http://rss.slashdot.org/~r/Slashdot/slashdot/to/~3/42oa4dK6FBQ/article.pl</link>
24
+ <description>&lt;p&gt;&lt;a href="http://feedads.googleadservices.com/~at/-1rxhXXhRQs2R45k9UK8rJWMt9Q/a"&gt;&lt;img src="http://feedads.googleadservices.com/~at/-1rxhXXhRQs2R45k9UK8rJWMt9Q/i" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/Slashdot/slashdot/to/~4/42oa4dK6FBQ" height="1" width="1"/&gt;</description><feedburner:origLink>http://hardware.slashdot.org/article.pl?sid=09/04/05/0029211&amp;from=rss</feedburner:origLink></item>
25
+
26
+ <item>
27
+ <title>Chrome EULA Reserves the Right To Filter Your Web</title>
28
+ <link>http://rss.slashdot.org/~r/Slashdot/slashdot/to/~3/t2Uoe2155P4/article.pl</link>
29
+ <description>&lt;p&gt;&lt;a href="http://feedads.googleadservices.com/~at/gaBNiJYktga6sDLVL-L_B8QcQSQ/a"&gt;&lt;img src="http://feedads.googleadservices.com/~at/gaBNiJYktga6sDLVL-L_B8QcQSQ/i" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/Slashdot/slashdot/to/~4/t2Uoe2155P4" height="1" width="1"/&gt;</description><feedburner:origLink>http://yro.slashdot.org/article.pl?sid=09/04/04/234208&amp;from=rss</feedburner:origLink></item>
30
+
31
+ <item>
32
+ <title>No More OpenMoko Phone</title>
33
+ <link>http://rss.slashdot.org/~r/Slashdot/slashdot/to/~3/S8Zom-mjrXI/article.pl</link>
34
+ <description>&lt;p&gt;&lt;a href="http://feedads.googleadservices.com/~at/XmLlMqiqRgwLFenKrFBWy9FX3dM/a"&gt;&lt;img src="http://feedads.googleadservices.com/~at/XmLlMqiqRgwLFenKrFBWy9FX3dM/i" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/Slashdot/slashdot/to/~4/S8Zom-mjrXI" height="1" width="1"/&gt;</description><feedburner:origLink>http://mobile.slashdot.org/article.pl?sid=09/04/04/228240&amp;from=rss</feedburner:origLink></item>
35
+
36
+ <item>
37
+ <title>Appeals Court Rules Against Google On Keyword Ads</title>
38
+ <link>http://rss.slashdot.org/~r/Slashdot/slashdot/to/~3/F5DuPJgp4mQ/article.pl</link>
39
+ <description>&lt;p&gt;&lt;a href="http://feedads.googleadservices.com/~at/rb4-68iQKt_C2O8gqGhtzeQi_zg/a"&gt;&lt;img src="http://feedads.googleadservices.com/~at/rb4-68iQKt_C2O8gqGhtzeQi_zg/i" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/Slashdot/slashdot/to/~4/F5DuPJgp4mQ" height="1" width="1"/&gt;</description><feedburner:origLink>http://news.slashdot.org/article.pl?sid=09/04/04/1921236&amp;from=rss</feedburner:origLink></item>
40
+
41
+ <item>
42
+ <title>Data Center Raid About Unpaid Telco Fees</title>
43
+ <link>http://rss.slashdot.org/~r/Slashdot/slashdot/to/~3/x2UyC_yoJfI/article.pl</link>
44
+ <description>&lt;p&gt;&lt;a href="http://feedads.googleadservices.com/~at/LBMosQrByLSE1GaPWCTQFMMj89U/a"&gt;&lt;img src="http://feedads.googleadservices.com/~at/LBMosQrByLSE1GaPWCTQFMMj89U/i" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/Slashdot/slashdot/to/~4/x2UyC_yoJfI" height="1" width="1"/&gt;</description><feedburner:origLink>http://tech.slashdot.org/article.pl?sid=09/04/04/2013200&amp;from=rss</feedburner:origLink></item>
45
+
46
+ <item>
47
+ <title>Coders, Your Days Are Numbered</title>
48
+ <link>http://rss.slashdot.org/~r/Slashdot/slashdot/to/~3/-GRV3zWRuiw/article.pl</link>
49
+ <description>&lt;p&gt;&lt;a href="http://feedads.googleadservices.com/~at/5UqLl0mY9ffxMo0vcTYa0IMOtkc/a"&gt;&lt;img src="http://feedads.googleadservices.com/~at/5UqLl0mY9ffxMo0vcTYa0IMOtkc/i" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/Slashdot/slashdot/to/~4/-GRV3zWRuiw" height="1" width="1"/&gt;</description><feedburner:origLink>http://developers.slashdot.org/article.pl?sid=09/04/04/1829239&amp;from=rss</feedburner:origLink></item>
50
+
51
+ <item>
52
+ <title>Windows 95 Almost Autodetected Floppy Disks</title>
53
+ <link>http://rss.slashdot.org/~r/Slashdot/slashdot/to/~3/jJCf0ijBdMk/article.pl</link>
54
+ <description>&lt;p&gt;&lt;a href="http://feedads.googleadservices.com/~at/02X9J3MmiaEwC1jWu-QnPzUCbb4/a"&gt;&lt;img src="http://feedads.googleadservices.com/~at/02X9J3MmiaEwC1jWu-QnPzUCbb4/i" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/Slashdot/slashdot/to/~4/jJCf0ijBdMk" height="1" width="1"/&gt;</description><feedburner:origLink>http://tech.slashdot.org/article.pl?sid=09/04/04/1717242&amp;from=rss</feedburner:origLink></item>
55
+
56
+ <item>
57
+ <title>Three Mile Island Memories</title>
58
+ <link>http://rss.slashdot.org/~r/Slashdot/slashdot/to/~3/xgHoPPdzAYY/article.pl</link>
59
+ <description>&lt;p&gt;&lt;a href="http://feedads.googleadservices.com/~at/Nwh7dkc8KT3AMVpZ-iavrb__tYY/a"&gt;&lt;img src="http://feedads.googleadservices.com/~at/Nwh7dkc8KT3AMVpZ-iavrb__tYY/i" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/Slashdot/slashdot/to/~4/xgHoPPdzAYY" height="1" width="1"/&gt;</description><feedburner:origLink>http://hardware.slashdot.org/article.pl?sid=09/04/04/1638249&amp;from=rss</feedburner:origLink></item>
60
+
61
+ <item>
62
+ <title>Phoenix Police Seize PCs of a Blogger Critical of the Department</title>
63
+ <link>http://rss.slashdot.org/~r/Slashdot/slashdot/to/~3/d2SEMDC96bI/article.pl</link>
64
+ <description>&lt;p&gt;&lt;a href="http://feedads.googleadservices.com/~at/TF64ggWHYGbTBgOmofPtcLVvDak/a"&gt;&lt;img src="http://feedads.googleadservices.com/~at/TF64ggWHYGbTBgOmofPtcLVvDak/i" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/Slashdot/slashdot/to/~4/d2SEMDC96bI" height="1" width="1"/&gt;</description><feedburner:origLink>http://yro.slashdot.org/article.pl?sid=09/04/04/1515239&amp;from=rss</feedburner:origLink></item>
65
+
66
+ <item>
67
+ <title>How Do I Put an Invention Into the Public Domain?</title>
68
+ <link>http://rss.slashdot.org/~r/Slashdot/slashdot/to/~3/Oq13ubPpSTs/article.pl</link>
69
+ <description>&lt;p&gt;&lt;a href="http://feedads.googleadservices.com/~at/DNJ7S7mvI0w3P6BVeVSkGNeGoQs/a"&gt;&lt;img src="http://feedads.googleadservices.com/~at/DNJ7S7mvI0w3P6BVeVSkGNeGoQs/i" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/Slashdot/slashdot/to/~4/Oq13ubPpSTs" height="1" width="1"/&gt;</description><feedburner:origLink>http://ask.slashdot.org/article.pl?sid=09/04/04/1338228&amp;from=rss</feedburner:origLink></item>
70
+
71
+ <item>
72
+ <title>Engineering Students Build Robotic Foosball Players</title>
73
+ <link>http://rss.slashdot.org/~r/Slashdot/slashdot/to/~3/a4G1EaJNg2s/article.pl</link>
74
+ <description>&lt;p&gt;&lt;a href="http://feedads.googleadservices.com/~at/Mz26LXLorHJ5MpbbhRA6lCCMwfU/a"&gt;&lt;img src="http://feedads.googleadservices.com/~at/Mz26LXLorHJ5MpbbhRA6lCCMwfU/i" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/Slashdot/slashdot/to/~4/a4G1EaJNg2s" height="1" width="1"/&gt;</description><feedburner:origLink>http://hardware.slashdot.org/article.pl?sid=09/04/04/1218214&amp;from=rss</feedburner:origLink></item>
75
+
76
+ <item>
77
+ <title>Believing In Medical Treatments That Don't Work</title>
78
+ <link>http://rss.slashdot.org/~r/Slashdot/slashdot/to/~3/gRePAK3sAgg/article.pl</link>
79
+ <description>&lt;p&gt;&lt;a href="http://feedads.googleadservices.com/~at/3hNpVvBfXOSJginuGNBpL8zy7ds/a"&gt;&lt;img src="http://feedads.googleadservices.com/~at/3hNpVvBfXOSJginuGNBpL8zy7ds/i" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/Slashdot/slashdot/to/~4/gRePAK3sAgg" height="1" width="1"/&gt;</description><feedburner:origLink>http://science.slashdot.org/article.pl?sid=09/04/04/042214&amp;from=rss</feedburner:origLink></item>
80
+
81
+ <item>
82
+ <title>Larrabee ISA Revealed</title>
83
+ <link>http://rss.slashdot.org/~r/Slashdot/slashdot/to/~3/v8S7f-QWjNM/article.pl</link>
84
+ <description>&lt;p&gt;&lt;a href="http://feedads.googleadservices.com/~at/3sEwn7Bdo_bLx-U2E5ukBxv7FLE/a"&gt;&lt;img src="http://feedads.googleadservices.com/~at/3sEwn7Bdo_bLx-U2E5ukBxv7FLE/i" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/Slashdot/slashdot/to/~4/v8S7f-QWjNM" height="1" width="1"/&gt;</description><feedburner:origLink>http://tech.slashdot.org/article.pl?sid=09/04/04/035224&amp;from=rss</feedburner:origLink></item>
85
+
86
+ <item>
87
+ <title>Large Ice Shelf Expected To Break From Antarctica</title>
88
+ <link>http://rss.slashdot.org/~r/Slashdot/slashdot/to/~3/zMkhhpLF0BM/article.pl</link>
89
+ <description>&lt;p&gt;&lt;a href="http://feedads.googleadservices.com/~at/3MFDbhb2EPCc5gsAPrcN3Afp8jM/a"&gt;&lt;img src="http://feedads.googleadservices.com/~at/3MFDbhb2EPCc5gsAPrcN3Afp8jM/i" border="0" ismap="true"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://feeds2.feedburner.com/~r/Slashdot/slashdot/to/~4/zMkhhpLF0BM" height="1" width="1"/&gt;</description><feedburner:origLink>http://news.slashdot.org/article.pl?sid=09/04/04/028249&amp;from=rss</feedburner:origLink></item>
90
+
91
+ <textinput>
92
+ <title>Search Slashdot</title>
93
+ <description>Search Slashdot stories</description>
94
+ <name>query</name>
95
+ <link>http://slashdot.org/search.pl</link>
96
+ </textinput>
97
+
98
+ </rdf:RDF>
@@ -0,0 +1,12 @@
1
+ module TestSource
2
+ def self.source(name, &block)
3
+ source = Smoke::Origin.allocate
4
+ source.stub!(:dispatch)
5
+ source.send(:initialize, name, &block || Proc.new {})
6
+ source.items = [
7
+ {:head => "Platypus"},
8
+ {:head => "Kangaroo"}
9
+ ]
10
+ return source
11
+ end
12
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: benschwarz-smoke
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.3
5
+ platform: ruby
6
+ authors:
7
+ - Ben Schwarz
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-29 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: simple-rss
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - "="
22
+ - !ruby/object:Gem::Version
23
+ version: "1.2"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: crack
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.1.1
34
+ version:
35
+ description:
36
+ email: ben.schwarz@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README.markdown
43
+ - LICENSE
44
+ files:
45
+ - README.markdown
46
+ - VERSION.yml
47
+ - lib/core_ext
48
+ - lib/core_ext/hash.rb
49
+ - lib/smoke
50
+ - lib/smoke/delayed_block.rb
51
+ - lib/smoke/origin.rb
52
+ - lib/smoke/request.rb
53
+ - lib/smoke/source
54
+ - lib/smoke/source/data.rb
55
+ - lib/smoke/source/feed.rb
56
+ - lib/smoke/source/yql.rb
57
+ - lib/smoke.rb
58
+ - spec/core_ext
59
+ - spec/core_ext/hash_spec.rb
60
+ - spec/smoke
61
+ - spec/smoke/origin_spec.rb
62
+ - spec/smoke/request_spec.rb
63
+ - spec/smoke/source
64
+ - spec/smoke/source/data_spec.rb
65
+ - spec/smoke/source/feed_spec.rb
66
+ - spec/smoke/source/yql_spec.rb
67
+ - spec/smoke_spec.rb
68
+ - spec/spec.opts
69
+ - spec/spec_helper.rb
70
+ - spec/supports
71
+ - spec/supports/flickr-photo.json
72
+ - spec/supports/mayo.rb
73
+ - spec/supports/search-web.yql
74
+ - spec/supports/slashdot.xml
75
+ - spec/supports/test_source.rb
76
+ - LICENSE
77
+ has_rdoc: true
78
+ homepage: http://github.com/benschwarz/smoke
79
+ post_install_message:
80
+ rdoc_options:
81
+ - --inline-source
82
+ - --charset=UTF-8
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: "0"
90
+ version:
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: "0"
96
+ version:
97
+ requirements: []
98
+
99
+ rubyforge_project:
100
+ rubygems_version: 1.2.0
101
+ signing_key:
102
+ specification_version: 3
103
+ summary: smoke is a DSL that allows you to take data from YQL, RSS / Atom
104
+ test_files: []
105
+