Module: Kanrisuru::OsPackage::Include

Included in:
Remote::Host
Defined in:
lib/kanrisuru/os_package/include.rb

Instance Method Summary collapse

Instance Method Details

#os_include(mod, opts = {}) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/kanrisuru/os_package/include.rb', line 6

def os_include(mod, opts = {})
  os_method_properties = mod.instance_variable_get(:@os_method_properties)
  os_method_names = os_method_properties.keys

  ## Need to encapsulate any helper methods called in the module
  ## to bind to the host instance. This acts as a psudeo module include.
  os_methods        = mod.instance_variable_get(:@os_methods)

  public_methods    = mod.instance_methods(false) - os_methods.to_a
  private_methods   = mod.private_instance_methods(false)
  protected_methods = mod.protected_instance_methods(false)
  include_methods   = (public_methods + protected_methods + private_methods).flatten

  include_method_bindings = proc do
    include_methods.each do |method_name|
      define_method method_name do |*args, &block|
        unbound_method = mod.instance_method(method_name)
        bind_method(unbound_method, *args, &block)
      end
    end

    private_methods.each { |method_name| private(method_name) }
    protected_methods.each { |method_name| protected(method_name) }

    private
    if RUBY_VERSION < '2.7'
      define_method 'bind_method' do |unbound_method, *args, &block|
        unbound_method.bind(self).call(*args, &block)
      end
    else
      define_method 'bind_method' do |unbound_method, *args, &block|
        unbound_method.bind_call(self, *args, &block)
      end
    end
  end

  namespace          = opts[:namespace]
  namespace_class    = nil
  namespace_instance = nil

  if namespace
    ## Define the namespace as an eigen class instance within the host class.
    ## Namespaced instances will access core host methods
    ## with @host instance variable.

    ## Check to see if the namespace was defined. If so, additional methods will be appended to the
    ## existing namespace class definition, otherwise, a new namespace class and instance will be
    ## defined with the methods added.
    if Kanrisuru::Remote::Host.instance_variable_defined?("@#{namespace}")
      namespace_class = Kanrisuru::Remote::Host.const_get(Kanrisuru::Util.camelize(namespace))
      namespace_instance = Kanrisuru::Remote::Host.instance_variable_get("@#{namespace}")
    else
      namespace_class    = Kanrisuru::Remote::Host.const_set(Kanrisuru::Util.camelize(namespace), Class.new)
      namespace_instance = Kanrisuru::Remote::Host.instance_variable_set("@#{namespace}", namespace_class.new)

      class_eval do
        define_method namespace do
          namespace_instance.instance_variable_set(:@host, self)
          namespace_instance
        end
      end
    end

    namespace_class.class_eval(&include_method_bindings)
  else
    class_eval(&include_method_bindings)
  end

  class_eval do
    os_method_names.each do |method_name|
      if namespace
        namespace_class.class_eval do
          define_method method_name do |*args, &block|
            unbound_method = nil

            host = namespace_instance.instance_variable_get(:@host)
            os_method_cache = host.instance_variable_get(:@os_method_cache) || {}

            if os_method_cache.key?("#{namespace}.#{method_name}")
              unbound_method = os_method_cache["#{namespace}.#{method_name}"]
            else
              ## Find the correct method to resolve based on the OS for the remote host.
              defined_method_name = host.resolve_os_method_name(os_method_properties, method_name)
              unless defined_method_name
                raise NoMethodError, "undefined method `#{method_name}' for #{self.class}"
              end

              ## Get reference to the unbound method defined in module
              unbound_method = mod.instance_method(defined_method_name)
              raise NoMethodError, "undefined method `#{method_name}' for #{self.class}" unless unbound_method

              ## Cache the unbound method on this host instance for faster resolution on
              ## the next invocation of this method
              os_method_cache["#{namespace}.#{method_name}"] = unbound_method
              host.instance_variable_set(:@os_method_cache, os_method_cache)
            end

            ## Bind the method to host instance and
            ## call it with args and block
            bind_method(unbound_method, *args, &block)
          end
        end
      else
        define_method method_name do |*args, &block|
          unbound_method = nil

          host = self
          os_method_cache = host.instance_variable_get(:@os_method_cache) || {}

          if os_method_cache.key?(method_name)
            unbound_method = os_method_cache[method_name]
          else
            ## Find the correct method to resolve based on the OS for the remote host.
            defined_method_name = host.resolve_os_method_name(os_method_properties, method_name)
            raise NoMethodError, "undefined method `#{method_name}' for #{self.class}" unless defined_method_name

            ## Get reference to the unbound method defined in module
            unbound_method = mod.instance_method(defined_method_name)
            raise NoMethodError, "undefined method `#{method_name}' for #{self.class}" unless unbound_method

            ## Cache the unbound method on this host instance for faster resolution on
            ## the next invocation of this method
            os_method_cache[method_name] = unbound_method
            host.instance_variable_set(:@os_method_cache, os_method_cache)
          end

          ## Bind the method to host instance and
          ## call it with args and block
          bind_method(unbound_method, *args, &block)
        end
      end
    end

    def resolve_os_method_name(properties, method_name)
      kernel  = os.kernel.downcase
      release = os.release.downcase

      properties[method_name].each do |property|
        os_name = property[:os_name]
        strict = property[:options] ? property[:options][:strict] : false
        except = property[:options] ? property[:options][:except] : ''

        next if except && (except == release || except.include?(release))

        if release == os_name || kernel == os_name ||
           (Kanrisuru::Util::OsFamily.family_include_distribution?(os_name, release) && !strict) ||
           (Kanrisuru::Util::OsFamily.upstream_include_distribution?(os_name, release) && !strict)
          return property[:method_name]
        end
      end

      nil
    end
  end
end