ironnails 0.0.1

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.
Files changed (60) hide show
  1. data/Rakefile +21 -0
  2. data/VERSION +1 -0
  3. data/init.rb +1 -0
  4. data/ironnails.gemspec +96 -0
  5. data/lib/iron_nails.rb +1 -0
  6. data/lib/ironnails/bin/IronNails.Library.dll +0 -0
  7. data/lib/ironnails/bin/IronRuby.Libraries.Yaml.dll +0 -0
  8. data/lib/ironnails/bin/IronRuby.Libraries.dll +0 -0
  9. data/lib/ironnails/bin/IronRuby.dll +0 -0
  10. data/lib/ironnails/bin/Microsoft.Dynamic.dll +0 -0
  11. data/lib/ironnails/bin/Microsoft.Scripting.Core.dll +0 -0
  12. data/lib/ironnails/bin/Microsoft.Scripting.ExtensionAttribute.dll +0 -0
  13. data/lib/ironnails/bin/Microsoft.Scripting.dll +0 -0
  14. data/lib/ironnails/config/configuration.rb +141 -0
  15. data/lib/ironnails/config/initializer.rb +144 -0
  16. data/lib/ironnails/controller/base.rb +135 -0
  17. data/lib/ironnails/controller/view_operations.rb +101 -0
  18. data/lib/ironnails/controller.rb +2 -0
  19. data/lib/ironnails/core_ext/array.rb +15 -0
  20. data/lib/ironnails/core_ext/class/attribute_accessors.rb +57 -0
  21. data/lib/ironnails/core_ext/class.rb +8 -0
  22. data/lib/ironnails/core_ext/fixnum.rb +22 -0
  23. data/lib/ironnails/core_ext/hash.rb +32 -0
  24. data/lib/ironnails/core_ext/kernel.rb +26 -0
  25. data/lib/ironnails/core_ext/string.rb +58 -0
  26. data/lib/ironnails/core_ext/symbol.rb +78 -0
  27. data/lib/ironnails/core_ext/system/net/web_request.rb +110 -0
  28. data/lib/ironnails/core_ext/system/security/secure_string.rb +18 -0
  29. data/lib/ironnails/core_ext/system/windows/markup/xaml_reader.rb +6 -0
  30. data/lib/ironnails/core_ext/system/windows/ui_element.rb +17 -0
  31. data/lib/ironnails/core_ext/time.rb +28 -0
  32. data/lib/ironnails/core_ext.rb +12 -0
  33. data/lib/ironnails/errors.rb +19 -0
  34. data/lib/ironnails/iron_xml.rb +83 -0
  35. data/lib/ironnails/logger.rb +4 -0
  36. data/lib/ironnails/logging/buffered_logger.rb +137 -0
  37. data/lib/ironnails/logging/class_logger.rb +29 -0
  38. data/lib/ironnails/models/base.rb +16 -0
  39. data/lib/ironnails/models/bindable_collection.rb +15 -0
  40. data/lib/ironnails/models/model_mixin.rb +69 -0
  41. data/lib/ironnails/models.rb +3 -0
  42. data/lib/ironnails/nails_engine.rb +398 -0
  43. data/lib/ironnails/observable.rb +117 -0
  44. data/lib/ironnails/security/secure_string.rb +61 -0
  45. data/lib/ironnails/version.rb +11 -0
  46. data/lib/ironnails/view/collections.rb +117 -0
  47. data/lib/ironnails/view/commands/add_sub_view_command.rb +33 -0
  48. data/lib/ironnails/view/commands/behavior_command.rb +29 -0
  49. data/lib/ironnails/view/commands/command.rb +208 -0
  50. data/lib/ironnails/view/commands/event_command.rb +32 -0
  51. data/lib/ironnails/view/commands/timed_command.rb +40 -0
  52. data/lib/ironnails/view/commands.rb +5 -0
  53. data/lib/ironnails/view/view.rb +190 -0
  54. data/lib/ironnails/view/view_model.rb +45 -0
  55. data/lib/ironnails/view/xaml_proxy.rb +226 -0
  56. data/lib/ironnails/view.rb +5 -0
  57. data/lib/ironnails/wpf.rb +113 -0
  58. data/lib/ironnails/wpf_application.rb +30 -0
  59. data/lib/ironnails.rb +68 -0
  60. metadata +133 -0
@@ -0,0 +1,117 @@
1
+ # This is an implementation of the observer pattern
2
+ # It serves to mimic the event system that is known
3
+ # in the CLR world.
4
+ # while ruby has a standard observable module that
5
+ # one doesn't allow us to listen for specific events
6
+ module IronNails::Core
7
+
8
+ module Observable
9
+
10
+ #
11
+ # Add +observer+ as an observer on this object. +observer+ will now receive
12
+ # notifications. +observer+ is interest in the specified +event+
13
+ #
14
+ def add_observer(event, &observer)
15
+ @observers = [] unless defined? @observers
16
+ unless observer.respond_to? :call
17
+ raise NoMethodError, "observer needs to respond to 'update'"
18
+ end
19
+ @observers << { :event => event, :observer => observer }
20
+ end
21
+
22
+ #
23
+ # Delete +observer+ as an observer on this object. It will no longer receive
24
+ # notifications of the specified +event+.
25
+ #
26
+ def delete_observer(event, &observer)
27
+ evt = { :event => event, :observer => observer }
28
+ @observers.delete evt if defined? @observers
29
+ end
30
+
31
+ #
32
+ # Delete all observers associated with this object.
33
+ #
34
+ def delete_observers
35
+ @observers.clear if defined? @observers
36
+ end
37
+
38
+ #
39
+ # Return the number of observers associated with this object.
40
+ #
41
+ def count_observers
42
+ if defined? @observers
43
+ @observers.size
44
+ else
45
+ 0
46
+ end
47
+ end
48
+
49
+ #
50
+ # Notifies the registered observers that some interesting
51
+ # +event+ has occurred. It will notify the interested parties
52
+ # by calling the block and passing it some context
53
+ #
54
+ def notify_observers(event, sender, *args)
55
+ @observers.select {|evt| evt[:event] == event }.each {|evt| evt[:observer].call sender, *args } unless count_observers.zero?
56
+ end
57
+
58
+
59
+ end
60
+
61
+ module ControllerObservable
62
+
63
+ #
64
+ # Add +observer+ as an observer on this object. +observer+ will now receive
65
+ # notifications. +observer+ is interest in the specified +event+
66
+ # +controller+ is the name of the controller that listens for this event
67
+ #
68
+ def add_observer(event, controller, &observer)
69
+ @controller_observers = [] unless defined? @controller_observers
70
+ unless observer.respond_to? :call
71
+ raise NoMethodError, "observer needs to respond to 'update'"
72
+ end
73
+ @controller_observers << { :event => event.to_sym, :observer => observer, :controller => controller.to_sym }
74
+ end
75
+
76
+ #
77
+ # Delete +observer+ as an observer on this object. It will no longer receive
78
+ # notifications of the specified +event+.
79
+ #
80
+ def delete_observer(event, controller, &observer)
81
+ evt = { :event => event.to_sym, :observer => observer, :controller => controller.to_sym }
82
+ @controller_observers.delete evt if defined? @controller_observers
83
+ end
84
+
85
+ #
86
+ # Delete all observers associated with this object.
87
+ #
88
+ def delete_observers
89
+ @controller_observers.clear if defined? @controller_observers
90
+ end
91
+
92
+ #
93
+ # Return the number of observers associated with this object.
94
+ #
95
+ def count_observers
96
+ if defined? @controller_observers
97
+ @controller_observers.size
98
+ else
99
+ 0
100
+ end
101
+ end
102
+
103
+ #
104
+ # Notifies the registered observers that some interesting
105
+ # +event+ has occurred. It will notify the interested parties
106
+ # by calling the block and passing it some context
107
+ #
108
+ def notify_observers(event, controller, sender, *args)
109
+ @controller_observers.
110
+ select {|evt| evt[:event] == event.to_sym && evt[:controller] == controller.to_sym }.
111
+ each {|evt| evt[:observer].call sender, *args }
112
+ end
113
+
114
+
115
+ end
116
+
117
+ end
@@ -0,0 +1,61 @@
1
+ module IronNails
2
+
3
+ module Security
4
+
5
+ class SecureString
6
+
7
+ ENCRYPTION_SALT = "SailsPasswordSalt".freeze
8
+
9
+
10
+
11
+ @@entropy = System::Text::Encoding.unicode.get_bytes(ENCRYPTION_SALT)
12
+
13
+ class << self
14
+ include System::Security::Cryptography
15
+ include System::Runtime::InteropServices
16
+
17
+ def encrypt_string(input)
18
+ encrypted_data = ProtectedData.protect(
19
+ System::Text::Encoding.unicode.get_bytes(unsecure_string(input)),
20
+ @@entropy,
21
+ DataProtectionScope.current_user)
22
+ System::Convert.to_base64_string(encrypted_data)
23
+ end
24
+
25
+ def secure_string(input)
26
+ secure = System::Security::SecureString.new
27
+ input.to_s.to_clr_string.to_char_array.each {|c| secure.append_char c }
28
+ secure.make_read_only
29
+ secure
30
+ end
31
+
32
+ def unsecure_string(input)
33
+ result = ""
34
+ ptr = System::Runtime::InteropServices::Marshal.SecureStringToBSTR(input);
35
+ begin
36
+ result = System::Runtime::InteropServices::Marshal.PtrToStringBSTR(ptr);
37
+ ensure
38
+ System::Runtime::InteropServices::Marshal.ZeroFreeBSTR(ptr);
39
+ end
40
+ result.to_s
41
+ end
42
+
43
+ def decrypt_string(encrypted_data)
44
+ begin
45
+ decrypted_data = ProtectedData.unprotect(
46
+ Convert.from_base64_string(encrypted_data),
47
+ @@entropy,
48
+ DataProtectionScope.current_user);
49
+ secure_string(System::Text::Encoding.unicode.get_bytes(decrypted_data));
50
+ rescue
51
+ System::Security::SecureString.new
52
+ end
53
+ end
54
+
55
+ end
56
+
57
+ end
58
+
59
+ end
60
+
61
+ end
@@ -0,0 +1,11 @@
1
+ module IronNails
2
+
3
+ module VERSION #:nodoc:
4
+ MAJOR = 0
5
+ MINOR = 0
6
+ TINY = 2
7
+
8
+ STRING = [MAJOR, MINOR, TINY].join('.')
9
+ end
10
+
11
+ end
@@ -0,0 +1,117 @@
1
+ module IronNails
2
+
3
+ module Core
4
+
5
+ class Collection
6
+
7
+ include Enumerable
8
+
9
+ def initialize(*items)
10
+ @items = items || []
11
+ end
12
+
13
+ def each
14
+ @items.each do |item|
15
+ yield item
16
+ end
17
+ end
18
+
19
+ def <<(item)
20
+ @items << item
21
+ end
22
+
23
+ def [](value)
24
+ @items[value]
25
+ end
26
+
27
+ def to_a
28
+ @items
29
+ end
30
+
31
+
32
+ end
33
+
34
+ end
35
+
36
+ module View
37
+
38
+ class CommandCollection < Core::Collection
39
+
40
+ def has_command?(command)
41
+ !self.find do |cmd|
42
+ command == cmd
43
+ end.nil?
44
+ end
45
+
46
+ end
47
+
48
+ class ViewCollection < Core::Collection
49
+
50
+ def has_view?(view)
51
+ find_view(view[:name]).nil?
52
+ end
53
+
54
+ def find_view(name)
55
+ self.find { |vw| vw[:name] == name }
56
+ end
57
+
58
+ end
59
+
60
+ class ViewModelCollection < Core::Collection
61
+
62
+ def has_viewmodel?(view_model)
63
+ if view_model.is_a?(String)
64
+ find_viewmodel(view_model).nil?
65
+ else
66
+ find_viewmodel(view_model.__view_model_name_).nil?
67
+ end
68
+ end
69
+
70
+ def find_viewmodel(name)
71
+ self.find { |vm| vm.__view_model_name_ == name }
72
+ end
73
+
74
+ def <<(model)
75
+ @items << model unless has_viewmodel?(model)
76
+ find_viewmodel(model.__view_model_name_)
77
+ end
78
+
79
+ end
80
+
81
+ class ModelCollection < Core::Collection
82
+
83
+ def has_model?(model)
84
+ !get_model(model).nil?
85
+ end
86
+
87
+ def add_model(model)
88
+ key = model.keys[0]
89
+ has_model?(model) ? get_model(model)[key] = model[key] : @items << model
90
+ end
91
+
92
+ def get_model(model)
93
+ @items.find do |m|
94
+ model.keys[0] == m.keys[0]
95
+ end
96
+ end
97
+
98
+ class << self
99
+
100
+ # Given a set of +objects+ it will generate
101
+ # a collection of objects for the view model
102
+ def generate_for(objects)
103
+ models = new
104
+ objects.each do |k, v|
105
+ models << { k => v }
106
+ end
107
+ models
108
+ end
109
+
110
+ end
111
+
112
+ end
113
+
114
+
115
+ end
116
+
117
+ end
@@ -0,0 +1,33 @@
1
+ module IronNails
2
+
3
+ module View
4
+
5
+ class AddSubViewCommand < Command
6
+
7
+ # the controller for the subview
8
+ attr_accessor :controller
9
+
10
+ # the target that will contain the view for this controller
11
+ attr_accessor :target
12
+
13
+ alias_method :nails_base_command_read_options, :read_options
14
+
15
+ def read_options(options)
16
+ nails_base_command_read_options options
17
+ raise ArgumentError.new("We need a target to be defined by the :to parameter") if options[:to].nil?
18
+ raise ArgumentError.new("We need a controller instance to be defined in the :controller parameter") if options[:controller].nil? || !options[:controller].respond_to?(:current_view)
19
+
20
+ @controller = options[:controller]
21
+ @target = options[:to]
22
+ end
23
+
24
+ # executes this command (it calls the action)
25
+ def execute
26
+ view.add_control target, controller.current_view.instance
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,29 @@
1
+ module IronNails
2
+
3
+ module View
4
+
5
+ class BehaviorCommand < Command
6
+
7
+ def to_clr_command
8
+ # I have to wrap the method calls into blocks because .to_proc hasn't been implemented yet on method
9
+ # the Func's have to be wrapped in lambda's to preserve the return statement.
10
+ unless asynchronous?
11
+ delegate_command.new(Action.new { execute }, Func[System::Boolean].new(&lambda{ can_execute? } ))
12
+ else
13
+ async_delegate_command.new(Action.new { execute }, Action.new { refresh_view },
14
+ Func[System::Boolean].new(&lambda{ can_execute? }))
15
+ end
16
+ end
17
+
18
+ def delegate_command
19
+ IronNails::Library::DelegateCommand
20
+ end
21
+
22
+ def async_delegate_command
23
+ IronNails::Library::AsynchronousDelegateCommand
24
+ end
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,208 @@
1
+ module IronNails
2
+
3
+ module View
4
+
5
+ # The base class for view commands in IronNails.
6
+ class Command
7
+
8
+ include IronNails::Logging::ClassLogger
9
+ include IronNails::Core::Observable
10
+
11
+ # the view this command is bound to
12
+ attr_accessor :view
13
+
14
+ # indicates whether to execute this command on the ui thread or on a different thread.
15
+ attr_accessor :mode
16
+
17
+ # the name of this command
18
+ attr_accessor :name
19
+
20
+ # the action that will be triggered
21
+ attr_accessor :action
22
+
23
+ # the predicate that decides whether this command can execute or not
24
+ attr_accessor :condition
25
+
26
+ # the name of the controller that built this command
27
+ attr_accessor :controller
28
+
29
+ # for asynchronous actions this gets executed after the view has been refreshed as a new command
30
+ attr_accessor :callback
31
+
32
+ def initialize(options)
33
+ read_options options
34
+ end
35
+
36
+ def read_options(options)
37
+ raise ArgumentError.new("A name is necesary") if options[:name].nil?
38
+ raise ArgumentError.new("An action is necesary") if options[:action].nil?
39
+ options.each do |k, v|
40
+ instance_variable_set "@#{k}", v
41
+ end
42
+ @mode ||= :synchronous
43
+ end
44
+
45
+ # flag to indicate whether this command needs a refresh in the view model
46
+ def changed?
47
+ !!@changed
48
+ end
49
+
50
+ def can_execute?
51
+ !!(condition.nil?||condition.call)
52
+ end
53
+
54
+ def asynchronous?
55
+ mode == :asynchronous
56
+ end
57
+
58
+ def has_callback?
59
+ !callback.nil?
60
+ end
61
+
62
+ def refresh_view
63
+ callback.call if asynchronous? && has_callback?
64
+ notify_observers :refreshing_view, self
65
+ end
66
+
67
+ def synchronise_viewmodel_with_controller
68
+ notify_observers :reading_input, self
69
+ end
70
+
71
+ def attached?
72
+ !view.nil?
73
+ end
74
+
75
+ # executes this command (it calls the action)
76
+ def execute
77
+ #log_on_error do
78
+ puts "calling the action"
79
+ synchronise_viewmodel_with_controller
80
+ action.call
81
+ refresh_view unless asynchronous?
82
+ #end if can_execute?
83
+ end
84
+
85
+ def ==(command)
86
+ self.name == command.name
87
+ end
88
+
89
+ alias_method :===, :==
90
+ alias_method :equals, :==
91
+
92
+ def <=>(command)
93
+ self.name <=> command.name
94
+ end
95
+
96
+ alias_method :compare_to, :<=>
97
+
98
+ end
99
+
100
+ class CommandBuilder
101
+
102
+ attr_accessor :controller
103
+
104
+ attr_accessor :command_mapping
105
+
106
+
107
+ def initialize(controller)
108
+ @controller = controller
109
+ @command_mapping= {
110
+ :event => EventCommand,
111
+ :timed => TimedCommand,
112
+ :behavior => BehaviorCommand
113
+ }
114
+ end
115
+
116
+ # Given a set of +command_definitions+ it will generate
117
+ # a collection of Command objects for the view model
118
+ def generate_for(cmd_def)
119
+ norm = normalize_command_definitions(cmd_def)
120
+ cmds = generate_command_collection_from_normalized_definitions norm
121
+ cmds
122
+ end
123
+
124
+ def view_model
125
+ WpfApplication.current.nails_engine
126
+ end
127
+
128
+ # Generates the command definitions for our view model.
129
+ def normalize_command_definitions(definitions)
130
+ command_definitions = {}
131
+
132
+ definitions.each do |k, v|
133
+ command_definitions[k] = normalize_command_definition(k, v)
134
+ end unless definitions.nil?
135
+
136
+ command_definitions
137
+ end
138
+
139
+ # Generates a command definition for our view model.
140
+ # When it can't find a key :action in the options hash for the view_action
141
+ # it will default to using the name as the command as the connected option.
142
+ # It will generate a series of commands for items that have more than one trigger
143
+ def normalize_command_definition(name, options)
144
+ mode = options[:mode]
145
+ act = options[:action]||name
146
+ action = act
147
+ action = controller.method(act) if act.is_a?(Symbol) || act.is_a?(String)
148
+ if options.has_key?(:triggers) && !options[:triggers].nil?
149
+ triggers = options[:triggers]
150
+
151
+ cmd_def =
152
+ if triggers.is_a?(String) || triggers.is_a?(Symbol)
153
+ {
154
+ :element => triggers,
155
+ :event => :click,
156
+ :action => action,
157
+ :mode => mode,
158
+ :type => :event
159
+ }
160
+ elsif triggers.is_a?(Hash)
161
+ {:action => action, :mode => mode, :type => :event }.merge triggers
162
+ elsif triggers.is_a?(Array)
163
+ defs = []
164
+ triggers.each do |trig|
165
+ trig = { :element => trig, :event => :click } unless trig.is_a? Hash
166
+ trig[:event] = :click unless trig.has_key? :event
167
+ defs << { :action => action, :mode => mode, :type => :event }.merge(trig)
168
+ end
169
+ defs
170
+ end
171
+ cmd_def
172
+ else
173
+ exec = options[:execute]
174
+ execute = exec
175
+ execute = controller.method(exec) if exec.is_a?(Symbol) || exec.is_a?(String)
176
+ callback = options[:callback]
177
+ callback = controller.method(callback) if callback.is_a?(Symbol) || callback.is_a?(String)
178
+ controller_action, controller_condition = execute || action, options[:condition]
179
+ res = {
180
+ :action => controller_action,
181
+ :condition => controller_condition,
182
+ :mode => mode,
183
+ :callback => callback,
184
+ :type => options[:type] || :behavior
185
+ }
186
+ res[:interval] = options[:interval] if options.key? :interval
187
+ res
188
+ end
189
+ end
190
+
191
+ def generate_command_collection_from_normalized_definitions(definitions)
192
+ commands = CommandCollection.new
193
+ definitions.each do |name, cmd_def|
194
+ cmd = create_command_from(cmd_def.merge({ :view_model => view_model, :name => name, :controller => controller.controller_name }))
195
+ commands << cmd
196
+ end if definitions.is_a?(Hash)
197
+ commands
198
+ end
199
+
200
+ def create_command_from(definition)
201
+ command_mapping[definition[:type]||:behavior].new definition
202
+ end
203
+
204
+ end
205
+
206
+ end
207
+
208
+ end
@@ -0,0 +1,32 @@
1
+ module IronNails
2
+
3
+ module View
4
+
5
+ # Encapsulates commands that will be attached to elements in the views.
6
+ class EventCommand < Command
7
+
8
+ # the name of the event that will trigger the action
9
+ attr_accessor :trigger
10
+
11
+ # the name of the element that will trigger the action
12
+ attr_accessor :element
13
+
14
+ # the name of the on which this command needs to be invoked
15
+ attr_accessor :affinity
16
+
17
+ alias_method :nails_base_command_read_options, :read_options
18
+
19
+ def read_options(options)
20
+ nails_base_command_read_options options
21
+ raise ArgumentException.new("An element name is necesary") if options[:element].nil?
22
+
23
+ @trigger = options[:event]||:click
24
+ @element = options[:element]
25
+ @affinity = options[:affinity]
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+
32
+ end
@@ -0,0 +1,40 @@
1
+ module IronNails
2
+
3
+ module View
4
+
5
+ class TimedCommand < Command
6
+
7
+ # gets the name to use for the timer
8
+ attr_reader :timer_name
9
+
10
+ attr_accessor :interval
11
+
12
+ alias_method :nails_base_command_read_options, :read_options
13
+
14
+ def read_options(options)
15
+ nails_base_command_read_options options
16
+
17
+ @timer_name = get_timer_name
18
+ end
19
+
20
+ # This stops the timer in the view proxy
21
+ def stop_timer
22
+ view.stop_timer self
23
+ end
24
+
25
+ # This starts the timer in the view proxy
26
+ def start_timer
27
+ view.start_timer self
28
+ end
29
+
30
+ private
31
+
32
+ def get_timer_name
33
+ "__#{name}_ironnails_view_timer"
34
+ end
35
+
36
+ end
37
+
38
+ end
39
+
40
+ end
@@ -0,0 +1,5 @@
1
+ require File.dirname(__FILE__) + '/commands/command'
2
+ require File.dirname(__FILE__) + '/commands/event_command'
3
+ require File.dirname(__FILE__) + '/commands/timed_command'
4
+ require File.dirname(__FILE__) + '/commands/behavior_command'
5
+ require File.dirname(__FILE__) + '/commands/add_sub_view_command'