wee 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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