RUIC 0.4.2 → 0.4.3

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