RUIC 0.4.4 → 0.4.5

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.
@@ -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