benschwarz-smoke 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.markdown +65 -0
- data/VERSION.yml +4 -0
- data/lib/core_ext/hash.rb +20 -0
- data/lib/smoke/delayed_block.rb +9 -0
- data/lib/smoke/origin.rb +120 -0
- data/lib/smoke/request.rb +57 -0
- data/lib/smoke/source/data.rb +57 -0
- data/lib/smoke/source/feed.rb +32 -0
- data/lib/smoke/source/yql.rb +74 -0
- data/lib/smoke.rb +96 -0
- data/spec/core_ext/hash_spec.rb +25 -0
- data/spec/smoke/origin_spec.rb +84 -0
- data/spec/smoke/request_spec.rb +38 -0
- data/spec/smoke/source/data_spec.rb +41 -0
- data/spec/smoke/source/feed_spec.rb +42 -0
- data/spec/smoke/source/yql_spec.rb +43 -0
- data/spec/smoke_spec.rb +61 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/supports/flickr-photo.json +9 -0
- data/spec/supports/mayo.rb +5 -0
- data/spec/supports/search-web.yql +1 -0
- data/spec/supports/slashdot.xml +98 -0
- data/spec/supports/test_source.rb +12 -0
- metadata +105 -0
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,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
|
data/lib/smoke/origin.rb
ADDED
@@ -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
|
data/spec/smoke_spec.rb
ADDED
@@ -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
|
data/spec/spec_helper.rb
ADDED
@@ -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 @@
|
|
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><p><a href="http://feedads.googleadservices.com/~at/ugeT-w-dcRANVSAFjeOyE1xWxLI/a"><img src="http://feedads.googleadservices.com/~at/ugeT-w-dcRANVSAFjeOyE1xWxLI/i" border="0" ismap="true"></img></a></p><img src="http://feeds2.feedburner.com/~r/Slashdot/slashdot/to/~4/Z3_sEVEWN44" height="1" width="1"/></description><feedburner:origLink>http://news.slashdot.org/article.pl?sid=09/04/05/0412228&from=rss</feedburner:origLink></item>
|
20
|
+
|
21
|
+
<item>
|
22
|
+
<title>ARM &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><p><a href="http://feedads.googleadservices.com/~at/-1rxhXXhRQs2R45k9UK8rJWMt9Q/a"><img src="http://feedads.googleadservices.com/~at/-1rxhXXhRQs2R45k9UK8rJWMt9Q/i" border="0" ismap="true"></img></a></p><img src="http://feeds2.feedburner.com/~r/Slashdot/slashdot/to/~4/42oa4dK6FBQ" height="1" width="1"/></description><feedburner:origLink>http://hardware.slashdot.org/article.pl?sid=09/04/05/0029211&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><p><a href="http://feedads.googleadservices.com/~at/gaBNiJYktga6sDLVL-L_B8QcQSQ/a"><img src="http://feedads.googleadservices.com/~at/gaBNiJYktga6sDLVL-L_B8QcQSQ/i" border="0" ismap="true"></img></a></p><img src="http://feeds2.feedburner.com/~r/Slashdot/slashdot/to/~4/t2Uoe2155P4" height="1" width="1"/></description><feedburner:origLink>http://yro.slashdot.org/article.pl?sid=09/04/04/234208&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><p><a href="http://feedads.googleadservices.com/~at/XmLlMqiqRgwLFenKrFBWy9FX3dM/a"><img src="http://feedads.googleadservices.com/~at/XmLlMqiqRgwLFenKrFBWy9FX3dM/i" border="0" ismap="true"></img></a></p><img src="http://feeds2.feedburner.com/~r/Slashdot/slashdot/to/~4/S8Zom-mjrXI" height="1" width="1"/></description><feedburner:origLink>http://mobile.slashdot.org/article.pl?sid=09/04/04/228240&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><p><a href="http://feedads.googleadservices.com/~at/rb4-68iQKt_C2O8gqGhtzeQi_zg/a"><img src="http://feedads.googleadservices.com/~at/rb4-68iQKt_C2O8gqGhtzeQi_zg/i" border="0" ismap="true"></img></a></p><img src="http://feeds2.feedburner.com/~r/Slashdot/slashdot/to/~4/F5DuPJgp4mQ" height="1" width="1"/></description><feedburner:origLink>http://news.slashdot.org/article.pl?sid=09/04/04/1921236&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><p><a href="http://feedads.googleadservices.com/~at/LBMosQrByLSE1GaPWCTQFMMj89U/a"><img src="http://feedads.googleadservices.com/~at/LBMosQrByLSE1GaPWCTQFMMj89U/i" border="0" ismap="true"></img></a></p><img src="http://feeds2.feedburner.com/~r/Slashdot/slashdot/to/~4/x2UyC_yoJfI" height="1" width="1"/></description><feedburner:origLink>http://tech.slashdot.org/article.pl?sid=09/04/04/2013200&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><p><a href="http://feedads.googleadservices.com/~at/5UqLl0mY9ffxMo0vcTYa0IMOtkc/a"><img src="http://feedads.googleadservices.com/~at/5UqLl0mY9ffxMo0vcTYa0IMOtkc/i" border="0" ismap="true"></img></a></p><img src="http://feeds2.feedburner.com/~r/Slashdot/slashdot/to/~4/-GRV3zWRuiw" height="1" width="1"/></description><feedburner:origLink>http://developers.slashdot.org/article.pl?sid=09/04/04/1829239&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><p><a href="http://feedads.googleadservices.com/~at/02X9J3MmiaEwC1jWu-QnPzUCbb4/a"><img src="http://feedads.googleadservices.com/~at/02X9J3MmiaEwC1jWu-QnPzUCbb4/i" border="0" ismap="true"></img></a></p><img src="http://feeds2.feedburner.com/~r/Slashdot/slashdot/to/~4/jJCf0ijBdMk" height="1" width="1"/></description><feedburner:origLink>http://tech.slashdot.org/article.pl?sid=09/04/04/1717242&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><p><a href="http://feedads.googleadservices.com/~at/Nwh7dkc8KT3AMVpZ-iavrb__tYY/a"><img src="http://feedads.googleadservices.com/~at/Nwh7dkc8KT3AMVpZ-iavrb__tYY/i" border="0" ismap="true"></img></a></p><img src="http://feeds2.feedburner.com/~r/Slashdot/slashdot/to/~4/xgHoPPdzAYY" height="1" width="1"/></description><feedburner:origLink>http://hardware.slashdot.org/article.pl?sid=09/04/04/1638249&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><p><a href="http://feedads.googleadservices.com/~at/TF64ggWHYGbTBgOmofPtcLVvDak/a"><img src="http://feedads.googleadservices.com/~at/TF64ggWHYGbTBgOmofPtcLVvDak/i" border="0" ismap="true"></img></a></p><img src="http://feeds2.feedburner.com/~r/Slashdot/slashdot/to/~4/d2SEMDC96bI" height="1" width="1"/></description><feedburner:origLink>http://yro.slashdot.org/article.pl?sid=09/04/04/1515239&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><p><a href="http://feedads.googleadservices.com/~at/DNJ7S7mvI0w3P6BVeVSkGNeGoQs/a"><img src="http://feedads.googleadservices.com/~at/DNJ7S7mvI0w3P6BVeVSkGNeGoQs/i" border="0" ismap="true"></img></a></p><img src="http://feeds2.feedburner.com/~r/Slashdot/slashdot/to/~4/Oq13ubPpSTs" height="1" width="1"/></description><feedburner:origLink>http://ask.slashdot.org/article.pl?sid=09/04/04/1338228&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><p><a href="http://feedads.googleadservices.com/~at/Mz26LXLorHJ5MpbbhRA6lCCMwfU/a"><img src="http://feedads.googleadservices.com/~at/Mz26LXLorHJ5MpbbhRA6lCCMwfU/i" border="0" ismap="true"></img></a></p><img src="http://feeds2.feedburner.com/~r/Slashdot/slashdot/to/~4/a4G1EaJNg2s" height="1" width="1"/></description><feedburner:origLink>http://hardware.slashdot.org/article.pl?sid=09/04/04/1218214&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><p><a href="http://feedads.googleadservices.com/~at/3hNpVvBfXOSJginuGNBpL8zy7ds/a"><img src="http://feedads.googleadservices.com/~at/3hNpVvBfXOSJginuGNBpL8zy7ds/i" border="0" ismap="true"></img></a></p><img src="http://feeds2.feedburner.com/~r/Slashdot/slashdot/to/~4/gRePAK3sAgg" height="1" width="1"/></description><feedburner:origLink>http://science.slashdot.org/article.pl?sid=09/04/04/042214&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><p><a href="http://feedads.googleadservices.com/~at/3sEwn7Bdo_bLx-U2E5ukBxv7FLE/a"><img src="http://feedads.googleadservices.com/~at/3sEwn7Bdo_bLx-U2E5ukBxv7FLE/i" border="0" ismap="true"></img></a></p><img src="http://feeds2.feedburner.com/~r/Slashdot/slashdot/to/~4/v8S7f-QWjNM" height="1" width="1"/></description><feedburner:origLink>http://tech.slashdot.org/article.pl?sid=09/04/04/035224&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><p><a href="http://feedads.googleadservices.com/~at/3MFDbhb2EPCc5gsAPrcN3Afp8jM/a"><img src="http://feedads.googleadservices.com/~at/3MFDbhb2EPCc5gsAPrcN3Afp8jM/i" border="0" ismap="true"></img></a></p><img src="http://feeds2.feedburner.com/~r/Slashdot/slashdot/to/~4/zMkhhpLF0BM" height="1" width="1"/></description><feedburner:origLink>http://news.slashdot.org/article.pl?sid=09/04/04/028249&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
|
+
|