nemo 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,86 @@
1
+ class Nemo::MetaObject::MetaObject
2
+
3
+ attr_accessor :attributes, :baseobject
4
+
5
+ def initialize(baseobject, options=Hash.new, &block)
6
+ @attributes = Array.new
7
+ @baseobject = baseobject
8
+ options.each { |k,v| send(k.to_s+'=', v) }
9
+ collect(&block)
10
+ refresh_cache
11
+ end
12
+
13
+ def <<(attribute)
14
+ attribute.metaobject = self
15
+ @attributes << attribute
16
+ end
17
+
18
+ def [](*symbols)
19
+ return attribute_of(symbols.first) if symbols.size == 1
20
+ symbols.collect { |s| attribute_of(s) }
21
+ end
22
+
23
+ def collector_class
24
+ Nemo::MetaObject::AttributeCollector
25
+ end
26
+
27
+ def collect(&block)
28
+ collector_class.new(self).collect(&block)
29
+ @attributes
30
+ end
31
+
32
+ def accept(visitor, &block)
33
+ visible_attributes.collect{ |a| a.accept(visitor) }.join
34
+ end
35
+
36
+ def attribute_of(symbol)
37
+ @attributes.find { |a| a.symbol == symbol }
38
+ end
39
+
40
+ def hide_attribute_of(symbol)
41
+ a = attribute_of(symbol)
42
+ a.hidden = true if a
43
+ end
44
+
45
+ def hide_attributes_of(*symbols)
46
+ symbols.each { |s| hide_attribute_of(s) }
47
+ end
48
+
49
+ def hide!(*symbols)
50
+ hide_attributes_of(*symbols)
51
+ self
52
+ end
53
+
54
+ def multiple_attributes
55
+ @attributes.find_all { |a| a.multiple_attribute? }
56
+ end
57
+
58
+ def remove_attribute_of(symbol)
59
+ @attributes.delete(attribute_of(symbol))
60
+ end
61
+
62
+ def remove_attributes_of(*symbols)
63
+ symbols.each { |s| remove_attribute(s) }
64
+ end
65
+
66
+ def symbols
67
+ @attributes.collect { |a| a.symbol }
68
+ end
69
+
70
+ def visible_attributes
71
+ @attributes.reject { |a| a.hidden? }
72
+ end
73
+
74
+ def commit_cache
75
+ @attributes.each { |a| a.commit_cache }
76
+ end
77
+
78
+ def refresh_cache
79
+ @attributes.each { |a| a.refresh_cache }
80
+ end
81
+
82
+ def validate
83
+ @attributes.collect{ |a| a.validate_cache }.compact
84
+ end
85
+
86
+ end
@@ -0,0 +1,32 @@
1
+ class Nemo::MetaObject::ValidationError
2
+
3
+ attr_accessor :attribute, :error_string
4
+
5
+ def initialize(a, error_string)
6
+ @attribute = a
7
+ @error_string = error_string
8
+ end
9
+
10
+ def to_s
11
+ sprintf('%s: %s', @attribute.label, @error_string)
12
+ end
13
+
14
+ end
15
+
16
+ class Nemo::MetaObject::ValidationRule
17
+
18
+ extend Nemo::Util::Accessors
19
+
20
+ call_accessor :error_string
21
+ proc_accessor :rule
22
+
23
+ def initialize(&block)
24
+ @error_string = 'not valid'
25
+ @rule = block
26
+ end
27
+
28
+ def valid?(value)
29
+ begin Nemo::Util.to_bool(@rule.call(value)) if @rule rescue false end
30
+ end
31
+
32
+ end
data/lib/nemo/util.rb ADDED
@@ -0,0 +1,31 @@
1
+ # Utility methods and objects
2
+ #
3
+ module Nemo::Util
4
+
5
+ module_function
6
+
7
+ # Format string as PascalCase (my_var, myVar become MyVar)
8
+ #
9
+ def pascal_case(value)
10
+ value.to_s.split('_').collect{ |w| w[0,1].upcase+w[1..-1] }.join
11
+ end
12
+
13
+ # Converts a value to true or false
14
+ #
15
+ # True:: <tt>true, :true, "true", :yes, "yes"</tt>
16
+ # False:: <tt>false, :false, "false", :no, "no", nil</tt>
17
+ #
18
+ # Otherwise (value && true) is applied
19
+ #
20
+ def to_bool(value)
21
+ value = false if [:false, 'false', :no, 'no', nil].include?(value)
22
+ value = true if [:true, 'true', :yes, 'yes'].include?(value)
23
+ value && true
24
+ end
25
+
26
+ end
27
+
28
+ require 'nemo/util/accessors'
29
+ require 'nemo/util/blankslate'
30
+ require 'nemo/util/collector'
31
+ require 'nemo/util/date'
@@ -0,0 +1,85 @@
1
+ # Alternate attr_accessor methods
2
+ #
3
+ module Nemo::Util::Accessors
4
+
5
+ # Call syntax
6
+ #
7
+ # class Foo
8
+ # extend Nemo::Util::Accessors
9
+ # call_accessor :bar
10
+ # end
11
+ # foo = Foo.new
12
+ # foo.bar = 1 # @bar = 1 (standard writer)
13
+ # foo.bar(1) # @bar = 1 (call writer)
14
+ # foo.bar # => 1 (standard reader)
15
+ #
16
+ def call_accessor(*symbols)
17
+ str = String.new
18
+ class_eval { attr_accessor(*symbols) }
19
+ symbols.each do |symbol|
20
+ str << "
21
+ def #{symbol}(*args)
22
+ @#{symbol} = args.first if args.size > 0
23
+ @#{symbol}
24
+ end
25
+ "
26
+ class_eval(str)
27
+ end
28
+ end
29
+
30
+ # Boolean syntax
31
+ #
32
+ # class Foo
33
+ # extend Nemo::Util::Accessors
34
+ # bool_accessor :bar
35
+ # end
36
+ # foo = Foo.new
37
+ # foo.bar = false # @bar = false (standard writer)
38
+ # foo.bar # @bar = true (always sets to true)
39
+ # foo.bar(false) # @bar = false (call writer)
40
+ # foo.bar? # => true (true or false)
41
+ #
42
+ def bool_accessor(*symbols)
43
+ str = String.new
44
+ class_eval { attr_accessor(*symbols) }
45
+ symbols.each do |symbol|
46
+ str << "
47
+ def #{symbol}(*args)
48
+ @#{symbol} = (args.size > 0) ? Nemo::Util.to_bool(args.first) : true
49
+ end
50
+ def #{symbol}?
51
+ Nemo::Util.to_bool(@#{symbol})
52
+ end
53
+ "
54
+ class_eval(str)
55
+ end
56
+ end
57
+
58
+ # Procedure syntax
59
+ #
60
+ # class Foo
61
+ # extend Nemo::Util::Accessors
62
+ # proc_accessor :bar
63
+ # end
64
+ # foo = Foo.new
65
+ # block = Proc.new { 1 }
66
+ # foo.bar = block # @bar = Proc.new { 1 } (standard writer)
67
+ # foo.bar { 1 } # @bar = Proc.new { 1 } (&block writer)
68
+ # foo.bar # @bar => <Proc:x> (standard reader)
69
+ # foo.bar.call # => 1
70
+ #
71
+ def proc_accessor(*symbols)
72
+ str = String.new
73
+ class_eval { attr_accessor(*symbols) }
74
+ symbols.each do |symbol|
75
+ str << "
76
+ def #{symbol}(&block)
77
+ @#{symbol} = block if block_given?
78
+ @#{symbol}
79
+ end
80
+ "
81
+ class_eval(str)
82
+ end
83
+ end
84
+
85
+ end
@@ -0,0 +1,6 @@
1
+ # Minimal object with only three methods: <tt>\_\_id__, \_\_send__ and instance_eval</tt>
2
+ #
3
+ class Nemo::Util::BlankSlate
4
+ safe_methods = ['__id__', '__send__', 'instance_eval']
5
+ (instance_methods-safe_methods).each { |m| undef_method m }
6
+ end
@@ -0,0 +1,54 @@
1
+ # Collect a list of objects using minimal syntax,
2
+ # used in conjunction with Nemo::Util::Accessors.
3
+ # Use the << or o methods to add an object.
4
+ # All other messages are forwarded to the most recently added object.
5
+ #
6
+ # class Person
7
+ # extend Nemo::Util::Accessors
8
+ # call_accessor :name, :age
9
+ # end
10
+ # collector = Nemo::Util::ObjectCollector.new
11
+ # people = collector.collect do
12
+ # o Person.new
13
+ # name 'Kevin'
14
+ # age 28
15
+ # o Person.new
16
+ # name 'Sara'
17
+ # age 22
18
+ # end
19
+ # people.inspect
20
+ # # => [<Person @age=28, @name="Kevin">, <Person @age=22, @name="Sara">]
21
+ #
22
+ class Nemo::Util::ObjectCollector < Nemo::Util::BlankSlate
23
+
24
+ attr_accessor :index_error
25
+ attr_accessor :objects
26
+
27
+ def self.collect(&block)
28
+ self.new.collect(&block)
29
+ end
30
+
31
+ def initialize
32
+ @objects = Array.new
33
+ @index_error = 'no objects defined'
34
+ end
35
+
36
+ def <<(object)
37
+ @objects << object
38
+ end
39
+
40
+ def o(object)
41
+ self << object
42
+ end
43
+
44
+ def collect(&block)
45
+ instance_eval(&block) if block_given?
46
+ @objects
47
+ end
48
+
49
+ def method_missing(symbol, *args, &block)
50
+ raise IndexError, @index_error if @objects.size == 0
51
+ @objects.last.send(symbol, *args, &block)
52
+ end
53
+
54
+ end
@@ -0,0 +1,41 @@
1
+ class Date
2
+
3
+ # Calendar representation of a month. Consists of an
4
+ # array of weeks, each week an array of 7 days, each day a Date object.
5
+ # Padded with days showing for previous and next month.
6
+ #
7
+ def calendar
8
+ # months
9
+ curr_month = Date.new(self.year, self.month, 1)
10
+ prev_month = (curr_month << 1)
11
+ next_month = (curr_month >> 1)
12
+
13
+ # previous month days
14
+ prev_days = Array.new
15
+ prev_in_curr = curr_month.wday
16
+ ((curr_month-1)-(prev_in_curr-1)).upto(curr_month-1) { |d| prev_days << d }
17
+
18
+ # current month days
19
+ curr_days = Array.new
20
+ curr_month.upto(next_month-1) { |d| curr_days << d }
21
+
22
+ # next month days
23
+ next_days = Array.new
24
+ days = prev_days+curr_days
25
+ weeks = (days.size.to_f/7).ceil
26
+ cdays_size = weeks*7
27
+ next_in_curr = (cdays_size-days.size)
28
+ next_month.upto(next_month+(next_in_curr-1)) { |d| next_days << d }
29
+ days += next_days
30
+
31
+ # split into weeks
32
+ table = Array.new
33
+ days.each do |day|
34
+ table << Array.new if table.size == 0 or table.last.size == 7
35
+ table.last << day
36
+ end
37
+
38
+ table
39
+ end
40
+
41
+ end
@@ -0,0 +1,4 @@
1
+ module Nemo::Visitors; end
2
+ require 'nemo/visitors/visitor'
3
+ require 'nemo/visitors/viewer'
4
+ require 'nemo/visitors/editor'
@@ -0,0 +1,186 @@
1
+ class Nemo::Visitors::Editor < Nemo::Visitors::Visitor
2
+
3
+ attr_accessor :validation_errors, :multiple_attribute_editors
4
+ bool_accessor :subform
5
+
6
+ def initialize(metaobject)
7
+ super
8
+ @subform = false
9
+ @validation_errors = Array.new
10
+ initialize_subforms
11
+ end
12
+
13
+ def multiattr_class
14
+ Nemo::Components::MultipleAttributeContainer
15
+ end
16
+
17
+ def calendar_class
18
+ Nemo::Components::MiniCalendar
19
+ end
20
+
21
+ def initialize_subforms
22
+ @multiple_attribute_editors = Hash.new
23
+ @metaobject.multiple_attributes.each do |a|
24
+ @multiple_attribute_editors[a] = multiattr_class.new(a)
25
+ end
26
+ end
27
+
28
+ def valid?
29
+ @validation_errors = @metaobject.validate
30
+ @validation_errors.empty?
31
+ end
32
+
33
+ def validation_errors?
34
+ @validation_errors.size > 0
35
+ end
36
+
37
+ def children
38
+ @multiple_attribute_editors.values
39
+ end
40
+
41
+ def backtrack_state(snapshot)
42
+ super
43
+ snapshot.add(self.children)
44
+ end
45
+
46
+ def commit
47
+ @metaobject.commit_cache
48
+ end
49
+
50
+ def cancel
51
+ answer nil
52
+ end
53
+
54
+ def save
55
+ if valid?
56
+ commit
57
+ answer @metaobject.baseobject
58
+ end
59
+ end
60
+
61
+ def remove_errors(a)
62
+ @validation_errors.delete_if { |e| e.attribute == a }
63
+ end
64
+
65
+ def call_date_selector(a)
66
+ current = (a.date_from_cache || Date.today)
67
+ if date = call( calendar_class.new(current) )
68
+ a.date_to_cache(date)
69
+ remove_errors(a)
70
+ if error = a.validate_cache
71
+ @validation_errors << error
72
+ end
73
+ end
74
+ end
75
+
76
+ def render_buttons
77
+ r.table_row do
78
+ r.table_data.colspan(2).with do
79
+ r.submit_button.id(@metaobject.object_id.to_s+'_save').value('save').callback{ save }
80
+ r.space
81
+ r.submit_button.id(@metaobject.object_id.to_s+'_cancel').value('cancel').callback{ cancel }
82
+ end
83
+ end
84
+ end
85
+
86
+ def render
87
+ if subform?
88
+ render_main
89
+ else
90
+ r.form.id(@metaobject.object_id).with { render_main }
91
+ end
92
+ end
93
+
94
+ def render_label(a, &block)
95
+ r.table_row do
96
+ r.table_header(a.label)
97
+ r.table_data { block ? block.call : r.space }
98
+ end
99
+ end
100
+
101
+ def render_main
102
+ r.table do
103
+ render_validation_errors
104
+ render_fields
105
+ render_buttons
106
+ end
107
+ end
108
+
109
+ def render_fields
110
+ @metaobject.accept(self)
111
+ end
112
+
113
+ def render_validation_errors
114
+ if validation_errors?
115
+ r.table_row { r.table_header.colspan(2).with('The following fields are not valid. Please correct.') }
116
+ r.table_row { r.table_data.colspan(2).with { @validation_errors.each { |e| r.text('&bull;&nbsp;'); r.encode_text(e.to_s); r.break } } }
117
+ end
118
+ end
119
+
120
+ def visit_text_attribute(a)
121
+ return if a.read_only?
122
+ render_label(a) do
123
+ if a.multiline?
124
+ r.textarea.id(a.object_id).callback{ |input| @metaobject[a.symbol].cache = input }.with { r.encode_text(a.cache) }
125
+ else
126
+ r.text_input.id(a.object_id).maxlength(a.maxlength).value(a.cache).callback { |input| @metaobject[a.symbol].cache = input }
127
+ end
128
+ end
129
+ end
130
+
131
+ def visit_password_attribute(a)
132
+ return if a.read_only?
133
+ render_label(a) do
134
+ r.text_input.id(a.object_id).type('password').maxlength(a.maxlength).value(a.cache).callback { |input| @metaobject[a.symbol].cache = input }
135
+ r.break
136
+ r.text_input.id(a.object_id.to_s+'_confirm').type('password').maxlength(a.maxlength).value(a.cache_confirm).callback { |input| @metaobject[a.symbol].cache_confirm = input }
137
+ end
138
+ end
139
+
140
+ def visit_date_attribute(a)
141
+ return if a.read_only?
142
+ date = a.cache
143
+ render_label(a) do
144
+ r.text_input.id(a.object_id).maxlength(a.maxlength).value(a.cache).callback { |input| @metaobject[a.symbol].cache = input }
145
+ r.space
146
+ r.nemo_calendar_button.callback { call_date_selector(a) }
147
+ end
148
+ end
149
+
150
+ def visit_boolean_attribute(a)
151
+ return if a.read_only?
152
+ render_label(a) do
153
+ r.select_list(a.items).id(a.object_id).labels(a.formatted_items).selected([a.cache]).callback do |input|
154
+ @metaobject[a.symbol].cache = Nemo::Util.to_bool(input.first)
155
+ end
156
+ end
157
+ end
158
+
159
+ def visit_multiple_attribute(a)
160
+ return if a.read_only?
161
+ render_label(a) do
162
+ r.render( @multiple_attribute_editors[a] )
163
+ end
164
+ end
165
+
166
+ def visit_single_relationship_attribute(a)
167
+ return if a.read_only?
168
+ render_label(a) do
169
+ r.select_list(a.items).id(a.object_id).labels(a.formatted_items).selected([a.cache]).callback do |input|
170
+ @metaobject[a.symbol].cache = input.first
171
+ end
172
+ end
173
+ end
174
+
175
+ def visit_multiple_relationship_attribute(a)
176
+ return if a.read_only?
177
+ items = a.items
178
+ size = (items.size < 8) ? items.size : 8
179
+ render_label(a) do
180
+ r.select_list(a.items).id(a.object_id).size(size).multiple.labels(a.formatted_items).selected(a.cache).callback do |input|
181
+ @metaobject[a.symbol].cache = input
182
+ end
183
+ end
184
+ end
185
+
186
+ end