RUIC 0.4.2 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
data/lib/ruic.rb CHANGED
@@ -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
@@ -164,7 +164,11 @@ class UIC::Application
164
164
  # layer4 = app.main/"Scene.Layer"
165
165
  #
166
166
  # assert layer1==layer2 && layer2==layer3 && layer3==layer4
167
- # @return [UIC::MetaData::Root] The found asset, or `nil` if it cannot be found.
167
+ #
168
+ # @return [MetaData::AssetBase] The found asset, or `nil` if it cannot be found.
169
+ #
170
+ # @see Presentation#at
171
+ # @see MetaData::AssetBase#at
168
172
  def at(path)
169
173
  parts = path.split(':')
170
174
  preso = parts.length==2 ? self["##{parts.first}"] : main_presentation
data/lib/ruic/assets.rb CHANGED
@@ -1,290 +1,313 @@
1
- #encoding: utf-8
2
- class UIC::MetaData
3
- class Root
4
- @properties = {}
5
- class << self
6
- attr_reader :name
7
- def properties
8
- (ancestors[1].respond_to?(:properties) ? ancestors[1].properties : {}).merge(@properties)
9
- end
10
-
11
- def each
12
- (@by_name.values - [self]).each{ |klass| yield klass }
13
- end
14
- include Enumerable
15
-
16
- def inspect
17
- "<#{@name}>"
18
- end
19
- end
20
-
21
- def properties
22
- self.class.properties
23
- end
24
-
25
- def at(sub_path)
26
- presentation.at(sub_path,@el)
27
- end
28
- alias_method :/, :at
29
-
30
- attr_accessor :presentation, :el
31
- def initialize( presentation, element )
32
- @presentation = presentation
33
- @el = element
34
- end
35
-
36
- def type
37
- self.class.name
38
- # self.class.name.split('::').last
39
- end
40
-
41
- def parent
42
- presentation.parent_asset(self)
43
- end
44
-
45
- def children
46
- presentation.child_assets(self)
47
- end
48
-
49
- def find(criteria={},&block)
50
- criteria[:_under] ||= self
51
- presentation.find(criteria,&block)
52
- end
53
-
54
- # Find the owning component (even if you are a component)
55
- def component
56
- presentation.owning_component(self)
57
- end
58
-
59
- def component?
60
- @el.name == 'Component'
61
- end
62
-
63
- def master?
64
- presentation.master?(self)
65
- end
66
-
67
- def slide?
68
- false
69
- end
70
-
71
- def has_slide?(slide_name_or_index)
72
- presentation.has_slide?(self,slide_name_or_index)
73
- end
74
-
75
- def slides
76
- presentation.slides_for(self)
77
- end
78
-
79
- def on_slide(slide_name_or_index)
80
- if has_slide?(slide_name_or_index)
81
- UIC::SlideValues.new( self, slide_name_or_index )
82
- end
83
- end
84
-
85
- def path
86
- @path ||= @presentation.path_to(self)
87
- end
88
-
89
- def name
90
- properties['name'].get( self, presentation.slide_index(self) )
91
- end
92
-
93
- def name=( new_name )
94
- properties['name'].set( self, new_name, presentation.slide_index(self) )
95
- end
96
-
97
- # Get the value(s) of an attribute
98
- def [](attribute_name, slide_name_or_index=nil)
99
- if property = properties[attribute_name]
100
- if slide_name_or_index
101
- property.get( self, slide_name_or_index ) if has_slide?(slide_name_or_index)
102
- else
103
- UIC::ValuesPerSlide.new(@presentation,self,property)
104
- end
105
- end
106
- end
107
-
108
- # Set the value of an attribute, either across all slides, or on a particular slide
109
- # el['foo'] = 42
110
- # el['foo',0] = 42
111
- def []=( attribute_name, slide_name_or_index=nil, new_value )
112
- if property = properties[attribute_name] then
113
- property.set(self,new_value,slide_name_or_index)
114
- end
115
- end
116
-
117
- def to_xml
118
- @el.to_xml
119
- end
120
- def inspect
121
- "<asset #{@el.name}##{@el['id']}>"
122
- end
123
-
124
- def to_s
125
- "<#{type} #{path}>"
126
- end
127
-
128
- def ==(other)
129
- (self.class==other.class) && (el==other.el)
130
- end
131
- alias_method :eql?, :==
132
- end
133
-
134
- attr_reader :by_name
135
-
136
- HIER = {}
137
- %w[Asset Slide Scene].each{ |s| HIER[s] = 'Root' }
138
- %w[Node Behavior Effect Image Layer MaterialBase PathAnchorPoint RenderPlugin].each{ |s| HIER[s]='Asset' }
139
- %w[Alias Camera Component Group Light Model Text Path].each{ |s| HIER[s]='Node' }
140
- %w[Material ReferencedMaterial].each{ |s| HIER[s]='MaterialBase' }
141
-
142
- def initialize(xml)
143
- @by_name = {'Root'=>Root}
144
-
145
- doc = Nokogiri.XML(xml)
146
- hack_in_slide_names!(doc)
147
-
148
- HIER.each do |class_name,parent_class_name|
149
- parent_class = @by_name[parent_class_name]
150
- el = doc.root.at(class_name)
151
- @by_name[class_name] = create_class(el,parent_class,el.name)
152
- end
153
-
154
- # Extend well-known classes with script interfaces after they are created
155
- @by_name['State'] = @by_name['Slide']
156
- @by_name['Slide'].instance_eval do
157
- attr_accessor :index, :name
158
- define_method :inspect do
159
- "<slide ##{index} of #{@el['component'] || @el.parent['component']}>"
160
- end
161
- define_method(:slide?){ true }
162
- end
163
-
164
- refmat = @by_name['ReferencedMaterial']
165
- @by_name['MaterialBase'].instance_eval do
166
- define_method :replace_with_referenced_material do
167
- type=='ReferencedMaterial' ? self : presentation.replace_asset( self, 'ReferencedMaterial', name:name )
168
- end
169
- end
170
-
171
- @by_name['Path'].instance_eval do
172
- define_method(:anchors){ find _type:'PathAnchorPoint' }
173
- end
174
-
175
- end
176
-
177
- # Creates a class from MetaData.xml with accessors for the <Property> listed.
178
- # Instances of the class are associated with a presentation and know how to
179
- # get/set values in that XML based on value types, slides, defaults.
180
- # Also used to create classes from effects, materials, and behavior preambles.
181
- def create_class(el,parent_class,name='CustomAsset')
182
- Class.new(parent_class) do
183
- @name = name.to_s
184
- @properties = Hash[ el.css("Property").map do |e|
185
- type = e['type'] || (e['list'] ? 'String' : 'Float')
186
- type = "Float" if type=="float"
187
- property = UIC::Property.const_get(type).new(e)
188
- [ property.name, UIC::Property.const_get(type).new(e) ]
189
- end ]
190
- def self.inspect
191
- @name
192
- end
193
- end
194
- end
195
-
196
- def new_instance(presentation,el)
197
- klass = @by_name[el.name] || create_class(el,@by_name['Asset'],el.name)
198
- klass.new(presentation,el)
199
- end
200
-
201
- def hack_in_slide_names!(doc)
202
- doc.at('Slide') << '<Property name="name" formalName="Name" type="String" default="Slide" hidden="True" />'
203
- end
204
- end
205
-
206
- def UIC.MetaData(metadata_path)
207
- UIC::MetaData.new(File.read(metadata_path,encoding:'utf-8'))
208
- end
209
-
210
- class UIC::SlideCollection
211
- include Enumerable
212
- attr_reader :length
213
- def initialize(slides)
214
- @length = slides.length-1
215
- @slides = slides
216
- @lookup = {}
217
- slides.each do |s|
218
- @lookup[s.index] = s
219
- @lookup[s.name] = s
220
- end
221
- end
222
- def each
223
- @slides.each{ |s| yield(s) }
224
- end
225
- def [](index_or_name)
226
- @lookup[ index_or_name ]
227
- end
228
- def inspect
229
- "[ #{@slides.map(&:inspect).join ', '} ]"
230
- end
231
- def to_ary
232
- @slides
233
- end
234
- end
235
-
236
- class UIC::ValuesPerSlide
237
- def initialize(presentation,asset,property)
238
- raise unless presentation.is_a?(UIC::Presentation)
239
- raise unless asset.is_a?(UIC::MetaData::Root)
240
- raise unless property.is_a?(UIC::Property)
241
- @preso = presentation
242
- @asset = asset
243
- @el = asset.el
244
- @property = property
245
- end
246
- def value
247
- values.first
248
- end
249
- def [](slide_name_or_index)
250
- @property.get( @asset, slide_name_or_index )
251
- end
252
- def []=(slide_name_or_index,new_value)
253
- @property.set( @asset, new_value, slide_name_or_index )
254
- end
255
- def linked?
256
- @preso.attribute_linked?( @asset, @property.name )
257
- end
258
- def unlink
259
- @preso.unlink_attribute( @asset, @property.name )
260
- end
261
- def link
262
- @preso.link_attribute( @asset, @property.name )
263
- end
264
- def values
265
- @asset.slides.map{ |s| self[s.name] }
266
- end
267
- def inspect
268
- "<Values of '#{@asset.name}.#{@property.name}' across slides>"
269
- end
270
- alias_method :to_s, :inspect
271
- end
272
-
273
- class UIC::SlideValues
274
- def initialize( asset, slide )
275
- @asset = asset
276
- @slide = slide
277
- end
278
- def [](attribute_name)
279
- @asset[attribute_name,@slide]
280
- end
281
- def []=( attribute_name, new_value )
282
- @asset[attribute_name,@slide] = new_value
283
- end
284
- def method_missing( name, *args, &blk )
285
- asset.send(name,*args,&blk)
286
- end
287
- def inspect
288
- "<#{@asset.inspect} on slide #{@slide.inspect}>"
289
- 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
+ attr_accessor :presentation, :el
51
+ def initialize( presentation, element )
52
+ @presentation = presentation
53
+ @el = element
54
+ end
55
+
56
+ def type
57
+ self.class.name
58
+ # self.class.name.split('::').last
59
+ end
60
+
61
+ def parent
62
+ presentation.parent_asset(self)
63
+ end
64
+
65
+ def children
66
+ presentation.child_assets(self)
67
+ end
68
+
69
+ def find(criteria={},&block)
70
+ criteria[:_under] ||= self
71
+ presentation.find(criteria,&block)
72
+ end
73
+
74
+ # Find the owning component (even if you are a component)
75
+ def component
76
+ presentation.owning_component(self)
77
+ end
78
+
79
+ def component?
80
+ @el.name == 'Component'
81
+ end
82
+
83
+ def master?
84
+ presentation.master?(self)
85
+ end
86
+
87
+ def slide?
88
+ false
89
+ end
90
+
91
+ def has_slide?(slide_name_or_index)
92
+ presentation.has_slide?(self,slide_name_or_index)
93
+ end
94
+
95
+ def slides
96
+ presentation.slides_for(self)
97
+ end
98
+
99
+ def on_slide(slide_name_or_index)
100
+ if has_slide?(slide_name_or_index)
101
+ UIC::SlideValues.new( self, slide_name_or_index )
102
+ end
103
+ end
104
+
105
+ def path
106
+ @path ||= @presentation.path_to(self)
107
+ end
108
+
109
+ def name
110
+ properties['name'].get( self, presentation.slide_index(self) )
111
+ end
112
+
113
+ def name=( new_name )
114
+ properties['name'].set( self, new_name, presentation.slide_index(self) )
115
+ end
116
+
117
+ # Get the value(s) of an attribute
118
+ def [](attribute_name, slide_name_or_index=nil)
119
+ if property = properties[attribute_name]
120
+ if slide_name_or_index
121
+ property.get( self, slide_name_or_index ) if has_slide?(slide_name_or_index)
122
+ else
123
+ UIC::ValuesPerSlide.new(@presentation,self,property)
124
+ end
125
+ end
126
+ end
127
+
128
+ # Set the value of an attribute, either across all slides, or on a particular slide
129
+ # el['foo'] = 42
130
+ # el['foo',0] = 42
131
+ def []=( attribute_name, slide_name_or_index=nil, new_value )
132
+ if property = properties[attribute_name] then
133
+ property.set(self,new_value,slide_name_or_index)
134
+ end
135
+ end
136
+
137
+ def to_xml
138
+ @el.to_xml
139
+ end
140
+ def inspect
141
+ "<asset #{@el.name}##{@el['id']}>"
142
+ end
143
+
144
+ def to_s
145
+ "<#{type} #{path}>"
146
+ end
147
+
148
+ def ==(other)
149
+ (self.class==other.class) && (el==other.el)
150
+ end
151
+ alias_method :eql?, :==
152
+ end
153
+
154
+ attr_reader :by_name
155
+
156
+ HIER = {}
157
+
158
+ %w[Asset Slide Scene].each{ |s| HIER[s] = 'AssetBase' }
159
+ %w[Node Behavior Effect Image Layer MaterialBase PathAnchorPoint RenderPlugin].each{ |s| HIER[s]='Asset' }
160
+ %w[Alias Camera Component Group Light Model Text Path].each{ |s| HIER[s]='Node' }
161
+ %w[Material ReferencedMaterial].each{ |s| HIER[s]='MaterialBase' }
162
+
163
+ def initialize(xml)
164
+
165
+ @by_name = {'AssetBase'=>AssetBase}
166
+
167
+ doc = Nokogiri.XML(xml)
168
+ hack_in_slide_names!(doc)
169
+
170
+ HIER.each do |class_name,parent_class_name|
171
+ parent_class = @by_name[parent_class_name]
172
+ el = doc.root.at(class_name)
173
+ @by_name[class_name] = create_class(el,parent_class,el.name)
174
+ end
175
+
176
+ # Extend well-known classes with script interfaces after they are created
177
+ @by_name['State'] = @by_name['Slide']
178
+ @by_name['Slide'].instance_eval do
179
+ attr_accessor :index, :name
180
+ define_method :inspect do
181
+ "<slide ##{index} of #{@el['component'] || @el.parent['component']}>"
182
+ end
183
+ define_method(:slide?){ true }
184
+ end
185
+
186
+ refmat = @by_name['ReferencedMaterial']
187
+ @by_name['MaterialBase'].instance_eval do
188
+ define_method :replace_with_referenced_material do
189
+ type=='ReferencedMaterial' ? self : presentation.replace_asset( self, 'ReferencedMaterial', name:name )
190
+ end
191
+ end
192
+
193
+ @by_name['Path'].instance_eval do
194
+ define_method(:anchors){ find _type:'PathAnchorPoint' }
195
+ end
196
+
197
+ end
198
+
199
+ # Creates a class from MetaData.xml with accessors for the <Property> listed.
200
+ # Instances of the class are associated with a presentation and know how to
201
+ # get/set values in that XML based on value types, slides, defaults.
202
+ # Also used to create classes from effects, materials, and behavior preambles.
203
+ def create_class(el,parent_class,name='CustomAsset')
204
+ Class.new(parent_class) do
205
+ @name = name.to_s
206
+ @properties = Hash[ el.css("Property").map do |e|
207
+ type = e['type'] || (e['list'] ? 'String' : 'Float')
208
+ type = "Float" if type=="float"
209
+ property = UIC::Property.const_get(type).new(e)
210
+ [ property.name, UIC::Property.const_get(type).new(e) ]
211
+ end ]
212
+ def self.inspect
213
+ @name
214
+ end
215
+ end
216
+ end
217
+
218
+ def new_instance(presentation,el)
219
+ klass = @by_name[el.name] || create_class(el,@by_name['Asset'],el.name)
220
+ klass.new(presentation,el)
221
+ end
222
+
223
+ def hack_in_slide_names!(doc)
224
+ doc.at('Slide') << '<Property name="name" formalName="Name" type="String" default="Slide" hidden="True" />'
225
+ end
226
+ end
227
+
228
+ def UIC.MetaData(metadata_path)
229
+ UIC::MetaData.new(File.read(metadata_path,encoding:'utf-8'))
230
+ end
231
+
232
+ class UIC::SlideCollection
233
+ include Enumerable
234
+ attr_reader :length
235
+ def initialize(slides)
236
+ @length = slides.length-1
237
+ @slides = slides
238
+ @lookup = {}
239
+ slides.each do |s|
240
+ @lookup[s.index] = s
241
+ @lookup[s.name] = s
242
+ end
243
+ end
244
+ def each
245
+ @slides.each{ |s| yield(s) }
246
+ end
247
+ def [](index_or_name)
248
+ @lookup[ index_or_name ]
249
+ end
250
+ def inspect
251
+ "[ #{@slides.map(&:inspect).join ', '} ]"
252
+ end
253
+ def to_ary
254
+ @slides
255
+ end
256
+ end
257
+
258
+ class UIC::ValuesPerSlide
259
+ def initialize(presentation,asset,property)
260
+ raise unless presentation.is_a?(UIC::Presentation)
261
+
262
+ raise unless asset.is_a?(UIC::MetaData::AssetBase)
263
+ raise unless property.is_a?(UIC::Property)
264
+ @preso = presentation
265
+ @asset = asset
266
+ @el = asset.el
267
+ @property = property
268
+ end
269
+ def value
270
+ values.first
271
+ end
272
+ def [](slide_name_or_index)
273
+ @property.get( @asset, slide_name_or_index )
274
+ end
275
+ def []=(slide_name_or_index,new_value)
276
+ @property.set( @asset, new_value, slide_name_or_index )
277
+ end
278
+ def linked?
279
+ @preso.attribute_linked?( @asset, @property.name )
280
+ end
281
+ def unlink
282
+ @preso.unlink_attribute( @asset, @property.name )
283
+ end
284
+ def link
285
+ @preso.link_attribute( @asset, @property.name )
286
+ end
287
+ def values
288
+ @asset.slides.map{ |s| self[s.name] }
289
+ end
290
+ def inspect
291
+ "<Values of '#{@asset.name}.#{@property.name}' across slides>"
292
+ end
293
+ alias_method :to_s, :inspect
294
+ end
295
+
296
+ class UIC::SlideValues
297
+ def initialize( asset, slide )
298
+ @asset = asset
299
+ @slide = slide
300
+ end
301
+ def [](attribute_name)
302
+ @asset[attribute_name,@slide]
303
+ end
304
+ def []=( attribute_name, new_value )
305
+ @asset[attribute_name,@slide] = new_value
306
+ end
307
+ def method_missing( name, *args, &blk )
308
+ asset.send(name,*args,&blk)
309
+ end
310
+ def inspect
311
+ "<#{@asset.inspect} on slide #{@slide.inspect}>"
312
+ end
290
313
  end