Class: FlossFunding::Wedge

Inherits:
Object
  • Object
show all
Defined in:
lib/floss_funding/wedge.rb

Constant Summary collapse

DANGEROUS =
if maybe_dangerous
  if DEBUG
    maybe_dangerous
  else
    warn("Unable to use DANGEROUS mode because DEBUG=false.")
    false
  end
else
  false
end

Class Method Summary collapse

Class Method Details

.loaded_specsObject

Exposed for specs: source of loaded Gem::Specification objects



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/floss_funding/wedge.rb', line 104

def loaded_specs
  # Only use Gem.loaded_specs.
  # It is pointless to try using Bundler's version because all it does is call Gem.loaded_specs.
  specs, strategy =
    begin
      [::Gem.loaded_specs, "Gem.loaded_specs"]
    rescue StandardError => e
      ::FlossFunding.debug_log { "[Wedge] Gem.loaded_specs failed: #{e.class}: #{e.message}" }
      [[], "(error)"]
    end

  ::FlossFunding.debug_log { "[Wedge] Using #{strategy}" }
  specs = specs.values if specs.respond_to?(:values)
  arr = Array(specs)
  ::FlossFunding.debug_log { "[Wedge] Loaded specs: count=#{arr.length}" }
  arr
rescue StandardError => e
  ::FlossFunding.debug_log { "[Wedge] loaded_specs failed: #{e.class}: #{e.message}" }
  []
end

.namespace_candidates_for(library_name) ⇒ Object

Turn a gem name into several candidate Ruby namespace strings
Examples:
“google-cloud-storage” => [
“Google::Cloud::Storage”, “Google::Cloud”, “Google”,
“GoogleCloudStorage”
]
“alpha_beta” => [“AlphaBeta”, “Alpha”, “Alpha::Beta”]



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
# File 'lib/floss_funding/wedge.rb', line 132

def namespace_candidates_for(library_name)
  ::FlossFunding.debug_log { "[Wedge] namespace_candidates_for input=#{library_name.inspect}" }
  return [] if library_name.nil? || library_name.empty?

  dash_parts = library_name.split("-")
  # Build CamelCase per dash part, where each part may contain underscores
  camel_parts = dash_parts.map { |p| camelize(p) }
  ::FlossFunding.debug_log { "[Wedge] camel_parts=#{camel_parts.inspect} from dash_parts=#{dash_parts.inspect}" }

  candidates = []
  # Most likely: top-level module per dash part
  if camel_parts.size > 1
    # Add full nested path, then its prefixes (e.g., Google::Cloud, Google)
    nested = camel_parts.join("::")
    candidates << nested
    (camel_parts.size - 1).downto(1) do |i|
      candidates << camel_parts[0, i].join("::")
    end
  end

  # Also attempt the fully collapsed CamelCase name
  candidates << camel_parts.join

  uniq = candidates.uniq
  ::FlossFunding.debug_log { "[Wedge] namespace_candidates_for output=#{uniq.inspect}" }
  uniq
end

.safe_const_resolve(path) ⇒ Object

Safe resolve of a constant path like “Foo::Bar” without raising



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/floss_funding/wedge.rb', line 161

def safe_const_resolve(path)
  ::FlossFunding.debug_log { "[Wedge] safe_const_resolve path=#{path.inspect}" }
  return if path.nil? || path.empty?
  parts = path.split("::")
  obj = Object
  parts.each do |name|
    # :nocov:
    exists = begin
      obj.const_defined?(name, false)
    rescue
      false
    end || begin
      Object.const_defined?(name)
    rescue
      false
    end
    # :nocov:
    ::FlossFunding.debug_log { "[Wedge]   checking part=#{name.inspect} exists=#{exists} in obj=#{obj}" }
    return nil unless exists
    obj = begin
      obj.const_get(name)
    rescue
      # :nocov:
      ::FlossFunding.debug_log { "[Wedge]   const_get failed for #{name.inspect}" }
      # :nocov:
      (return nil)
    end
  end
  ::FlossFunding.debug_log { "[Wedge] safe_const_resolve resolved=#{obj.inspect}" }
  obj
rescue StandardError => e
  # :nocov:
  ::FlossFunding.debug_log { "[Wedge] safe_const_resolve error: #{e.class}: #{e.message}" }
  nil
  # :nocov:
end

.wedge!Hash

Perform the wedge across all currently loaded specs.

Returns:

  • (Hash)

    summary with keys :tried, :injected, :details



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
# File 'lib/floss_funding/wedge.rb', line 41

def wedge!
  ::FlossFunding.debug_log { "[Wedge] Starting wedge! DEBUG=#{::FlossFunding::DEBUG}" }
  results = {:tried => 0, :injected => 0, :details => []}

  specs = loaded_specs
  ::FlossFunding.debug_log { "[Wedge] Loaded specs count=#{specs.length}" }

  specs.each do |spec|
    unless valid_spec?(spec)
      ::FlossFunding.debug_log { "[Wedge] Skipping invalid spec=#{spec.inspect}" }
      next
    end
    if spec.name == "floss_funding"
      ::FlossFunding.debug_log { "[Wedge] Skipping self gem: #{spec.name}" }
      next
    end

    ::FlossFunding.debug_log { "[Wedge] Processing gem=#{spec.name}" }
    candidates = namespace_candidates_for(spec.name)
    ::FlossFunding.debug_log { "[Wedge] Candidates for #{spec.name}: #{candidates.inspect}" }
    injected_into = []

    # Dangerous effort: try to require the gem before resolving constants
    attempt_require_for_spec(spec, candidates) if DANGEROUS

    candidates.each do |ns|
      ::FlossFunding.debug_log { "[Wedge] Resolving constant path=#{ns} for gem=#{spec.name}" }
      mod = safe_const_resolve(ns)
      unless mod.is_a?(Module)
        ::FlossFunding.debug_log { "[Wedge] Not a Module or missing: #{ns} => #{mod.inspect}" }
        next
      end

      begin
        inc_path = spec.loaded_from || guess_including_path(spec)
        ::FlossFunding.debug_log { "[Wedge] Including Poke into #{ns} with path=#{inc_path.inspect}" }
        mod.send(:include, ::FlossFunding::Poke.new(inc_path, :wedge => true))
        injected_into << ns
        ::FlossFunding.debug_log { "[Wedge] Included successfully into #{ns}" }
      rescue StandardError => e
        # :nocov:
        ::FlossFunding.debug_log { "[Wedge] Include failed for #{ns}: #{e.class}: #{e.message}" }
        # Swallow and continue; this is best-effort to probe many libs
        # :nocov:
      end
    end

    results[:tried] += 1
    results[:injected] += injected_into.size.positive? ? 1 : 0
    details = {:gem => spec.name, :injected_into => injected_into}
    results[:details] << details
    ::FlossFunding.debug_log { "[Wedge] Result for #{spec.name}: #{details.inspect}" }
  end

  if DEBUG
    puts "[Wedge] Finished wedge!\n#{results.inspect}"
  else
    puts "[Wedge] Finished wedge!\n#{render_summary_table(results)}"
  end
  results
end