gossamer 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ = Gossamer CHANGELOG
2
+
3
+ === 0.1 (2008-07-23)
4
+
5
+ * Initial release.
@@ -0,0 +1,14 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ examples/colors
6
+ examples/skype
7
+ examples/text
8
+ examples/world_clock
9
+ lib/gossamer.rb
10
+ lib/gossamer/broker.rb
11
+ lib/gossamer/params_processor.rb
12
+ lib/gossamer/resource.rb
13
+ lib/gossamer/typed_string.rb
14
+ test/test_gossamer.rb
@@ -0,0 +1,168 @@
1
+ = gossamer
2
+
3
+ A microframework to spin websites out of distributed, lightweight, ephemeral resources.
4
+
5
+ Home page: http://gossamer.rubyforge.com
6
+
7
+
8
+ == Description
9
+
10
+ Gossamer is a web framework that emphasizes the use of distributed, independent resources. With Gossamer, you construct websites out of a network of lightweight objects that manage particular resources. Resources can utilize other resources through fault-tolerant, loosely coupled RESTful[http://en.wikipedia.org/wiki/Representational_State_Transfer] communications. Resource brokers manage the resource objects, storing them in a distributed cache (eg, <tt>memcache</tt>). Resources can easily serve their content in multiple formats, such as HTML, Atom, and RDF.
11
+
12
+ Gossamer is intended to be a useful platform for aggregators, mashups, web services, implementing the semantic web, and other applications that depend on external network resources rather than internal databases.
13
+
14
+
15
+ == Features
16
+
17
+ * Object-oriented "resource" view of data encourages encapsulation, and maps nicely to the semantic/programmable web.
18
+
19
+ * Loosely-coupled connections between resources encourages both cooperation and error correction.
20
+
21
+ * Resources can be updated automatically.
22
+
23
+ * Resources are stored (frozen) in a distributed cache, accessible by multiple processes on the same machine or multiple machines.
24
+
25
+
26
+ == Overview
27
+
28
+ Instead of implementing models, controllers, views, and server scripts, you implement <em>resources, brokers</em>, and <em>servers.</em>
29
+
30
+ A server creates a broker, and a broker manages one or more resources. Each resource is fully responsible for creating, updating, and rendering its data.
31
+
32
+ A small site may have only one server and a few resources; a large site may have many servers, each responsible for a set of resources.
33
+
34
+
35
+ === Resources
36
+
37
+ <em>Resources</em> are independent entities that encapsulate the initialization, updating, and rendering of some kind of information. The implementation of a resource depends on its complexity and the data it represents.
38
+
39
+ Some examples of resources:
40
+
41
+ * The copyright terms for a site. This would be static text, perhaps with some links to contacts at an organization.
42
+
43
+ * A report based on a query of a local database.
44
+
45
+ * The presence information for a particular user, using several APIs to fetch that information from various services.
46
+
47
+ * The aggregation of several blogs.
48
+
49
+ Each resource has three parts, or phases:
50
+
51
+ initialize:: The resource configures itself, and any data that is static and quickly generated is stored in instance variables.
52
+
53
+ update:: The resource performs any long-running tasks, like complex queries or accessing remote data. If the resource has set its <tt>update_frequency</tt> and implemented an <tt>#update</tt> method, this updating phase will be performed again at that frequency.
54
+
55
+ render:: The resource renders itself in the requested format. Often this is HTML, but it could be Atom, XML, or other formats. A resource "publishes" its formats simply by implementing the appropriate method: <tt>#to_html</tt> for HTML, <tt>#to_atom</tt> for Atom, and so on. If a resource doesn't implement any rendering methods, it is only visible to other resources, not to web servers.
56
+
57
+ A resource may use other resources. For example, a resource that represents the presence info of a person may depend on other resources that represent particular presence services, such as AIM, Skype, and Twitter. The general presence resource will make requests of the individual presence services, combining that data into a single resource.
58
+
59
+ A website may implement all its "pages" as Gossamer resources, each of which may depend on other resources to gather and render the final HTML.
60
+
61
+ Here is a small example resource that simply creates and renders a static string:
62
+
63
+ class MyResource < Gossamer::Resource
64
+
65
+ def update
66
+ @text = "Some information about something."
67
+ end
68
+
69
+ def to_html
70
+ @text
71
+ end
72
+
73
+ end
74
+
75
+
76
+ === Brokers
77
+
78
+ A <em>broker</em> is the go-between for resources. A broker can be asked to store a resource at a particular path (a local URI) for later access by that broker or another broker who is configured similarly. Once the resource is stored in the distributed cache, it is "frozen" and inactive. When a broker is asked for a resource, the resource is thawed out, and either returned to the caller as an object or rendered in a particular format (eg, HTML, Atom, XML).
79
+
80
+ There can be multiple brokers, and even brokers on different machines. Any broker using the same cache configuration can access any resource saved into the cache by any broker.
81
+
82
+ If a broker is asked to store a resource that wants to be updated, the resource is scheduled for updates according to its configured frequency. When the update time comes, the resource is pulled out of the cache, its update method is called, and the resource is put back into the cache. (There is currently no notification mechanism, so a resource that depends on another updating resource will have to update itself slightly after the first resource has updated.)
83
+
84
+ Here is a simple broker:
85
+
86
+ class MyBroker < Gossamer::Broker
87
+
88
+ def initialize
89
+ super(:cache_address => "localhost:11211", :cache_namespace => "text")
90
+ end
91
+
92
+ end
93
+
94
+
95
+ === Servers
96
+
97
+ A server isn't an actual object class in Gossamer, but just a convention of how to create a broker and resources, and, optionally, hook in a Rack application. Here's a simple server that uses the resource and broker examples above, and connects a Mongrel web server into the broker.
98
+
99
+ broker = MyBroker.new
100
+ broker["/info"] = MyResource.new
101
+ app = Rack::Builder.new { run broker }.to_app
102
+ Rack::Handler::Mongrel.run(app, :Port => 3000)
103
+
104
+ When this example is run, a HTTP request for "http://localhost:3000/info" will show the sample text.
105
+
106
+
107
+ == Limitations & problems
108
+
109
+ * Data flows only one way: from a resource to the user or into another resource. There is currently no facility for submitting forms or modifying data inside resources. Nor are there any sort of notifications. This means most resources will work on a polling model, which is suboptimal.
110
+
111
+ * Performance is undetermined. It might be fast enough for you. It might be really slow.
112
+
113
+ * Because Gossamer was built as a research experiment and not a specified, production system, there are no unit or functional tests.
114
+
115
+ * Gossamer is not a full-stack framework. It knows nothing of databases, configuration, or templating systems.
116
+
117
+
118
+ == Synopsis
119
+
120
+ See the <tt>examples/</tt> directory for some sample servers and resources.
121
+
122
+
123
+ == Requirements
124
+
125
+ * ruby 1.8.6 or higher
126
+ * Rack[http://rack.rubyforge.org]
127
+ * a Rack-compatible web server
128
+ * memcached[http://blog.evanweaver.com/files/doc/fauna/memcached/] (which requires libmemcached[http://tangent.org/552/libmemcached.html] and memcached[http://danga.com/memcached])
129
+ * Scheduler (<tt>sudo gem install scheduler</tt>)
130
+
131
+ If you install from the Gossamer gem, you'll get all these for free, except for <tt>libmemcached</tt>.
132
+
133
+
134
+ == Installation
135
+
136
+ sudo gem install gossamer
137
+
138
+
139
+ == Author
140
+
141
+ John Labovitz
142
+
143
+ johnl@johnlabovitz.com
144
+
145
+
146
+ == License
147
+
148
+ (The BSD License)
149
+
150
+ Copyright (c) 2008, John Labovitz. All rights reserved.
151
+
152
+ Redistribution and use in source and binary forms, with or without
153
+ modification, are permitted provided that the following conditions are met:
154
+
155
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
156
+
157
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
158
+
159
+ THIS SOFTWARE IS PROVIDED BY JOHN LABOVITZ "AS IS" AND ANY
160
+ EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
161
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
162
+ DISCLAIMED. IN NO EVENT SHALL JOHN LABOVITZ BE LIABLE FOR ANY
163
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
164
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
165
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
166
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
167
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
168
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,22 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+
6
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), 'lib')
7
+
8
+ require 'gossamer'
9
+
10
+ Hoe.new('gossamer', Gossamer::VERSION) do |p|
11
+ p.developer('John Labovitz', 'johnl@johnlabovitz.com')
12
+ p.url = 'http://gossamer.rubyforge.org'
13
+ p.remote_rdoc_dir = '' # Release to root
14
+ p.summary = 'A microframework to spin websites out of distributed, lightweight, ephemeral resources'
15
+ # p.description = p.paragraphs_of('README.txt', 2..6).join("\n\n")
16
+ # p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
17
+ p.extra_deps << ['memcached', '>= 0.11']
18
+ p.extra_deps << ['rack', '>= 0.3.0']
19
+ p.extra_deps << ['scheduler', '>= 0.3']
20
+ end
21
+
22
+ # vim: syntax=Ruby
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
4
+
5
+ require 'gossamer'
6
+ require 'builder'
7
+
8
+ module Colors
9
+
10
+ COLORS = %w{red blue green}
11
+
12
+ class Color < Gossamer::Resource
13
+
14
+ attr_accessor :name
15
+
16
+ def to_html
17
+ html = Builder::XmlMarkup.new
18
+ html.div(name, :style => "background-color: #{name}; color: white; height: 200; width: 200")
19
+ html.target!
20
+ end
21
+
22
+ end
23
+
24
+ class HomePage < Gossamer::Resource
25
+
26
+ def to_html
27
+ html = Builder::XmlMarkup.new
28
+ html.html do
29
+ html.body do
30
+ html.h1("Colors")
31
+ COLORS.each do |name|
32
+ path = "/color/#{name}"
33
+ html.a(:href => path) do
34
+ html << @broker.get(path)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ html.target!
40
+ end
41
+
42
+ end
43
+
44
+ class Broker < Gossamer::Broker
45
+
46
+ def initialize(params={})
47
+ super(params.merge(:cache_address => "localhost:11211", :cache_namespace => "colors"))
48
+ end
49
+
50
+ end
51
+
52
+ class ColorServer
53
+
54
+ def self.run(name)
55
+ color = Color.new(:name => name)
56
+ broker = Broker.new
57
+ broker["/color/#{name}"] = color
58
+ end
59
+
60
+ end
61
+
62
+ class WebServer
63
+
64
+ def self.run
65
+ broker = Broker.new
66
+ broker['/'] = HomePage.new
67
+ app = Rack::Builder.new { run broker }.to_app
68
+ Rack::Handler::Mongrel.run(app, :Port => 3000)
69
+ end
70
+
71
+ end
72
+
73
+ class SingleServer
74
+
75
+ def self.run
76
+ COLORS.each do |name|
77
+ ColorServer.run(name)
78
+ end
79
+ WebServer.run
80
+ end
81
+
82
+ end
83
+
84
+ class MultiServer
85
+
86
+ def self.run
87
+ COLORS.each do |name|
88
+ fork { ColorServer.run(name) }
89
+ end
90
+ sleep 1
91
+ WebServer.run
92
+ end
93
+
94
+ end
95
+
96
+ end
97
+
98
+ case ARGV.shift
99
+ when '-m'
100
+ Colors::MultiServer.run
101
+ else
102
+ Colors::SingleServer.run
103
+ end
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
4
+
5
+ require 'gossamer'
6
+ require 'builder'
7
+ require 'open-uri'
8
+ require 'hpricot'
9
+ require 'units/standard'
10
+
11
+ module Skype
12
+
13
+ class SkypeStatus < Gossamer::Resource
14
+
15
+ STATUS_CODES = {
16
+ 1 => "offline", # offline or invisible
17
+ 2 => "online",
18
+ 3 => "away",
19
+ 4 => "not available",
20
+ 5 => "do not disturb",
21
+ 7 => "skype me",
22
+ }
23
+
24
+ attr_accessor :user_id
25
+ attr_accessor :state
26
+ attr_accessor :call_link
27
+
28
+ def initialize(params)
29
+ super(params.merge(:REQUIRED => [:user_id], :DEFAULTS => {:update_frequency => 5.seconds}))
30
+ end
31
+
32
+ def update
33
+ status = Hpricot(open("http://mystatus.skype.com/#{@user_id}.xml"))
34
+ status_code = status.at('//statuscode').inner_text.to_i
35
+ @state = STATUS_CODES[status_code]
36
+ @call_link = "skype:#{@user_id}?call"
37
+ end
38
+
39
+ def to_html
40
+ html = Builder::XmlMarkup.new
41
+ html.a(@user_id, :href => @call_link)
42
+ html.text! " (#{@state})" if @state
43
+ html.target!
44
+ end
45
+
46
+ end
47
+
48
+ class HomePage < Gossamer::Resource
49
+
50
+ attr_accessor :users
51
+
52
+ def to_html
53
+ html = Builder::XmlMarkup.new
54
+ html.html do
55
+ html.head do
56
+ html.title("Skype status")
57
+ html.script(:type => 'text/javascript', :src => "http://ajax.googleapis.com/ajax/libs/prototype/1.6.0.2/prototype.js")
58
+ end
59
+ html.body do
60
+ html.h1("Skype status")
61
+ html.ul do
62
+ @users.each do |user|
63
+ html.li(:id => user) do
64
+ status = @broker["/#{user}"]
65
+ html << status.to_html
66
+ html.script(%Q{new Ajax.PeriodicalUpdater('#{user}', '#{status.path}', { method: 'post', frequency: #{status.update_frequency} });})
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ html.target!
73
+ end
74
+
75
+ end
76
+
77
+ class Broker < Gossamer::Broker
78
+
79
+ def initialize(params={})
80
+ super(params.merge(:cache_address => "localhost:11211", :cache_namespace => "skype"))
81
+ end
82
+
83
+ end
84
+
85
+ class Server
86
+
87
+ def self.run
88
+ users = %w{jslabovitz echo123}
89
+ broker = Broker.new
90
+ broker['/'] = HomePage.new(:users => users)
91
+ users.each do |user|
92
+ broker["/#{user}"] = SkypeStatus.new(:user_id => user)
93
+ end
94
+ app = Rack::Builder.new { run broker }.to_app
95
+ Rack::Handler::Mongrel.run(app, :Port => 3000)
96
+ end
97
+
98
+ end
99
+
100
+ end
101
+
102
+ Skype::Server.run
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
4
+
5
+ require 'gossamer'
6
+
7
+ class MyResource < Gossamer::Resource
8
+
9
+ def update
10
+ @text = "Some information about something."
11
+ end
12
+
13
+ def to_html
14
+ @text
15
+ end
16
+
17
+ end
18
+
19
+ class MyBroker < Gossamer::Broker
20
+
21
+ def initialize
22
+ super(:cache_address => "localhost:11211", :cache_namespace => "text")
23
+ end
24
+
25
+ end
26
+
27
+ broker = MyBroker.new
28
+ broker["/info"] = MyResource.new
29
+ app = Rack::Builder.new { run broker }.to_app
30
+ Rack::Handler::Mongrel.run(app, :Port => 3000)
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
4
+
5
+ require 'gossamer'
6
+ require 'builder'
7
+ require 'date'
8
+
9
+ module WorldClock
10
+
11
+ class Clock < Gossamer::Resource
12
+
13
+ attr_accessor :name
14
+ attr_accessor :offset
15
+ attr_accessor :time
16
+
17
+ def initialize(params={})
18
+ super(params.merge(:REQUIRED => [:name, :offset], :DEFAULTS => { :update_frequency => 1 }))
19
+ end
20
+
21
+ def update
22
+ @time = DateTime.now.new_offset(@offset / 24.0)
23
+ end
24
+
25
+ def to_html
26
+ html = Builder::XmlMarkup.new
27
+ html.div(:style => 'border: 1px black solid; text-align: center') do
28
+ html.h2(@name.to_s)
29
+ html.h1(@time.to_s)
30
+ end
31
+ html.target!
32
+ end
33
+
34
+ end
35
+
36
+ class HomePage < Gossamer::Resource
37
+
38
+ attr_accessor :cities
39
+
40
+ def to_html
41
+ html = Builder::XmlMarkup.new
42
+ html.html do
43
+ html.head do
44
+ html.title("World Clock")
45
+ html.script(:type => 'text/javascript', :src => "http://ajax.googleapis.com/ajax/libs/prototype/1.6.0.2/prototype.js")
46
+ end
47
+ html.body do
48
+ html.h1("World Clock")
49
+ cities.each do |name, offset|
50
+ html_id = name.downcase
51
+ html.span(:id => html_id) do
52
+ clock = @broker["/clock/#{name}"]
53
+ html << clock.to_html
54
+ html.script(%Q{new Ajax.PeriodicalUpdater('#{html_id}', '#{clock.path}', { method: 'post', frequency: #{clock.update_frequency} });})
55
+ end
56
+ end
57
+ end
58
+ end
59
+ html.target!
60
+ end
61
+
62
+ end
63
+
64
+ class Broker < Gossamer::Broker
65
+
66
+ def initialize(params={})
67
+ super(params.merge(:cache_address => "localhost:11211", :cache_namespace => "world_clock"))
68
+ end
69
+
70
+ end
71
+
72
+ class Server
73
+
74
+ def self.run
75
+
76
+ cities = {
77
+ 'Portland' => -8,
78
+ 'Denver' => -7,
79
+ 'NewYork' => -5,
80
+ }
81
+
82
+ broker = Broker.new
83
+
84
+ cities.each do |name, offset|
85
+ broker["/clock/#{name}"] = Clock.new(:name => name, :offset => offset)
86
+ end
87
+
88
+ broker['/'] = HomePage.new(:cities => cities)
89
+
90
+ app = Rack::Builder.new { run broker }.to_app
91
+ Rack::Handler::Mongrel.run(app, :Port => 3000)
92
+ end
93
+
94
+ end
95
+
96
+ end
97
+
98
+ WorldClock::Server.run
@@ -0,0 +1,8 @@
1
+ require 'gossamer/broker'
2
+ require 'gossamer/resource'
3
+
4
+ module Gossamer
5
+
6
+ VERSION = '0.1'
7
+
8
+ end
@@ -0,0 +1,120 @@
1
+ require 'memcached'
2
+ require 'rack'
3
+ require 'scheduler'
4
+ require 'gossamer/typed_string'
5
+ require 'gossamer/params_processor'
6
+
7
+ module Gossamer
8
+
9
+ class Broker
10
+
11
+ class UnknownPath < Exception; end
12
+ class UnknownFormat < Exception; end
13
+
14
+ attr_accessor :cache_address
15
+ attr_accessor :cache_namespace
16
+ attr_accessor :cache
17
+ attr_accessor :default_format
18
+
19
+ include ParamsProcessor
20
+
21
+ def initialize(params={})
22
+ process_params(params.merge(:REQUIRED => [:cache_address, :cache_namespace], :DEFAULTS => { :default_format => 'html' }))
23
+ @cache = Memcached.new(@cache_address, :namespace => @cache_namespace)
24
+ @resources = {} # key is resource path, value is Scheduler::Event if scheduled for update
25
+ end
26
+
27
+ def clone
28
+ clone = dup
29
+ clone.cache = @cache.clone
30
+ clone
31
+ end
32
+
33
+ def [](path)
34
+ begin
35
+ resource = @cache.get(path)
36
+ resource.broker = self
37
+ resource
38
+ rescue Memcached::NotFound
39
+ nil
40
+ end
41
+ end
42
+
43
+ def []=(path, resource)
44
+ put(path, resource)
45
+ end
46
+
47
+ def put(path, resource)
48
+ resource.path = path
49
+ post(path, resource)
50
+ if resource.respond_to?(:update) && @resources[path].nil?
51
+ @resources[path] = Scheduler.after_delay(0) { update(resource.path) }
52
+ end
53
+ end
54
+
55
+ def post(path, resource)
56
+ resource.broker = nil
57
+ begin
58
+ @cache.set(path, resource)
59
+ rescue TypeError => e
60
+ raise e, "Couldn't save #{resource.class} to path #{path.inspect}: #{e.inspect}"
61
+ end
62
+ resource.broker = self
63
+ end
64
+
65
+ def update(path)
66
+ if (event = @resources[path])
67
+ Scheduler.cancel(event)
68
+ @resources[path] = nil
69
+ end
70
+ broker = clone
71
+ resource = broker[path]
72
+ resource.update
73
+ broker.post(path, resource)
74
+ @resources[path] = Scheduler.after_delay(resource.update_frequency) { update(resource.path) }
75
+ end
76
+
77
+ def call(env)
78
+ request = Rack::Request.new(env)
79
+ response = Rack::Response.new
80
+ begin
81
+ response.body = get(request.path_info, request.params['format'])
82
+ response['Content-Type'] = response.body.type
83
+ rescue UnknownPath, UnknownFormat => e
84
+ ;;warn "Error while handling request for #{request.path_info.inspect}: #{e.inspect}"
85
+ response.status, response.body = 404, "Resource does not exist"
86
+ rescue => e
87
+ ;;warn "Error while handling request for #{request.path_info.inspect}: #{e.inspect}"
88
+ e.backtrace.each { |t| ;;warn "\t" + t }
89
+ response.status, response.body = 500, "Internal error"
90
+ end
91
+ response.finish
92
+ end
93
+
94
+ def get(path, format=nil)
95
+ format = (format.to_s.empty? ? @default_format : format.to_s.downcase)
96
+ raise UnknownFormat, "Unknown format #{format.inspect} for path #{path.inspect}" if format =~ /[^\w]/
97
+ resource = self[path] or raise UnknownPath, "Unknown path #{path.inspect}"
98
+ begin
99
+ method = resource.method("to_#{format}")
100
+ rescue NameError => e
101
+ raise UnknownFormat, "Unknown format #{format.inspect} for path #{path.inspect}"
102
+ end
103
+ ;;warn "#{path} [#{format}] => #{resource.class}#{(method && method.name) ? '.' + method.name : ''}"
104
+ case format
105
+ when 'html'
106
+ TypedString.new(method.call, "text/html")
107
+ when 'xml'
108
+ TypedString.new(method.call, "application/xml")
109
+ when 'atom'
110
+ TypedString.new(method.call, "application/atom+xml")
111
+ when 'text'
112
+ TypedString.new(method.call, "text/plain")
113
+ else
114
+ TypedString.new(method.call, "application/octet-stream")
115
+ end
116
+ end
117
+
118
+ end
119
+
120
+ end
@@ -0,0 +1,16 @@
1
+ module ParamsProcessor
2
+
3
+ def process_params(params={})
4
+ if (required = params.delete(:REQUIRED))
5
+ required.each { |name| raise "#{name.inspect} required" unless params[name] }
6
+ end
7
+
8
+ if (defaults = params.delete(:DEFAULTS))
9
+ defaults.each { |name, value| params[name] ||= value }
10
+ end
11
+
12
+ config = params.delete(:CONFIG) || []
13
+ params.each { |name, value| method("#{name}=").call(value) unless config.include?(name) }
14
+ end
15
+
16
+ end
@@ -0,0 +1,19 @@
1
+ require 'gossamer/params_processor'
2
+
3
+ module Gossamer
4
+
5
+ class Resource
6
+
7
+ include ParamsProcessor
8
+
9
+ attr_accessor :path
10
+ attr_accessor :broker
11
+ attr_accessor :update_frequency
12
+
13
+ def initialize(params={})
14
+ process_params(params)
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -0,0 +1,13 @@
1
+ class TypedString < String
2
+
3
+ def initialize(string, type)
4
+ super(string.to_s)
5
+ @type = type
6
+ @encoding = "utf8"
7
+ end
8
+
9
+ def type
10
+ "#{@type}; charset=#{@encoding}"
11
+ end
12
+
13
+ end
File without changes
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gossamer
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.1"
5
+ platform: ruby
6
+ authors:
7
+ - John Labovitz
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-07-22 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: memcached
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0.11"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: rack
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.3.0
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: scheduler
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0.3"
44
+ version:
45
+ - !ruby/object:Gem::Dependency
46
+ name: hoe
47
+ type: :development
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 1.7.0
54
+ version:
55
+ description: Gossamer is a web framework that emphasizes the use of distributed, independent resources. With Gossamer, you construct websites out of a network of lightweight objects that manage particular resources. Resources can utilize other resources through fault-tolerant, loosely coupled RESTful[http://en.wikipedia.org/wiki/Representational_State_Transfer] communications. Resource brokers manage the resource objects, storing them in a distributed cache (eg, <tt>memcache</tt>). Resources can easily serve their content in multiple formats, such as HTML, Atom, and RDF. Gossamer is intended to be a useful platform for aggregators, mashups, web services, implementing the semantic web, and other applications that depend on external network resources rather than internal databases.
56
+ email:
57
+ - johnl@johnlabovitz.com
58
+ executables: []
59
+
60
+ extensions: []
61
+
62
+ extra_rdoc_files:
63
+ - History.txt
64
+ - Manifest.txt
65
+ - README.txt
66
+ files:
67
+ - History.txt
68
+ - Manifest.txt
69
+ - README.txt
70
+ - Rakefile
71
+ - examples/colors
72
+ - examples/skype
73
+ - examples/text
74
+ - examples/world_clock
75
+ - lib/gossamer.rb
76
+ - lib/gossamer/broker.rb
77
+ - lib/gossamer/params_processor.rb
78
+ - lib/gossamer/resource.rb
79
+ - lib/gossamer/typed_string.rb
80
+ - test/test_gossamer.rb
81
+ has_rdoc: true
82
+ homepage: http://gossamer.rubyforge.org
83
+ post_install_message:
84
+ rdoc_options:
85
+ - --main
86
+ - README.txt
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: "0"
94
+ version:
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: "0"
100
+ version:
101
+ requirements: []
102
+
103
+ rubyforge_project: gossamer
104
+ rubygems_version: 1.2.0
105
+ signing_key:
106
+ specification_version: 2
107
+ summary: A microframework to spin websites out of distributed, lightweight, ephemeral resources
108
+ test_files:
109
+ - test/test_gossamer.rb