el 0.5.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,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