el 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,96 @@
1
+ class E
2
+ def cache(*a, &b); app.cache(*a, &b); end
3
+ def clear_cache!(*a); app.clear_cache!(*a); end
4
+ def cache_pool; app.cache_pool; end
5
+ end
6
+
7
+ class EBuilder
8
+
9
+ # very basic cache implementation.
10
+ # by default the cache will be kept in memory.
11
+ # if you want to use a different pool,
12
+ # set it by using `cache_pool` at app level.
13
+ # make sure your pool behaves just like a Hash,
14
+ # meant it responds to `[]=`, `[]`, `keys`, `delete` and `clear`
15
+ #
16
+ def cache_pool pool = nil
17
+ return @cache_pool if @cache_pool
18
+ @cache_pool = pool if pool
19
+ @cache_pool ||= Hash.new
20
+ end
21
+
22
+ # simply running a block and store returned value.
23
+ # on next request the stored value will be returned.
24
+ #
25
+ # @note
26
+ # value is not stored if block returns false or nil
27
+ #
28
+ def cache key = nil, &proc
29
+ key ||= proc.source_location
30
+ cache_pool[key] || ( (val = proc.call) && (cache_pool[key] = val) )
31
+ end
32
+
33
+ # a simple way to manage stored cache.
34
+ # any number of arguments(actually matchers) accepted.
35
+ # matchers can be of String, Symbol or Regexp type. any other arguments ignored
36
+ #
37
+ # @example
38
+ # class App < E
39
+ #
40
+ # before do
41
+ # if 'some condition occurred'
42
+ # # clearing cache only for @red_banners and @db_items
43
+ # clear_cache! :red_banners, :db_items
44
+ # end
45
+ # if 'some another condition occurred'
46
+ # # clearing all cache
47
+ # clear_cache!
48
+ # end
49
+ # if 'Yet another condition occurred'
50
+ # # clearing cache by regexp
51
+ # clear_cache! /banners/, /db/
52
+ # end
53
+ # end
54
+ # end
55
+ #
56
+ # def index
57
+ # @db_items = cache :db_items do
58
+ # # ...
59
+ # end
60
+ # @red_banners = cache :red_banners do
61
+ # # ...
62
+ # end
63
+ # @blue_banners = cache :blue_banners do
64
+ # # ...
65
+ # end
66
+ # # ...
67
+ # end
68
+ #
69
+ # def products
70
+ # cache do
71
+ # # fetch and render products
72
+ # end
73
+ # end
74
+ # end
75
+ #
76
+ # @param [Array] keys
77
+ def clear_cache! *matchers
78
+ clear_cache *matchers
79
+ ipcm_trigger :clear_cache, *matchers
80
+ end
81
+
82
+ # same as `clear_cache!` except it is working only on current process
83
+ #
84
+ # @param [Array] keys
85
+ def clear_cache *matchers
86
+ return cache_pool.clear if matchers.empty?
87
+ keys = Array.new(cache_pool.keys)
88
+ matchers.each do |matcher|
89
+ mkeys = matcher.is_a?(Regexp) ?
90
+ keys.select {|k| k =~ matcher} :
91
+ keys.select {|k| k == matcher}
92
+ mkeys.each {|k| cache_pool.delete k}
93
+ end
94
+ end
95
+
96
+ end
@@ -0,0 +1,21 @@
1
+ module EConstants
2
+ GENERIC_TAGS = %w[
3
+ html head title body h1 h2 h3 h4 h5 h6 p a
4
+ abbr address b bdi bdo blockquote cite code del dfn em i ins kbd
5
+ mark meter pre progress q rp rt ruby s samp small strong sub sup
6
+ time u var wbr form textarea button select optgroup option label
7
+ fieldset legend datalist output iframe map area canvas figcaption figure
8
+ audio source track video nav ul ol li dl dt dd
9
+ table caption th tr td thead tbody tfoot colgroup col
10
+ div span header footer hgroup section article aside details dialog summary
11
+ style script noscript object
12
+ ].freeze
13
+
14
+ EMPTY_TAGS = %w[
15
+ img link br hr input keygen meta base embed param
16
+ ].freeze
17
+
18
+ IMAGE_TAGS = %w[
19
+ jpg jpeg png gif tif tiff bmp svg xpm
20
+ ].freeze
21
+ end
@@ -0,0 +1,61 @@
1
+ module EL
2
+ module ContentHelpers
3
+
4
+ # capture content and then render it into a different place
5
+ #
6
+ # @example
7
+ # content_for :assets do
8
+ # js_tag :jquery
9
+ # end
10
+ #
11
+ # p content_for?(:assets)
12
+ # #=> #<Proc:0x00...
13
+ #
14
+ # yield_content :assets
15
+ # #=> '<script src="jquery.js" type="text/javascript"></script>'
16
+ #
17
+ # @example
18
+ # content_for :account do |name, email|
19
+ # form_tag! do
20
+ # input_tag(value: name) +
21
+ # input_tag(value: email)
22
+ # end
23
+ # end
24
+ #
25
+ # yield_content :account, :foo, 'foo@bar.com'
26
+ # #=> '<form><input value="foo"><input value="foo@bar.com"></form>'
27
+ #
28
+ def content_for key, &proc
29
+ (@__el__content_for ||= {})[key] = proc
30
+ end
31
+
32
+ # check whether content block exists for some key
33
+ def content_for? key
34
+ (@__el__content_for || {})[key]
35
+ end
36
+
37
+ # render a content block captured by `content_for`
38
+ def yield_content key, *args
39
+ (proc = content_for?(key)) && proc.call(*args)
40
+ end
41
+
42
+ # execute given content block and return the result.
43
+ # useful when you need to display same snippet multiple times
44
+ # and want it rendered only once
45
+ #
46
+ # @example
47
+ # assets = capture_html do
48
+ # js_tag(:jquery) +
49
+ # css_tag(:ui)
50
+ # end
51
+ #
52
+ def capture_html &proc
53
+ proc.call
54
+ end
55
+ end
56
+
57
+ end
58
+
59
+ class E
60
+ include EL::ContentHelpers
61
+ end
@@ -0,0 +1,239 @@
1
+ class << E
2
+
3
+ E__CRUD__AUTH_TEST_PAYLOAD_KEY = "E__CRUD__AUTH_TEST_PAYLOAD_#{rand(2**64)}".freeze
4
+
5
+ # automatically creates POST/PUT/DELETE actions
6
+ # and map them to corresponding methods of given resource,
7
+ # so sending a POST request to the controller
8
+ # resulting in creating new object of given resource.
9
+ # PUT/PATCH requests will update objects by given id.
10
+ # DELETE requests will delete objects by given id.
11
+ #
12
+ # @params [Class] resource
13
+ # @params [Array] path_or_opts
14
+ # @option path_or_opts [String || Array] :exclude
15
+ # sometimes forms sending extra params. exclude them using :exclude option
16
+ # @option path_or_opts [Integer] :pkey
17
+ # item's primary key(default :id) to be returned when item created /updated.
18
+ # if no pkey given and item does not respond to :id, the item itself returned.
19
+ # @option path_or_opts [Integer] :halt_on_errors
20
+ # if created/updated item contain errors,
21
+ # halt processing unconditionally, even if proc given.
22
+ # if this option is false(default) and a proc given,
23
+ # it will pass item object and extracted errors to proc rather than halt.
24
+ # @option path_or_opts [Integer] :halt_with
25
+ # when resource are not created/updated because of errors,
26
+ # Espresso will halt operation with 500 error status code.
27
+ # use :halt_with option to set a custom error status code.
28
+ # @option path_or_opts [String] :join_with
29
+ # if resource thrown some errors, Espresso will join them using a coma.
30
+ # use :join_with option to set a custom glue.
31
+ #
32
+ def crudify resource, *path_or_opts, &proc
33
+ opts = path_or_opts.last.is_a?(Hash) ? path_or_opts.pop : {}
34
+ if opts[:exclude]
35
+ opts[:exclude] = [ opts[:exclude] ] unless opts[:exclude].is_a?(Array)
36
+ else
37
+ opts[:exclude] = []
38
+ end
39
+ path = path_or_opts.first
40
+ action = '%s_' << (path || :index).to_s
41
+ orm = :ar if resource.respond_to?(:arel_table)
42
+ orm = :dm if resource.respond_to?(:storage_name)
43
+ orm = :sq if resource.respond_to?(:db_schema)
44
+ orm_map = {
45
+ ar: {
46
+ get: :find,
47
+ put: :update_attributes,
48
+ patch: :update_attributes
49
+ },
50
+ sq: {
51
+ get: :[]
52
+ }
53
+ }[orm] || {}
54
+ resource_method = {
55
+ get: opts.fetch(:get, orm_map[:get] || :get),
56
+ put: opts.fetch(:put, orm_map[:put] || :update),
57
+ post: opts.fetch(:post, :create),
58
+ patch: opts.fetch(:patch, orm_map[:patch] || :update),
59
+ delete: opts.fetch(:delete, :destroy),
60
+ }
61
+
62
+ proc_accept_object, proc_accept_errors = nil
63
+ if proc
64
+ proc_accept_object = proc.arity > 0
65
+ proc_accept_errors = proc.arity > 1
66
+ end
67
+
68
+ pkey = opts[:pkey] || :id
69
+ join_with = opts[:join_with] || ', '
70
+ halt_with = opts[:halt_with] || 500
71
+
72
+ presenter = lambda do |controller_instance, obj, err|
73
+
74
+ # extracting errors, if any
75
+ errors = nil
76
+ if err || (obj.respond_to?(:errors) && (err = obj.errors) &&
77
+ err.respond_to?(:size) && err.size > 0)
78
+
79
+ if err.respond_to?(:join)
80
+ errors = err
81
+ else
82
+ if err.respond_to?(:each_pair) # seems error is a Hash
83
+ # some objects may respond to `each_pair` but not respond to `inject`,
84
+ # so using trivial looping to extract error messages.
85
+ errors = []
86
+ err.each_pair do |k,v|
87
+ # usually DataMapper returns errors in the following format:
88
+ # { :property => ['error 1', 'error 2'] }
89
+ # flatten is here just in case we get nested arrays.
90
+ error = v.is_a?(Array) ? v.flatten.join(join_with) : v.to_s
91
+ errors << '%s: %s' % [k, error]
92
+ end
93
+ elsif err.respond_to?(:to_a) # not Array nor Hash, but convertible to Array
94
+ # converting error to Array and joining
95
+ errors = err.to_a
96
+ elsif err.is_a?(String)
97
+ errors = [err]
98
+ else
99
+ errors = [err.inspect]
100
+ end
101
+ end
102
+ end
103
+ errors && errors = controller_instance.escape_html(errors.join(join_with))
104
+
105
+ if proc
106
+ if errors && opts[:halt_on_errors]
107
+ controller_instance.halt halt_with, errors
108
+ end
109
+ proc_args = []
110
+ proc_args = [obj] if proc_accept_object
111
+ proc_args = [obj, errors] if proc_accept_errors
112
+ controller_instance.instance_exec(*proc_args, &proc)
113
+ else
114
+ if errors
115
+ # no proc given, so halting when some errors occurring
116
+ controller_instance.styled_halt halt_with, errors
117
+ else
118
+ # no proc given and no errors detected,
119
+ # so extracting and returning object's pkey.
120
+ # if no pkey given and object does not respond to :id nor to :[],
121
+ # returning object as is.
122
+ if obj.respond_to?(:[]) && obj.respond_to?(:has_key?)
123
+ obj.has_key?(pkey) ? obj[pkey] : obj
124
+ elsif obj.respond_to?(pkey)
125
+ obj.send(pkey)
126
+ else
127
+ obj
128
+ end
129
+ end
130
+ end
131
+ end
132
+
133
+ fetch_object = lambda do |controller_instance, id|
134
+ obj, err = nil
135
+ begin
136
+ id = id.to_i if id =~ /\A\d+\Z/
137
+ obj = resource.send(resource_method[:get], id) ||
138
+ controller_instance.halt(404, 'object with ID %s not found' % controller_instance.escape_html(id))
139
+ rescue => e
140
+ err = e.message
141
+ end
142
+ [obj, err]
143
+ end
144
+
145
+ create_object = lambda do |controller_instance|
146
+ obj, err = nil
147
+ begin
148
+ data = controller_instance.params.reject { |k,v| opts[:exclude].include?(k) }
149
+ unless data.has_key?(E__CRUD__AUTH_TEST_PAYLOAD_KEY)
150
+ obj = resource.send(resource_method[:post], data)
151
+ end
152
+ rescue => e
153
+ err = e.message
154
+ end
155
+ [obj, err]
156
+ end
157
+
158
+ update_object = lambda do |controller_instance, request_method, id|
159
+ obj, err = nil
160
+ begin
161
+ obj, err = fetch_object.call(controller_instance, id)
162
+ unless err
163
+ data = controller_instance.params.reject { |k,v| opts[:exclude].include?(k) }
164
+ obj.send(resource_method[request_method], data)
165
+ end
166
+ rescue => e
167
+ err = e.message
168
+ end
169
+ [obj, err]
170
+ end
171
+
172
+ # fetching object by given id
173
+ # and calling #destroy(or whatever in options for delete) on it
174
+ #
175
+ # @return [String] empty string
176
+ delete_object = lambda do |id|
177
+ err = nil
178
+ begin
179
+ obj, err = fetch_object.call(self, id)
180
+ unless err
181
+ meth = resource_method[:delete]
182
+ if obj.respond_to?(meth)
183
+ obj.send(meth)
184
+ else
185
+ err = '%s does not respond to %s' % [obj.inspect, escape_html(meth)]
186
+ end
187
+ end
188
+ rescue => e
189
+ err = e.message
190
+ end
191
+ [nil, err]
192
+ end
193
+
194
+ options = lambda do |controller_instance|
195
+ EConstants::HTTP__REQUEST_METHODS.reject do |rm|
196
+ next if rm == 'OPTIONS'
197
+ args = rm == 'POST' ?
198
+ [{E__CRUD__AUTH_TEST_PAYLOAD_KEY => 'true'}] :
199
+ [E__CRUD__AUTH_TEST_PAYLOAD_KEY]
200
+ s,h,b = controller_instance.invoke(action % rm.downcase, *args) do |env|
201
+ env.update EConstants::ENV__REQUEST_METHOD => rm
202
+ end
203
+ s == EConstants::STATUS__PROTECTED
204
+ end.join(', ')
205
+ end
206
+
207
+ self.class_exec do
208
+
209
+ define_method action % :get do |id|
210
+ presenter.call self, *fetch_object.call(self, id)
211
+ end
212
+
213
+ define_method action % :head do |id|
214
+ presenter.call self, *fetch_object.call(self, id)
215
+ end
216
+
217
+ define_method action % :post do
218
+ presenter.call self, *create_object.call(self)
219
+ end
220
+
221
+ [:put, :patch].each do |request_method|
222
+ define_method action % request_method do |id|
223
+ presenter.call self, *update_object.call(self, request_method, id)
224
+ end
225
+ end
226
+
227
+ define_method action % :delete do |id|
228
+ presenter.call self, *delete_object.call(id)
229
+ end
230
+
231
+ define_method action % :options do
232
+ options.call self
233
+ end
234
+ end
235
+
236
+ end
237
+ alias crud crudify
238
+
239
+ end
@@ -0,0 +1,67 @@
1
+ # Inter-Process Cache Manager
2
+ class EBuilder
3
+
4
+ def ipcm_trigger *args
5
+ if pids_reader
6
+ pids = pids_reader.call rescue nil
7
+ if pids.is_a?(Array)
8
+ pids.map(&:to_i).reject {|p| p < 2 || p == Process.pid }.each do |pid|
9
+ file = '%s/%s.%s-%s' % [ipcm_tmpdir, pid, args.hash, Time.now.to_f]
10
+ begin
11
+ File.open(file, 'w') {|f| f << Marshal.dump(args)}
12
+ Process.kill ipcm_signal, pid
13
+ rescue => e
14
+ warn "was unable to perform IPCM operation because of error: %s" % ::CGI.escapeHTML(e.message)
15
+ File.unlink(file) if File.file?(file)
16
+ end
17
+ end
18
+ else
19
+ warn "pids_reader should return an array of pids. Exiting IPCM..."
20
+ end
21
+ end
22
+ end
23
+
24
+ def ipcm_tmpdir path = nil
25
+ return @ipcm_tmpdir if @ipcm_tmpdir
26
+ if path
27
+ @ipcm_tmpdir = ((path =~ /\A\// ? path : root + path) + '/').freeze
28
+ else
29
+ @ipcm_tmpdir = (root + 'tmp/ipcm/').freeze
30
+ end
31
+ FileUtils.mkdir_p @ipcm_tmpdir
32
+ @ipcm_tmpdir
33
+ end
34
+
35
+ def ipcm_signal signal = nil
36
+ return @ipcm_signal if @ipcm_signal
37
+ @ipcm_signal = signal.to_s if signal
38
+ @ipcm_signal ||= 'ALRM'
39
+ end
40
+
41
+ def register_ipcm_signal
42
+ Signal.trap ipcm_signal do
43
+ Dir[ipcm_tmpdir + '%s.*' % Process.pid].each do |file|
44
+ unless (setup = Marshal.restore(File.read(file)) rescue nil).is_a?(Array)
45
+ warn "Was unable to process \"%s\" cache file, skipping cache cleaning" % file
46
+ end
47
+ File.unlink(file) if File.file?(file)
48
+ meth = setup.shift
49
+ [ :clear_cache,
50
+ :clear_cache_like,
51
+ :clear_compiler,
52
+ :clear_compiler_like,
53
+ ].include?(meth) && self.send(meth, *setup)
54
+ end
55
+ end
56
+ end
57
+
58
+ def pids_reader &proc
59
+ return @pids_reader if @pids_reader
60
+ if proc.is_a?(Proc)
61
+ @pids_reader = proc
62
+ register_ipcm_signal
63
+ end
64
+ end
65
+ alias pids pids_reader
66
+
67
+ end