smoke 0.5.17 → 0.5.19

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/README.markdown +5 -5
  2. data/Rakefile +1 -0
  3. data/VERSION.yml +2 -2
  4. data/lib/core_ext/array.rb +12 -0
  5. data/lib/smoke.rb +24 -13
  6. data/lib/smoke/cache.rb +4 -4
  7. data/lib/smoke/origin.rb +6 -11
  8. data/lib/smoke/request.rb +5 -14
  9. data/lib/smoke/transformer.rb +7 -0
  10. data/lib/smoke/transformers/json.rb +15 -0
  11. data/lib/smoke/transformers/ruby.rb +11 -0
  12. data/lib/smoke/{output → transformers}/xml.rb +12 -6
  13. data/lib/smoke/transformers/yaml.rb +15 -0
  14. data/spec/smoke/origin_spec.rb +4 -0
  15. data/spec/smoke/request_spec.rb +7 -0
  16. data/spec/smoke/{output → transformers}/xml_spec.rb +4 -6
  17. data/spec/smoke/transformers_spec.rb +35 -0
  18. data/spec/smoke_spec.rb +18 -10
  19. data/spec/supports/twitter_timeline.json +22 -0
  20. metadata +23 -30
  21. data/rdoc/classes/Smoke.html +0 -260
  22. data/rdoc/classes/Smoke/Origin.html +0 -340
  23. data/rdoc/classes/Smoke/Source/Data.html +0 -126
  24. data/rdoc/classes/Smoke/Source/Feed.html +0 -117
  25. data/rdoc/classes/Smoke/Source/YQL.html +0 -223
  26. data/rdoc/created.rid +0 -1
  27. data/rdoc/files/README_markdown.html +0 -180
  28. data/rdoc/files/lib/core_ext/hash_rb.html +0 -49
  29. data/rdoc/files/lib/smoke/origin_rb.html +0 -49
  30. data/rdoc/files/lib/smoke/request_rb.html +0 -49
  31. data/rdoc/files/lib/smoke/source/data_rb.html +0 -49
  32. data/rdoc/files/lib/smoke/source/feed_rb.html +0 -49
  33. data/rdoc/files/lib/smoke/source/join_rb.html +0 -49
  34. data/rdoc/files/lib/smoke/source/yql_rb.html +0 -49
  35. data/rdoc/files/lib/smoke_rb.html +0 -65
  36. data/rdoc/fr_class_index.html +0 -21
  37. data/rdoc/fr_file_index.html +0 -28
  38. data/rdoc/fr_method_index.html +0 -4459
  39. data/rdoc/index.html +0 -15
  40. data/rdoc/rdoc-style.css +0 -319
  41. data/spec/smoke/input/xls_spec.rb +0 -15
  42. data/spec/smoke/shared_spec.rb +0 -182
  43. data/spec/supports/gov_act_toliets.xls +0 -0
@@ -14,6 +14,7 @@ Then you can output as a plain ruby object or one of your other favourites (JSON
14
14
 
15
15
  ## Media
16
16
 
17
+ * [Presentation from Webjam11 in Perth](http://www.slideshare.net/benschwarz/how-to-reinterpret-the-web-in-180-seconds)
17
18
  * [Presentation from Melbourne #roro](http://www.slideshare.net/benschwarz/smoke-1371124)
18
19
  * Early [screencast](http://vimeo.com/4272804) to get developer / peer feedback
19
20
 
@@ -77,11 +78,10 @@ Execution:
77
78
 
78
79
 
79
80
  ### TODO (working on, just mental notes)
80
- #### Later / maybe
81
- * YQL w/oAuth
82
- * YQL Subqueries?
83
- * Implement basic auth for sources
84
-
81
+ * Items returned from smoke to be Hashie "rich" objects
82
+ * Output as a web feed (atom, rss)
83
+ * How to push through values like "author"?
84
+ * Passing modified time headers through to the atom formatter
85
85
 
86
86
  #### For wiki pages (docs, later)
87
87
  * Document all sources with their irrespective differential methods
data/Rakefile CHANGED
@@ -18,6 +18,7 @@ begin
18
18
  gem.add_dependency("moneta", "0.6.0")
19
19
  gem.add_dependency("rest-client", "1.0.3")
20
20
  gem.add_dependency("nokogiri", "1.3.2")
21
+ gem.add_dependency("registry", ">=0.1.2")
21
22
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
22
23
  end
23
24
  rescue LoadError
@@ -1,5 +1,5 @@
1
1
  ---
2
- :patch: 17
2
+ :minor: 5
3
+ :patch: 19
3
4
  :major: 0
4
5
  :build:
5
- :minor: 5
@@ -0,0 +1,12 @@
1
+ class Array # :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
+ end
@@ -1,5 +1,6 @@
1
1
  require 'logger'
2
2
  require 'digest/md5'
3
+ require 'yaml'
3
4
 
4
5
  require 'simple-rss'
5
6
  require 'json'
@@ -7,8 +8,11 @@ require 'crack'
7
8
  require 'moneta'
8
9
  require 'restclient'
9
10
  require 'nokogiri'
11
+ require 'registry'
10
12
 
11
- module Smoke
13
+ module Smoke
14
+ class NotRegistered < StandardError; end
15
+
12
16
  class << self
13
17
  @@active_sources = {}
14
18
  @@config = {
@@ -22,6 +26,15 @@ module Smoke
22
26
  }
23
27
  }
24
28
 
29
+ def root
30
+ File.join(File.dirname(__FILE__))
31
+ end
32
+
33
+ def version
34
+ @@version ||= YAML::load(File.read("#{root}/../VERSION.YML"))
35
+ "#{@@version[:major]}.#{@@version[:minor]}.#{@@version[:patch]}"
36
+ end
37
+
25
38
  # Access registered smoke source instances
26
39
  #
27
40
  # Define your source:
@@ -39,6 +52,8 @@ module Smoke
39
52
  # Usage:
40
53
  # Smoke.twitter(:username => "benschwarz")
41
54
  def method_missing(sym, args = {})
55
+ raise NotRegistered, "Smoke source not registered" if self[sym].nil?
56
+
42
57
  args.each_pair {|k, v| self[sym].send(k, v) }
43
58
  self[sym]
44
59
  end
@@ -47,8 +62,8 @@ module Smoke
47
62
  # Source instances are stored within the
48
63
  # @@active_sources class variable for later use
49
64
  def activate(name, source)
50
- if active_sources.has_key?(name)
51
- Smoke.log.warn "Smoke source activation: Source with idential name already initialized"
65
+ if active_sources.key?(name)
66
+ Smoke.log.warn "Smoke source activation: Source with identical name already initialized"
52
67
  end
53
68
  active_sources.update({ name => source })
54
69
  end
@@ -115,15 +130,11 @@ module Smoke
115
130
  # end
116
131
  def join(*names, &block); Smoke::Join.new(names, &block); end
117
132
  end
133
+
134
+ autoload :YQL, "smoke/source/yql"
135
+ autoload :Data, "smoke/source/data"
136
+ autoload :Feed, "smoke/source/feed"
137
+ autoload :Join, "smoke/source/join"
118
138
  end
119
139
 
120
- %w(core_ext/hash core_ext/string smoke/cache smoke/request smoke/origin smoke/output/xml).each {|r| require File.join(File.dirname(__FILE__), r)}
121
-
122
- # Autoload the source classes
123
- %w(YQL Data Feed Join).each do |r|
124
- Smoke.autoload(r.to_sym, File.join(File.dirname(__FILE__), "smoke", "source", r.downcase))
125
- end
126
-
127
- class Object # :nodoc:
128
- include Smoke
129
- end
140
+ Dir["#{File.dirname(__FILE__)}/{core_ext,smoke,smoke/transformers}/*.rb"].each {|r| require r}
@@ -39,8 +39,8 @@ module Smoke
39
39
 
40
40
  def query(uri, options)
41
41
  request = RestClient.get(uri, options)
42
- write(uri, request, request.headers[:content_type]) if enabled?
43
- {:body => request, :content_type => request.headers[:content_type]}
42
+ write(uri, request, request.headers) if enabled?
43
+ {:body => request, :headers => request.headers}
44
44
  end
45
45
 
46
46
  def read(uri)
@@ -48,8 +48,8 @@ module Smoke
48
48
  return cache[key]
49
49
  end
50
50
 
51
- def write(uri, body, content_type)
52
- store = {:body => body, :content_type => content_type}
51
+ def write(uri, body, headers)
52
+ store = {:body => body, :headers => headers}
53
53
  self.cache.store(generate_key(uri), store, :expire_in => Smoke.config[:cache][:expiry])
54
54
  end
55
55
 
@@ -1,5 +1,7 @@
1
1
  module Smoke
2
2
  class Origin
3
+ class UnavailableFormat < StandardError; end
4
+
3
5
  attr_reader :items, :requirements, :exposed
4
6
  attr_accessor :name
5
7
 
@@ -13,7 +15,7 @@ module Smoke
13
15
  instance_eval(&block) if block_given?
14
16
  end
15
17
 
16
- # Output your items in a range of formats (:ruby, :json and :yaml currently)
18
+ # Output your items in a range of formats (:ruby, :json, :xml and :yaml currently)
17
19
  # Ruby is the default format and will automagically yielded from your source
18
20
  #
19
21
  # Usage
@@ -24,16 +26,9 @@ module Smoke
24
26
  prepare!
25
27
  dispatch if respond_to? :dispatch
26
28
 
27
- case type
28
- when :json
29
- return ::JSON.generate(@items)
30
- when :yaml
31
- return YAML.dump(@items)
32
- when :xml
33
- return Smoke::Output::XML.generate(@name, @items)
34
- else
35
- return @items
36
- end
29
+ Transformer.for(type).generate(name, items)
30
+ rescue Registry::NotRegistered => e
31
+ raise UnavailableFormat, e.message
37
32
  end
38
33
 
39
34
  def items=(response) # :nodoc:
@@ -9,7 +9,7 @@ module Smoke
9
9
  end
10
10
  end
11
11
 
12
- SUPPORTED_TYPES = %w(json xml javascript excel)
12
+ SUPPORTED_TYPES = %w(json xml javascript)
13
13
  @@request_options = {
14
14
  :user_agent => Smoke.config[:user_agent],
15
15
  :accept_encoding => "gzip, deflate"
@@ -30,7 +30,7 @@ module Smoke
30
30
  def dispatch
31
31
  get = Smoke::Cache.fetch @uri, @@request_options
32
32
  @body = get[:body]
33
- @content_type = get[:content_type]
33
+ @content_type = get[:headers][:content_type]
34
34
 
35
35
  parse! unless @options[:raw_response]
36
36
 
@@ -39,18 +39,9 @@ module Smoke
39
39
  end
40
40
 
41
41
  def parse!
42
- case type
43
- when :json, :javascript
44
- @body = ::Crack::JSON.parse(@body).symbolize_keys!
45
- when :xml
46
- @body = ::Crack::XML.parse(@body).symbolize_keys!
47
- when :excel
48
- # Convert the excel document into an XML document
49
- doc = @body
50
- ::Crack::XML.parse().symbolize_keys!
51
- when :unknown
52
- Smoke.log.warn "Smoke Request: Format unknown for #{@uri} (#{@content_type})"
53
- end
42
+ @body = Transformer.for(type).parse(body).symbolize_keys!
43
+ rescue Registry::NotRegistered
44
+ Smoke.log.warn "Smoke Request: Format unknown for #{@uri} (#{@content_type})"
54
45
  end
55
46
  end
56
47
  end
@@ -0,0 +1,7 @@
1
+ module Smoke
2
+ # A class that will either transfer objects from Ruby to another format
3
+ # or it will parse objects into Ruby. (JSON, XML & Yaml)
4
+ class Transformer
5
+ extend Registry
6
+ end
7
+ end
@@ -0,0 +1,15 @@
1
+ module Smoke
2
+ module Transformers
3
+ class Json < Transformer
4
+ identifier :json
5
+
6
+ def self.generate(tree_name, objects)
7
+ ::JSON.generate(objects)
8
+ end
9
+
10
+ def self.parse(string)
11
+ ::Crack::JSON.parse(string)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module Smoke
2
+ module Transformers
3
+ class Ruby < Transformer
4
+ identifier :ruby
5
+
6
+ def self.generate(tree_name, objects)
7
+ objects
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,23 +1,29 @@
1
1
  module Smoke
2
- module Output
3
- class XML
2
+ module Transformers
3
+ class XML < Transformer
4
+ identifier :xml
5
+
4
6
  def self.generate(tree_name, items)
5
7
  builder = Nokogiri::XML::Builder.new do |xml|
6
8
  xml.items {
7
9
  items.each do |item|
8
10
  xml.item {
9
- %w(id type class).each{|m| item["#{m}_".to_sym] = item.delete(m.to_sym) }
10
-
11
+ %w(id type class fork).each{|m| item["#{m}_".to_sym] = item.delete(m.to_sym) }
12
+
11
13
  item.each do |k, v|
12
- xml.send(k, v)
14
+ xml.__send__(k, v)
13
15
  end
14
16
  }
15
17
  end
16
18
  }
17
19
  end
18
-
20
+
19
21
  builder.to_xml
20
22
  end
23
+
24
+ def self.parse(string)
25
+ ::Crack::XML.parse(string)
26
+ end
21
27
  end
22
28
  end
23
29
  end
@@ -0,0 +1,15 @@
1
+ module Smoke
2
+ module Transformers
3
+ class Yaml < Transformer
4
+ identifier :yml, :yaml
5
+
6
+ def self.generate(tree_name, objects)
7
+ YAML.dump(objects)
8
+ end
9
+
10
+ def self.parse(string)
11
+ YAML.load(string)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -49,6 +49,10 @@ describe Smoke::Origin do
49
49
  Smoke.test.output(:xml).should include "<?xml version=\"1.0\"?>"
50
50
  end
51
51
 
52
+ it "should raise a UnavailableFormat error" do
53
+ lambda { Smoke.test.output(:xyz) }.should raise_error(Smoke::Origin::UnavailableFormat)
54
+ end
55
+
52
56
  describe "filtering" do
53
57
  before :all do
54
58
  TestSource.source(:keep) do
@@ -35,6 +35,13 @@ describe Smoke::Request do
35
35
  end
36
36
  end
37
37
 
38
+ it "should handle direct array responses" do
39
+ url = "http://fake.tld/canned/twitter.json"
40
+ FakeWeb.register_uri(:get, url, :response => File.join(SPEC_DIR, 'supports', 'twitter_timeline.json'))
41
+
42
+ lambda { Smoke::Request.new(url) }.should_not raise_error(NoMethodError)
43
+ end
44
+
38
45
  describe "format returns" do
39
46
  it "should have a content type of :manual" do
40
47
  request = Smoke::Request.new(@url, :type => :manual)
@@ -1,18 +1,18 @@
1
1
  require File.join(File.dirname(__FILE__), "..", "..", "spec_helper.rb")
2
2
 
3
- describe Smoke::Output::XML do
3
+ describe Smoke::Transformers::XML do
4
4
  before do
5
5
  @tree = :tree
6
6
  @items = [
7
7
  {:id => 1, :class => "first", :type => "mammal", :animal => "monkey"},
8
8
  {:id => 2, :class => "second", :type => "mammal", :animal => "elephant"}
9
9
  ]
10
- @xml = Smoke::Output::XML.generate(@tree, @items)
10
+ @xml = Smoke::Transformers::XML.generate(@tree, @items)
11
11
  @document = Nokogiri::XML(@xml)
12
12
  end
13
13
 
14
14
  it "should respond to generate" do
15
- Smoke::Output::XML.should respond_to(:generate)
15
+ Smoke::Transformers::XML.should respond_to(:generate)
16
16
  end
17
17
 
18
18
  it "should start the tree off with a named key" do
@@ -20,9 +20,7 @@ describe Smoke::Output::XML do
20
20
  end
21
21
 
22
22
  it "should contain items" do
23
- @document.css("items").each do |item|
24
- item.content.should =~ /monkey/
25
- end
23
+ @document.css("item").size.should == 2
26
24
  end
27
25
  end
28
26
 
@@ -0,0 +1,35 @@
1
+ require "#{File.dirname(__FILE__)}/../spec_helper"
2
+
3
+ describe Smoke::Transformer do
4
+ describe "parsers" do
5
+ it "should respond to parse" do
6
+ Smoke::Transformers::Json.should respond_to(:parse)
7
+ end
8
+
9
+ it "should respond to parse" do
10
+ Smoke::Transformers::XML.should respond_to(:parse)
11
+ end
12
+
13
+ it "should respond to parse" do
14
+ Smoke::Transformers::Yaml.should respond_to(:parse)
15
+ end
16
+ end
17
+
18
+ describe "generators" do
19
+ it "should respond to generate" do
20
+ Smoke::Transformers::Json.should respond_to(:generate)
21
+ end
22
+
23
+ it "should respond to generate" do
24
+ Smoke::Transformers::XML.should respond_to(:generate)
25
+ end
26
+
27
+ it "should respond to generate" do
28
+ Smoke::Transformers::Yaml.should respond_to(:generate)
29
+ end
30
+
31
+ it "should respond to generate" do
32
+ Smoke::Transformers::Ruby.should respond_to(:generate)
33
+ end
34
+ end
35
+ end
@@ -6,6 +6,16 @@ describe Smoke do
6
6
  @source_b = TestSource.source :b
7
7
  end
8
8
 
9
+ it "should have a root" do
10
+ Smoke.should respond_to(:root)
11
+ Smoke.root.should be_an_instance_of(String)
12
+ end
13
+
14
+ it "should have a version string" do
15
+ Smoke.should respond_to :version
16
+ Smoke.version.should be_an_instance_of(String)
17
+ end
18
+
9
19
  describe "active sources" do
10
20
  it "should allow access to sources via an array accessor" do
11
21
  Smoke[:a].should == @source_a
@@ -17,17 +27,15 @@ describe Smoke do
17
27
 
18
28
  it "should have its name as the hash key" do
19
29
  key = Smoke.active_sources.keys.first
20
- Smoke.active_sources[key].name.should == key
30
+ Smoke.active_sources[key].name.to_s.should == key.to_s
21
31
  end
22
32
 
23
- describe "accessing via method call" do
24
- it "should allow access to the sources via a method call" do
25
- Smoke.a.should == @source_a
26
- end
27
-
28
- it "should throw an argument error when missing" do
29
- Smoke.b.should raise_error(NoMethodError)
30
- end
33
+ it "should allow access to the sources via a method call" do
34
+ Smoke.a.should == @source_a
35
+ end
36
+
37
+ it "should throw an error when missing" do
38
+ lambda { Smoke.frank }.should raise_error(Smoke::NotRegistered)
31
39
  end
32
40
 
33
41
  it "should be able to be renamed" do
@@ -40,7 +48,7 @@ describe Smoke do
40
48
  Smoke[:b].name.should == "b"
41
49
  end
42
50
  end
43
-
51
+
44
52
  describe "configuration" do
45
53
  it "should be configurable" do
46
54
  Smoke.should respond_to(:configure)