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/validators.rb,
lib/floss_funding/file_finder.rb,
lib/floss_funding/fingerprint.rb,
lib/floss_funding/rake_helpers.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/terminal_helpers.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, RakeHelpers, Rakelib, Silent, Terminal, UnderBar, Validators, 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_CFG_FUND_DEBUG’] case-insensitively equals “true”.
begin v = ENV.fetch("FLOSS_CFG_FUND_DEBUG", nil) v.to_s.casecmp("true") == 0 rescue StandardError false end
- CONFIG_FILE_NAME =
The file name to look for in the project root.
".floss_funding.yml"- FF_ROOT =
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", :detained => "detained", }.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}). -
.progress_bar(activated_count, total_count) ⇒ Object
Display a concise progress indicator for activated vs total libraries.
-
.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, contraindicated = false) ⇒ 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
253 254 255 |
# File 'lib/floss_funding.rb', line 253 def loaded_at @loaded_at end |
Class Method Details
.add_or_update_namespace_with_event(namespace, event) ⇒ Object
394 395 396 397 398 399 400 401 402 403 404 405 406 407 |
# File 'lib/floss_funding.rb', line 394 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
412 413 414 |
# File 'lib/floss_funding.rb', line 412 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.
451 452 453 454 455 456 |
# File 'lib/floss_funding.rb', line 451 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
461 462 463 464 465 466 467 468 469 470 471 472 473 474 |
# File 'lib/floss_funding.rb', line 461 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
432 433 434 |
# File 'lib/floss_funding.rb', line 432 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).
419 420 421 422 423 424 425 426 427 428 429 430 |
# File 'lib/floss_funding.rb', line 419 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.
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 |
# File 'lib/floss_funding.rb', line 272 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.
320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 |
# File 'lib/floss_funding.rb', line 320 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
438 439 440 441 442 |
# File 'lib/floss_funding.rb', line 438 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.
303 304 305 306 307 308 309 310 311 312 313 314 315 316 |
# File 'lib/floss_funding.rb', line 303 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.
296 297 298 |
# File 'lib/floss_funding.rb', line 296 def errored? @mutex.synchronize { !!@errored } end |
.initiate_begging(event) ⇒ Object
515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 |
# File 'lib/floss_funding.rb', line 515 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 when ::FlossFunding::STATES[:detained] unless lock && lock.nagged?(library) lock.record_nag(library, event, "on_load") if lock puts %(FLOSS Funding: Configuration for #{library_name} (#{ns}) contains invalid values and has been detained; details will be shown at exit.) unless ::FlossFunding::ContraIndications.at_exit_contraindicated? 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
262 263 264 |
# File 'lib/floss_funding.rb', line 262 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
372 373 374 |
# File 'lib/floss_funding.rb', line 372 def namespaces @mutex.synchronize { @namespaces.dup } end |
.namespaces=(value) ⇒ Object
Replace the namespaces hash (expects Hash{String => ::FlossFunding::Namespace})
377 378 379 |
# File 'lib/floss_funding.rb', line 377 def namespaces=(value) @mutex.synchronize { @namespaces = value } end |
.progress_bar(activated_count, total_count) ⇒ Object
Display a concise progress indicator for activated vs total libraries.
Falls back gracefully if ruby-progressbar is not available or when total is zero.
- In TTY: renders a progress bar and ensures a trailing newline.
- In non-TTY: prints a stable summary with counts to avoid spinner artifacts.
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 |
# File 'lib/floss_funding.rb', line 161 def (activated_count, total_count) begin require "ruby-progressbar" rescue LoadError # Fallback without ruby-progressbar: print a concise summary with percentage and counts total_i = total_count.to_i if total_i <= 0 puts "FUNDED🦷%: 0% (0/0)" else pct = ((activated_count.to_f / total_i.to_f) * 100).round puts "FUNDED🦷%: #{pct}% (#{activated_count}/#{total_i})" end return end total = [total_count, 0].max activated = [[activated_count, 0].max, total].min if total.zero? # Avoid creating a progressbar with zero total; print a stable fallback puts "FUNDED🦷%: 0% (0/0)" return end = ProgressBar.create(:title => "FUNDED🦷%", :total => total, :format => "%t: |%B| %p%% (%c/%C)") .progress = activated # Ensure we end with a newline after progress bar output without forcing completion if $stdout.tty? puts "" else # In non-TTY (e.g., CI capture), provide a stable summary with counts puts "(#{activated}/#{total})" end end |
.project_root ⇒ String?
Expose the discovered project root (may be nil when running inside this gem’s own repo)
257 258 259 |
# File 'lib/floss_funding.rb', line 257 def project_root ::FlossFunding::ConfigFinder.project_root end |
.register_wedge(base, custom_namespace = nil, contraindicated = false) ⇒ Object
Register a minimal activation event for wedge-injected libraries to ensure
they are counted in the final summary without performing config discovery.
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
# File 'lib/floss_funding.rb', line 199 def register_wedge(base, custom_namespace = nil, contraindicated = false) # 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) unless contraindicated 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)
383 384 385 |
# File 'lib/floss_funding.rb', line 383 def silenced @mutex.synchronize { @silenced } end |
.silenced=(value) ⇒ void
This method returns an undefined value.
Set the global silenced flag
390 391 392 |
# File 'lib/floss_funding.rb', line 390 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
510 511 512 513 |
# File 'lib/floss_funding.rb', line 510 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
481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 |
# File 'lib/floss_funding.rb', line 481 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 |