Module: Featurevisor
- Defined in:
- lib/featurevisor.rb,
lib/featurevisor/hooks.rb,
lib/featurevisor/events.rb,
lib/featurevisor/logger.rb,
lib/featurevisor/emitter.rb,
lib/featurevisor/version.rb,
lib/featurevisor/bucketer.rb,
lib/featurevisor/evaluate.rb,
lib/featurevisor/instance.rb,
lib/featurevisor/conditions.rb,
lib/featurevisor/murmurhash.rb,
lib/featurevisor/child_instance.rb,
lib/featurevisor/datafile_reader.rb,
lib/featurevisor/compare_versions.rb
Defined Under Namespace
Modules: Bucketer, Conditions, Evaluate, EvaluationReason, Events, Hooks Classes: ChildInstance, DatafileReader, Emitter, Error, Instance, Logger
Constant Summary collapse
- LOG_LEVELS =
Log levels for the logger
%w[fatal error warn info debug].freeze
- DEFAULT_LOG_LEVEL =
"info".freeze
- LOGGER_PREFIX =
"[Featurevisor]".freeze
- EVENT_NAMES =
Event names for the emitter
%w[datafile_set context_set sticky_set].freeze
- VERSION =
"0.2.0"- EVALUATION_TYPES =
Evaluation types
%w[flag variation variable].freeze
- SEMVER_REGEX =
Regular expression for semantic version parsing
/^[v^~<>=]*?(\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+))?(?:-([\da-z\-]+(?:\.[\da-z\-]+)*))?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?)?)?$/i
Class Method Summary collapse
-
.compare_segments(a, b) ⇒ Integer
Compares version segments.
-
.compare_strings(a, b) ⇒ Integer
Compares two strings for version comparison.
-
.compare_versions(v1, v2) ⇒ Integer
Compares two version strings.
-
.create_instance(options = {}) ⇒ Instance
Create a new Featurevisor instance.
-
.create_logger(options = {}) ⇒ Logger
Create a new logger instance.
-
.default_log_handler(level, message, details = nil) ⇒ Object
Default log handler function.
-
.force_type(a, b) ⇒ Array
Forces types to be the same for comparison.
-
.murmur_hash_v3(key, seed) ⇒ Integer
MurmurHash v3 implementation ported from TypeScript Original: github.com/perezd/node-murmurhash.
-
.try_parse(v) ⇒ Integer, String
Tries to parse a string as an integer.
-
.validate_and_parse(version) ⇒ Array<String>
Validates and parses a version string.
-
.wildcard?(s) ⇒ Boolean
Checks if a string is a wildcard.
Class Method Details
.compare_segments(a, b) ⇒ Integer
Compares version segments
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/featurevisor/compare_versions.rb', line 80 def self.compare_segments(a, b) # Convert to arrays if needed a_array = a.is_a?(MatchData) ? a.to_a[1..-1] : a.to_a b_array = b.is_a?(MatchData) ? b.to_a[1..-1] : b.to_a max_length = [a_array.length, b_array.length].max (0...max_length).each do |i| a_val = a_array[i] || "0" b_val = b_array[i] || "0" result = compare_strings(a_val, b_val) return result unless result == 0 end 0 end |
.compare_strings(a, b) ⇒ Integer
Compares two strings for version comparison
62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/featurevisor/compare_versions.rb', line 62 def self.compare_strings(a, b) return 0 if wildcard?(a) || wildcard?(b) ap, bp = force_type(try_parse(a), try_parse(b)) if ap > bp 1 elsif ap < bp -1 else 0 end end |
.compare_versions(v1, v2) ⇒ Integer
Compares two version strings
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/featurevisor/compare_versions.rb', line 104 def self.compare_versions(v1, v2) # Validate input and split into segments n1 = validate_and_parse(v1) n2 = validate_and_parse(v2) # Pop off the patch p1 = n1.pop p2 = n2.pop # Validate numbers r = compare_segments(n1, n2) return r unless r == 0 # Validate pre-release if p1 && p2 compare_segments(p1.split("."), p2.split(".")) elsif p1 || p2 p1 ? -1 : 1 else 0 end end |
.create_instance(options = {}) ⇒ Instance
Create a new Featurevisor instance
460 461 462 |
# File 'lib/featurevisor/instance.rb', line 460 def self.create_instance( = {}) Instance.new() end |
.create_logger(options = {}) ⇒ Logger
Create a new logger instance
119 120 121 |
# File 'lib/featurevisor/logger.rb', line 119 def self.create_logger( = {}) Logger.new() end |
.default_log_handler(level, message, details = nil) ⇒ Object
Default log handler function
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/featurevisor/logger.rb', line 127 def self.default_log_handler(level, , details = nil) method_name = case level when "info" then "puts" when "warn" then "warn" when "error", "fatal" then "warn" else "puts" end case method_name when "puts" if details && !details.empty? Kernel.puts("#{LOGGER_PREFIX} #{} #{details.inspect}") else Kernel.puts("#{LOGGER_PREFIX} #{}") end when "warn" if details && !details.empty? Kernel.warn("#{LOGGER_PREFIX} #{} #{details.inspect}") else Kernel.warn("#{LOGGER_PREFIX} #{}") end end end |
.force_type(a, b) ⇒ Array
Forces types to be the same for comparison
41 42 43 44 45 46 47 |
# File 'lib/featurevisor/compare_versions.rb', line 41 def self.force_type(a, b) if a.is_a?(Integer) != b.is_a?(Integer) [a.to_s, b.to_s] else [a, b] end end |
.murmur_hash_v3(key, seed) ⇒ Integer
MurmurHash v3 implementation ported from TypeScript Original: github.com/perezd/node-murmurhash
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 |
# File 'lib/featurevisor/murmurhash.rb', line 9 def self.murmur_hash_v3(key, seed) # Convert string to bytes if needed key = key.bytes if key.is_a?(String) remainder = key.length & 3 # key.length % 4 bytes = key.length - remainder h1 = seed c1 = 0xcc9e2d51 c2 = 0x1b873593 i = 0 # Process 4-byte chunks while i < bytes k1 = (key[i] & 0xff) | ((key[i + 1] & 0xff) << 8) | ((key[i + 2] & 0xff) << 16) | ((key[i + 3] & 0xff) << 24) i += 4 k1 = ((k1 & 0xffff) * c1 + ((((k1 >> 16) * c1) & 0xffff) << 16)) & 0xffffffff k1 = (k1 << 15) | (k1 >> 17) k1 = ((k1 & 0xffff) * c2 + ((((k1 >> 16) * c2) & 0xffff) << 16)) & 0xffffffff h1 ^= k1 h1 = (h1 << 13) | (h1 >> 19) h1b = ((h1 & 0xffff) * 5 + ((((h1 >> 16) * 5) & 0xffff) << 16)) & 0xffffffff h1 = (h1b & 0xffff) + 0x6b64 + ((((h1b >> 16) + 0xe654) & 0xffff) << 16) end # Process remaining bytes k1 = 0 # Handle remainder processing with fall-through behavior like TypeScript switch if remainder >= 3 k1 ^= (key[i + 2] & 0xff) << 16 end if remainder >= 2 k1 ^= (key[i + 1] & 0xff) << 8 end if remainder >= 1 k1 ^= key[i] & 0xff k1 = ((k1 & 0xffff) * c1 + ((((k1 >> 16) * c1) & 0xffff) << 16)) & 0xffffffff k1 = (k1 << 15) | (k1 >> 17) k1 = ((k1 & 0xffff) * c2 + ((((k1 >> 16) * c2) & 0xffff) << 16)) & 0xffffffff h1 ^= k1 end h1 ^= key.length # Final mixing - use unsigned right shift equivalent h1 ^= h1 >> 16 h1 = ((h1 & 0xffff) * 0x85ebca6b + ((((h1 >> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff h1 ^= h1 >> 13 h1 = ((h1 & 0xffff) * 0xc2b2ae35 + ((((h1 >> 16) * 0xc2b2ae35) & 0xffff) << 16)) & 0xffffffff h1 ^= h1 >> 16 # Convert to unsigned 32-bit integer (equivalent to >>> 0 in TypeScript) h1 & 0xffffffff end |
.try_parse(v) ⇒ Integer, String
Tries to parse a string as an integer
52 53 54 55 56 |
# File 'lib/featurevisor/compare_versions.rb', line 52 def self.try_parse(v) Integer(v, 10) rescue ArgumentError v end |
.validate_and_parse(version) ⇒ Array<String>
Validates and parses a version string
16 17 18 19 20 21 22 23 24 25 26 27 28 |
# File 'lib/featurevisor/compare_versions.rb', line 16 def self.validate_and_parse(version) unless version.is_a?(String) raise TypeError, "Invalid argument expected string" end match = version.match(SEMVER_REGEX) unless match raise ArgumentError, "Invalid argument not valid semver ('#{version}' received)" end # Remove the first element (full match) and return the rest match.to_a[1..-1] end |
.wildcard?(s) ⇒ Boolean
Checks if a string is a wildcard
33 34 35 |
# File 'lib/featurevisor/compare_versions.rb', line 33 def self.wildcard?(s) s == "*" || s.downcase == "x" end |