featurevisor 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +722 -0
- data/bin/cli.rb +142 -0
- data/bin/commands/assess_distribution.rb +236 -0
- data/bin/commands/benchmark.rb +274 -0
- data/bin/commands/test.rb +793 -0
- data/bin/commands.rb +10 -0
- data/bin/featurevisor +18 -0
- data/lib/featurevisor/bucketer.rb +95 -0
- data/lib/featurevisor/child_instance.rb +311 -0
- data/lib/featurevisor/compare_versions.rb +126 -0
- data/lib/featurevisor/conditions.rb +152 -0
- data/lib/featurevisor/datafile_reader.rb +350 -0
- data/lib/featurevisor/emitter.rb +60 -0
- data/lib/featurevisor/evaluate.rb +818 -0
- data/lib/featurevisor/events.rb +76 -0
- data/lib/featurevisor/hooks.rb +159 -0
- data/lib/featurevisor/instance.rb +463 -0
- data/lib/featurevisor/logger.rb +150 -0
- data/lib/featurevisor/murmurhash.rb +69 -0
- data/lib/featurevisor/version.rb +3 -0
- data/lib/featurevisor.rb +17 -0
- metadata +89 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Featurevisor
|
|
4
|
+
# Log levels for the logger
|
|
5
|
+
LOG_LEVELS = %w[fatal error warn info debug].freeze
|
|
6
|
+
DEFAULT_LOG_LEVEL = "info".freeze
|
|
7
|
+
LOGGER_PREFIX = "[Featurevisor]".freeze
|
|
8
|
+
|
|
9
|
+
# Logger class for handling different log levels
|
|
10
|
+
class Logger
|
|
11
|
+
attr_reader :level, :handler
|
|
12
|
+
|
|
13
|
+
# Initialize a new logger
|
|
14
|
+
# @param options [Hash] Logger options
|
|
15
|
+
# @option options [String] :level Log level (default: "info")
|
|
16
|
+
# @option options [Proc] :handler Custom log handler (default: default_log_handler)
|
|
17
|
+
def initialize(options = {})
|
|
18
|
+
@level = options[:level] || DEFAULT_LOG_LEVEL
|
|
19
|
+
@handler = options[:handler] || method(:default_log_handler)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Set the log level
|
|
23
|
+
# @param level [String] New log level
|
|
24
|
+
def set_level(level)
|
|
25
|
+
@level = level
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Log a message at a specific level
|
|
29
|
+
# @param level [String] Log level
|
|
30
|
+
# @param message [String] Log message
|
|
31
|
+
# @param details [Hash, nil] Additional details
|
|
32
|
+
def log(level, message, details = nil)
|
|
33
|
+
return unless should_handle?(level)
|
|
34
|
+
|
|
35
|
+
@handler.call(level, message, details)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Log a debug message
|
|
39
|
+
# @param message [String] Log message
|
|
40
|
+
# @param details [Hash, nil] Additional details
|
|
41
|
+
def debug(message, details = nil)
|
|
42
|
+
log("debug", message, details)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Log an info message
|
|
46
|
+
# @param message [String] Log message
|
|
47
|
+
# @param details [Hash, nil] Additional details
|
|
48
|
+
def info(message, details = nil)
|
|
49
|
+
log("info", message, details)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Log a warning message
|
|
53
|
+
# @param message [String] Log message
|
|
54
|
+
# @param details [Hash, nil] Additional details
|
|
55
|
+
def warn(message, details = nil)
|
|
56
|
+
log("warn", message, details)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Log an error message
|
|
60
|
+
# @param message [String] Log message
|
|
61
|
+
# @param details [Hash, nil] Additional details
|
|
62
|
+
def error(message, details = nil)
|
|
63
|
+
log("error", message, details)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Log a fatal message
|
|
67
|
+
# @param message [String] Log message
|
|
68
|
+
# @param details [Hash, nil] Additional details
|
|
69
|
+
def fatal(message, details = nil)
|
|
70
|
+
log("fatal", message, details)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
# Check if the current level should handle the given log level
|
|
76
|
+
# @param log_level [String] Log level to check
|
|
77
|
+
# @return [Boolean] True if should handle
|
|
78
|
+
def should_handle?(log_level)
|
|
79
|
+
current_index = LOG_LEVELS.index(@level)
|
|
80
|
+
target_index = LOG_LEVELS.index(log_level)
|
|
81
|
+
|
|
82
|
+
return false if current_index.nil? || target_index.nil?
|
|
83
|
+
|
|
84
|
+
current_index >= target_index
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Default log handler that outputs to console
|
|
88
|
+
# @param level [String] Log level
|
|
89
|
+
# @param message [String] Log message
|
|
90
|
+
# @param details [Hash, nil] Additional details
|
|
91
|
+
def default_log_handler(level, message, details = nil)
|
|
92
|
+
method_name = case level
|
|
93
|
+
when "info" then "puts"
|
|
94
|
+
when "warn" then "warn"
|
|
95
|
+
when "error", "fatal" then "warn"
|
|
96
|
+
else "puts"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
case method_name
|
|
100
|
+
when "puts"
|
|
101
|
+
if details && !details.empty?
|
|
102
|
+
Kernel.puts("#{LOGGER_PREFIX} #{message} #{details.inspect}")
|
|
103
|
+
else
|
|
104
|
+
Kernel.puts("#{LOGGER_PREFIX} #{message}")
|
|
105
|
+
end
|
|
106
|
+
when "warn"
|
|
107
|
+
if details && !details.empty?
|
|
108
|
+
Kernel.warn("#{LOGGER_PREFIX} #{message} #{details.inspect}")
|
|
109
|
+
else
|
|
110
|
+
Kernel.warn("#{LOGGER_PREFIX} #{message}")
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Create a new logger instance
|
|
117
|
+
# @param options [Hash] Logger options
|
|
118
|
+
# @return [Logger] New logger instance
|
|
119
|
+
def self.create_logger(options = {})
|
|
120
|
+
Logger.new(options)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Default log handler function
|
|
124
|
+
# @param level [String] Log level
|
|
125
|
+
# @param message [String] Log message
|
|
126
|
+
# @param details [Hash, nil] Additional details
|
|
127
|
+
def self.default_log_handler(level, message, details = nil)
|
|
128
|
+
method_name = case level
|
|
129
|
+
when "info" then "puts"
|
|
130
|
+
when "warn" then "warn"
|
|
131
|
+
when "error", "fatal" then "warn"
|
|
132
|
+
else "puts"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
case method_name
|
|
136
|
+
when "puts"
|
|
137
|
+
if details && !details.empty?
|
|
138
|
+
Kernel.puts("#{LOGGER_PREFIX} #{message} #{details.inspect}")
|
|
139
|
+
else
|
|
140
|
+
Kernel.puts("#{LOGGER_PREFIX} #{message}")
|
|
141
|
+
end
|
|
142
|
+
when "warn"
|
|
143
|
+
if details && !details.empty?
|
|
144
|
+
Kernel.warn("#{LOGGER_PREFIX} #{message} #{details.inspect}")
|
|
145
|
+
else
|
|
146
|
+
Kernel.warn("#{LOGGER_PREFIX} #{message}")
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Featurevisor
|
|
4
|
+
# MurmurHash v3 implementation ported from TypeScript
|
|
5
|
+
# Original: https://github.com/perezd/node-murmurhash
|
|
6
|
+
# @param key [String, Array<Integer>] Input key to hash
|
|
7
|
+
# @param seed [Integer] Seed value for the hash
|
|
8
|
+
# @return [Integer] 32-bit hash value
|
|
9
|
+
def self.murmur_hash_v3(key, seed)
|
|
10
|
+
# Convert string to bytes if needed
|
|
11
|
+
key = key.bytes if key.is_a?(String)
|
|
12
|
+
|
|
13
|
+
remainder = key.length & 3 # key.length % 4
|
|
14
|
+
bytes = key.length - remainder
|
|
15
|
+
h1 = seed
|
|
16
|
+
c1 = 0xcc9e2d51
|
|
17
|
+
c2 = 0x1b873593
|
|
18
|
+
i = 0
|
|
19
|
+
|
|
20
|
+
# Process 4-byte chunks
|
|
21
|
+
while i < bytes
|
|
22
|
+
k1 = (key[i] & 0xff) |
|
|
23
|
+
((key[i + 1] & 0xff) << 8) |
|
|
24
|
+
((key[i + 2] & 0xff) << 16) |
|
|
25
|
+
((key[i + 3] & 0xff) << 24)
|
|
26
|
+
i += 4
|
|
27
|
+
|
|
28
|
+
k1 = ((k1 & 0xffff) * c1 + ((((k1 >> 16) * c1) & 0xffff) << 16)) & 0xffffffff
|
|
29
|
+
k1 = (k1 << 15) | (k1 >> 17)
|
|
30
|
+
k1 = ((k1 & 0xffff) * c2 + ((((k1 >> 16) * c2) & 0xffff) << 16)) & 0xffffffff
|
|
31
|
+
|
|
32
|
+
h1 ^= k1
|
|
33
|
+
h1 = (h1 << 13) | (h1 >> 19)
|
|
34
|
+
h1b = ((h1 & 0xffff) * 5 + ((((h1 >> 16) * 5) & 0xffff) << 16)) & 0xffffffff
|
|
35
|
+
h1 = (h1b & 0xffff) + 0x6b64 + ((((h1b >> 16) + 0xe654) & 0xffff) << 16)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Process remaining bytes
|
|
39
|
+
k1 = 0
|
|
40
|
+
|
|
41
|
+
# Handle remainder processing with fall-through behavior like TypeScript switch
|
|
42
|
+
if remainder >= 3
|
|
43
|
+
k1 ^= (key[i + 2] & 0xff) << 16
|
|
44
|
+
end
|
|
45
|
+
if remainder >= 2
|
|
46
|
+
k1 ^= (key[i + 1] & 0xff) << 8
|
|
47
|
+
end
|
|
48
|
+
if remainder >= 1
|
|
49
|
+
k1 ^= key[i] & 0xff
|
|
50
|
+
|
|
51
|
+
k1 = ((k1 & 0xffff) * c1 + ((((k1 >> 16) * c1) & 0xffff) << 16)) & 0xffffffff
|
|
52
|
+
k1 = (k1 << 15) | (k1 >> 17)
|
|
53
|
+
k1 = ((k1 & 0xffff) * c2 + ((((k1 >> 16) * c2) & 0xffff) << 16)) & 0xffffffff
|
|
54
|
+
h1 ^= k1
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
h1 ^= key.length
|
|
58
|
+
|
|
59
|
+
# Final mixing - use unsigned right shift equivalent
|
|
60
|
+
h1 ^= h1 >> 16
|
|
61
|
+
h1 = ((h1 & 0xffff) * 0x85ebca6b + ((((h1 >> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff
|
|
62
|
+
h1 ^= h1 >> 13
|
|
63
|
+
h1 = ((h1 & 0xffff) * 0xc2b2ae35 + ((((h1 >> 16) * 0xc2b2ae35) & 0xffff) << 16)) & 0xffffffff
|
|
64
|
+
h1 ^= h1 >> 16
|
|
65
|
+
|
|
66
|
+
# Convert to unsigned 32-bit integer (equivalent to >>> 0 in TypeScript)
|
|
67
|
+
h1 & 0xffffffff
|
|
68
|
+
end
|
|
69
|
+
end
|
data/lib/featurevisor.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require_relative "featurevisor/version"
|
|
2
|
+
require_relative "featurevisor/murmurhash"
|
|
3
|
+
require_relative "featurevisor/compare_versions"
|
|
4
|
+
require_relative "featurevisor/logger"
|
|
5
|
+
require_relative "featurevisor/emitter"
|
|
6
|
+
require_relative "featurevisor/conditions"
|
|
7
|
+
require_relative "featurevisor/datafile_reader"
|
|
8
|
+
require_relative "featurevisor/bucketer"
|
|
9
|
+
require_relative "featurevisor/hooks"
|
|
10
|
+
require_relative "featurevisor/evaluate"
|
|
11
|
+
require_relative "featurevisor/instance"
|
|
12
|
+
require_relative "featurevisor/child_instance"
|
|
13
|
+
require_relative "featurevisor/events"
|
|
14
|
+
|
|
15
|
+
module Featurevisor
|
|
16
|
+
class Error < StandardError; end
|
|
17
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: featurevisor
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Fahad Heylaal
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rspec
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '3.12'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '3.12'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rake
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '13.0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '13.0'
|
|
40
|
+
description: Featurevisor Ruby SDK with CLI tools for feature flags management
|
|
41
|
+
executables:
|
|
42
|
+
- featurevisor
|
|
43
|
+
extensions: []
|
|
44
|
+
extra_rdoc_files: []
|
|
45
|
+
files:
|
|
46
|
+
- LICENSE
|
|
47
|
+
- README.md
|
|
48
|
+
- bin/cli.rb
|
|
49
|
+
- bin/commands.rb
|
|
50
|
+
- bin/commands/assess_distribution.rb
|
|
51
|
+
- bin/commands/benchmark.rb
|
|
52
|
+
- bin/commands/test.rb
|
|
53
|
+
- bin/featurevisor
|
|
54
|
+
- lib/featurevisor.rb
|
|
55
|
+
- lib/featurevisor/bucketer.rb
|
|
56
|
+
- lib/featurevisor/child_instance.rb
|
|
57
|
+
- lib/featurevisor/compare_versions.rb
|
|
58
|
+
- lib/featurevisor/conditions.rb
|
|
59
|
+
- lib/featurevisor/datafile_reader.rb
|
|
60
|
+
- lib/featurevisor/emitter.rb
|
|
61
|
+
- lib/featurevisor/evaluate.rb
|
|
62
|
+
- lib/featurevisor/events.rb
|
|
63
|
+
- lib/featurevisor/hooks.rb
|
|
64
|
+
- lib/featurevisor/instance.rb
|
|
65
|
+
- lib/featurevisor/logger.rb
|
|
66
|
+
- lib/featurevisor/murmurhash.rb
|
|
67
|
+
- lib/featurevisor/version.rb
|
|
68
|
+
homepage: https://featurevisor.com
|
|
69
|
+
licenses:
|
|
70
|
+
- MIT
|
|
71
|
+
metadata: {}
|
|
72
|
+
rdoc_options: []
|
|
73
|
+
require_paths:
|
|
74
|
+
- lib
|
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
76
|
+
requirements:
|
|
77
|
+
- - ">="
|
|
78
|
+
- !ruby/object:Gem::Version
|
|
79
|
+
version: 3.0.0
|
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
81
|
+
requirements:
|
|
82
|
+
- - ">="
|
|
83
|
+
- !ruby/object:Gem::Version
|
|
84
|
+
version: '0'
|
|
85
|
+
requirements: []
|
|
86
|
+
rubygems_version: 3.6.9
|
|
87
|
+
specification_version: 4
|
|
88
|
+
summary: Featurevisor Ruby SDK
|
|
89
|
+
test_files: []
|