chef-resource 0.2.2
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +23 -0
- data/LICENSE +201 -0
- data/README.md +264 -0
- data/Rakefile +8 -0
- data/files/lib/chef_resource.rb +24 -0
- data/files/lib/chef_resource/camel_case.rb +23 -0
- data/files/lib/chef_resource/chef.rb +102 -0
- data/files/lib/chef_resource/chef_dsl/chef_cookbook_compiler.rb +44 -0
- data/files/lib/chef_resource/chef_dsl/chef_recipe.rb +10 -0
- data/files/lib/chef_resource/chef_dsl/chef_recipe_dsl_extensions.rb +84 -0
- data/files/lib/chef_resource/chef_dsl/chef_resource_base.rb +12 -0
- data/files/lib/chef_resource/chef_dsl/chef_resource_class_extensions.rb +30 -0
- data/files/lib/chef_resource/chef_dsl/chef_resource_extensions.rb +224 -0
- data/files/lib/chef_resource/chef_dsl/chef_resource_log.rb +54 -0
- data/files/lib/chef_resource/chef_dsl/resource_container_module.rb +80 -0
- data/files/lib/chef_resource/chef_dsl/resource_definition_dsl.rb +128 -0
- data/files/lib/chef_resource/constants.rb +8 -0
- data/files/lib/chef_resource/errors.rb +31 -0
- data/files/lib/chef_resource/lazy_proc.rb +82 -0
- data/files/lib/chef_resource/output/nested_converge.rb +91 -0
- data/files/lib/chef_resource/output/nested_converge/open_resource.rb +113 -0
- data/files/lib/chef_resource/output/region_stream.rb +145 -0
- data/files/lib/chef_resource/output/simple_output.rb +83 -0
- data/files/lib/chef_resource/resource.rb +428 -0
- data/files/lib/chef_resource/resource/resource_log.rb +197 -0
- data/files/lib/chef_resource/resource/resource_type.rb +74 -0
- data/files/lib/chef_resource/resource/struct_property.rb +39 -0
- data/files/lib/chef_resource/resource/struct_property_type.rb +185 -0
- data/files/lib/chef_resource/resource/struct_resource.rb +410 -0
- data/files/lib/chef_resource/resource/struct_resource_base.rb +11 -0
- data/files/lib/chef_resource/resource/struct_resource_type.rb +275 -0
- data/files/lib/chef_resource/simple_struct.rb +121 -0
- data/files/lib/chef_resource/type.rb +371 -0
- data/files/lib/chef_resource/types.rb +4 -0
- data/files/lib/chef_resource/types/boolean.rb +16 -0
- data/files/lib/chef_resource/types/byte_size.rb +10 -0
- data/files/lib/chef_resource/types/date_time_type.rb +18 -0
- data/files/lib/chef_resource/types/date_type.rb +18 -0
- data/files/lib/chef_resource/types/float_type.rb +28 -0
- data/files/lib/chef_resource/types/integer_type.rb +53 -0
- data/files/lib/chef_resource/types/interval.rb +21 -0
- data/files/lib/chef_resource/types/path.rb +39 -0
- data/files/lib/chef_resource/types/pathname_type.rb +34 -0
- data/files/lib/chef_resource/types/string_type.rb +16 -0
- data/files/lib/chef_resource/types/symbol_type.rb +18 -0
- data/files/lib/chef_resource/types/uri_type.rb +37 -0
- data/files/lib/chef_resource/version.rb +3 -0
- data/spec/integration/chef.rb +81 -0
- data/spec/integration/struct_spec.rb +611 -0
- data/spec/integration/struct_state_spec.rb +538 -0
- data/spec/integration/type_spec.rb +1123 -0
- data/spec/integration/validation_spec.rb +207 -0
- data/spec/support/spec_support.rb +7 -0
- metadata +167 -0
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'chef/log'
|
2
|
+
|
3
|
+
module ChefResource
|
4
|
+
module ChefDSL
|
5
|
+
#
|
6
|
+
# Handles events from the ChefResource Resource model and spits the data out
|
7
|
+
# to Chef for pretty green text.
|
8
|
+
#
|
9
|
+
class ChefResourceLog < ChefResource::Resource::ResourceLog
|
10
|
+
def log(level, str)
|
11
|
+
Chef::Log.public_send(level, "#{resource.resource_short_name} #{str}")
|
12
|
+
end
|
13
|
+
|
14
|
+
def action
|
15
|
+
resource.action[0]
|
16
|
+
end
|
17
|
+
|
18
|
+
# When load happens, notify Chef that the resource's current state is loaded.
|
19
|
+
def load_succeeded
|
20
|
+
super
|
21
|
+
resource.events.resource_current_state_loaded(resource, action, resource.current_resource)
|
22
|
+
end
|
23
|
+
|
24
|
+
# When an update succeeds, we mark the resource
|
25
|
+
def update_succeeded
|
26
|
+
super
|
27
|
+
|
28
|
+
if resource.updated_by_last_action?
|
29
|
+
resource.events.resource_updated(resource, action)
|
30
|
+
else
|
31
|
+
resource.events.resource_up_to_date(resource, action)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def action_skipped(description, update_guaranteed: true)
|
36
|
+
super
|
37
|
+
|
38
|
+
if update_guaranteed
|
39
|
+
resource.events.resource_update_applied(resource, action, description)
|
40
|
+
resource.updated_by_last_action true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# When an action succeeds, we mark the resource updated if it did anything.
|
45
|
+
def action_succeeded(**args)
|
46
|
+
description, updated = super
|
47
|
+
if updated
|
48
|
+
resource.events.resource_update_applied(resource, action, description)
|
49
|
+
resource.updated_by_last_action true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module ChefResource
|
2
|
+
module ChefDSL
|
3
|
+
module ResourceContainerModule
|
4
|
+
attr_reader :recipe_dsl_module
|
5
|
+
|
6
|
+
def resource_types
|
7
|
+
@resource_types ||= {}
|
8
|
+
end
|
9
|
+
|
10
|
+
# TODO also run this after libraries/ are parsed, and perhaps when method_missing
|
11
|
+
# is triggered.
|
12
|
+
def update_resource_definition_methods!
|
13
|
+
const_module = self
|
14
|
+
|
15
|
+
# Go through the constants in the module, trawling for Resources
|
16
|
+
seen = []
|
17
|
+
const_module.constants.each do |class_name|
|
18
|
+
resource_class = const_module.const_get(class_name)
|
19
|
+
next if !resource_class.is_a?(Class)
|
20
|
+
next if !(resource_class <= Chef::Resource)
|
21
|
+
|
22
|
+
resource_name = resource_class.dsl_name
|
23
|
+
|
24
|
+
seen << resource_name
|
25
|
+
|
26
|
+
# Detect conflicts: two Resources with the same resource_name
|
27
|
+
current_class_name = resource_types[resource_name]
|
28
|
+
if current_class_name != class_name
|
29
|
+
if current_class_name && const_module.const_defined?(current_class_name)
|
30
|
+
current_resource = const_module.const_get(current_class_name)
|
31
|
+
if current_resource != resource_class && current_resource.is_a?(Class) && current_resource <= Chef::Resource && current_resource_name == resource_name
|
32
|
+
raise "Both #{current_class_name} and #{class_name} map to #{resource_name}. Choose a different dsl_name for one of them!"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# We don't overwrite real methods in the recipe DSL (if any?)
|
37
|
+
if !current_class_name && method_defined?(resource_name)
|
38
|
+
raise "Method #{resource_name} already exists in #{recipe_dsl_module}! Not overwriting with resource definition for #{class_name}."
|
39
|
+
end
|
40
|
+
|
41
|
+
# Create / overwrite the current methods
|
42
|
+
emit_resource_definition_method(resource_name, class_name, resource_class)
|
43
|
+
|
44
|
+
resource_types[resource_name] = class_name
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Remove methods for constants that no longer exist
|
49
|
+
(seen - resource_types.keys).each do |resource_name|
|
50
|
+
const_module.remove_method(resource_name)
|
51
|
+
resource_types.delete(resource_name)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def emit_resource_definition_method(resource_name, class_name, actual_class)
|
56
|
+
# TODO handle definitions too?
|
57
|
+
if actual_class <= ChefResource::Resource
|
58
|
+
recipe_dsl_module.module_eval <<-EOM, __FILE__, __LINE__+1
|
59
|
+
def #{resource_name}(*identity, &update_block)
|
60
|
+
# TODO fix Chef: let declare_resource take the resource class
|
61
|
+
if update_block
|
62
|
+
declare_resource(#{actual_class.name}, *identity, caller[0], &update_block)
|
63
|
+
else
|
64
|
+
# If you don't pass a block, we assume you just wanted to construct
|
65
|
+
# a resource to use for reading.
|
66
|
+
build_resource(#{actual_class.name}, *identity, caller[0])
|
67
|
+
end
|
68
|
+
end
|
69
|
+
EOM
|
70
|
+
# else
|
71
|
+
# recipe_dsl_module.module_eval <<-EOM, __FILE__, __LINE__+1
|
72
|
+
# def #{resource_name}(name, &block)
|
73
|
+
# declare_resource(#{resource_name.inspect}, name, caller[0], &block)
|
74
|
+
# end
|
75
|
+
# EOM
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'chef_resource/chef_dsl/chef_resource_extensions'
|
2
|
+
require 'chef_resource/chef_dsl/chef_resource_class_extensions'
|
3
|
+
require 'chef_resource/chef_dsl/chef_resource_base'
|
4
|
+
require 'chef_resource/camel_case'
|
5
|
+
require 'chef_resource/resource'
|
6
|
+
require 'chef/dsl/recipe'
|
7
|
+
require 'chef/resource'
|
8
|
+
|
9
|
+
module ChefResource
|
10
|
+
module ChefDSL
|
11
|
+
#
|
12
|
+
# DSL for defining resources
|
13
|
+
#
|
14
|
+
module ResourceDefinitionDSL
|
15
|
+
#
|
16
|
+
# Create a new resource type under `Chef::Resource`.
|
17
|
+
#
|
18
|
+
# @param name [Symbol,String] The name of the resource (e.g. :my_resource)
|
19
|
+
# @param base_resource_class [Class,Symbol,String] The base resource class.
|
20
|
+
# The new resource will derive from this base resource, and will have
|
21
|
+
# all the same properties. If a symbol or a string is passed, the name
|
22
|
+
# (e.g. `:resource_name`) is searched for under Chef::Resource.
|
23
|
+
# @param class_name [Symbol,String] The name of the class to create under
|
24
|
+
# Chef::Resource. If not specified, `name` is converted to a class name
|
25
|
+
# (e.g. resource_name -> ResourceName).
|
26
|
+
# @param overwrite_resource [Boolean] Whether to overwrite the resource if
|
27
|
+
# it already exists. If set to `true`, and the resource class already
|
28
|
+
# exists, it will be removed and re-created according to the new resource
|
29
|
+
# definition. Defaults to false.
|
30
|
+
# @param override_block A block that will be run in the context of the new
|
31
|
+
# class, allowing you to type `property :name ...` and `recipe do`,
|
32
|
+
# as well as `def self.blah` and `def blah`.
|
33
|
+
#
|
34
|
+
# @raise If the resource class already exists and `overwrite_resource` is
|
35
|
+
# set to `false`.
|
36
|
+
#
|
37
|
+
# @example
|
38
|
+
#
|
39
|
+
# resource :resource_name, Chef::Resource::File do
|
40
|
+
# property :mode, Fixnum, default: 0666
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
def resource(name, base_resource_class=nil, class_name: nil, overwrite_resource: false, &override_block)
|
44
|
+
base_resource_class = ResourceDefinitionDSL.base_resource_class_for(base_resource_class)
|
45
|
+
|
46
|
+
name = name.to_sym
|
47
|
+
class_name ||= CamelCase.from_snake_case(name)
|
48
|
+
|
49
|
+
if Chef::Resource.const_defined?(class_name, false)
|
50
|
+
if overwrite_resource
|
51
|
+
Chef::Resource.const_set(class_name, nil)
|
52
|
+
else
|
53
|
+
raise "Cannot redefine resource #{name}, because Chef::Resource::#{class_name} already exists! Pass overwrite_resource: true if you really meant to overwrite."
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
resource_class = Chef::Resource.class_eval <<-EOM, __FILE__, __LINE__+1
|
58
|
+
class #{class_name} < base_resource_class
|
59
|
+
if !(self <= ChefResource::ChefDSL::ChefResourceExtensions)
|
60
|
+
include ChefResource::ChefDSL::ChefResourceExtensions
|
61
|
+
extend ChefResource::ChefDSL::ChefResourceClassExtensions
|
62
|
+
end
|
63
|
+
self
|
64
|
+
end
|
65
|
+
EOM
|
66
|
+
resource_class.class_eval(&override_block)
|
67
|
+
Chef::Resource.update_resource_definition_methods!
|
68
|
+
resource_class
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
# ChefResource.defaults :my_file, :file, mode: 0666, owner: 'jkeiser'
|
73
|
+
#
|
74
|
+
def defaults(name, old_name=name, **defaults)
|
75
|
+
base_resource_class = ResourceDefinitionDSL.base_resource_class_for(old_name)
|
76
|
+
if base_resource_class.is_a?(ChefResource::Resource)
|
77
|
+
resource(name, base_resource_class, overwrite_resource: true) do
|
78
|
+
defaults.each do |name, value|
|
79
|
+
property name, default: value
|
80
|
+
end
|
81
|
+
end
|
82
|
+
else
|
83
|
+
Chef::DSL::Recipe.send(:define_method, name) do |name, &block|
|
84
|
+
declare_resource(old_name, name, caller[0]) do
|
85
|
+
defaults.each do |name, value|
|
86
|
+
public_send(name, value)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
#
|
94
|
+
# ChefResource.define :resource_name, a: 1, b: 2 do
|
95
|
+
# file 'x.txt' do
|
96
|
+
# content 'hi'
|
97
|
+
# end
|
98
|
+
# end
|
99
|
+
#
|
100
|
+
def define(name, *identity_params, overwrite_resource: true, **params, &recipe_block)
|
101
|
+
resource name do
|
102
|
+
identity_params.each do |name|
|
103
|
+
property name, identity: true
|
104
|
+
end
|
105
|
+
params.each do |name, value|
|
106
|
+
property name, default: value
|
107
|
+
end
|
108
|
+
recipe(&recipe_block)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def self.base_resource_class_for(base_resource_class)
|
115
|
+
case base_resource_class
|
116
|
+
when Class
|
117
|
+
# resource_class is a-ok if it's already a Class
|
118
|
+
base_resource_class
|
119
|
+
when nil
|
120
|
+
ChefResource::ChefDSL::ChefResourceBase
|
121
|
+
else
|
122
|
+
resource_class_name = CamelCase.from_snake_case(base_resource_class.to_s)
|
123
|
+
eval("Chef::Resource::#{resource_class_name}")
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module ChefResource
|
2
|
+
class ValidationError < StandardError
|
3
|
+
def initialize(message, value)
|
4
|
+
super("#{value} #{message}")
|
5
|
+
@value = value
|
6
|
+
end
|
7
|
+
attr_reader :value
|
8
|
+
end
|
9
|
+
|
10
|
+
class MustNotBeNullError < ValidationError
|
11
|
+
end
|
12
|
+
|
13
|
+
class ResourceStateError < StandardError
|
14
|
+
def initialize(message, resource)
|
15
|
+
super(message)
|
16
|
+
@resource = resource
|
17
|
+
end
|
18
|
+
attr_reader :resource
|
19
|
+
end
|
20
|
+
|
21
|
+
class ResourceCannotBeOpenedError < ResourceStateError
|
22
|
+
end
|
23
|
+
|
24
|
+
class PropertyDefinedError < ResourceStateError
|
25
|
+
def initialize(message, resource, property_type)
|
26
|
+
super(message, resource)
|
27
|
+
@property_type = property_type
|
28
|
+
end
|
29
|
+
attr_reader :property_type
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'chef_resource/constants'
|
2
|
+
require 'chef_resource/simple_struct'
|
3
|
+
|
4
|
+
module ChefResource
|
5
|
+
class LazyProc < Proc
|
6
|
+
#
|
7
|
+
# Create a new LazyProc
|
8
|
+
#
|
9
|
+
# @param switches A list of switches to turn on.
|
10
|
+
# - :should_instance_eval: true if instance_eval is expected, false if
|
11
|
+
# disallowed. Default: false, except for type attributes like "default"
|
12
|
+
# where it gets flipped to true.
|
13
|
+
# @param should_instance_eval [Boolean] true if instance_eval is expected, false if
|
14
|
+
# disallowed. Default: false, except for type attributes like "default"
|
15
|
+
# where it gets flipped to true.
|
16
|
+
# @param block The block to run on get()
|
17
|
+
#
|
18
|
+
def initialize(*switches, should_instance_eval: NOT_PASSED, &block)
|
19
|
+
super(&block)
|
20
|
+
switches.each do |switch|
|
21
|
+
case switch
|
22
|
+
when :should_instance_eval
|
23
|
+
@should_instance_eval = true
|
24
|
+
else
|
25
|
+
raise ArgumentError, "Unrecognized argument #{switch.inspect}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
@should_instance_eval = should_instance_eval if should_instance_eval != NOT_PASSED
|
29
|
+
end
|
30
|
+
|
31
|
+
extend SimpleStruct
|
32
|
+
|
33
|
+
#
|
34
|
+
# Whether to use instance_eval on this lazy instance.
|
35
|
+
#
|
36
|
+
# Defaults to `false`.
|
37
|
+
#
|
38
|
+
boolean_property :should_instance_eval, default: "false"
|
39
|
+
|
40
|
+
#
|
41
|
+
# Get the value of this LazyProc.
|
42
|
+
#
|
43
|
+
# If #instance_eval? is true:
|
44
|
+
# - If the proc takes no arguments, runs `instance_eval()` with no parameters
|
45
|
+
# - If the proc takes arguments, runs `instance_exec(*args)`
|
46
|
+
# If #instance_eval? is false:
|
47
|
+
# - If the proc takes no arguments, runs `call()` with no arguments
|
48
|
+
# - If the proc takes exactly one mandatory argument, runs `call(instance)` with no other arguments
|
49
|
+
# - Otherwise, runs `call(instance, *args)`
|
50
|
+
#
|
51
|
+
# @param instance The instance to instance_eval against.
|
52
|
+
# @param args [Array] Arguments to pass to the function. Do not pass (or pass `nil`)
|
53
|
+
# to indicate the function never takes arguments.
|
54
|
+
#
|
55
|
+
# @return The computed value
|
56
|
+
#
|
57
|
+
def get(instance: nil, args: nil, instance_eval_by_default: NOT_PASSED)
|
58
|
+
if instance_eval_by_default != NOT_PASSED && !defined?(@should_instance_eval)
|
59
|
+
should_instance_eval = instance_eval_by_default
|
60
|
+
else
|
61
|
+
should_instance_eval = self.should_instance_eval?
|
62
|
+
end
|
63
|
+
|
64
|
+
if should_instance_eval
|
65
|
+
if arity == 0
|
66
|
+
instance.instance_eval(&self)
|
67
|
+
else
|
68
|
+
instance.instance_exec(*args, &self)
|
69
|
+
end
|
70
|
+
else
|
71
|
+
case arity
|
72
|
+
when 0
|
73
|
+
call()
|
74
|
+
when 1
|
75
|
+
call(instance)
|
76
|
+
else
|
77
|
+
call(instance, *args)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'chef_resource/output/nested_converge/styles'
|
2
|
+
require 'chef_resource/output/nested_converge/open_resource'
|
3
|
+
require 'chef_resource/resource/resource_events'
|
4
|
+
require 'chef_resource/constants'
|
5
|
+
|
6
|
+
module ChefResource
|
7
|
+
module Chef
|
8
|
+
module Output
|
9
|
+
class NestedConverge < StructResource
|
10
|
+
include ResourceEvents
|
11
|
+
|
12
|
+
property :indent_step, default: 2
|
13
|
+
property :styles, Styles, default: {
|
14
|
+
default nil
|
15
|
+
updated :green
|
16
|
+
warn [ :timestamp, :gray ]
|
17
|
+
error [ :timestamp, :red ]
|
18
|
+
fatal [ :timestamp, :on_red, :white ]
|
19
|
+
}
|
20
|
+
|
21
|
+
class Styles < StructResource
|
22
|
+
property :default
|
23
|
+
property :identity_defined { default }
|
24
|
+
property :fully_defined { default }
|
25
|
+
property :updating { default }
|
26
|
+
property :updated { default }
|
27
|
+
property :not_updated { default }
|
28
|
+
property :updated { default }
|
29
|
+
property :update_failed { default }
|
30
|
+
property :debug { default }
|
31
|
+
property :info { default }
|
32
|
+
property :warn { default }
|
33
|
+
property :error { default }
|
34
|
+
property :fatal { default }
|
35
|
+
end
|
36
|
+
|
37
|
+
def open_resources
|
38
|
+
@open_resources ||= {}
|
39
|
+
end
|
40
|
+
|
41
|
+
def output_mutex
|
42
|
+
@mutex ||= Mutex.new
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_accessor :current_resource
|
46
|
+
attr_accessor :current_stream
|
47
|
+
attr_accessor :at_line_begin
|
48
|
+
|
49
|
+
def print(open_resource, stream, str=NOT_PASSED, style)
|
50
|
+
if str == NOT_PASSED
|
51
|
+
str, stream = stream, :default
|
52
|
+
end
|
53
|
+
output_mutex.synchronize do
|
54
|
+
lines = str.lines
|
55
|
+
switch_stream(open_resource, stream, lines)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def switch_stream(open_resource, stream, lines, style)
|
60
|
+
if current_resource != resource
|
61
|
+
current_resource = resource
|
62
|
+
current_stream = stream
|
63
|
+
open_resource.header_lines(lines.shift, style)
|
64
|
+
elsif current_stream != stream
|
65
|
+
current_stream = stream
|
66
|
+
# TODO look at parents
|
67
|
+
puts open_resource.header_line(lines.shift)
|
68
|
+
end
|
69
|
+
output_mutex.synchronize do
|
70
|
+
if current_resource != resource
|
71
|
+
prev_resource, current_resource = current_resource, resource
|
72
|
+
prev_stream, current_stream = current_stream, stream
|
73
|
+
elsif current_stream != stream
|
74
|
+
prev_stream, current_stream = current_stream, stream
|
75
|
+
end
|
76
|
+
block.call(prev_resource, prev_stream)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def resource_event(resource, event, *args)
|
81
|
+
open_resources[resource] ||= ResourceFormat.new(self, resource)
|
82
|
+
open_resources[resource].resource_event(resource, event, *args)
|
83
|
+
end
|
84
|
+
|
85
|
+
def resource_closed(resource)
|
86
|
+
open_resources.delete(resource)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|