wee 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,158 @@
1
+ # Abstract super class of all cache implementations.
2
+ class Cache; end
3
+
4
+ # Abstract super class of all caching strategies.
5
+ class Cache::Strategy; end
6
+
7
+ # Implements an unbounded cache strategy. The cache size can grow to infinity.
8
+ class Cache::Strategy::Unbounded < Cache::Strategy
9
+ class Item < Struct.new(:value); end
10
+ def item_class() Item end
11
+
12
+ def access(item) end
13
+ def delete(item) end
14
+ def insert_or_extrude(item, enum) end
15
+ end
16
+
17
+ # Abstract class for a capacity bounded strategy. Only up to _capacity_ items
18
+ # are allowed to be stored in the cache at any time.
19
+ class Cache::Strategy::CapacityBounded < Cache::Strategy
20
+ attr_accessor :capacity
21
+
22
+ def initialize(capacity)
23
+ @capacity = capacity
24
+ @n_items = 0 # number of items in cache
25
+ end
26
+
27
+ def inc(item)
28
+ raise if full?
29
+ @n_items += 1
30
+ end
31
+
32
+ def dec(item)
33
+ raise if empty?
34
+ @n_items -= 1
35
+ end
36
+
37
+ def full?
38
+ @n_items >= @capacity
39
+ end
40
+
41
+ def empty?
42
+ @n_items == 0
43
+ end
44
+ end
45
+
46
+ # Implements the least frequently used (LFU) strategy.
47
+ class Cache::Strategy::LFU < Cache::Strategy::CapacityBounded
48
+ class Item < Struct.new(:value, :freq); end
49
+ def item_class() Item end
50
+
51
+ def access(item)
52
+ item.freq += 1
53
+ end
54
+
55
+ def delete(item)
56
+ dec(item)
57
+ end
58
+
59
+ # enum::
60
+ # a [key, item] enumerable
61
+ #
62
+ def insert_or_extrude(item, enum)
63
+ # find least recently used key/item and yield
64
+ yield enum.min_by {|key, it| it.freq} while full?
65
+ item.freq = 0
66
+ inc(item)
67
+ end
68
+ end
69
+
70
+ # Implements the least recently used (LRU) strategy.
71
+ class Cache::Strategy::LRU < Cache::Strategy::CapacityBounded
72
+ class Item < Struct.new(:value, :time); end
73
+ def item_class() Item end
74
+
75
+ def access(item)
76
+ item.time = Time.now
77
+ end
78
+
79
+ def delete(item)
80
+ dec(item)
81
+ end
82
+
83
+ # enum::
84
+ # a [key, item] enumerable
85
+ #
86
+ def insert_or_extrude(item, enum)
87
+ # find least recently used key/item and yield
88
+ yield enum.min_by {|key, it| it.time} while full?
89
+ item.time = Time.now
90
+ inc(item)
91
+ end
92
+ end
93
+
94
+ #
95
+ # Implements a cache using a parameterizable strategy and a storage.
96
+ # The protocol that the _store_ must understand is:
97
+ #
98
+ # fetch(key) -> val
99
+ # has_key?(key)
100
+ # delete(key) -> val
101
+ # each {|key, val| }
102
+ #
103
+ class Cache::StorageCache < Cache
104
+
105
+ def initialize(strategy, store=Hash.new, store_on_update=false)
106
+ @strategy = strategy
107
+ @store = store
108
+ @store_on_update = store_on_update
109
+ end
110
+
111
+ def has_key?(key)
112
+ @store.has_key?(key)
113
+ end
114
+
115
+ def delete(key)
116
+ if @store.has_key?(key)
117
+ item = @store.delete(key)
118
+ @strategy.delete(item)
119
+ item.value
120
+ else
121
+ nil
122
+ end
123
+ end
124
+
125
+ def fetch(key, default_value=nil)
126
+ if @store.has_key?(key)
127
+ item = @store.fetch(key)
128
+ @strategy.access(item)
129
+ @store.store(key, item) if @store_on_update
130
+ item.value
131
+ else
132
+ default_value
133
+ end
134
+ end
135
+
136
+ def store(key, value)
137
+ if @store.has_key?(key)
138
+ # update only
139
+ item = @store.fetch(key)
140
+ item.value = value
141
+ @strategy.access(item)
142
+ @store.store(key, item) if @store_on_update
143
+ else
144
+ # insert new item
145
+ item = @strategy.item_class.new
146
+ item.value = value
147
+ @strategy.insert_or_extrude(item, @store) do |k, i|
148
+ @strategy.delete(i)
149
+ @store.delete(k)
150
+ end
151
+ @store.store(key, item) # correct!
152
+ end
153
+ value
154
+ end
155
+
156
+ alias [] fetch
157
+ alias []= store
158
+ end
data/lib/wee.rb ADDED
@@ -0,0 +1,11 @@
1
+ module Wee; end
2
+
3
+ require 'wee/application'
4
+ require 'wee/component'
5
+ require 'wee/delegate_decoration'
6
+ require 'wee/context'
7
+ require 'wee/session'
8
+ require 'wee/holder'
9
+ require 'wee/html_writer'
10
+ require 'wee/html_canvas'
11
+ require 'wee/stuff'
@@ -0,0 +1,91 @@
1
+ class Wee::Application
2
+ attr_accessor :name, :path
3
+ attr_accessor :session_store, :session_class
4
+ attr_accessor :dumpfile
5
+
6
+ def initialize(&block)
7
+ setup_session_id_generator
8
+
9
+ block.call(self)
10
+
11
+ if [@name, @path, @session_class, @session_store, @dumpfile].any? {|i| i.nil?}
12
+ raise ArgumentError, "missing name, path, session_class, session_store or dumpfile"
13
+ end
14
+ end
15
+
16
+ def setup_session_id_generator
17
+ @session_cnt = rand(1000_000)
18
+ end
19
+
20
+ def handle_request(req, res)
21
+ hash = parse_url(req)
22
+
23
+ session_id = hash['s']
24
+ if session_id.nil?
25
+ # TODO:
26
+ # gen session, or assign default session (by default, do not show).
27
+ # but for a default session, we have to have thread-safe components (without @context=... hack)
28
+
29
+ session = @session_class.new
30
+ session_id = gen_unique_session_id
31
+ @session_store[session_id] = session
32
+ end
33
+
34
+ session = @session_store[session_id]
35
+ if session.nil?
36
+ # TODO: redirect to session-less page, or do whatever
37
+ error_invalid_session(req, res); return
38
+ end
39
+
40
+ context = Wee::Context.new(req, res, session, session_id)
41
+ context.application = self
42
+ context.session = session
43
+ context.page_id = hash['p']
44
+ context.handler_id = hash['h']
45
+ context.resource_id = hash['r']
46
+ session.handle_request(context)
47
+ end
48
+
49
+ # TODO: UrlModel, which knows how to create and parse URLs
50
+ def gen_handler_url(session_id, page_id, handler_id)
51
+ [
52
+ self.path,
53
+ ['s', session_id].join(':'),
54
+ ['p', page_id].join(':'),
55
+ ['h', handler_id].join(':')
56
+ ].join('/')
57
+ end
58
+
59
+ def gen_resource_url(session_id, page_id, resource_id)
60
+ [
61
+ self.path,
62
+ ['s', session_id].join(':'),
63
+ ['p', page_id].join(':'),
64
+ ['r', resource_id].join(':')
65
+ ].join('/')
66
+ end
67
+
68
+ def error_invalid_session(req, res)
69
+ Wee::ErrorPage.new('Invalid Session').respond(Wee::Context.new(req,res,nil,nil))
70
+ end
71
+
72
+ def store_to_disk(dumpfile=nil)
73
+ File.open(dumpfile||@dumpfile, 'w+b') {|f| f << Marshal.dump(self) }
74
+ end
75
+
76
+ def self.load_from_disk(filename)
77
+ Marshal.load(File.read(filename))
78
+ end
79
+
80
+ def shutdown
81
+ store_to_disk
82
+ end
83
+
84
+ private
85
+
86
+ # TODO: that's insecure. it's just for development!
87
+ def gen_unique_session_id
88
+ (@session_cnt += 1).to_s
89
+ end
90
+
91
+ end
@@ -0,0 +1,126 @@
1
+ class Wee::Component
2
+
3
+ attr_accessor :caller # who is calling us
4
+
5
+ # call another component
6
+ def call(component, return_method=nil)
7
+ component.caller = [self, return_method]
8
+ add_decoration(Wee::Delegate.new(component))
9
+ end
10
+
11
+ # return from a called component
12
+ def answer(*args)
13
+ c, return_method = self.caller
14
+ c.remove_first_decoration
15
+ c.send(return_method, *args) if return_method
16
+ end
17
+
18
+ def process_request(context)
19
+ process_request_inputs(context)
20
+ children.each {|c| c.decoration.process_request(context) }
21
+ process_request_actions(context)
22
+ end
23
+
24
+ def process_request_inputs(context)
25
+ context.request.query.each do |hid, value|
26
+ if act = context.handler_registry.get_input(hid, self)
27
+ send(act, value)
28
+ end
29
+ end
30
+ end
31
+
32
+ def process_request_actions(context)
33
+ # handle URL actions
34
+ if act = context.handler_registry.get_action(context.handler_id, self)
35
+ act.invoke
36
+ end
37
+
38
+ # handle form actions
39
+ context.request.query.each do |hid, value|
40
+ if act = context.handler_registry.get_action(hid, self)
41
+ act.invoke
42
+ end
43
+ end
44
+ end
45
+
46
+ # Creates a new renderer for this component and renders it.
47
+ def render_with_context(rendering_context)
48
+ r = renderer_class.new(rendering_context)
49
+ r.current_component = self
50
+ render_content_on(r)
51
+ end
52
+
53
+ # You call render_on to render a component.
54
+ def render_on(renderer)
55
+ decoration.render_with_context(renderer.context)
56
+ end
57
+
58
+ def render_content_on(r)
59
+ end
60
+
61
+ def renderer_class
62
+ Wee::HtmlCanvas
63
+ end
64
+
65
+ def children
66
+ @children || []
67
+ end
68
+
69
+ def add_child(c)
70
+ @children ||= []
71
+ @children << c
72
+ end
73
+
74
+ def add_children(*c)
75
+ @children ||= []
76
+ @children.push(*c)
77
+ end
78
+
79
+ # returns the first decoration in the decoration chain, or the component
80
+ # itself, if none was specified
81
+
82
+ attr_writer :decoration
83
+
84
+ # FIXME: @decoration may never be self when backtracking
85
+ # in any case, self==nil
86
+ def decoration
87
+ @decoration || self
88
+ end
89
+
90
+ # add decoration _d_ in front of the decoration chain.
91
+ def add_decoration_in_front(d)
92
+ d.next = self.decoration
93
+ self.decoration = d
94
+ end
95
+ alias add_decoration add_decoration_in_front
96
+
97
+ def remove_decoration(d)
98
+ if d == @decoration
99
+ # d is in front
100
+ @decoration = d.next
101
+ else
102
+ last_decoration = @decoration
103
+ loop do
104
+ raise "not found" if last_decoration == self
105
+ break if d == last_decoration.next
106
+ last_decoration = last_decoration.next
107
+ end
108
+ last_decoration.next = d.next
109
+ end
110
+ end
111
+
112
+ def add_decoration(d)
113
+ d.next = self.decoration
114
+ self.decoration = d
115
+ end
116
+
117
+ def remove_first_decoration
118
+ self.decoration = self.decoration.next
119
+ self.decoration = nil if self.decoration == self
120
+ end
121
+
122
+ def session
123
+ Wee::Session.current
124
+ end
125
+
126
+ end
@@ -0,0 +1,12 @@
1
+ class Wee::Context
2
+ attr_accessor :application
3
+ attr_accessor :request, :response, :session, :session_id
4
+ attr_accessor :page_id, :handler_id, :resource_id
5
+ attr_accessor :handler_registry
6
+
7
+ def initialize(request, response, session, session_id)
8
+ @request, @response, @session, @session_id, @root = request, response, session, session_id
9
+ end
10
+ end
11
+
12
+ class Wee::RenderingContext < Struct.new(:context, :document); end
@@ -0,0 +1,22 @@
1
+ class Wee::Delegate
2
+ attr_accessor :next # next decoration in chain
3
+
4
+ def initialize(component)
5
+ @component = component
6
+ end
7
+
8
+ def process_request(context)
9
+ @component.decoration.process_request(context)
10
+ end
11
+
12
+ # Creates a new renderer for this component and renders it.
13
+ def render_with_context(rendering_context)
14
+ r = renderer_class.new(rendering_context)
15
+ r.current_component = self
16
+ r.render(@component)
17
+ end
18
+
19
+ def renderer_class
20
+ Wee::HtmlCanvas
21
+ end
22
+ end
@@ -0,0 +1,89 @@
1
+ class Wee::ActionHandler
2
+ attr_accessor :obj, :meth, :args
3
+
4
+ def self.[](obj, meth, *args)
5
+ new(obj, meth, *args)
6
+ end
7
+
8
+ def initialize(obj, meth, *args)
9
+ @obj, @meth, @args = obj, meth.to_s, args
10
+ end
11
+
12
+ def invoke
13
+ @obj.send(@meth, *@args)
14
+ end
15
+ end
16
+
17
+ class Wee::ResourceHandler
18
+ attr_accessor :content, :content_type
19
+ def initialize(content, content_type)
20
+ @content, @content_type = content, content_type
21
+ end
22
+ end
23
+
24
+ class Wee::HandlerRegistry
25
+ def initialize
26
+ @next_handler_id = 1
27
+ @action_registry = Hash.new
28
+ @input_registry = Hash.new
29
+ @resource_registry = Hash.new
30
+ end
31
+
32
+ def handler_id_for_action(action_handler)
33
+ hid = get_next_handler_id()
34
+ raise if @action_registry.has_key?(hid)
35
+ @action_registry[hid] = action_handler
36
+ return hid
37
+ end
38
+
39
+ def handler_id_for_input(obj, input)
40
+ hid = get_next_handler_id()
41
+ raise if @input_registry.has_key?(hid)
42
+ @input_registry[hid] = [obj, input.to_s]
43
+ return hid
44
+ end
45
+
46
+ def handler_id_for_resource(resource)
47
+ hid = get_next_handler_id()
48
+ raise if @resource_registry.has_key?(hid)
49
+ @resource_registry[hid] = resource
50
+ return hid
51
+ end
52
+
53
+ def get_action(handler_id, obj)
54
+ action_handler = @action_registry[handler_id]
55
+ return nil unless action_handler
56
+
57
+ if action_handler.obj == obj
58
+ action_handler
59
+ else
60
+ nil
61
+ end
62
+ end
63
+
64
+ def get_input(handler_id, obj)
65
+ return nil unless @input_registry.has_key?(handler_id)
66
+
67
+ component, input = @input_registry[handler_id]
68
+
69
+ if component == obj
70
+ input
71
+ else
72
+ nil
73
+ end
74
+ end
75
+
76
+ def get_resource(handler_id)
77
+ return @resource_registry[handler_id]
78
+ end
79
+
80
+ private
81
+
82
+ # TODO: randomize
83
+ def get_next_handler_id
84
+ @next_handler_id.to_s
85
+ ensure
86
+ @next_handler_id += 1
87
+ end
88
+
89
+ end