Module: FlossFunding
- Defined in:
- lib/floss_funding/wedge.rb,
lib/floss_funding/poke.rb,
lib/floss_funding/config.rb,
lib/floss_funding/silent.rb,
lib/floss_funding/library.rb,
lib/floss_funding/version.rb,
lib/floss_funding/lockfile.rb,
lib/floss_funding/constants.rb,
lib/floss_funding/inclusion.rb,
lib/floss_funding/namespace.rb,
lib/floss_funding/under_bar.rb,
lib/floss_funding/file_finder.rb,
lib/floss_funding/fingerprint.rb,
lib/floss_funding/config_finder.rb,
lib/floss_funding/config_loader.rb,
lib/floss_funding/configuration.rb,
lib/floss_funding/final_summary.rb,
lib/floss_funding/activation_event.rb,
lib/floss_funding/contra_indications.rb,
lib/floss_funding/rakelib/gem_spec_reader.rb,
lib/floss_funding.rb
Overview
Now declare some constants
Defined Under Namespace
Modules: Config, Constants, FileFinder, Fingerprint, Lockfile, Poke, Rakelib, Silent, UnderBar, Version Classes: ActivationEvent, AtExitLockfile, ConfigFinder, ConfigLoader, ConfigNotFoundError, Configuration, ContraIndications, Error, FinalSummary, Inclusion, Library, LockfileBase, Namespace, OnLoadLockfile, Wedge
Constant Summary collapse
- DEBUG =
Debug toggle controlled by ENV; set true when ENV[‘FLOSS_FUNDING_DEBUG’] case-insensitively equals “true”.
ENV.fetch("FLOSS_FUNDING_DEBUG", "").casecmp("true") == 0
- CONFIG_FILE_NAME =
The file name to look for in the project root.
".floss_funding.yml"
- FLOSS_FUNDING_HOME =
File.realpath(File.join(File.dirname(__FILE__), ".."))
- REQUIRED_YAML_KEYS =
Minimum required keys for a valid .floss_funding.yml file
Used to validate presence when integrating without :wedge mode %w[library_name funding_uri].freeze
- FREE_AS_IN_BEER =
Unpaid activation option intended for open-source and not-for-profit use.
"Free-as-in-beer"
- BUSINESS_IS_NOT_GOOD_YET =
Unpaid activation option acknowledging commercial use without payment.
"Business-is-not-good-yet"
- NOT_FINANCIALLY_SUPPORTING =
Activation option to explicitly opt out of funding prompts for a namespace.
"Not-financially-supporting"
- STATES =
{ :activated => "activated", :unactivated => "unactivated", :invalid => "invalid", }.freeze
- STATE_VALUES =
STATES.values.freeze
- DEFAULT_STATE =
The default state is unknown / unactivated until proven otherwise.
STATES[:unactivated]
- START_MONTH =
First month index against which base words are validated.
Do not change once released as it would invalidate existing activation keys. Month.new(2025, 7).to_i
- BASE_WORDS_PATH =
Absolute path to the base words list used for paid activation validation.
File.("../../base.txt", __FILE__)
- EIGHT_BYTES =
Number of hex characters required for a paid activation key (64 = 256 bits).
64
- HEX_LICENSE_RULE =
Format for a paid activation key (64 hex chars).
/\A[0-9a-fA-F]{#{EIGHT_BYTES}}\z/
- FOOTER =
Footer text appended to messages shown to users when activation is missing
or invalid. Includes gem version and attribution. <<-FOOTER ===================================================================================== - Please buy FLOSS "peace-of-mind" activation keys to support open source developers. floss_funding v#{::FlossFunding::Version::VERSION} is made with ❤️ in 🇺🇸 & 🇮🇩 by Galtzo FLOSS (galtzo.com) FOOTER
Class Attribute Summary collapse
-
.loaded_at ⇒ Time
readonly
Read the deterministic time source.
Class Method Summary collapse
-
.add_or_update_namespace_with_event(namespace, event) ⇒ Object
-
.all_namespaces ⇒ Array<::FlossFunding::Namespace>
All namespaces that have any activation events recorded Returns array of Namespace objects.
-
.base_words(num_valid_words = nil) ⇒ Array<String>
Reads the first N lines from the base words file to validate paid activation keys.
-
.check_activation(plain_text) ⇒ Boolean
Check whether a plaintext activation base word is currently valid.
-
.configuration(namespace) ⇒ Object
-
.configurations(namespace = nil) ⇒ Object
Configuration storage and helpers (derived from namespaces and activation events) When namespace is nil, returns a Hash mapping namespace String => Array<FlossFunding::Configuration>.
-
.debug_log(*args) ⇒ void
Debug logging helper.
-
.debug_logger ⇒ Object
Lazily build a Logger instance when FLOSS_CFG_FUNDING_LOGFILE is set and ‘logger’ is available.
-
.env_var_names ⇒ Object
ENV var name mapping helpers (derived from namespaces) Returns a Hash mapping namespace String => ENV variable name String.
-
.error!(error, where = nil) ⇒ Object
Mark an internal error, log useful context for diagnostics, and set inert flag.
-
.errored? ⇒ Boolean
Global error flag; when set true, library should become inert.
-
.initiate_begging(event) ⇒ Object
-
.install_tasks ⇒ Object
Tasks for both development and test environments.
-
.namespaces ⇒ Hash{String => ::FlossFunding::Namespace}
Accessor for namespaces hash: keys are namespace strings, values are Namespace objects.
-
.namespaces=(value) ⇒ Object
Replace the namespaces hash (expects
Hash{String => ::FlossFunding::Namespace}
). -
.project_root ⇒ String?
Expose the discovered project root (may be nil when running inside this gem’s own repo).
-
.register_wedge(base, custom_namespace = nil) ⇒ Object
Register a minimal activation event for wedge-injected libraries to ensure they are counted in the final summary without performing config discovery.
-
.silenced ⇒ Boolean
Global silenced flag accessor (boolean).
-
.silenced=(value) ⇒ void
Set the global silenced flag.
-
.start_begging(namespace, env_var_name, library_name) ⇒ void
Emit the standard friendly funding message for unactivated usage.
-
.start_coughing(activation_key, namespace, env_var_name) ⇒ void
Emit a diagnostic message when an activation key is invalid.
Class Attribute Details
.loaded_at ⇒ Time (readonly)
Read the deterministic time source
204 205 206 |
# File 'lib/floss_funding.rb', line 204 def loaded_at @loaded_at end |
Class Method Details
.add_or_update_namespace_with_event(namespace, event) ⇒ Object
345 346 347 348 349 350 351 352 353 354 355 356 357 358 |
# File 'lib/floss_funding.rb', line 345 def add_or_update_namespace_with_event(namespace, event) @mutex.synchronize do ns_obj = namespace # Append in place to reduce allocations and avoid extra @mutex churn ns_obj.activation_events << event @namespaces[namespace.name] = ns_obj begin lib_name = (event.library ? event.library.library_name : nil) ::FlossFunding.debug_log { "[registry] add_or_update ns=#{namespace.name.inspect} events=#{ns_obj.activation_events.size} state=#{event.state} lib=#{lib_name.inspect}" } rescue StandardError # ignore log errors end end end |
.all_namespaces ⇒ Array<::FlossFunding::Namespace>
All namespaces that have any activation events recorded
Returns array of Namespace objects
363 364 365 |
# File 'lib/floss_funding.rb', line 363 def all_namespaces @mutex.synchronize { @namespaces.values.flatten.dup } end |
.base_words(num_valid_words = nil) ⇒ Array<String>
Reads the first N lines from the base words file to validate paid activation keys.
Reads base words used to validate paid activation keys.
When called without an argument, uses the current month window to
determine how many words are valid.
402 403 404 405 406 407 |
# File 'lib/floss_funding.rb', line 402 def base_words(num_valid_words = nil) n = num_valid_words.nil? ? @num_valid_words_for_month : num_valid_words return [] if n.nil? || n.zero? @base_words_all.slice(0, n) end |
.check_activation(plain_text) ⇒ Boolean
Check whether a plaintext activation base word is currently valid
412 413 414 415 416 417 418 419 420 421 422 423 424 425 |
# File 'lib/floss_funding.rb', line 412 def check_activation(plain_text) return false if @num_valid_words_for_month.nil? || @num_valid_words_for_month <= 0 # Cache a Set for fast membership per current n sets = @base_words_set_cache set = sets[@num_valid_words_for_month] unless set words = base_words(@num_valid_words_for_month) # Warning inside a cache-protected lookup means it should only happen once per process, at most. warn("[FlossFunding] ZOMG! Base words missing. Did you time travel? Is it #{@loaded_month}? Is system clock set in the past?") if words.empty? set = Set.new(words) sets[@num_valid_words_for_month] = set end set.include?(plain_text) end |
.configuration(namespace) ⇒ Object
383 384 385 |
# File 'lib/floss_funding.rb', line 383 def configuration(namespace) configurations(namespace) end |
.configurations(namespace = nil) ⇒ Object
Configuration storage and helpers (derived from namespaces and activation events)
When namespace is nil, returns a Hash mapping namespace String => Array<FlossFunding::Configuration>.
When namespace is provided, returns FlossFunding::Configuration for that namespace (or nil if not found).
370 371 372 373 374 375 376 377 378 379 380 381 |
# File 'lib/floss_funding.rb', line 370 def configurations(namespace = nil) @mutex.synchronize do if namespace nobj = @namespaces[namespace] nobj ? nobj.merged_config : nil else @namespaces.each_with_object({}) do |(ns, nobj), acc| acc[ns] = nobj.configs end end end end |
.debug_log(*args) ⇒ void
This method returns an undefined value.
Debug logging helper. Only outputs when FlossFunding::DEBUG is true.
Accepts either a message (or multiple args joined by space) or a block
for lazy construction of the message.
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 |
# File 'lib/floss_funding.rb', line 223 def debug_log(*args) return unless ::FlossFunding::DEBUG msg = if block_given? yield else args.map(&:to_s).join(" ") end # Prefer Logger to file when configured and available; otherwise STDOUT logger = debug_logger if logger begin logger.debug(msg.to_s) return rescue StandardError # fall back to STDOUT below end end puts(msg) rescue StandardError # Never fail the caller due to logging issues nil end |
.debug_logger ⇒ Object
Lazily build a Logger instance when FLOSS_CFG_FUNDING_LOGFILE is set and ‘logger’ is available.
Returns a Logger or nil when unavailable or initialization failed.
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 |
# File 'lib/floss_funding.rb', line 271 def debug_logger path = begin ENV["FLOSS_CFG_FUNDING_LOGFILE"] rescue StandardError nil end return if path.nil? || path.to_s.strip.empty? begin require "logger" rescue LoadError return rescue StandardError => e # Log but do not set inert for logger init failures debug_log { "[WARN][debug_logger] #{e.class}: #{e.}" } return end @mutex.synchronize do return @debug_logger if defined?(@debug_logger) && @debug_logger # Ensure directory exists; best-effort begin dir = File.dirname(path) FileUtils.mkdir_p(dir) unless dir.nil? || dir.empty? || Dir.exist?(dir) rescue StandardError # ignore; Logger.new may still succeed if dir already exists or is current dir end begin # Truncate the debug log file on first initialization to keep runs readable begin File.open(path, "w") { |f| f.truncate(0) } rescue StandardError => e debug_log { "[WARN][debug_logger] unable to truncate #{path}: #{e.class}: #{e.}" } end logger = Logger.new(path) logger.level = Logger::DEBUG # Keep output minimal: message only with newline logger.formatter = proc { |_severity, _datetime, _progname, | (.to_s.end_with?("\n") ? .to_s : .to_s + "\n") } @debug_logger = logger rescue StandardError @debug_logger = nil end @debug_logger end end |
.env_var_names ⇒ Object
ENV var name mapping helpers (derived from namespaces)
Returns a Hash mapping namespace String => ENV variable name String
389 390 391 392 393 |
# File 'lib/floss_funding.rb', line 389 def env_var_names @mutex.synchronize do @namespaces.transform_values { |nobj| nobj.env_var_name } end end |
.error!(error, where = nil) ⇒ Object
Mark an internal error, log useful context for diagnostics, and set inert flag.
254 255 256 257 258 259 260 261 262 263 264 265 266 267 |
# File 'lib/floss_funding.rb', line 254 def error!(error, where = nil) begin lbl = where ? "[ERROR][#{where}]" : "[ERROR]" msg = "#{lbl} #{error.class}: #{error.}" debug_log { msg } bt = (error.backtrace || [])[0, 5].join("\n") debug_log { "#{lbl} backtrace:\n#{bt}" } unless bt.empty? rescue StandardError # ignore logging failures ensure @mutex.synchronize { @errored = true } end true end |
.errored? ⇒ Boolean
Global error flag; when set true, library should become inert.
247 248 249 |
# File 'lib/floss_funding.rb', line 247 def errored? @mutex.synchronize { !!@errored } end |
.initiate_begging(event) ⇒ Object
466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 |
# File 'lib/floss_funding.rb', line 466 def initiate_begging(event) library = event.library ns = library.namespace env_var_name = ::FlossFunding::UnderBar.env_variable_name(ns) library_name = library.library_name activation_key = event.activation_key # On-load nag sentinel: allow each library to nag at most once per lockfile lifetime lock = ::FlossFunding::Lockfile.on_load case event.state when ::FlossFunding::STATES[:activated] nil when ::FlossFunding::STATES[:invalid] unless lock && lock.nagged?(library) lock.record_nag(library, event, "on_load") if lock ::FlossFunding.start_coughing(activation_key, ns, env_var_name) end else unless lock && lock.nagged?(library) lock.record_nag(library, event, "on_load") if lock ::FlossFunding.start_begging(ns, env_var_name, library_name) end end end |
.install_tasks ⇒ Object
Tasks for both development and test environments
213 214 215 |
# File 'lib/floss_funding.rb', line 213 def install_tasks load("floss_funding/tasks.rb") end |
.namespaces ⇒ Hash{String => ::FlossFunding::Namespace}
Accessor for namespaces hash: keys are namespace strings, values are Namespace objects
323 324 325 |
# File 'lib/floss_funding.rb', line 323 def namespaces @mutex.synchronize { @namespaces.dup } end |
.namespaces=(value) ⇒ Object
Replace the namespaces hash (expects Hash{String => ::FlossFunding::Namespace}
)
328 329 330 |
# File 'lib/floss_funding.rb', line 328 def namespaces=(value) @mutex.synchronize { @namespaces = value } end |
.project_root ⇒ String?
Expose the discovered project root (may be nil when running inside this gem’s own repo)
208 209 210 |
# File 'lib/floss_funding.rb', line 208 def project_root ::FlossFunding::ConfigFinder.project_root end |
.register_wedge(base, custom_namespace = nil) ⇒ Object
Register a minimal activation event for wedge-injected libraries to ensure
they are counted in the final summary without performing config discovery.
150 151 152 153 154 155 156 157 158 159 160 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 197 198 |
# File 'lib/floss_funding.rb', line 150 def register_wedge(base, custom_namespace = nil) # Derive namespace string ns_name = (custom_namespace.is_a?(String) && custom_namespace.strip != "") ? custom_namespace : base.name.to_s # Build Namespace (derives activation key/state from ENV) namespace = ::FlossFunding::Namespace.new(ns_name, base) # Derive a library (gem) name from the namespace: underscore segments and downcase # Example: "FlossFunding" => "floss_funding"; "My::Lib" => "my_lib" derived_lib_name = ns_name.split("::").map { |seg| ::FlossFunding::UnderBar.(seg) }.join("__").downcase # Minimal configuration: include required keys so downstream consumers have something sensible cfg_hash = { "library_name" => ["wedge_#{derived_lib_name}"], "funding_uri" => ["https://floss-funding.dev"], } config = ::FlossFunding::Configuration.new(cfg_hash) # Minimal Library record; many fields are nil or placeholders in wedge mode library = ::FlossFunding::Library.new( derived_lib_name, # library_name namespace, # ns custom_namespace, # custom_ns base.name.to_s, # base_name nil, # including_path nil, # root_path nil, # config_path namespace.env_var_name, # env_var_name config, # configuration nil, # silent ) # Event with the derived state and key event = ::FlossFunding::ActivationEvent.new( library, namespace.activation_key, namespace.state, nil, ) add_or_update_namespace_with_event(namespace, event) initiate_begging(event) event rescue StandardError => e # Never raise; wedge registration is best-effort only ::FlossFunding.error!(e, "register_wedge") nil end |
.silenced ⇒ Boolean
Global silenced flag accessor (boolean)
334 335 336 |
# File 'lib/floss_funding.rb', line 334 def silenced @mutex.synchronize { @silenced } end |
.silenced=(value) ⇒ void
This method returns an undefined value.
Set the global silenced flag
341 342 343 |
# File 'lib/floss_funding.rb', line 341 def silenced=(value) @mutex.synchronize { @silenced = !!value } end |
.start_begging(namespace, env_var_name, library_name) ⇒ void
This method returns an undefined value.
Emit the standard friendly funding message for unactivated usage
461 462 463 464 |
# File 'lib/floss_funding.rb', line 461 def start_begging(namespace, env_var_name, library_name) return if ::FlossFunding::ContraIndications.at_exit_contraindicated? puts %(FLOSS Funding: Activation key missing for #{library_name} (#{namespace}). Set ENV["#{env_var_name}"] to your activation key; details will be shown at exit.) end |
.start_coughing(activation_key, namespace, env_var_name) ⇒ void
This method returns an undefined value.
Emit a diagnostic message when an activation key is invalid
432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 |
# File 'lib/floss_funding.rb', line 432 def start_coughing(activation_key, namespace, env_var_name) return if ::FlossFunding::ContraIndications.at_exit_contraindicated? puts <<-COUGHING ============================================================== COUGH, COUGH. Ahem, it appears as though you tried to set an activation key for #{namespace}, but it was invalid. Current (Invalid) Activation Key: #{activation_key} Namespace: #{namespace} ENV Variable: #{env_var_name} Paid activation keys are 8 bytes, 64 hex characters, long. Unpaid activation keys have varying lengths, depending on type and namespace. Yours is #{activation_key.length} characters long, and doesn't match any paid or unpaid keys. Please unset the current ENV variable #{env_var_name}, since it is invalid. Then find the correct one, or get a new one @ https://floss-funding.dev and set it. #{FlossFunding::FOOTER} COUGHING end |