RUIC 0.4.4 → 0.4.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,176 +1,176 @@
1
- #encoding:utf-8
2
-
3
- class RUIC; end
4
- module UIC; end
5
-
6
- require 'nokogiri'
7
- require_relative 'ruic/version'
8
- require_relative 'ruic/attributes'
9
- require_relative 'ruic/assets'
10
- require_relative 'ruic/interfaces'
11
- require_relative 'ruic/application'
12
- require_relative 'ruic/behaviors'
13
- require_relative 'ruic/statemachine'
14
- require_relative 'ruic/presentation'
15
-
16
- # The `RUIC` class provides the interface for running scripts using the special DSL,
17
- # and for running the interactive REPL.
18
- # See the {file:README.md README} file for description of the DSL.
19
- class RUIC
20
- DEFAULTMETADATA = 'C:/Program Files (x86)/NVIDIA Corporation/UI Composer 8.0/res/DataModelMetadata/en-us/MetaData.xml'
21
-
22
- # Execute a script and/or launch the interactive REPL.
23
- #
24
- # If you both run a `:script` and then enter the `:repl` all local variables created
25
- # by the script will be available in the REPL.
26
- #
27
- # # Just run a script
28
- # RUIC.run script:'my.ruic'
29
- #
30
- # # Load an application and then enter the REPL
31
- # RUIC.run uia:'my.uia', repl:true
32
- #
33
- # # Run a script and then drop into the REPL
34
- # RUIC.run script:'my.ruic', repl:true
35
- #
36
- # The working directory for scripts is set to the directory of the script.
37
- #
38
- # The working directory for the repl is set to the directory of the first
39
- # loaded application (if any);
40
- # failing that, the directory of the script (if any);
41
- # failing that, the current directory.
42
- #
43
- # @option opts [String] :script A path to a `.ruic` script to run _(optional)_.
44
- # @option opts [String] :uia An `.uia` file to load (before running the script and/or REPL) _(optional)_.
45
- # @option opts [Boolean] :repl Pass `true` to enter the command-line REPL after executing the script (if any).
46
- # @return [nil]
47
- def self.run(opts={})
48
- opts = opts
49
- ruic = nil
50
- if opts[:script]
51
- script = File.read(opts[:script],encoding:'utf-8')
52
- Dir.chdir(File.dirname(opts[:script])) do
53
- ruic = self.new
54
- ruic.uia(opts[:uia]) if opts[:uia]
55
- ruic.env.eval(script,opts[:script])
56
- end
57
- end
58
-
59
- if opts[:repl]
60
- location = (ruic && ruic.app && ruic.app.respond_to?(:file) && ruic.app.file) || opts[:uia] || opts[:script] || '.'
61
- Dir.chdir( File.dirname(location) ) do
62
- ruic ||= self.new.tap{ |r| r.uia(opts[:uia]) if opts[:uia] }
63
- require 'ripl/irb'
64
- require 'ripl/multi_line'
65
- require 'ripl/multi_line/live_error.rb'
66
- require_relative 'ruic/ripl-after-result'
67
- Ripl::MultiLine.engine = Ripl::MultiLine::LiveError
68
- Ripl::Shell.include Ripl::MultiLine.engine
69
- Ripl::Shell.include Ripl::AfterResult
70
- Ripl.config.merge! prompt:"", result_prompt:'#=> ', multi_line_prompt:' ', irb_verbose:false, after_result:"\n"
71
- ARGV.clear # So that RIPL doesn't try to interpret the options
72
- puts "(RUIC v#{RUIC::VERSION} interactive session; 'quit' or ctrl-d to end)"
73
- ruic.instance_eval{ puts @apps.map{ |n,app| "(#{n} is #{app.inspect})" } }
74
- puts "" # blank line before first input
75
- Ripl.start binding:ruic.env
76
- end
77
- end
78
- end
79
-
80
- # Creates a new environment for executing a RUIC script.
81
- # @param metadata [String] Path to the `MetaData.xml` file to use.
82
- def initialize( metadata=DEFAULTMETADATA )
83
- @metadata = metadata
84
- @apps = {}
85
- end
86
-
87
- # Set the metadata to use; generally called from the RUIC DSL.
88
- # @param path [String] Path to the `MetaData.xml` file, either absolute or relative to the working directory.
89
- def metadata(path)
90
- @metadata = path
91
- end
92
-
93
- # Load an application, making it available as `app`, `app2`, etc.
94
- # @param path [String] Path to the `*.uia` application file.
95
- # @return [UIC::Application] The new application loaded.
96
- def uia(path)
97
- meta = UIC.MetaData @metadata
98
- name = @apps.empty? ? :app : :"app#{@apps.length+1}"
99
- @apps[name] = UIC.Application(meta,path)
100
- end
101
-
102
- # @return [Binding] the shared binding used for evaluating the script and REPL
103
- def env
104
- @env ||= binding
105
- end
106
-
107
- # @private used as a one-off
108
- module SelfInspecting; def inspect; to_s; end; end
109
-
110
- # Used to resolve bare `app` and `app2` calls to a loaded {UIC::Application Application}.
111
- # @return [UIC::Application] the new application loaded.
112
- def method_missing(name,*a)
113
- @apps[name] || (name=~/^app\d*/ ? "(no #{name} loaded)".extend(SelfInspecting) : super)
114
- end
115
-
116
- # Simple assertion mechanism to be used within scripts.
117
- #
118
- # @example 1) simple call syntax
119
- # # Provides a generic failure message
120
- # assert a==b
121
- # #=> assertion failed (my.ruic line 17)
122
- #
123
- # # Provides a custom failure message
124
- # assert a==b, "a should equal b"
125
- # #=> a should equal b : assertion failed (my.ruic line 17)
126
- #
127
- # @example 2) block with string syntax
128
- # # The code in the string to eval is also the failure message
129
- # assert{ "a==b" }
130
- # #=> a==b : assertion failed (my.ruic line 17)
131
- #
132
- # @param condition [Boolean] the value to evaluate.
133
- # @param msg [String] the nice error message to display.
134
- # @yieldreturn [String] the code to evaluate as a condition.
135
- def assert(condition=:CONDITIONNOTSUPPLIED,msg=nil,&block)
136
- if block && condition==:CONDITIONNOTSUPPLIED
137
- msg = yield
138
- condition = msg.is_a?(String) ? eval(msg,block.binding) : msg
139
- end
140
- condition || begin
141
- file, line, _ = caller.first.split(':')
142
- puts "#{"#{msg} : " unless msg.nil?}assertion failed (#{file} line #{line})"
143
- exit 1
144
- end
145
- end
146
-
147
- # Nicer name for `puts` to be used in the DSL, printing the
148
- # 'nice' string equivalent for all supplied arguments.
149
- def show(*a); puts *a.map(&:to_s); end
150
-
151
- def inspect
152
- "<RUIC #{@apps.empty? ? "(no app loaded)" : Hash[ @apps.map{ |id,app| [id,File.basename(app.file)] } ]}>"
153
- end
154
- end
155
-
156
- # Run a series of commands inside the RUIC DSL.
157
- #
158
- # @example
159
- # require 'ruic'
160
- # RUIC do
161
- # uia 'test/MyProject/MyProject.uia'
162
- # show app
163
- # #=>UIC::Application 'MyProject.uia'>
164
- # end
165
- #
166
- # If no block is supplied, this is the same as {RUIC.run RUIC.run(opts)}.
167
- # @option opts [String] :uia Optionally load an application before running the script.
168
- def RUIC(opts={},&block)
169
- if block
170
- Dir.chdir(File.dirname($0)) do
171
- RUIC.new.tap{ |r| r.uia(opts[:uia]) if opts[:uia] }.instance_eval(&block)
172
- end
173
- else
174
- RUIC.run(opts)
175
- end
1
+ #encoding:utf-8
2
+
3
+ class RUIC; end
4
+ module UIC; end
5
+
6
+ require 'nokogiri'
7
+ require_relative 'ruic/version'
8
+ require_relative 'ruic/attributes'
9
+ require_relative 'ruic/assets'
10
+ require_relative 'ruic/interfaces'
11
+ require_relative 'ruic/application'
12
+ require_relative 'ruic/behaviors'
13
+ require_relative 'ruic/statemachine'
14
+ require_relative 'ruic/presentation'
15
+
16
+ # The `RUIC` class provides the interface for running scripts using the special DSL,
17
+ # and for running the interactive REPL.
18
+ # See the {file:README.md README} file for description of the DSL.
19
+ class RUIC
20
+ DEFAULTMETADATA = 'C:/Program Files (x86)/NVIDIA Corporation/UI Composer 8.0/res/DataModelMetadata/en-us/MetaData.xml'
21
+
22
+ # Execute a script and/or launch the interactive REPL.
23
+ #
24
+ # If you both run a `:script` and then enter the `:repl` all local variables created
25
+ # by the script will be available in the REPL.
26
+ #
27
+ # # Just run a script
28
+ # RUIC.run script:'my.ruic'
29
+ #
30
+ # # Load an application and then enter the REPL
31
+ # RUIC.run uia:'my.uia', repl:true
32
+ #
33
+ # # Run a script and then drop into the REPL
34
+ # RUIC.run script:'my.ruic', repl:true
35
+ #
36
+ # The working directory for scripts is set to the directory of the script.
37
+ #
38
+ # The working directory for the repl is set to the directory of the first
39
+ # loaded application (if any);
40
+ # failing that, the directory of the script (if any);
41
+ # failing that, the current directory.
42
+ #
43
+ # @option opts [String] :script A path to a `.ruic` script to run _(optional)_.
44
+ # @option opts [String] :uia An `.uia` file to load (before running the script and/or REPL) _(optional)_.
45
+ # @option opts [Boolean] :repl Pass `true` to enter the command-line REPL after executing the script (if any).
46
+ # @return [nil]
47
+ def self.run(opts={})
48
+ opts = opts
49
+ ruic = nil
50
+ if opts[:script]
51
+ script = File.read(opts[:script],encoding:'utf-8')
52
+ Dir.chdir(File.dirname(opts[:script])) do
53
+ ruic = self.new
54
+ ruic.uia(opts[:uia]) if opts[:uia]
55
+ ruic.env.eval(script,opts[:script])
56
+ end
57
+ end
58
+
59
+ if opts[:repl]
60
+ location = (ruic && ruic.app && ruic.app.respond_to?(:file) && ruic.app.file) || opts[:uia] || opts[:script] || '.'
61
+ Dir.chdir( File.dirname(location) ) do
62
+ ruic ||= self.new.tap{ |r| r.uia(opts[:uia]) if opts[:uia] }
63
+ require 'ripl/irb'
64
+ require 'ripl/multi_line'
65
+ require 'ripl/multi_line/live_error.rb'
66
+ require_relative 'ruic/ripl-after-result'
67
+ Ripl::MultiLine.engine = Ripl::MultiLine::LiveError
68
+ Ripl::Shell.include Ripl::MultiLine.engine
69
+ Ripl::Shell.include Ripl::AfterResult
70
+ Ripl.config.merge! prompt:"", result_prompt:'#=> ', multi_line_prompt:' ', irb_verbose:false, after_result:"\n"
71
+ ARGV.clear # So that RIPL doesn't try to interpret the options
72
+ puts "(RUIC v#{RUIC::VERSION} interactive session; 'quit' or ctrl-d to end)"
73
+ ruic.instance_eval{ puts @apps.map{ |n,app| "(#{n} is #{app.inspect})" } }
74
+ puts "" # blank line before first input
75
+ Ripl.start binding:ruic.env
76
+ end
77
+ end
78
+ end
79
+
80
+ # Creates a new environment for executing a RUIC script.
81
+ # @param metadata [String] Path to the `MetaData.xml` file to use.
82
+ def initialize( metadata=DEFAULTMETADATA )
83
+ @metadata = metadata
84
+ @apps = {}
85
+ end
86
+
87
+ # Set the metadata to use; generally called from the RUIC DSL.
88
+ # @param path [String] Path to the `MetaData.xml` file, either absolute or relative to the working directory.
89
+ def metadata(path)
90
+ @metadata = path
91
+ end
92
+
93
+ # Load an application, making it available as `app`, `app2`, etc.
94
+ # @param path [String] Path to the `*.uia` application file.
95
+ # @return [UIC::Application] The new application loaded.
96
+ def uia(path)
97
+ meta = UIC.MetaData @metadata
98
+ name = @apps.empty? ? :app : :"app#{@apps.length+1}"
99
+ @apps[name] = UIC.Application(meta,path)
100
+ end
101
+
102
+ # @return [Binding] the shared binding used for evaluating the script and REPL
103
+ def env
104
+ @env ||= binding
105
+ end
106
+
107
+ # @private used as a one-off
108
+ module SelfInspecting; def inspect; to_s; end; end
109
+
110
+ # Used to resolve bare `app` and `app2` calls to a loaded {UIC::Application Application}.
111
+ # @return [UIC::Application] the new application loaded.
112
+ def method_missing(name,*a)
113
+ @apps[name] || (name=~/^app\d*/ ? "(no #{name} loaded)".extend(SelfInspecting) : super)
114
+ end
115
+
116
+ # Simple assertion mechanism to be used within scripts.
117
+ #
118
+ # @example 1) simple call syntax
119
+ # # Provides a generic failure message
120
+ # assert a==b
121
+ # #=> assertion failed (my.ruic line 17)
122
+ #
123
+ # # Provides a custom failure message
124
+ # assert a==b, "a should equal b"
125
+ # #=> a should equal b : assertion failed (my.ruic line 17)
126
+ #
127
+ # @example 2) block with string syntax
128
+ # # The code in the string to eval is also the failure message
129
+ # assert{ "a==b" }
130
+ # #=> a==b : assertion failed (my.ruic line 17)
131
+ #
132
+ # @param condition [Boolean] the value to evaluate.
133
+ # @param msg [String] the nice error message to display.
134
+ # @yieldreturn [String] the code to evaluate as a condition.
135
+ def assert(condition=:CONDITIONNOTSUPPLIED,msg=nil,&block)
136
+ if block && condition==:CONDITIONNOTSUPPLIED
137
+ msg = yield
138
+ condition = msg.is_a?(String) ? eval(msg,block.binding) : msg
139
+ end
140
+ condition || begin
141
+ file, line, _ = caller.first.split(':')
142
+ puts "#{"#{msg} : " unless msg.nil?}assertion failed (#{file} line #{line})"
143
+ exit 1
144
+ end
145
+ end
146
+
147
+ # Nicer name for `puts` to be used in the DSL, printing the
148
+ # 'nice' string equivalent for all supplied arguments.
149
+ def show(*a); puts *a.map(&:to_s); end
150
+
151
+ def inspect
152
+ "<RUIC #{@apps.empty? ? "(no app loaded)" : Hash[ @apps.map{ |id,app| [id,File.basename(app.file)] } ]}>"
153
+ end
154
+ end
155
+
156
+ # Run a series of commands inside the RUIC DSL.
157
+ #
158
+ # @example
159
+ # require 'ruic'
160
+ # RUIC do
161
+ # uia 'test/MyProject/MyProject.uia'
162
+ # show app
163
+ # #=>UIC::Application 'MyProject.uia'>
164
+ # end
165
+ #
166
+ # If no block is supplied, this is the same as {RUIC.run RUIC.run(opts)}.
167
+ # @option opts [String] :uia Optionally load an application before running the script.
168
+ def RUIC(opts={},&block)
169
+ if block
170
+ Dir.chdir(File.dirname($0)) do
171
+ RUIC.new.tap{ |r| r.uia(opts[:uia]) if opts[:uia] }.instance_eval(&block)
172
+ end
173
+ else
174
+ RUIC.run(opts)
175
+ end
176
176
  end
File without changes
@@ -1,422 +1,422 @@
1
- #encoding: utf-8
2
- class UIC::MetaData
3
-
4
- # The base class for all assets. All other classes are dynamically created when a `MetaData.xml` file is loaded.
5
- class AssetBase
6
- @properties = {}
7
- @name = "AssetBase"
8
-
9
- class << self
10
- # @return [String] The scene graph name of the asset.
11
- attr_reader :name
12
-
13
- # @return [Hash] a hash mapping attribute names to {Property} instances.
14
- def properties
15
- (ancestors[1].respond_to?(:properties) ? ancestors[1].properties : {}).merge(@properties)
16
- end
17
-
18
- # @private
19
- def inspect
20
- "<#{@name}>"
21
- end
22
- end
23
-
24
- # @return [Hash] a hash mapping attribute names to {Property} instances.
25
- def properties
26
- self.class.properties
27
- end
28
-
29
- # Find an asset by relative scripting path.
30
- #
31
- # @example
32
- # preso = app.main
33
- # layer = preso/"Scene.Layer"
34
- # cam1 = app/"main:Scene.Layer.Camera"
35
- # cam2 = preso/"Scene.Layer.Camera"
36
- # cam3 = layer/"Camera"
37
- # cam4 = cam1/"parent.Camera"
38
- #
39
- # assert cam1==cam2 && cam2==cam3 && cam3==cam4
40
- #
41
- # @return [MetaData::AssetBase] The found asset, or `nil` if it cannot be found.
42
- #
43
- # @see Application#at
44
- # @see Presentation#at
45
- def at(sub_path)
46
- presentation.at(sub_path,@el)
47
- end
48
- alias_method :/, :at
49
-
50
- # @return [Presentation] the presentation that this asset is part of.
51
- attr_accessor :presentation
52
-
53
- # @return [Nokogiri::XML::Element] the internal XML element in the scene graph of the presentation.
54
- attr_accessor :el
55
-
56
- # Create a new asset. This is called for you automatically; you likely should not be using it.
57
- # @param presentation [Presentation] the presentation owning this asset.
58
- # @param element [Nokogiri::XML::Element] the internal XML element in the scene graph of the presentation.
59
- def initialize( presentation, element )
60
- @presentation = presentation
61
- @el = element
62
- end
63
-
64
- # @return [String] the type of this asset. For example: `"Model"`, `"Material"`, `"ReferencedMaterial"`, `"PathAnchorPoint"`, etc.
65
- def type
66
- self.class.name
67
- end
68
-
69
- # @return [AssetBase] the parent of this asset in the scene graph.
70
- # @see Presentation#parent_asset
71
- def parent
72
- presentation.parent_asset(self)
73
- end
74
-
75
- # @return [Array<AssetBase>] array of child assets in the scene graph. Children are in scene graph order.
76
- # @see Presentation#child_assets
77
- def children
78
- presentation.child_assets(self)
79
- end
80
-
81
- # Find descendant assets matching criteria.
82
- # This method is the same as (but more convenient than) {Presentation#find} using the `:_under` criteria.
83
- # See that method for documentation of the `criteria`.
84
- #
85
- # @example
86
- # preso = app.main
87
- # group = preso/"Scene.Layer.Vehicle"
88
- # tires = group.find name:/^Tire/
89
- #
90
- # # alternative
91
- # tires = preso.find name:/^Tire/, _under:group
92
- #
93
- # @return [Array<AssetBase>]
94
- #
95
- # @see Presentation#find
96
- def find(criteria={},&block)
97
- criteria[:_under] ||= self
98
- presentation.find(criteria,&block)
99
- end
100
-
101
- # @return [AssetBase] the component or scene that owns this asset.
102
- # If this asset is a component, does not return itself.
103
- # @see Presentation#owning_component
104
- def component
105
- presentation.owning_component(self)
106
- end
107
-
108
- # @return [Boolean] `true` if this asset is a component or Scene.
109
- def component?
110
- @el.name=='Component' || @el.name=='Scene'
111
- end
112
-
113
- # @return [Boolean] `true` if this asset is on the master slide.
114
- # @see Presentation#master?
115
- def master?
116
- presentation.master?(self)
117
- end
118
-
119
- # @return [Boolean] `true` if this asset is a Slide.
120
- def slide?
121
- false
122
- end
123
-
124
- # @param slide_name_or_index [Integer,String] the slide number of name to check for presence on.
125
- # @return [Boolean] `true` if this asset is present on the specified slide.
126
- # @see Presentation#has_slide?
127
- def has_slide?(slide_name_or_index)
128
- presentation.has_slide?(self,slide_name_or_index)
129
- end
130
-
131
- # @return [SlideCollection] an array-like collection of all slides that the asset is available on.
132
- # @see Presentation#slides_for
133
- def slides
134
- presentation.slides_for(self)
135
- end
136
-
137
- # @example
138
- # logo = app/"main:Scene.UI.Logo"
139
- # assert logo.master? # It's a master object
140
- #
141
- # show logo['endtime'].values #=> [10000,500,1000,750]
142
- # show logo['opacity'].values #=> [100,0,0,100]
143
- # logo1 = logo.on_slide(1)
144
- # logo2 = logo.on_slide(2)
145
- # show logo1['endtime'] #=> 500
146
- # show logo2['endtime'] #=> 1000
147
- # show logo1['opacity'] #=> 0
148
- # show logo2['opacity'] #=> 0
149
- #
150
- # logo2['opacity'] = 66
151
- # show logo['opacity'].values #=> [100,0,66,100]
152
- #
153
- # @param slide_name_or_index [Integer,String] the slide number or name to create the proxy for.
154
- # @return [SlideValues] a proxy that yields attribute values for a specific slide.
155
- def on_slide(slide_name_or_index)
156
- if has_slide?(slide_name_or_index)
157
- UIC::SlideValues.new( self, slide_name_or_index )
158
- end
159
- end
160
-
161
- # @return [String] the script path to this asset.
162
- # @see #path_to
163
- # @see Presentation#path_to
164
- def path
165
- @path ||= @presentation.path_to(self)
166
- end
167
-
168
- # @param other_asset [AssetBase] the asset to find the relative path to.
169
- # @return [String] the script path to another asset, relative to this one.
170
- # @see #path
171
- # @see Presentation#path_to
172
- def path_to(other_asset)
173
- @presentation.path_to(other_asset,self)
174
- end
175
-
176
- # @return [String] the name of this asset in the scene graph.
177
- def name
178
- properties['name'].get( self, presentation.slide_index(self) )
179
- end
180
-
181
- # Change the name of the asset in the scene graph.
182
- # @param new_name [String] the new name for this asset.
183
- # @return [String] the new name.
184
- def name=( new_name )
185
- @path = nil # invalidate the memoization
186
- properties['name'].set( self, new_name, presentation.slide_index(self) )
187
- end
188
-
189
- # Get the value(s) of an attribute.
190
- # If `slide_name_or_index` is omitted, creates a ValuesPerSlide proxy for the specified attribute.
191
- # @example
192
- # logo = app/"main:Scene.UI.Logo"
193
- # show logo.master? #=> true (it's a master object)
194
- # show logo['endtime'].linked? #=> false (the endtime property is unlinked)
195
- # show logo['endtime'].values #=> [10000,500,1000,750]
196
- # show logo['endtime',0] #=> 10000 (the master slide value)
197
- # show logo['endtime',"Slide 1"] #=> 500
198
- # show logo['endtime',2] #=> 1000
199
- #
200
- # @param attribute_name [String,Symbol] the name of the attribute.
201
- # @param slide_name_or_index [Integer,String] the slide number or name to find the value on.
202
- # @return [Object] the value of the property on the given slide.
203
- # @return [ValuesPerSlide] if no slide is specified.
204
- # @see #on_slide
205
- # @see Presentation#get_attribute
206
- def [](attribute_name, slide_name_or_index=nil)
207
- if property = properties[attribute_name.to_s]
208
- if slide_name_or_index
209
- property.get( self, slide_name_or_index ) if has_slide?(slide_name_or_index)
210
- else
211
- UIC::ValuesPerSlide.new(@presentation,self,property)
212
- end
213
- end
214
- end
215
-
216
- # Set the value of an attribute, either across all slides, or on a particular slide.
217
- #
218
- # @example
219
- # logo = app/"main:Scene.UI.Logo"
220
- # show logo.master? #=> true (it's a master object)
221
- # show logo['endtime'].linked? #=> false (the endtime property is unlinked)
222
- # show logo['endtime'].values #=> [10000,500,1000,750]
223
- #
224
- # logo['endtime',1] = 99
225
- # show logo['endtime'].values #=> [10000,99,1000,750]
226
- #
227
- # logo['endtime'] = 42
228
- # show logo['endtime'].values #=> [42,42,42,42]
229
- # show logo['endtime'].linked? #=> false (the endtime property is still unlinked)
230
- #
231
- # @param attribute_name [String,Symbol] the name of the attribute.
232
- # @param slide_name_or_index [Integer,String] the slide number or name to set the value on.
233
- # @param new_value [Numeric,String] the new value for the attribute.
234
- # @see Presentation#set_attribute
235
- def []=( attribute_name, slide_name_or_index=nil, new_value )
236
- if property = properties[attribute_name.to_s] then
237
- property.set(self,new_value,slide_name_or_index)
238
- end
239
- end
240
-
241
- # @return [String] the XML representation of the scene graph element.
242
- def to_xml
243
- @el.to_xml
244
- end
245
-
246
- # @private no need to document this
247
- def inspect
248
- "<asset #{@el.name}##{@el['id']}>"
249
- end
250
-
251
- # @private no need to document this
252
- def to_s
253
- "<#{type} #{path}>"
254
- end
255
-
256
- # @private no need to document this
257
- def ==(other)
258
- (self.class==other.class) && (el==other.el)
259
- end
260
- alias_method :eql?, :==
261
- end
262
-
263
- attr_reader :by_name
264
-
265
- HIER = {}
266
-
267
- %w[Asset Slide Scene].each{ |s| HIER[s] = 'AssetBase' }
268
- %w[Node Behavior Effect Image Layer MaterialBase PathAnchorPoint RenderPlugin].each{ |s| HIER[s]='Asset' }
269
- %w[Alias Camera Component Group Light Model Text Path].each{ |s| HIER[s]='Node' }
270
- %w[Material ReferencedMaterial].each{ |s| HIER[s]='MaterialBase' }
271
-
272
- def initialize(xml)
273
-
274
- @by_name = {'AssetBase'=>AssetBase}
275
-
276
- doc = Nokogiri.XML(xml)
277
- hack_in_slide_names!(doc)
278
-
279
- HIER.each do |class_name,parent_class_name|
280
- parent_class = @by_name[parent_class_name]
281
- el = doc.root.at(class_name)
282
- @by_name[class_name] = create_class(el,parent_class,el.name)
283
- end
284
-
285
- # Extend well-known classes with script interfaces after they are created
286
- @by_name['State'] = @by_name['Slide']
287
- @by_name['Slide'].instance_eval do
288
- attr_accessor :index, :name
289
- define_method :inspect do
290
- "<slide ##{index} of #{@el['component'] || @el.parent['component']}>"
291
- end
292
- define_method(:slide?){ true }
293
- end
294
-
295
- refmat = @by_name['ReferencedMaterial']
296
- @by_name['MaterialBase'].instance_eval do
297
- define_method :replace_with_referenced_material do
298
- type=='ReferencedMaterial' ? self : presentation.replace_asset( self, 'ReferencedMaterial', name:name )
299
- end
300
- end
301
-
302
- @by_name['Path'].instance_eval do
303
- define_method(:anchors){ find _type:'PathAnchorPoint' }
304
- end
305
-
306
- end
307
-
308
- # Creates a class from MetaData.xml with accessors for the <Property> listed.
309
- # Instances of the class are associated with a presentation and know how to
310
- # get/set values in that XML based on value types, slides, defaults.
311
- # Also used to create classes from effects, materials, and behavior preambles.
312
- def create_class(el,parent_class,name='CustomAsset')
313
- Class.new(parent_class) do
314
- @name = name.to_s
315
- @properties = Hash[ el.css("Property").map do |e|
316
- type = e['type'] || (e['list'] ? 'String' : 'Float')
317
- type = "Float" if type=="float"
318
- property = UIC::Property.const_get(type).new(e)
319
- [ property.name, UIC::Property.const_get(type).new(e) ]
320
- end ]
321
- def self.inspect
322
- @name
323
- end
324
- end
325
- end
326
-
327
- def new_instance(presentation,el)
328
- klass = @by_name[el.name] || create_class(el,@by_name['Asset'],el.name)
329
- klass.new(presentation,el)
330
- end
331
-
332
- def hack_in_slide_names!(doc)
333
- doc.at('Slide') << '<Property name="name" formalName="Name" type="String" default="Slide" hidden="True" />'
334
- end
335
- end
336
-
337
- def UIC.MetaData(metadata_path)
338
- UIC::MetaData.new(File.read(metadata_path,encoding:'utf-8'))
339
- end
340
-
341
- class UIC::SlideCollection
342
- include Enumerable
343
- attr_reader :length
344
- def initialize(slides)
345
- @length = slides.length-1
346
- @slides = slides
347
- @lookup = {}
348
- slides.each do |s|
349
- @lookup[s.index] = s
350
- @lookup[s.name] = s
351
- end
352
- end
353
- def each
354
- @slides.each{ |s| yield(s) }
355
- end
356
- def [](index_or_name)
357
- @lookup[ index_or_name ]
358
- end
359
- def inspect
360
- "[ #{@slides.map(&:inspect).join ', '} ]"
361
- end
362
- def to_ary
363
- @slides
364
- end
365
- end
366
-
367
- class UIC::ValuesPerSlide
368
- def initialize(presentation,asset,property)
369
- raise unless presentation.is_a?(UIC::Presentation)
370
-
371
- raise unless asset.is_a?(UIC::MetaData::AssetBase)
372
- raise unless property.is_a?(UIC::Property)
373
- @preso = presentation
374
- @asset = asset
375
- @el = asset.el
376
- @property = property
377
- end
378
- def value
379
- values.first
380
- end
381
- def [](slide_name_or_index)
382
- @property.get( @asset, slide_name_or_index )
383
- end
384
- def []=(slide_name_or_index,new_value)
385
- @property.set( @asset, new_value, slide_name_or_index )
386
- end
387
- def linked?
388
- @preso.attribute_linked?( @asset, @property.name )
389
- end
390
- def unlink
391
- @preso.unlink_attribute( @asset, @property.name )
392
- end
393
- def link
394
- @preso.link_attribute( @asset, @property.name )
395
- end
396
- def values
397
- @asset.slides.map{ |s| self[s.name] }
398
- end
399
- def inspect
400
- "<Values of '#{@asset.name}.#{@property.name}' across slides>"
401
- end
402
- alias_method :to_s, :inspect
403
- end
404
-
405
- class UIC::SlideValues
406
- def initialize( asset, slide )
407
- @asset = asset
408
- @slide = slide
409
- end
410
- def [](attribute_name)
411
- @asset[attribute_name,@slide]
412
- end
413
- def []=( attribute_name, new_value )
414
- @asset[attribute_name,@slide] = new_value
415
- end
416
- def method_missing( name, *args, &blk )
417
- asset.send(name,*args,&blk)
418
- end
419
- def inspect
420
- "<#{@asset.inspect} on slide #{@slide.inspect}>"
421
- end
1
+ #encoding: utf-8
2
+ class UIC::MetaData
3
+
4
+ # The base class for all assets. All other classes are dynamically created when a `MetaData.xml` file is loaded.
5
+ class AssetBase
6
+ @properties = {}
7
+ @name = "AssetBase"
8
+
9
+ class << self
10
+ # @return [String] The scene graph name of the asset.
11
+ attr_reader :name
12
+
13
+ # @return [Hash] a hash mapping attribute names to {Property} instances.
14
+ def properties
15
+ (ancestors[1].respond_to?(:properties) ? ancestors[1].properties : {}).merge(@properties)
16
+ end
17
+
18
+ # @private
19
+ def inspect
20
+ "<#{@name}>"
21
+ end
22
+ end
23
+
24
+ # @return [Hash] a hash mapping attribute names to {Property} instances.
25
+ def properties
26
+ self.class.properties
27
+ end
28
+
29
+ # Find an asset by relative scripting path.
30
+ #
31
+ # @example
32
+ # preso = app.main
33
+ # layer = preso/"Scene.Layer"
34
+ # cam1 = app/"main:Scene.Layer.Camera"
35
+ # cam2 = preso/"Scene.Layer.Camera"
36
+ # cam3 = layer/"Camera"
37
+ # cam4 = cam1/"parent.Camera"
38
+ #
39
+ # assert cam1==cam2 && cam2==cam3 && cam3==cam4
40
+ #
41
+ # @return [MetaData::AssetBase] The found asset, or `nil` if it cannot be found.
42
+ #
43
+ # @see Application#at
44
+ # @see Presentation#at
45
+ def at(sub_path)
46
+ presentation.at(sub_path,@el)
47
+ end
48
+ alias_method :/, :at
49
+
50
+ # @return [Presentation] the presentation that this asset is part of.
51
+ attr_accessor :presentation
52
+
53
+ # @return [Nokogiri::XML::Element] the internal XML element in the scene graph of the presentation.
54
+ attr_accessor :el
55
+
56
+ # Create a new asset. This is called for you automatically; you likely should not be using it.
57
+ # @param presentation [Presentation] the presentation owning this asset.
58
+ # @param element [Nokogiri::XML::Element] the internal XML element in the scene graph of the presentation.
59
+ def initialize( presentation, element )
60
+ @presentation = presentation
61
+ @el = element
62
+ end
63
+
64
+ # @return [String] the type of this asset. For example: `"Model"`, `"Material"`, `"ReferencedMaterial"`, `"PathAnchorPoint"`, etc.
65
+ def type
66
+ self.class.name
67
+ end
68
+
69
+ # @return [AssetBase] the parent of this asset in the scene graph.
70
+ # @see Presentation#parent_asset
71
+ def parent
72
+ presentation.parent_asset(self)
73
+ end
74
+
75
+ # @return [Array<AssetBase>] array of child assets in the scene graph. Children are in scene graph order.
76
+ # @see Presentation#child_assets
77
+ def children
78
+ presentation.child_assets(self)
79
+ end
80
+
81
+ # Find descendant assets matching criteria.
82
+ # This method is the same as (but more convenient than) {Presentation#find} using the `:_under` criteria.
83
+ # See that method for documentation of the `criteria`.
84
+ #
85
+ # @example
86
+ # preso = app.main
87
+ # group = preso/"Scene.Layer.Vehicle"
88
+ # tires = group.find name:/^Tire/
89
+ #
90
+ # # alternative
91
+ # tires = preso.find name:/^Tire/, _under:group
92
+ #
93
+ # @return [Array<AssetBase>]
94
+ #
95
+ # @see Presentation#find
96
+ def find(criteria={},&block)
97
+ criteria[:_under] ||= self
98
+ presentation.find(criteria,&block)
99
+ end
100
+
101
+ # @return [AssetBase] the component or scene that owns this asset.
102
+ # If this asset is a component, does not return itself.
103
+ # @see Presentation#owning_component
104
+ def component
105
+ presentation.owning_component(self)
106
+ end
107
+
108
+ # @return [Boolean] `true` if this asset is a component or Scene.
109
+ def component?
110
+ @el.name=='Component' || @el.name=='Scene'
111
+ end
112
+
113
+ # @return [Boolean] `true` if this asset is on the master slide.
114
+ # @see Presentation#master?
115
+ def master?
116
+ presentation.master?(self)
117
+ end
118
+
119
+ # @return [Boolean] `true` if this asset is a Slide.
120
+ def slide?
121
+ false
122
+ end
123
+
124
+ # @param slide_name_or_index [Integer,String] the slide number of name to check for presence on.
125
+ # @return [Boolean] `true` if this asset is present on the specified slide.
126
+ # @see Presentation#has_slide?
127
+ def has_slide?(slide_name_or_index)
128
+ presentation.has_slide?(self,slide_name_or_index)
129
+ end
130
+
131
+ # @return [SlideCollection] an array-like collection of all slides that the asset is available on.
132
+ # @see Presentation#slides_for
133
+ def slides
134
+ presentation.slides_for(self)
135
+ end
136
+
137
+ # @example
138
+ # logo = app/"main:Scene.UI.Logo"
139
+ # assert logo.master? # It's a master object
140
+ #
141
+ # show logo['endtime'].values #=> [10000,500,1000,750]
142
+ # show logo['opacity'].values #=> [100,0,0,100]
143
+ # logo1 = logo.on_slide(1)
144
+ # logo2 = logo.on_slide(2)
145
+ # show logo1['endtime'] #=> 500
146
+ # show logo2['endtime'] #=> 1000
147
+ # show logo1['opacity'] #=> 0
148
+ # show logo2['opacity'] #=> 0
149
+ #
150
+ # logo2['opacity'] = 66
151
+ # show logo['opacity'].values #=> [100,0,66,100]
152
+ #
153
+ # @param slide_name_or_index [Integer,String] the slide number or name to create the proxy for.
154
+ # @return [SlideValues] a proxy that yields attribute values for a specific slide.
155
+ def on_slide(slide_name_or_index)
156
+ if has_slide?(slide_name_or_index)
157
+ UIC::SlideValues.new( self, slide_name_or_index )
158
+ end
159
+ end
160
+
161
+ # @return [String] the script path to this asset.
162
+ # @see #path_to
163
+ # @see Presentation#path_to
164
+ def path
165
+ @path ||= @presentation.path_to(self)
166
+ end
167
+
168
+ # @param other_asset [AssetBase] the asset to find the relative path to.
169
+ # @return [String] the script path to another asset, relative to this one.
170
+ # @see #path
171
+ # @see Presentation#path_to
172
+ def path_to(other_asset)
173
+ @presentation.path_to(other_asset,self)
174
+ end
175
+
176
+ # @return [String] the name of this asset in the scene graph.
177
+ def name
178
+ properties['name'].get( self, presentation.slide_index(self) )
179
+ end
180
+
181
+ # Change the name of the asset in the scene graph.
182
+ # @param new_name [String] the new name for this asset.
183
+ # @return [String] the new name.
184
+ def name=( new_name )
185
+ @path = nil # invalidate the memoization
186
+ properties['name'].set( self, new_name, presentation.slide_index(self) )
187
+ end
188
+
189
+ # Get the value(s) of an attribute.
190
+ # If `slide_name_or_index` is omitted, creates a ValuesPerSlide proxy for the specified attribute.
191
+ # @example
192
+ # logo = app/"main:Scene.UI.Logo"
193
+ # show logo.master? #=> true (it's a master object)
194
+ # show logo['endtime'].linked? #=> false (the endtime property is unlinked)
195
+ # show logo['endtime'].values #=> [10000,500,1000,750]
196
+ # show logo['endtime',0] #=> 10000 (the master slide value)
197
+ # show logo['endtime',"Slide 1"] #=> 500
198
+ # show logo['endtime',2] #=> 1000
199
+ #
200
+ # @param attribute_name [String,Symbol] the name of the attribute.
201
+ # @param slide_name_or_index [Integer,String] the slide number or name to find the value on.
202
+ # @return [Object] the value of the property on the given slide.
203
+ # @return [ValuesPerSlide] if no slide is specified.
204
+ # @see #on_slide
205
+ # @see Presentation#get_attribute
206
+ def [](attribute_name, slide_name_or_index=nil)
207
+ if property = properties[attribute_name.to_s]
208
+ if slide_name_or_index
209
+ property.get( self, slide_name_or_index ) if has_slide?(slide_name_or_index)
210
+ else
211
+ UIC::ValuesPerSlide.new(@presentation,self,property)
212
+ end
213
+ end
214
+ end
215
+
216
+ # Set the value of an attribute, either across all slides, or on a particular slide.
217
+ #
218
+ # @example
219
+ # logo = app/"main:Scene.UI.Logo"
220
+ # show logo.master? #=> true (it's a master object)
221
+ # show logo['endtime'].linked? #=> false (the endtime property is unlinked)
222
+ # show logo['endtime'].values #=> [10000,500,1000,750]
223
+ #
224
+ # logo['endtime',1] = 99
225
+ # show logo['endtime'].values #=> [10000,99,1000,750]
226
+ #
227
+ # logo['endtime'] = 42
228
+ # show logo['endtime'].values #=> [42,42,42,42]
229
+ # show logo['endtime'].linked? #=> false (the endtime property is still unlinked)
230
+ #
231
+ # @param attribute_name [String,Symbol] the name of the attribute.
232
+ # @param slide_name_or_index [Integer,String] the slide number or name to set the value on.
233
+ # @param new_value [Numeric,String] the new value for the attribute.
234
+ # @see Presentation#set_attribute
235
+ def []=( attribute_name, slide_name_or_index=nil, new_value )
236
+ if property = properties[attribute_name.to_s] then
237
+ property.set(self,new_value,slide_name_or_index)
238
+ end
239
+ end
240
+
241
+ # @return [String] the XML representation of the scene graph element.
242
+ def to_xml
243
+ @el.to_xml
244
+ end
245
+
246
+ # @private no need to document this
247
+ def inspect
248
+ "<asset #{@el.name}##{@el['id']}>"
249
+ end
250
+
251
+ # @private no need to document this
252
+ def to_s
253
+ "<#{type} #{path}>"
254
+ end
255
+
256
+ # @private no need to document this
257
+ def ==(other)
258
+ (self.class==other.class) && (el==other.el)
259
+ end
260
+ alias_method :eql?, :==
261
+ end
262
+
263
+ attr_reader :by_name
264
+
265
+ HIER = {}
266
+
267
+ %w[Asset Slide Scene].each{ |s| HIER[s] = 'AssetBase' }
268
+ %w[Node Behavior Effect Image Layer MaterialBase PathAnchorPoint RenderPlugin].each{ |s| HIER[s]='Asset' }
269
+ %w[Alias Camera Component Group Light Model Text Path].each{ |s| HIER[s]='Node' }
270
+ %w[Material ReferencedMaterial].each{ |s| HIER[s]='MaterialBase' }
271
+
272
+ def initialize(xml)
273
+
274
+ @by_name = {'AssetBase'=>AssetBase}
275
+
276
+ doc = Nokogiri.XML(xml)
277
+ hack_in_slide_names!(doc)
278
+
279
+ HIER.each do |class_name,parent_class_name|
280
+ parent_class = @by_name[parent_class_name]
281
+ el = doc.root.at(class_name)
282
+ @by_name[class_name] = create_class(el,parent_class,el.name)
283
+ end
284
+
285
+ # Extend well-known classes with script interfaces after they are created
286
+ @by_name['State'] = @by_name['Slide']
287
+ @by_name['Slide'].instance_eval do
288
+ attr_accessor :index, :name
289
+ define_method :inspect do
290
+ "<slide ##{index} of #{@el['component'] || @el.parent['component']}>"
291
+ end
292
+ define_method(:slide?){ true }
293
+ end
294
+
295
+ refmat = @by_name['ReferencedMaterial']
296
+ @by_name['MaterialBase'].instance_eval do
297
+ define_method :replace_with_referenced_material do
298
+ type=='ReferencedMaterial' ? self : presentation.replace_asset( self, 'ReferencedMaterial', name:name )
299
+ end
300
+ end
301
+
302
+ @by_name['Path'].instance_eval do
303
+ define_method(:anchors){ find _type:'PathAnchorPoint' }
304
+ end
305
+
306
+ end
307
+
308
+ # Creates a class from MetaData.xml with accessors for the <Property> listed.
309
+ # Instances of the class are associated with a presentation and know how to
310
+ # get/set values in that XML based on value types, slides, defaults.
311
+ # Also used to create classes from effects, materials, and behavior preambles.
312
+ def create_class(el,parent_class,name='CustomAsset')
313
+ Class.new(parent_class) do
314
+ @name = name.to_s
315
+ @properties = Hash[ el.css("Property").map do |e|
316
+ type = e['type'] || (e['list'] ? 'String' : 'Float')
317
+ type = "Float" if type=="float"
318
+ property = UIC::Property.const_get(type).new(e)
319
+ [ property.name, UIC::Property.const_get(type).new(e) ]
320
+ end ]
321
+ def self.inspect
322
+ @name
323
+ end
324
+ end
325
+ end
326
+
327
+ def new_instance(presentation,el)
328
+ klass = @by_name[el.name] || create_class(el,@by_name['Asset'],el.name)
329
+ klass.new(presentation,el)
330
+ end
331
+
332
+ def hack_in_slide_names!(doc)
333
+ doc.at('Slide') << '<Property name="name" formalName="Name" type="String" default="Slide" hidden="True" />'
334
+ end
335
+ end
336
+
337
+ def UIC.MetaData(metadata_path)
338
+ UIC::MetaData.new(File.read(metadata_path,encoding:'utf-8'))
339
+ end
340
+
341
+ class UIC::SlideCollection
342
+ include Enumerable
343
+ attr_reader :length
344
+ def initialize(slides)
345
+ @length = slides.length-1
346
+ @slides = slides
347
+ @lookup = {}
348
+ slides.each do |s|
349
+ @lookup[s.index] = s
350
+ @lookup[s.name] = s
351
+ end
352
+ end
353
+ def each
354
+ @slides.each{ |s| yield(s) }
355
+ end
356
+ def [](index_or_name)
357
+ @lookup[ index_or_name ]
358
+ end
359
+ def inspect
360
+ "[ #{@slides.map(&:inspect).join ', '} ]"
361
+ end
362
+ def to_ary
363
+ @slides
364
+ end
365
+ end
366
+
367
+ class UIC::ValuesPerSlide
368
+ def initialize(presentation,asset,property)
369
+ raise unless presentation.is_a?(UIC::Presentation)
370
+
371
+ raise unless asset.is_a?(UIC::MetaData::AssetBase)
372
+ raise unless property.is_a?(UIC::Property)
373
+ @preso = presentation
374
+ @asset = asset
375
+ @el = asset.el
376
+ @property = property
377
+ end
378
+ def value
379
+ values.first
380
+ end
381
+ def [](slide_name_or_index)
382
+ @property.get( @asset, slide_name_or_index )
383
+ end
384
+ def []=(slide_name_or_index,new_value)
385
+ @property.set( @asset, new_value, slide_name_or_index )
386
+ end
387
+ def linked?
388
+ @preso.attribute_linked?( @asset, @property.name )
389
+ end
390
+ def unlink
391
+ @preso.unlink_attribute( @asset, @property.name )
392
+ end
393
+ def link
394
+ @preso.link_attribute( @asset, @property.name )
395
+ end
396
+ def values
397
+ @asset.slides.map{ |s| self[s.name] }
398
+ end
399
+ def inspect
400
+ "<Values of '#{@asset.name}.#{@property.name}' across slides>"
401
+ end
402
+ alias_method :to_s, :inspect
403
+ end
404
+
405
+ class UIC::SlideValues
406
+ def initialize( asset, slide )
407
+ @asset = asset
408
+ @slide = slide
409
+ end
410
+ def [](attribute_name)
411
+ @asset[attribute_name,@slide]
412
+ end
413
+ def []=( attribute_name, new_value )
414
+ @asset[attribute_name,@slide] = new_value
415
+ end
416
+ def method_missing( name, *args, &blk )
417
+ asset.send(name,*args,&blk)
418
+ end
419
+ def inspect
420
+ "<#{@asset.inspect} on slide #{@slide.inspect}>"
421
+ end
422
422
  end