yodel 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +9 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +63 -0
- data/LICENSE +1 -0
- data/README.rdoc +20 -0
- data/Rakefile +1 -0
- data/bin/yodel +4 -0
- data/lib/yodel.rb +22 -0
- data/lib/yodel/application/application.rb +44 -0
- data/lib/yodel/application/extension.rb +59 -0
- data/lib/yodel/application/request_handler.rb +48 -0
- data/lib/yodel/application/yodel.rb +25 -0
- data/lib/yodel/command/command.rb +94 -0
- data/lib/yodel/command/deploy.rb +67 -0
- data/lib/yodel/command/dns_server.rb +16 -0
- data/lib/yodel/command/installer.rb +229 -0
- data/lib/yodel/config/config.rb +30 -0
- data/lib/yodel/config/environment.rb +16 -0
- data/lib/yodel/config/yodel.rb +21 -0
- data/lib/yodel/exceptions/destroyed_record.rb +2 -0
- data/lib/yodel/exceptions/domain_not_found.rb +16 -0
- data/lib/yodel/exceptions/duplicate_layout.rb +2 -0
- data/lib/yodel/exceptions/exceptions.rb +3 -0
- data/lib/yodel/exceptions/inconsistent_lock_state.rb +2 -0
- data/lib/yodel/exceptions/invalid_field.rb +2 -0
- data/lib/yodel/exceptions/invalid_index.rb +2 -0
- data/lib/yodel/exceptions/invalid_mixin.rb +2 -0
- data/lib/yodel/exceptions/invalid_model_field.rb +2 -0
- data/lib/yodel/exceptions/layout_not_found.rb +2 -0
- data/lib/yodel/exceptions/mass_assignment.rb +2 -0
- data/lib/yodel/exceptions/missing_migration.rb +2 -0
- data/lib/yodel/exceptions/missing_root_directory.rb +15 -0
- data/lib/yodel/exceptions/unable_to_acquire_lock.rb +2 -0
- data/lib/yodel/exceptions/unauthorised.rb +2 -0
- data/lib/yodel/exceptions/unknown_field.rb +2 -0
- data/lib/yodel/middleware/development_server.rb +180 -0
- data/lib/yodel/middleware/error_pages.rb +72 -0
- data/lib/yodel/middleware/public_assets.rb +78 -0
- data/lib/yodel/middleware/request.rb +16 -0
- data/lib/yodel/middleware/site_detector.rb +22 -0
- data/lib/yodel/mime_types/default_mime_set.rb +28 -0
- data/lib/yodel/mime_types/mime_type.rb +68 -0
- data/lib/yodel/mime_types/mime_type_set.rb +41 -0
- data/lib/yodel/mime_types/mime_types.rb +6 -0
- data/lib/yodel/mime_types/yodel.rb +15 -0
- data/lib/yodel/models/api/api.rb +1 -0
- data/lib/yodel/models/api/api_call.rb +87 -0
- data/lib/yodel/models/core/associations/association.rb +37 -0
- data/lib/yodel/models/core/associations/associations.rb +22 -0
- data/lib/yodel/models/core/associations/counts/many_association.rb +18 -0
- data/lib/yodel/models/core/associations/counts/one_association.rb +22 -0
- data/lib/yodel/models/core/associations/embedded/embedded_association.rb +47 -0
- data/lib/yodel/models/core/associations/embedded/embedded_record_array.rb +12 -0
- data/lib/yodel/models/core/associations/embedded/many_embedded_association.rb +62 -0
- data/lib/yodel/models/core/associations/embedded/one_embedded_association.rb +49 -0
- data/lib/yodel/models/core/associations/query/many_query_association.rb +10 -0
- data/lib/yodel/models/core/associations/query/one_query_association.rb +10 -0
- data/lib/yodel/models/core/associations/query/query_association.rb +64 -0
- data/lib/yodel/models/core/associations/record_association.rb +38 -0
- data/lib/yodel/models/core/associations/store/many_store_association.rb +32 -0
- data/lib/yodel/models/core/associations/store/one_store_association.rb +14 -0
- data/lib/yodel/models/core/associations/store/store_association.rb +51 -0
- data/lib/yodel/models/core/attachments/attachment.rb +73 -0
- data/lib/yodel/models/core/attachments/image.rb +38 -0
- data/lib/yodel/models/core/core.rb +15 -0
- data/lib/yodel/models/core/fields/alias_field.rb +32 -0
- data/lib/yodel/models/core/fields/array_field.rb +64 -0
- data/lib/yodel/models/core/fields/attachment_field.rb +42 -0
- data/lib/yodel/models/core/fields/boolean_field.rb +28 -0
- data/lib/yodel/models/core/fields/change_sensitive_array.rb +96 -0
- data/lib/yodel/models/core/fields/change_sensitive_hash.rb +53 -0
- data/lib/yodel/models/core/fields/color_field.rb +4 -0
- data/lib/yodel/models/core/fields/date_field.rb +35 -0
- data/lib/yodel/models/core/fields/decimal_field.rb +19 -0
- data/lib/yodel/models/core/fields/email_field.rb +10 -0
- data/lib/yodel/models/core/fields/enum_field.rb +33 -0
- data/lib/yodel/models/core/fields/field.rb +154 -0
- data/lib/yodel/models/core/fields/fields.rb +29 -0
- data/lib/yodel/models/core/fields/fields_field.rb +31 -0
- data/lib/yodel/models/core/fields/filter_mixin.rb +9 -0
- data/lib/yodel/models/core/fields/filtered_string_field.rb +5 -0
- data/lib/yodel/models/core/fields/filtered_text_field.rb +5 -0
- data/lib/yodel/models/core/fields/function_field.rb +28 -0
- data/lib/yodel/models/core/fields/hash_field.rb +54 -0
- data/lib/yodel/models/core/fields/html_field.rb +15 -0
- data/lib/yodel/models/core/fields/image_field.rb +11 -0
- data/lib/yodel/models/core/fields/integer_field.rb +25 -0
- data/lib/yodel/models/core/fields/password_field.rb +21 -0
- data/lib/yodel/models/core/fields/self_field.rb +27 -0
- data/lib/yodel/models/core/fields/string_field.rb +15 -0
- data/lib/yodel/models/core/fields/tags_field.rb +7 -0
- data/lib/yodel/models/core/fields/text_field.rb +7 -0
- data/lib/yodel/models/core/fields/time_field.rb +36 -0
- data/lib/yodel/models/core/functions/function.rb +471 -0
- data/lib/yodel/models/core/functions/functions.rb +2 -0
- data/lib/yodel/models/core/functions/trigger.rb +14 -0
- data/lib/yodel/models/core/log/log.rb +33 -0
- data/lib/yodel/models/core/log/log_entry.rb +12 -0
- data/lib/yodel/models/core/model/abstract_model.rb +59 -0
- data/lib/yodel/models/core/model/model.rb +460 -0
- data/lib/yodel/models/core/model/mongo_model.rb +25 -0
- data/lib/yodel/models/core/model/site_model.rb +17 -0
- data/lib/yodel/models/core/mongo/mongo.rb +3 -0
- data/lib/yodel/models/core/mongo/primary_key_factory.rb +12 -0
- data/lib/yodel/models/core/mongo/query.rb +68 -0
- data/lib/yodel/models/core/mongo/record_index.rb +89 -0
- data/lib/yodel/models/core/record/abstract_record.rb +411 -0
- data/lib/yodel/models/core/record/embedded_record.rb +47 -0
- data/lib/yodel/models/core/record/mongo_record.rb +83 -0
- data/lib/yodel/models/core/record/record.rb +386 -0
- data/lib/yodel/models/core/record/section.rb +21 -0
- data/lib/yodel/models/core/record/site_record.rb +31 -0
- data/lib/yodel/models/core/site/migration.rb +52 -0
- data/lib/yodel/models/core/site/remote.rb +61 -0
- data/lib/yodel/models/core/site/site.rb +202 -0
- data/lib/yodel/models/core/validations/email_address_validation.rb +24 -0
- data/lib/yodel/models/core/validations/embedded_records_validation.rb +31 -0
- data/lib/yodel/models/core/validations/errors.rb +51 -0
- data/lib/yodel/models/core/validations/excluded_from_validation.rb +10 -0
- data/lib/yodel/models/core/validations/excludes_combinations_validation.rb +18 -0
- data/lib/yodel/models/core/validations/format_validation.rb +10 -0
- data/lib/yodel/models/core/validations/included_in_validation.rb +10 -0
- data/lib/yodel/models/core/validations/includes_combinations_validation.rb +14 -0
- data/lib/yodel/models/core/validations/length_validation.rb +28 -0
- data/lib/yodel/models/core/validations/password_confirmation_validation.rb +11 -0
- data/lib/yodel/models/core/validations/required_validation.rb +9 -0
- data/lib/yodel/models/core/validations/unique_validation.rb +9 -0
- data/lib/yodel/models/core/validations/validation.rb +39 -0
- data/lib/yodel/models/core/validations/validations.rb +15 -0
- data/lib/yodel/models/email/email.rb +79 -0
- data/lib/yodel/models/migrations/01_record_model.rb +29 -0
- data/lib/yodel/models/migrations/02_page_model.rb +45 -0
- data/lib/yodel/models/migrations/03_layout_model.rb +38 -0
- data/lib/yodel/models/migrations/04_group_model.rb +61 -0
- data/lib/yodel/models/migrations/05_user_model.rb +24 -0
- data/lib/yodel/models/migrations/06_snippet_model.rb +13 -0
- data/lib/yodel/models/migrations/07_search_page_model.rb +32 -0
- data/lib/yodel/models/migrations/08_default_site_options.rb +21 -0
- data/lib/yodel/models/migrations/09_security_page_models.rb +36 -0
- data/lib/yodel/models/migrations/10_record_proxy_page_model.rb +17 -0
- data/lib/yodel/models/migrations/11_email_model.rb +28 -0
- data/lib/yodel/models/migrations/12_api_call_model.rb +23 -0
- data/lib/yodel/models/migrations/13_redirect_page_model.rb +13 -0
- data/lib/yodel/models/migrations/14_menu_model.rb +20 -0
- data/lib/yodel/models/models.rb +8 -0
- data/lib/yodel/models/pages/form_builder.rb +379 -0
- data/lib/yodel/models/pages/html_decorator.rb +132 -0
- data/lib/yodel/models/pages/layout.rb +120 -0
- data/lib/yodel/models/pages/menu.rb +32 -0
- data/lib/yodel/models/pages/page.rb +378 -0
- data/lib/yodel/models/pages/pages.rb +7 -0
- data/lib/yodel/models/pages/record_proxy_page.rb +188 -0
- data/lib/yodel/models/pages/redirect_page.rb +11 -0
- data/lib/yodel/models/search/search.rb +1 -0
- data/lib/yodel/models/search/search_page.rb +58 -0
- data/lib/yodel/models/security/facebook_login_page.rb +55 -0
- data/lib/yodel/models/security/group.rb +10 -0
- data/lib/yodel/models/security/guests_group.rb +5 -0
- data/lib/yodel/models/security/login_page.rb +20 -0
- data/lib/yodel/models/security/logout_page.rb +13 -0
- data/lib/yodel/models/security/noone_group.rb +5 -0
- data/lib/yodel/models/security/owner_group.rb +8 -0
- data/lib/yodel/models/security/password.rb +5 -0
- data/lib/yodel/models/security/password_reset_page.rb +47 -0
- data/lib/yodel/models/security/security.rb +10 -0
- data/lib/yodel/models/security/user.rb +33 -0
- data/lib/yodel/public/core/css/core.css +257 -0
- data/lib/yodel/public/core/css/reset.css +48 -0
- data/lib/yodel/public/core/images/cross.png +0 -0
- data/lib/yodel/public/core/images/spinner.gif +0 -0
- data/lib/yodel/public/core/images/tick.png +0 -0
- data/lib/yodel/public/core/images/yodel.png +0 -0
- data/lib/yodel/public/core/js/jquery.min.js +18 -0
- data/lib/yodel/public/core/js/json2.js +480 -0
- data/lib/yodel/public/core/js/yodel_jquery.js +238 -0
- data/lib/yodel/request/authentication.rb +76 -0
- data/lib/yodel/request/flash.rb +28 -0
- data/lib/yodel/request/request.rb +4 -0
- data/lib/yodel/requires.rb +47 -0
- data/lib/yodel/task_queue/queue_daemon.rb +33 -0
- data/lib/yodel/task_queue/queue_worker.rb +32 -0
- data/lib/yodel/task_queue/stats_thread.rb +27 -0
- data/lib/yodel/task_queue/task.rb +62 -0
- data/lib/yodel/task_queue/task_queue.rb +40 -0
- data/lib/yodel/types/date.rb +5 -0
- data/lib/yodel/types/object_id.rb +11 -0
- data/lib/yodel/types/time.rb +5 -0
- data/lib/yodel/version.rb +3 -0
- data/system/Library/LaunchDaemons/com.yodelcms.dns.plist +26 -0
- data/system/Library/LaunchDaemons/com.yodelcms.server.plist +26 -0
- data/system/etc/resolver/yodel +2 -0
- data/system/usr/local/bin/yodel_command_runner +2 -0
- data/system/usr/local/etc/yodel/development_settings.rb +28 -0
- data/system/usr/local/etc/yodel/production_settings.rb +27 -0
- data/system/var/log/yodel.log +0 -0
- data/test/helper.rb +18 -0
- data/test/test_yodel.rb +4 -0
- data/yodel.gemspec +47 -0
- metadata +501 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
class HTMLField < TextField
|
2
|
+
def search_terms_set(record)
|
3
|
+
to_text(record.get(name)).gsub(/\W+/, ' ').split
|
4
|
+
end
|
5
|
+
|
6
|
+
def to_text(html)
|
7
|
+
Hpricot(html.to_s).search('//text()').collect(&:to_s).collect(&:strip).join(' ').strip
|
8
|
+
end
|
9
|
+
|
10
|
+
def default_input_type
|
11
|
+
:html
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
Field::TYPES['html'] = HTMLField
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class IntegerField < Field
|
2
|
+
def numeric?
|
3
|
+
true
|
4
|
+
end
|
5
|
+
|
6
|
+
def json_action(action, value, record)
|
7
|
+
case action
|
8
|
+
when 'set'
|
9
|
+
record.set_raw(name, value.to_i)
|
10
|
+
when 'increment'
|
11
|
+
record.increment!(name, value.to_i)
|
12
|
+
end
|
13
|
+
record.changed!(name)
|
14
|
+
end
|
15
|
+
|
16
|
+
def untypecast(value, record)
|
17
|
+
value.to_i
|
18
|
+
end
|
19
|
+
|
20
|
+
def from_json(value, record)
|
21
|
+
value.to_i
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
Field::TYPES['integer'] = IntegerField
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class PasswordField < StringField
|
2
|
+
undef search_terms_set
|
3
|
+
def default_input_type
|
4
|
+
:password
|
5
|
+
end
|
6
|
+
|
7
|
+
def validate(record, errors)
|
8
|
+
PasswordConfirmationValidation.validate(nil, self, name, nil, record, errors)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def from_json(value, record)
|
13
|
+
if value.blank?
|
14
|
+
throw :ignore_value
|
15
|
+
else
|
16
|
+
value.to_s
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
Field::TYPES['password'] = PasswordField
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class SelfField < Field
|
2
|
+
def strip_nil?
|
3
|
+
true
|
4
|
+
end
|
5
|
+
|
6
|
+
def default_input_type
|
7
|
+
nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def validate(record, errors)
|
11
|
+
# noop
|
12
|
+
end
|
13
|
+
|
14
|
+
def typecast(value, record)
|
15
|
+
record
|
16
|
+
end
|
17
|
+
|
18
|
+
def untypecast(value, record)
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def from_json(value, record)
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
Field::TYPES['self'] = SelfField
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class StringField < Field
|
2
|
+
def search_terms_set(record)
|
3
|
+
record.get(name).to_s.gsub(/\W+/, ' ').split
|
4
|
+
end
|
5
|
+
|
6
|
+
def untypecast(value, record)
|
7
|
+
value.nil? ? nil : value.to_s
|
8
|
+
end
|
9
|
+
|
10
|
+
def from_json(value, record)
|
11
|
+
value.to_s
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
Field::TYPES['string'] = StringField
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class TimeField < Field
|
2
|
+
def default_input_type
|
3
|
+
:datetime
|
4
|
+
end
|
5
|
+
|
6
|
+
def before_create(record)
|
7
|
+
return unless name == 'created_at' || name == 'updated_at'
|
8
|
+
record.set(name, Time.now.utc)
|
9
|
+
end
|
10
|
+
|
11
|
+
def before_update(record)
|
12
|
+
return unless name == 'updated_at'
|
13
|
+
record.set(name, Time.now.utc)
|
14
|
+
end
|
15
|
+
|
16
|
+
def typecast(value, record)
|
17
|
+
value.nil? ? nil : Time.at(value)
|
18
|
+
end
|
19
|
+
|
20
|
+
def untypecast(value, record)
|
21
|
+
value.blank? ? nil : Time.at(value.to_i).utc
|
22
|
+
end
|
23
|
+
|
24
|
+
def from_json(value, record)
|
25
|
+
return nil unless value.present? && (value.is_a?(String) || value.is_a?(Hash))
|
26
|
+
if value.is_a?(Hash)
|
27
|
+
return nil unless ['year', 'month', 'day', 'hour', 'min'].all? {|field| value.key?(field) && !value[field].blank?}
|
28
|
+
sec = value['sec'] || 0
|
29
|
+
Time.new(value['year'], value['month'], value['day'], value['hour'], value['min'], sec).utc
|
30
|
+
else
|
31
|
+
Time.parse(value).utc
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
Field::TYPES['time'] = TimeField
|
@@ -0,0 +1,471 @@
|
|
1
|
+
class Function
|
2
|
+
attr_accessor :instructions, :source
|
3
|
+
|
4
|
+
def initialize(param)
|
5
|
+
if param.is_a?(String)
|
6
|
+
@source = param
|
7
|
+
@instructions = compile(param)
|
8
|
+
else
|
9
|
+
@instructions = param
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def inspect(instruction=nil)
|
14
|
+
instruction ||= self.instructions
|
15
|
+
|
16
|
+
name = instruction.shift
|
17
|
+
parameters = instruction.collect do |parameter|
|
18
|
+
if parameter.is_a?(Array)
|
19
|
+
inspect(parameter)
|
20
|
+
else
|
21
|
+
parameter
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
"#{name}(#{parameters.join(', ')})"
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
# ----------------------------------------
|
30
|
+
# Compilation
|
31
|
+
# ----------------------------------------
|
32
|
+
CALL_TOKEN = '.'
|
33
|
+
START_PARAMS_TOKEN = '('
|
34
|
+
END_PARAMS_TOKEN = ')'
|
35
|
+
START_HASH_TOKEN = '{'
|
36
|
+
END_HASH_TOKEN = '}'
|
37
|
+
PARAM_DELIM_TOKEN = ','
|
38
|
+
HASH_DELIM_TOKEN = ':'
|
39
|
+
ENTRY_FLAG = '!'
|
40
|
+
DOUBLE_QUOTE_TOKEN = '"'
|
41
|
+
SINGLE_QUOTE_TOKEN = "'"
|
42
|
+
|
43
|
+
def compile(source)
|
44
|
+
tokens = source.scan(/[\w\-\+]+|\.|\(|\)|\{|\}|:|,|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/)
|
45
|
+
parse(tokens)
|
46
|
+
end
|
47
|
+
|
48
|
+
def parse(tokens)
|
49
|
+
params = false
|
50
|
+
chain = false
|
51
|
+
hash = false
|
52
|
+
instructions = []
|
53
|
+
|
54
|
+
until tokens.empty?
|
55
|
+
token = tokens.shift
|
56
|
+
case token[0]
|
57
|
+
when CALL_TOKEN
|
58
|
+
chain = true
|
59
|
+
when START_PARAMS_TOKEN
|
60
|
+
params = true
|
61
|
+
instructions += parse(tokens)
|
62
|
+
when END_PARAMS_TOKEN
|
63
|
+
tokens.unshift(END_PARAMS_TOKEN) unless params
|
64
|
+
break
|
65
|
+
when ENTRY_FLAG
|
66
|
+
hash = true
|
67
|
+
instructions << ['entry'] + parse(tokens)
|
68
|
+
when START_HASH_TOKEN
|
69
|
+
tokens.unshift(ENTRY_FLAG)
|
70
|
+
instructions << ['hash'] + parse(tokens)
|
71
|
+
when END_HASH_TOKEN
|
72
|
+
tokens.unshift(END_HASH_TOKEN) if hash
|
73
|
+
break
|
74
|
+
when HASH_DELIM_TOKEN
|
75
|
+
instructions += parse(tokens)
|
76
|
+
when PARAM_DELIM_TOKEN
|
77
|
+
if params || hash
|
78
|
+
tokens.unshift(ENTRY_FLAG) if hash
|
79
|
+
instructions += parse(tokens)
|
80
|
+
else
|
81
|
+
tokens.unshift(PARAM_DELIM_TOKEN)
|
82
|
+
break
|
83
|
+
end
|
84
|
+
when DOUBLE_QUOTE_TOKEN, SINGLE_QUOTE_TOKEN
|
85
|
+
instructions << ['string', token[1...-1]]
|
86
|
+
else
|
87
|
+
if tokens.first == START_PARAMS_TOKEN
|
88
|
+
instructions << [token] + parse(tokens)
|
89
|
+
elsif token.to_i.to_s == token
|
90
|
+
instructions << ['int', token]
|
91
|
+
else
|
92
|
+
instructions << ['field', token]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
if chain
|
98
|
+
[['chain'] + instructions]
|
99
|
+
else
|
100
|
+
instructions
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# ----------------------------------------
|
105
|
+
# Execution
|
106
|
+
# ----------------------------------------
|
107
|
+
def execute(context, instruction=nil, parent_context=nil)
|
108
|
+
instruction ||= self.instructions.first
|
109
|
+
name, *params = instruction
|
110
|
+
parent_context ||= context
|
111
|
+
|
112
|
+
case name
|
113
|
+
when 'chain'
|
114
|
+
chain(context, parent_context, params)
|
115
|
+
when 'field'
|
116
|
+
get_field(context, parent_context, params.first)
|
117
|
+
when 'find'
|
118
|
+
find_record(context, parent_context, params[0], params[1])
|
119
|
+
when 'changed'
|
120
|
+
changed(context, parent_context, params.first)
|
121
|
+
when 'previous_value'
|
122
|
+
previous_value(context, parent_context, params.first)
|
123
|
+
when 'collect'
|
124
|
+
collect(context, parent_context, params.first)
|
125
|
+
when 'majority'
|
126
|
+
majority(context, parent_context, params.first)
|
127
|
+
when 'count'
|
128
|
+
count(context, parent_context, params.first)
|
129
|
+
when 'invert'
|
130
|
+
invert(context)
|
131
|
+
when 'unique'
|
132
|
+
unique(context, parent_context, params.first)
|
133
|
+
when 'average'
|
134
|
+
average(context, parent_context, params)
|
135
|
+
when 'as_a_percentage_of'
|
136
|
+
as_a_percentage_of(context, parent_context, params.first)
|
137
|
+
when 'present'
|
138
|
+
present(context)
|
139
|
+
when 'blank'
|
140
|
+
blank(context)
|
141
|
+
when 'sum'
|
142
|
+
sum(context, parent_context, params)
|
143
|
+
when 'subtract'
|
144
|
+
subtract(context, parent_context, params)
|
145
|
+
when 'multiply'
|
146
|
+
multiply(context, parent_context, params)
|
147
|
+
when 'round'
|
148
|
+
round(context)
|
149
|
+
when 'if'
|
150
|
+
binary_if(context, parent_context, params[0], params[1], params[2])
|
151
|
+
when 'greater_than'
|
152
|
+
greater_than(context, parent_context, params.first)
|
153
|
+
when 'greater_than_or_equal_to'
|
154
|
+
greater_than_or_equal_to(context, parent_context, params.first)
|
155
|
+
when 'less_than'
|
156
|
+
less_than(context, parent_context, params.first)
|
157
|
+
when 'less_than_or_equal_to'
|
158
|
+
less_than_or_equal_to(context, parent_context, params.first)
|
159
|
+
when 'not_equal'
|
160
|
+
not_equal(context, parent_context, params.first)
|
161
|
+
when 'and'
|
162
|
+
binary_and(context, parent_context, params[0], params[1])
|
163
|
+
when 'or'
|
164
|
+
binary_or(context, parent_context, params[0], params[1])
|
165
|
+
when 'include'
|
166
|
+
set_include(context, parent_context, params.first)
|
167
|
+
when 'strip'
|
168
|
+
strip(context)
|
169
|
+
when 'format'
|
170
|
+
format(context, parent_context, params.first)
|
171
|
+
when 'set'
|
172
|
+
set_field(context, parent_context, params[0], params[1])
|
173
|
+
when 'update'
|
174
|
+
update_field(context, parent_context, params[0], params[1])
|
175
|
+
when 'min'
|
176
|
+
min(context, parent_context, params[0], params[1])
|
177
|
+
when 'max'
|
178
|
+
max(context, parent_context, params[0], params[1])
|
179
|
+
when 'increment'
|
180
|
+
increment(context, parent_context, params[0], params[1])
|
181
|
+
when 'complement'
|
182
|
+
complement(context, parent_context, params[0], params[1])
|
183
|
+
when 'each'
|
184
|
+
each(context, parent_context, params.first)
|
185
|
+
when 'deliver'
|
186
|
+
deliver_email(context, parent_context, params[0], params[1])
|
187
|
+
when 'call_api'
|
188
|
+
call_api(context, parent_context, params[0], params[1])
|
189
|
+
|
190
|
+
# literals
|
191
|
+
when 'string'
|
192
|
+
params.first
|
193
|
+
when 'int'
|
194
|
+
params.first.to_i
|
195
|
+
when 'hash'
|
196
|
+
Hash[params.collect {|entry| execute(context, entry, parent_context)}]
|
197
|
+
when 'entry'
|
198
|
+
[execute(context, params.first, parent_context), execute(context, params.last, parent_context)]
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
protected
|
203
|
+
def chain(context, parent_context, methods)
|
204
|
+
#parent_context = context
|
205
|
+
methods.each do |method|
|
206
|
+
context = execute(context, method, parent_context)
|
207
|
+
end
|
208
|
+
context
|
209
|
+
end
|
210
|
+
|
211
|
+
def get_field(context, parent_context, name)
|
212
|
+
case name
|
213
|
+
when 'self'
|
214
|
+
context
|
215
|
+
when 'root'
|
216
|
+
parent_context
|
217
|
+
# TODO: these should be caught as booleans instead of being treated as field names
|
218
|
+
when 'true'
|
219
|
+
true
|
220
|
+
when 'false'
|
221
|
+
false
|
222
|
+
else
|
223
|
+
context.get(name)
|
224
|
+
end
|
225
|
+
rescue
|
226
|
+
nil
|
227
|
+
end
|
228
|
+
|
229
|
+
def find_record(context, parent_context, model_name, key)
|
230
|
+
raise "Parent context of find_record must respond to site" unless parent_context.respond_to?(:site)
|
231
|
+
model_name = execute(context, model_name, parent_context)
|
232
|
+
key = execute(context, key, parent_context)
|
233
|
+
model = parent_context.site.model_by_plural_name(model_name)
|
234
|
+
|
235
|
+
if key == 'id'
|
236
|
+
value = BSON::ObjectId.from_string(context)
|
237
|
+
else
|
238
|
+
value = context
|
239
|
+
end
|
240
|
+
|
241
|
+
model.where(key => value).first
|
242
|
+
end
|
243
|
+
|
244
|
+
def previous_value(context, parent_context, name)
|
245
|
+
context.field_was(execute(context, name, parent_context))
|
246
|
+
end
|
247
|
+
|
248
|
+
# TODO: change format from changed('name') to name.changed
|
249
|
+
def changed(context, parent_context, name)
|
250
|
+
context.changed?(execute(context, name, parent_context))
|
251
|
+
end
|
252
|
+
|
253
|
+
def set_field(context, parent_context, field, value)
|
254
|
+
field = execute(parent_context, field, parent_context)
|
255
|
+
value = execute(parent_context, value, parent_context)
|
256
|
+
context.set(field, value)
|
257
|
+
end
|
258
|
+
|
259
|
+
def update_field(context, parent_context, field, value)
|
260
|
+
set_field(context, parent_context, field, value)
|
261
|
+
context.save
|
262
|
+
end
|
263
|
+
|
264
|
+
def increment(context, parent_context, field, value)
|
265
|
+
field = execute(context, field, parent_context)
|
266
|
+
value = execute(context, value, parent_context)
|
267
|
+
context.increment!(field, value)
|
268
|
+
end
|
269
|
+
|
270
|
+
def collect(context, parent_context, field)
|
271
|
+
raise "Context to collect must respond to collect" unless context.respond_to?(:collect)
|
272
|
+
context.collect {|item| execute(item, field, parent_context)}
|
273
|
+
end
|
274
|
+
|
275
|
+
def each(context, parent_context, statement)
|
276
|
+
raise "Context to each must respond to each" unless context.respond_to?(:each)
|
277
|
+
context.each {|item| execute(item, statement, parent_context)}
|
278
|
+
end
|
279
|
+
|
280
|
+
def majority(context, parent_context, field)
|
281
|
+
unless context.respond_to?(:size) && context.respond_to?(:count)
|
282
|
+
raise "Majority context must be enumerable"
|
283
|
+
end
|
284
|
+
|
285
|
+
valid = context.count {|item| execute(item, field, parent_context)}
|
286
|
+
valid >= (context.size - valid)
|
287
|
+
end
|
288
|
+
|
289
|
+
def count(context, parent_context, field)
|
290
|
+
unless context.respond_to?(:size) && context.respond_to?(:count)
|
291
|
+
raise "Count context must be enumerable"
|
292
|
+
end
|
293
|
+
|
294
|
+
if field.nil?
|
295
|
+
context.size
|
296
|
+
else
|
297
|
+
context.count {|item| execute(item, field, parent_context)}
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
def invert(context)
|
302
|
+
!context
|
303
|
+
end
|
304
|
+
|
305
|
+
def unique(context, parent_context, field)
|
306
|
+
collect(context, parent_context, field).uniq
|
307
|
+
end
|
308
|
+
|
309
|
+
def average(context, parent_context, params)
|
310
|
+
if context.respond_to?(:collect) && context.respond_to?(:size) && params.size == 1
|
311
|
+
count = context.size
|
312
|
+
else
|
313
|
+
count = params.size
|
314
|
+
end
|
315
|
+
|
316
|
+
return 0.0 if count == 0
|
317
|
+
sum(context, parent_context, params).to_f / count.to_f
|
318
|
+
end
|
319
|
+
|
320
|
+
def as_a_percentage_of(context, parent_context, count)
|
321
|
+
count = execute(context, count, parent_context)
|
322
|
+
context = context.to_f
|
323
|
+
count = count.to_f
|
324
|
+
|
325
|
+
if context == 0 || count == 0
|
326
|
+
return 0
|
327
|
+
else
|
328
|
+
(context / count) * 100
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
def present(context)
|
333
|
+
context.present?
|
334
|
+
end
|
335
|
+
|
336
|
+
def blank(context)
|
337
|
+
context.blank?
|
338
|
+
end
|
339
|
+
|
340
|
+
def sum(context, parent_context, params)
|
341
|
+
if context.respond_to?(:collect) && params.size == 1
|
342
|
+
collect(context, parent_context, params.first).compact.inject(&:+)
|
343
|
+
else
|
344
|
+
params.collect {|method| execute(context, method, parent_context)}.compact.inject(&:+)
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
def subtract(context, parent_context, params)
|
349
|
+
if context.respond_to?(:collect) && params.size == 1
|
350
|
+
collect(context, parent_context, params.first).compact.inject(&:-)
|
351
|
+
else
|
352
|
+
params.collect {|method| execute(context, method, parent_context)}.compact.inject(&:-)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
def multiply(context, parent_context, params)
|
357
|
+
if context.respond_to?(:collect) && params.size == 1
|
358
|
+
collect(context, parent_context, params.first).compact.inject(&:*)
|
359
|
+
else
|
360
|
+
params.collect {|method| execute(context, method, parent_context)}.compact.inject(&:*)
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
def complement(context, parent_context, set1, set2)
|
365
|
+
set1 = execute(context, set1, parent_context)
|
366
|
+
set2 = execute(context, set2, parent_context)
|
367
|
+
raise "Sets must be iterable" unless set1.respond_to?(:to_a) && set2.respond_to?(:to_a)
|
368
|
+
set2.to_a - set1.to_a
|
369
|
+
end
|
370
|
+
|
371
|
+
def set_include(context, parent_context, item)
|
372
|
+
item = execute(context, item, parent_context)
|
373
|
+
raise "Context must respond to include?" unless context.respond_to?(:include?)
|
374
|
+
context.include?(item)
|
375
|
+
end
|
376
|
+
|
377
|
+
def round(context)
|
378
|
+
context.to_f.round
|
379
|
+
end
|
380
|
+
|
381
|
+
def binary_if(context, parent_context, condition, true_exp, false_exp)
|
382
|
+
if execute(context, condition, parent_context)
|
383
|
+
execute(context, true_exp, parent_context) if true_exp
|
384
|
+
else
|
385
|
+
execute(context, false_exp, parent_context) if false_exp
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
def greater_than(context, parent_context, value)
|
390
|
+
value = execute(parent_context, value, parent_context)
|
391
|
+
context > value
|
392
|
+
end
|
393
|
+
|
394
|
+
def less_than(context, parent_context, value)
|
395
|
+
value = execute(parent_context, value, parent_context)
|
396
|
+
context < value
|
397
|
+
end
|
398
|
+
|
399
|
+
def greater_than_or_equal_to(context, parent_context, value)
|
400
|
+
value = execute(parent_context, value, parent_context)
|
401
|
+
context >= value
|
402
|
+
end
|
403
|
+
|
404
|
+
def less_than_or_equal_to(context, parent_context, value)
|
405
|
+
value = execute(parent_context, value, parent_context)
|
406
|
+
context <= value
|
407
|
+
end
|
408
|
+
|
409
|
+
def not_equal(context, parent_context, value)
|
410
|
+
value = execute(parent_context, value, parent_context)
|
411
|
+
context != value
|
412
|
+
end
|
413
|
+
|
414
|
+
def binary_and(context, parent_context, operand1, operand2)
|
415
|
+
operand1 = execute(context, operand1, parent_context)
|
416
|
+
|
417
|
+
if operand2
|
418
|
+
operand2 = execute(context, operand2, parent_context)
|
419
|
+
operand1 && operand2
|
420
|
+
else
|
421
|
+
context && operand1
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
def binary_or(context, parent_context, operand1, operand2)
|
426
|
+
operand1 = execute(context, operand1, parent_context)
|
427
|
+
|
428
|
+
if operand2
|
429
|
+
operand2 = execute(context, operand2, parent_context)
|
430
|
+
operand1 || operand2
|
431
|
+
else
|
432
|
+
context || operand1
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
def strip(context)
|
437
|
+
context.to_s.strip
|
438
|
+
end
|
439
|
+
|
440
|
+
def deliver_email(context, parent_context, name, hash)
|
441
|
+
raise "Context of deliver_email must respond to site" unless context.respond_to?(:site)
|
442
|
+
email = context.site.emails[execute(context, name, parent_context)]
|
443
|
+
email.deliver(execute(context, hash, parent_context))
|
444
|
+
end
|
445
|
+
|
446
|
+
def call_api(context, parent_context, name, hash)
|
447
|
+
raise "Context of call_api must respond to site" unless context.respond_to?(:site)
|
448
|
+
api = context.site.api_calls[execute(context, name, parent_context)]
|
449
|
+
api.call(execute(context, hash, parent_context))
|
450
|
+
end
|
451
|
+
|
452
|
+
def format(context, parent_context, str)
|
453
|
+
str = execute(context, str, parent_context)
|
454
|
+
str.gsub(/{{\s*([\w\.]+)\s*}}/) do |field|
|
455
|
+
fn = Function.new($1)
|
456
|
+
fn.execute(context, nil, parent_context)
|
457
|
+
end
|
458
|
+
end
|
459
|
+
|
460
|
+
# TODO: add call styles to min and max:
|
461
|
+
# items.min(index)
|
462
|
+
# items.collect(index).min
|
463
|
+
# min(one, two)
|
464
|
+
def min(context, parent_context, one, two)
|
465
|
+
[execute(context, one, parent_context), execute(context, two, parent_context)].min
|
466
|
+
end
|
467
|
+
|
468
|
+
def max(context, parent_context, one, two)
|
469
|
+
[execute(context, one, parent_context), execute(context, two, parent_context)].max
|
470
|
+
end
|
471
|
+
end
|