omf_ec 6.0.0.pre.3 → 6.0.0.pre.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. data/Rakefile +1 -0
  2. data/bin/omf_ec +264 -0
  3. data/example/engine_oedl.rb +52 -0
  4. data/example/engine_test.rb +90 -0
  5. data/example/test_exp/test00.rb +12 -0
  6. data/example/test_exp/test01.rb +22 -0
  7. data/example/test_exp/test02.rb +30 -0
  8. data/{exp_repo/test/exp → example/test_exp}/test03.rb +2 -42
  9. data/{exp_repo/test/exp → example/test_exp}/test04.rb +2 -2
  10. data/{exp_repo/test/exp → example/test_exp}/test05.rb +4 -4
  11. data/{exp_repo/test/exp → example/test_exp}/test06.rb +6 -6
  12. data/example/test_exp/test07.rb +65 -0
  13. data/lib/omf_ec/app_definition.rb +57 -0
  14. data/lib/omf_ec/backward/app_definition.rb +35 -0
  15. data/lib/omf_ec/backward/core_ext/array.rb +19 -0
  16. data/lib/omf_ec/backward/default_events.rb +71 -0
  17. data/lib/omf_ec/backward/dsl.rb +83 -0
  18. data/lib/omf_ec/backward/exp/testbed.rb +47 -0
  19. data/lib/omf_ec/backward/group.rb +72 -0
  20. data/lib/omf_ec/context/app_context.rb +54 -0
  21. data/lib/omf_ec/context/def_app_context.rb +6 -0
  22. data/lib/omf_ec/context/group_context.rb +88 -0
  23. data/lib/omf_ec/context/net_context.rb +46 -0
  24. data/lib/omf_ec/context.rb +10 -0
  25. data/lib/omf_ec/dsl.rb +159 -0
  26. data/lib/omf_ec/experiment.rb +71 -0
  27. data/lib/omf_ec/group.rb +158 -0
  28. data/lib/omf_ec/version.rb +1 -1
  29. data/lib/omf_ec.rb +34 -1
  30. data/omf_ec.gemspec +2 -3
  31. data/test/omf_ec/context_spec.rb +19 -0
  32. data/test/omf_ec/group_spec.rb +14 -0
  33. data/test/test_helper.rb +19 -0
  34. metadata +44 -89
  35. data/bin/omf +0 -147
  36. data/bin/omf_pre +0 -76
  37. data/bin/omf_test +0 -161
  38. data/example/net_devices.rb +0 -75
  39. data/exp_repo/system/exp/eventlib.rb +0 -96
  40. data/exp_repo/system/exp/imageNode.rb +0 -283
  41. data/exp_repo/system/exp/reset.rb +0 -21
  42. data/exp_repo/system/exp/saveNode.rb +0 -99
  43. data/exp_repo/system/exp/stat.rb +0 -49
  44. data/exp_repo/system/exp/stdlib.rb +0 -122
  45. data/exp_repo/system/exp/tell.rb +0 -53
  46. data/exp_repo/system/exp/testlib.rb +0 -12
  47. data/exp_repo/system/exp/winlib.rb +0 -154
  48. data/exp_repo/system/topo/active.rb +0 -9
  49. data/exp_repo/system/topo/all.rb +0 -10
  50. data/exp_repo/system/topo/circle.rb +0 -23
  51. data/exp_repo/test/app/aodvd.rb +0 -73
  52. data/exp_repo/test/app/appDef1.rb +0 -102
  53. data/exp_repo/test/app/athstats.rb +0 -76
  54. data/exp_repo/test/app/echo.rb +0 -36
  55. data/exp_repo/test/app/gennyReceiverAppDef.rb +0 -100
  56. data/exp_repo/test/app/gennySenderAppDef.rb +0 -106
  57. data/exp_repo/test/app/itgdec.rb +0 -79
  58. data/exp_repo/test/app/itgr.rb +0 -77
  59. data/exp_repo/test/app/itgs.rb +0 -105
  60. data/exp_repo/test/app/nop.rb +0 -36
  61. data/exp_repo/test/app/otg2.rb +0 -68
  62. data/exp_repo/test/app/otg2_mp.rb +0 -56
  63. data/exp_repo/test/app/otr2.rb +0 -56
  64. data/exp_repo/test/app/otr2_mp.rb +0 -51
  65. data/exp_repo/test/app/trace_oml2.rb +0 -62
  66. data/exp_repo/test/app/wlanconfig_oml2.rb +0 -42
  67. data/exp_repo/test/exp/conf-room-demo.rb +0 -118
  68. data/exp_repo/test/exp/planetlab.rb +0 -82
  69. data/exp_repo/test/exp/test01.rb +0 -42
  70. data/exp_repo/test/exp/test02.rb +0 -60
  71. data/exp_repo/test/exp/tutorial/hello-world-wired.rb +0 -79
  72. data/exp_repo/test/exp/tutorial/hello-world-wireless.rb +0 -87
  73. data/exp_repo/test/exp/tutorial/using-filters.rb +0 -69
  74. data/exp_repo/test/exp/tutorial/using-groups.rb +0 -64
  75. data/exp_repo/test/exp/tutorial/using-properties.rb +0 -93
  76. data/exp_repo/test/exp/tutorial/using-prototypes.rb +0 -111
  77. data/exp_repo/test/proto/aodvrouter.rb +0 -55
  78. data/exp_repo/test/proto/driverqueryapp.rb +0 -59
  79. data/exp_repo/test/proto/forwarder.rb +0 -73
  80. data/exp_repo/test/proto/itgcbrsender.rb +0 -70
  81. data/exp_repo/test/proto/itgdecoder.rb +0 -64
  82. data/exp_repo/test/proto/itgreceiver.rb +0 -55
  83. data/exp_repo/test/proto/itgvoipsender.rb +0 -64
  84. data/exp_repo/test/proto/listener2.rb +0 -62
  85. data/exp_repo/test/proto/nop.rb +0 -47
  86. data/exp_repo/test/proto/probelink.rb +0 -48
  87. data/exp_repo/test/proto/raw_receiver.rb +0 -64
  88. data/exp_repo/test/proto/receiver2_mp.rb +0 -48
  89. data/exp_repo/test/proto/sender2_mp.rb +0 -54
  90. data/exp_repo/test/proto/udp_receiver.rb +0 -55
  91. data/exp_repo/test/proto/udp_sender.rb +0 -61
data/lib/omf_ec/dsl.rb ADDED
@@ -0,0 +1,159 @@
1
+ require 'active_support/core_ext'
2
+ require 'eventmachine'
3
+
4
+ module OmfEc
5
+ # DSL methods to be used for OEDL scripts
6
+ module DSL
7
+ # Use EM timer to execute after certain time
8
+ #
9
+ # @example do something after 2 seconds
10
+ #
11
+ # after 2.seconds { 'do something' }
12
+ def after(time, &block)
13
+ OmfEc.comm.add_timer(time, block)
14
+ end
15
+
16
+ # Use EM periodic timer to execute after certain time
17
+ #
18
+ # @example do something every 2 seconds
19
+ #
20
+ # every 2.seconds { 'do something' }
21
+ def every(time, &block)
22
+ OmfEc.comm.add_periodic_timer(time, block)
23
+ end
24
+
25
+ def def_application(name,&block)
26
+ app_def = OmfEc::AppDefinition.new(name)
27
+ OmfEc.exp.app_definitions[name] = app_def
28
+ block.call(app_def) if block
29
+ end
30
+
31
+ # Define a group, create a pubsub topic for the group
32
+ #
33
+ # @param [String] name name of the group
34
+ #
35
+ # @example add resource 'a' to group 'bob'
36
+ # def_group('bob') do |g|
37
+ # g.add_resource('a')
38
+ # end
39
+ #
40
+ # @see OmfEc::Backward::DSL#defGroup
41
+ def def_group(name, &block)
42
+ group = OmfEc::Group.new(name)
43
+ OmfEc.exp.groups << group
44
+
45
+ OmfEc.comm.subscribe(group.id, create_if_non_existent: true) do |m|
46
+ unless m.error?
47
+ block.call group if block
48
+
49
+ rg = OmfEc.comm.get_topic(group.id)
50
+
51
+ rg.on_message lambda {|m| m.operation == :inform && m.read_content('inform_type') == 'FAILED' && m.context_id.nil? } do |i|
52
+ warn "RC reports failure: '#{i.read_content("reason")}'"
53
+ end
54
+
55
+ rg.on_message lambda {|m| m.operation == :inform && m.read_content('inform_type') == 'STATUS' && m.context_id.nil? } do |i|
56
+ r = OmfEc.exp.state.find { |v| v[:uid] == i.read_property(:uid) }
57
+ unless r.nil?
58
+ i.each_property do |p|
59
+ key = p.attr('key').to_sym
60
+ r[key] = i.read_property(key)
61
+ end
62
+ end
63
+ Experiment.instance.process_events
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ # Get a group instance
70
+ #
71
+ # @param [String] name name of the group
72
+ def group(name, &block)
73
+ group = OmfEc.exp.groups.find {|v| v.name == name}
74
+ block.call(group) if block
75
+ group
76
+ end
77
+
78
+ # Iterator for all defined groups
79
+ def all_groups(&block)
80
+ OmfEc.exp.groups.each do |g|
81
+ block.call(g) if block
82
+ end
83
+ end
84
+
85
+ alias_method :all_nodes!, :all_groups
86
+
87
+ # Exit the experiment
88
+ #
89
+ # @see OmfEc::Experiment.done
90
+ def done!
91
+ OmfEc::Experiment.done
92
+ end
93
+
94
+ alias_method :done, :done!
95
+
96
+ # Define an experiment property which can be used to bind
97
+ # to application and other properties. Changing an experiment
98
+ # property should also change the bound properties, or trigger
99
+ # commands to change them.
100
+ #
101
+ # @param name of property
102
+ # @param default_value for this property
103
+ # @param description short text description of this property
104
+ #
105
+ def def_property(name, default_value, description = nil)
106
+ OmfEc.exp.property[name] ||= default_value
107
+ end
108
+
109
+ # Return the context for setting experiment wide properties
110
+ def property
111
+ OmfEc.exp.property
112
+ end
113
+
114
+ alias_method :prop, :property
115
+
116
+ # Check if all elements in array equal the value provided
117
+ #
118
+ def all_equal(array, value = nil, &block)
119
+ if array.empty?
120
+ false
121
+ else
122
+ if value
123
+ array.all? { |v| v.to_s == value.to_s }
124
+ else
125
+ array.all?(&block)
126
+ end
127
+ end
128
+ end
129
+
130
+ # Check if any elements in array equals the value provided
131
+ #
132
+ def one_equal(array, value)
133
+ array.any? ? false : array.all? { |v| v.to_s == value.to_s }
134
+ end
135
+
136
+ # Define an event
137
+ def def_event(name, &trigger)
138
+ if OmfEc.exp.events.find { |v| v[:name] == name }
139
+ raise RuntimeError, "Event '#{name}' has been defined"
140
+ else
141
+ OmfEc.exp.events << { name: name, trigger: trigger }
142
+ end
143
+ end
144
+
145
+ # Define an event callback
146
+ def on_event(name, consume_event = true, &callback)
147
+ event = OmfEc.exp.events.find { |v| v[:name] == name }
148
+ if event.nil?
149
+ raise RuntimeError, "Event '#{name}' not defined"
150
+ else
151
+ event[:callbacks] ||= []
152
+ event[:callbacks] << callback
153
+ event[:consume_event] = consume_event
154
+ end
155
+ end
156
+
157
+ include OmfEc::Backward::DSL
158
+ end
159
+ end
@@ -0,0 +1,71 @@
1
+ require 'hashie'
2
+ require 'singleton'
3
+
4
+ module OmfEc
5
+ # Experiment class to hold relevant state information
6
+ #
7
+ class Experiment
8
+ include Singleton
9
+
10
+ attr_accessor :property,:state, :comm, :groups, :events, :name, :app_definitions, :sub_groups, :oml_uri
11
+
12
+ def initialize
13
+ @id = Time.now.utc.iso8601
14
+ self.property ||= Hashie::Mash.new
15
+ self.comm ||= OmfCommon::Comm.new(:xmpp)
16
+ self.state ||= []
17
+ self.groups ||= []
18
+ self.events ||= []
19
+ self.app_definitions ||= Hash.new
20
+ self.sub_groups ||= []
21
+ end
22
+
23
+ # Unique experiment id
24
+ def id
25
+ @name.nil? ? @id : "#{@name}-#{@id}"
26
+ end
27
+
28
+ # Parsing user defined events, checking conditions against internal state, and execute callbacks if triggered
29
+ def process_events
30
+ EM.next_tick do
31
+ self.events.find_all { |v| v[:callbacks] && !v[:callbacks].empty? }.each do |event|
32
+ if event[:trigger].call(self.state)
33
+ info "Event triggered: '#{event[:name]}'"
34
+ self.events.delete(event) if event[:consume_event]
35
+
36
+ # Last in first serve callbacks
37
+ event[:callbacks].reverse.each do |callback|
38
+ callback.call
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ # Purely for backward compatibility
46
+ class << self
47
+ # Disconnect communicator, try to delete any XMPP affiliations
48
+ def done
49
+ info "Exit in up to 20 seconds..."
50
+
51
+ OmfEc.comm.add_timer(10) do
52
+ info "Release applications and network interfaces"
53
+
54
+ allGroups do |g|
55
+ g.resources[type: 'application'].release
56
+ g.resources[type: 'net'].release unless g.net_ifs.find_all { |v| v.conf[:type] == 'net' }.empty?
57
+ g.resources[type: 'wlan'].release unless g.net_ifs.find_all { |v| v.conf[:type] == 'wlan' }.empty?
58
+ end
59
+
60
+ OmfEc.comm.add_timer(5) do
61
+ OmfEc.comm.disconnect(delete_affiliations: true)
62
+
63
+ OmfEc.comm.add_timer(5) do
64
+ OmfEc.comm.disconnect
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,158 @@
1
+ require 'securerandom'
2
+
3
+ module OmfEc
4
+ # Group instance used in experiment script
5
+ #
6
+ # @!attribute name [String] name of the resource
7
+ # @!attribute id [String] pubsub topic id of the resource
8
+ # @!attribute net_ifs [Array] network interfaces defined to be added to group
9
+ # @!attribute members [Array] holding members to be added to group
10
+ # @!attribute apps [Array] holding applications to be added to group
11
+ class Group
12
+ attr_accessor :name, :id, :net_ifs, :members, :app_contexts
13
+
14
+ # @param [String] name name of the group
15
+ # @param [Hash] opts
16
+ # @option opts [Boolean] :unique Should the group be unique or not, default is true
17
+ def initialize(name, opts = {})
18
+ @opts = {unique: true}.merge!(opts)
19
+ self.name = name
20
+ self.id = @opts[:unique] ? SecureRandom.uuid : self.name
21
+ # Add empty holders for members, network interfaces, and apps
22
+ self.net_ifs = []
23
+ self.members = []
24
+ self.app_contexts = []
25
+ end
26
+
27
+ # Add existing resources to the group
28
+ #
29
+ # Resources to be added could be a list of resources, groups, or the mixture of both.
30
+ def add_resource(*names)
31
+ names.each do |name|
32
+ # resource to add is a group
33
+ if OmfEc.exp.groups.any? { |v| v.name == name }
34
+ self.add_resource(*group(name).members.uniq)
35
+ else
36
+ OmfEc.comm.subscribe(name) do |m|
37
+ unless m.error?
38
+ # resource with uid: name is available
39
+ unless OmfEc.exp.state.any? { |v| v[:uid] == name }
40
+ OmfEc.exp.state << { uid: name }
41
+ end
42
+
43
+ r = OmfEc.comm.get_topic(name)
44
+
45
+ r.on_message lambda {|m| m.operation == :inform && m.read_content('inform_type') == 'STATUS' && m.context_id.nil? } do |i|
46
+ r = OmfEc.exp.state.find { |v| v[:uid] == i.read_property(:uid) }
47
+ unless r.nil?
48
+ i.each_property do |p|
49
+ key = p.attr('key').to_sym
50
+ r[key] = i.read_property(key)
51
+ end
52
+ end
53
+ Experiment.instance.process_events
54
+ end
55
+
56
+ # Receive failed inform message
57
+ r.on_message lambda {|m| m.operation == :inform && m.read_content('inform_type') == 'FAILED' && m.context_id.nil? } do |i|
58
+ warn "RC reports failure: '#{i.read_content("reason")}'"
59
+ end
60
+
61
+ c = OmfEc.comm.configure_message(self.id) do |m|
62
+ m.property(:membership, self.id)
63
+ end
64
+
65
+ c.publish name
66
+
67
+ c.on_inform_status do |i|
68
+ r = OmfEc.exp.state.find { |v| v[:uid] == name }
69
+ r[:membership] = i.read_property(:membership)
70
+ Experiment.instance.process_events
71
+ end
72
+
73
+ c.on_inform_failed do |i|
74
+ warn "RC reports failure: '#{i.read_content("reason")}'"
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ # Create a set of new resources and add them to the group
83
+ #
84
+ # @param [String] name
85
+ # @param [Hash] opts to be used to create new resources
86
+ def create_resource(name, opts, &block)
87
+
88
+ # Make a deep copy of opts in case it contains structures of structures
89
+ begin
90
+ opts = Marshal.load ( Marshal.dump(opts.merge(hrn: name)))
91
+ rescue Exception => e
92
+ raise "#{e.message} - Could not deep copy opts: '#{opts.inspect}'"
93
+ end
94
+
95
+ # Naming convention of child resource group
96
+ resource_group_name = "#{self.id}_#{opts[:type]}"
97
+
98
+
99
+ unless OmfEc.exp.sub_groups.include?(resource_group_name)
100
+ OmfEc.exp.sub_groups << resource_group_name
101
+
102
+ rg = OmfEc.comm.get_topic(resource_group_name)
103
+ # Receive status inform message
104
+ rg.on_message lambda {|m| m.operation == :inform && m.read_content('inform_type') == 'STATUS' && m.context_id.nil? } do |i|
105
+ r = OmfEc.exp.state.find { |v| v[:uid] == i.read_property(:uid) }
106
+ unless r.nil?
107
+ if i.read_property("status_type") == 'APP_EVENT'
108
+ info "APP_EVENT #{i.read_property('event')} "+
109
+ "from app #{i.read_property("app")} - msg: #{i.read_property("msg")}"
110
+ end
111
+ i.each_property do |p|
112
+ r[p.attr('key').to_sym] = p.content.ducktype
113
+ end
114
+ end
115
+ Experiment.instance.process_events
116
+ end
117
+
118
+ # Receive failed inform message
119
+ rg.on_message lambda {|m| m.operation == :inform && m.read_content('inform_type') == 'FAILED' && m.context_id.nil? } do |i|
120
+ warn "RC reports failure: '#{i.read_content("reason")}'"
121
+ end
122
+ end
123
+
124
+ # We create another group topic for new resouce
125
+ OmfEc.comm.subscribe(resource_group_name, create_if_non_existent: true) do |m|
126
+ unless m.error?
127
+
128
+ c = OmfEc.comm.create_message(self.id) do |m|
129
+ m.property(:membership, resource_group_name)
130
+ opts.each_pair do |k, v|
131
+ m.property(k, v)
132
+ end
133
+ end
134
+
135
+ c.publish self.id
136
+
137
+ c.on_inform_created do |i|
138
+ info "#{opts[:type]} #{i.resource_id} created"
139
+ OmfEc.exp.state << { uid: i.resource_id, type: opts[:type], hrn: name, membership: [resource_group_name]}
140
+ block.call if block
141
+ Experiment.instance.process_events
142
+ end
143
+
144
+ c.on_inform_failed do |i|
145
+ warn "RC reports failure: '#{i.read_content("reason")}'"
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ # @return [OmfEc::Context::GroupContext]
152
+ def resources
153
+ OmfEc::Context::GroupContext.new(group: self.id)
154
+ end
155
+
156
+ include OmfEc::Backward::Group
157
+ end
158
+ end
@@ -1,3 +1,3 @@
1
1
  module OmfEc
2
- VERSION = "6.0.0.pre.3"
2
+ VERSION = "6.0.0.pre.4"
3
3
  end
data/lib/omf_ec.rb CHANGED
@@ -1,5 +1,38 @@
1
+ require "omf_common"
2
+ require 'omf_ec/backward/dsl'
3
+ require 'omf_ec/backward/group'
4
+ require 'omf_ec/backward/app_definition'
5
+ require 'omf_ec/backward/default_events'
6
+ require 'omf_ec/backward/core_ext/array'
1
7
  require "omf_ec/version"
8
+ require "omf_ec/experiment"
9
+ require "omf_ec/group"
10
+ require "omf_ec/app_definition"
11
+ require "omf_ec/context"
12
+ require "omf_ec/dsl"
2
13
 
3
14
  module OmfEc
4
- # Your code goes here...
15
+ class << self
16
+ # Experiment instance
17
+ #
18
+ # @return [OmfEc::Experiment]
19
+ def experiment
20
+ Experiment.instance
21
+ end
22
+
23
+ alias_method :exp, :experiment
24
+
25
+ # Experiment's communicator instance
26
+ # @return [OmfCommon::Comm]
27
+ def communicator
28
+ Experiment.instance.comm
29
+ end
30
+
31
+ # Full path of lib directory
32
+ def lib_root
33
+ File.expand_path("../..", "#{__FILE__}/lib")
34
+ end
35
+
36
+ alias_method :comm, :communicator
37
+ end
5
38
  end
data/omf_ec.gemspec CHANGED
@@ -15,12 +15,11 @@ Gem::Specification.new do |s|
15
15
 
16
16
  s.files = `git ls-files`.split("\n")
17
17
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
- #s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
- s.executables = ["omf_pre"]
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
19
  s.require_paths = ["lib"]
21
20
 
22
21
  # specify any dependencies here; for example:
23
22
  s.add_development_dependency "minitest", "~> 3.2"
24
23
  s.add_runtime_dependency "omf_common", "~> 6.0.0.pre"
25
- s.add_runtime_dependency "gli", "~> 2.0.0.pre"
24
+ s.add_runtime_dependency "gli", "~> 2.4.1"
26
25
  end
@@ -0,0 +1,19 @@
1
+ require 'test_helper'
2
+ require 'omf_ec/context'
3
+
4
+ describe OmfEc::Context do
5
+ before do
6
+ @context = OmfEc::Context::GroupContext.new(group: 'universe')
7
+ end
8
+
9
+ describe "when initialised with options" do
10
+ it "must be able to respond to random group operation method" do
11
+ skip
12
+ end
13
+
14
+ it "must be able to construct guards" do
15
+ g = @context[name: 'bob'][type: 'engine', group: 'universe'].guard
16
+ g.must_equal({ name: 'bob', type: 'engine', group: 'universe' })
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,14 @@
1
+ require 'test_helper'
2
+ require 'omf_ec/group'
3
+
4
+ describe OmfEc::Group do
5
+ describe "when initialised" do
6
+ it "must be generate unique id if :unique option is on" do
7
+ OmfEc::Group.new('bob').id.wont_equal 'bob'
8
+ end
9
+
10
+ it "must use name as id if :unique option is off" do
11
+ OmfEc::Group.new('bob', unique: false).id.must_equal 'bob'
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ require 'simplecov'
2
+ SimpleCov.start { add_filter "/test" }
3
+
4
+ gem 'minitest'
5
+ require 'minitest/autorun'
6
+ require 'minitest/pride'
7
+
8
+ require 'omf_ec'
9
+
10
+ # Default fixture directory
11
+ FIXTURE_DIR = "#{File.dirname(__FILE__)}/fixture"
12
+
13
+ # Shut up all the loggers
14
+ Logging.logger.root.clear_appenders
15
+
16
+ # Reading fixture file
17
+ def fixture(name)
18
+ File.read("#{FIXTURE_DIR}/#{name.to_s}")
19
+ end