configcat 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f09ffb7f9d1bba3ecd5da0f306df08dead784a95f2a87e4998d7842ebfdf20b8
4
+ data.tar.gz: fbe49ce6b2cd1e643829c6092aa8d3f8b17393d2f273e5f1d23fee5ebf889b64
5
+ SHA512:
6
+ metadata.gz: ddeb7ec223e24bdd30d2f436fd9c23817b2e77424f1c15ea99e5d7b9c548fe26d2c568307230a42ad8d29541d3b9b09fb09a8440deae4f3014d58bd09298b674
7
+ data.tar.gz: e826984124b63d61b54ecc862caeabf870161fdb712424916d6cc2e7d793dd2770305ab714af97e9a2644048d8b8e520bfb03e2fe4f379b2ec2e3f1999dbeaf5
@@ -0,0 +1,83 @@
1
+ require 'configcat/interfaces'
2
+ require 'concurrent'
3
+
4
+ module ConfigCat
5
+ class AutoPollingCachePolicy < CachePolicy
6
+ def initialize(config_fetcher, config_cache, poll_interval_seconds=60, max_init_wait_time_seconds=5, on_configuration_changed_callback=nil)
7
+ if poll_interval_seconds < 1
8
+ poll_interval_seconds = 1
9
+ end
10
+ if max_init_wait_time_seconds < 0
11
+ max_init_wait_time_seconds = 0
12
+ end
13
+ @_config_fetcher = config_fetcher
14
+ @_config_cache = config_cache
15
+ @_poll_interval_seconds = poll_interval_seconds
16
+ @_max_init_wait_time_seconds = max_init_wait_time_seconds
17
+ @_on_configuration_changed_callback = on_configuration_changed_callback
18
+ @_initialized = false
19
+ @_is_running = false
20
+ @_start_time = Time.now.utc
21
+ @_lock = Concurrent::ReadWriteLock.new()
22
+ @_is_started = Concurrent::Event.new()
23
+ @thread = Thread.new{_run()}
24
+ @_is_started.wait()
25
+ end
26
+
27
+ def _run()
28
+ @_is_running = true
29
+ @_is_started.set()
30
+ loop do
31
+ force_refresh()
32
+ sleep(@_poll_interval_seconds)
33
+ break if !@_is_running
34
+ end
35
+ end
36
+
37
+ def get()
38
+ while !@_initialized && (Time.now.utc < @_start_time + @_max_init_wait_time_seconds)
39
+ sleep(0.5)
40
+ end
41
+ begin
42
+ @_lock.acquire_read_lock()
43
+ return @_config_cache.get()
44
+ ensure
45
+ @_lock.release_read_lock()
46
+ end
47
+ end
48
+
49
+ def force_refresh()
50
+ begin
51
+ @_lock.acquire_read_lock()
52
+ old_configuration = @_config_cache.get()
53
+ ensure
54
+ @_lock.release_read_lock()
55
+ end
56
+ begin
57
+ configuration = @_config_fetcher.get_configuration_json()
58
+ begin
59
+ @_lock.acquire_write_lock()
60
+ @_config_cache.set(configuration)
61
+ @_initialized = true
62
+ ensure
63
+ @_lock.release_write_lock()
64
+ end
65
+ begin
66
+ if !@_on_configuration_changed_callback.equal?(nil) && configuration != old_configuration
67
+ @_on_configuration_changed_callback.call()
68
+ end
69
+ rescue Exception => e
70
+ ConfigCat.logger.error "threw exception #{e.class}:'#{e}'"
71
+ ConfigCat.logger.error "stacktrace: #{e.backtrace}"
72
+ end
73
+ rescue StandardError => e
74
+ ConfigCat.logger.error "threw exception #{e.class}:'#{e}'"
75
+ ConfigCat.logger.error "stacktrace: #{e.backtrace}"
76
+ end
77
+ end
78
+
79
+ def stop()
80
+ @_is_running = false
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,17 @@
1
+ require 'configcat/interfaces'
2
+
3
+ module ConfigCat
4
+ class InMemoryConfigCache < ConfigCache
5
+ def initialize()
6
+ @_value = nil
7
+ end
8
+
9
+ def get()
10
+ return @_value
11
+ end
12
+
13
+ def set(value)
14
+ @_value = value
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,69 @@
1
+ require 'configcat/interfaces'
2
+ require 'configcat/configcache'
3
+ require 'configcat/configfetcher'
4
+ require 'configcat/autopollingcachepolicy'
5
+ require 'configcat/manualpollingcachepolicy'
6
+ require 'configcat/lazyloadingcachepolicy'
7
+ require 'configcat/rolloutevaluator'
8
+
9
+ module ConfigCat
10
+ class ConfigCatClient
11
+ def initialize(api_key,
12
+ poll_interval_seconds=60,
13
+ max_init_wait_time_seconds=5,
14
+ on_configuration_changed_callback=nil,
15
+ cache_time_to_live_seconds=60,
16
+ config_cache_class=nil,
17
+ base_url=nil)
18
+ if api_key === nil
19
+ raise ConfigCatClientException, "API Key is required."
20
+ end
21
+ @_api_key = api_key
22
+
23
+ if config_cache_class
24
+ @_config_cache = config_cache_class.new()
25
+ else
26
+ @_config_cache = InMemoryConfigCache.new()
27
+ end
28
+
29
+ if poll_interval_seconds > 0
30
+ @_config_fetcher = CacheControlConfigFetcher.new(api_key, "p", base_url)
31
+ @_cache_policy = AutoPollingCachePolicy.new(@_config_fetcher, @_config_cache, poll_interval_seconds, max_init_wait_time_seconds, on_configuration_changed_callback)
32
+ else
33
+ if cache_time_to_live_seconds > 0
34
+ @_config_fetcher = CacheControlConfigFetcher.new(api_key, "l", base_url)
35
+ @_cache_policy = LazyLoadingCachePolicy.new(@_config_fetcher, @_config_cache, cache_time_to_live_seconds)
36
+ else
37
+ @_config_fetcher = CacheControlConfigFetcher.new(api_key, "m", base_url)
38
+ @_cache_policy = ManualPollingCachePolicy.new(@_config_fetcher, @_config_cache)
39
+ end
40
+ end
41
+ end
42
+
43
+ def get_value(key, default_value, user=nil)
44
+ config = @_cache_policy.get()
45
+ if config === nil
46
+ return default_value
47
+ end
48
+ return RolloutEvaluator.evaluate(key, user, default_value, config)
49
+ end
50
+
51
+ def get_all_keys()
52
+ config = @_cache_policy.get()
53
+ if config === nil
54
+ return []
55
+ end
56
+ return config.keys
57
+ end
58
+
59
+ def force_refresh()
60
+ @_cache_policy.force_refresh()
61
+ end
62
+
63
+ def stop()
64
+ @_cache_policy.stop()
65
+ @_config_fetcher.close()
66
+ end
67
+
68
+ end
69
+ end
@@ -0,0 +1,45 @@
1
+ require 'configcat/interfaces'
2
+ require 'configcat/version'
3
+ require 'net/http'
4
+ require 'uri'
5
+ require 'json'
6
+
7
+ module ConfigCat
8
+ BASE_URL = "https://cdn.configcat.com"
9
+ BASE_PATH = "configuration-files/"
10
+ BASE_EXTENSION = "/config_v2.json"
11
+
12
+ class CacheControlConfigFetcher < ConfigFetcher
13
+ def initialize(api_key, mode, base_url=nil)
14
+ @_api_key = api_key
15
+ @_headers = {"User-Agent" => ((("ConfigCat-Ruby/") + mode) + ("-")) + VERSION, "X-ConfigCat-UserAgent" => ((("ConfigCat-Ruby/") + mode) + ("-")) + VERSION, "Content-Type" => "application/json"}
16
+ if !base_url.equal?(nil)
17
+ @_base_url = base_url.chomp("/")
18
+ else
19
+ @_base_url = BASE_URL
20
+ end
21
+ uri = URI.parse(@_base_url)
22
+ @_http = Net::HTTP.new(uri.host, uri.port)
23
+ @_http.use_ssl = true if uri.scheme == 'https'
24
+ @_http.open_timeout = 10 # in seconds
25
+ @_http.read_timeout = 30 # in seconds
26
+ end
27
+
28
+ def get_configuration_json()
29
+ ConfigCat.logger.debug "Fetching configuration from ConfigCat"
30
+ uri = URI.parse((((@_base_url + ("/")) + BASE_PATH) + @_api_key) + BASE_EXTENSION)
31
+ request = Net::HTTP::Get.new(uri.request_uri, @_headers)
32
+ response = @_http.request(request)
33
+ json = JSON.parse(response.body)
34
+ # TODO: check cached entry
35
+ ConfigCat.logger.debug "ConfigCat configuration json fetch response code:#{response.code} Cached:#{response['ETag']}"
36
+ return json
37
+ end
38
+
39
+ def close()
40
+ if @_http
41
+ @_http = nil
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,71 @@
1
+ module ConfigCat
2
+
3
+ class ConfigFetcher
4
+ #
5
+ # Config fetcher interface
6
+ #
7
+
8
+ def get_configuration_json()
9
+ #
10
+ # :return: Returns the configuration json Dictionary
11
+ #
12
+ end
13
+
14
+ def close()
15
+ #
16
+ # Closes the ConfigFetcher's resources
17
+ #
18
+ end
19
+ end
20
+
21
+ class ConfigCache
22
+ #
23
+ # Config cache interface
24
+ #
25
+
26
+ def get()
27
+ #
28
+ # :returns the config json object from the cache
29
+ #
30
+ end
31
+
32
+ def set(value)
33
+ #
34
+ # Sets the config json cache.
35
+ #
36
+ end
37
+ end
38
+
39
+ class CachePolicy
40
+ #
41
+ # Config cache interface
42
+ #
43
+
44
+ def get()
45
+ #
46
+ # :returns the config json object from the cache
47
+ #
48
+ end
49
+
50
+ def force_refresh()
51
+ #
52
+ #
53
+ # :return:
54
+ #
55
+ end
56
+
57
+ def stop()
58
+ #
59
+ #
60
+ # :return:
61
+ #
62
+ end
63
+ end
64
+
65
+ class ConfigCatClientException < Exception
66
+ #
67
+ # Generic ConfigCatClientException
68
+ #
69
+ end
70
+
71
+ end
@@ -0,0 +1,62 @@
1
+ require 'configcat/interfaces'
2
+ require 'concurrent'
3
+
4
+ module ConfigCat
5
+ class LazyLoadingCachePolicy < CachePolicy
6
+
7
+ def initialize(config_fetcher, config_cache, cache_time_to_live_seconds=60)
8
+ if cache_time_to_live_seconds < 1
9
+ cache_time_to_live_seconds = 1
10
+ end
11
+ @_config_fetcher = config_fetcher
12
+ @_config_cache = config_cache
13
+ @_cache_time_to_live = cache_time_to_live_seconds
14
+ @_lock = Concurrent::ReadWriteLock.new()
15
+ @_last_updated = nil
16
+ end
17
+
18
+ def get()
19
+ begin
20
+ @_lock.acquire_read_lock()
21
+ utc_now = Time.now.utc
22
+ if !@_last_updated.equal?(nil) && (@_last_updated + @_cache_time_to_live > utc_now)
23
+ config = @_config_cache.get()
24
+ if !config.equal?(nil)
25
+ return config
26
+ end
27
+ end
28
+ ensure
29
+ @_lock.release_read_lock()
30
+ end
31
+ force_refresh()
32
+ begin
33
+ @_lock.acquire_read_lock()
34
+ config = @_config_cache.get()
35
+ return config
36
+ ensure
37
+ @_lock.release_read_lock()
38
+ end
39
+ end
40
+
41
+ def force_refresh()
42
+ begin
43
+ configuration = @_config_fetcher.get_configuration_json()
44
+ begin
45
+ @_lock.acquire_write_lock()
46
+ @_config_cache.set(configuration)
47
+ @_last_updated = Time.now.utc
48
+ ensure
49
+ @_lock.release_write_lock()
50
+ end
51
+ rescue StandardError => e
52
+ ConfigCat.logger.error "threw exception #{e.class}:'#{e}'"
53
+ ConfigCat.logger.error "stacktrace: #{e.backtrace}"
54
+ end
55
+ end
56
+
57
+ def stop()
58
+ end
59
+ end
60
+
61
+ end
62
+
@@ -0,0 +1,41 @@
1
+ require 'configcat/interfaces'
2
+ require 'concurrent'
3
+
4
+ module ConfigCat
5
+ class ManualPollingCachePolicy < CachePolicy
6
+ def initialize(config_fetcher, config_cache)
7
+ @_config_fetcher = config_fetcher
8
+ @_config_cache = config_cache
9
+ @_lock = Concurrent::ReadWriteLock.new()
10
+ end
11
+
12
+ def get()
13
+ begin
14
+ @_lock.acquire_read_lock()
15
+ config = @_config_cache.get()
16
+ return config
17
+ ensure
18
+ @_lock.release_read_lock()
19
+ end
20
+ end
21
+
22
+ def force_refresh()
23
+ begin
24
+ configuration = @_config_fetcher.get_configuration_json()
25
+ begin
26
+ @_lock.acquire_write_lock()
27
+ @_config_cache.set(configuration)
28
+ ensure
29
+ @_lock.release_write_lock()
30
+ end
31
+ rescue StandardError => e
32
+ ConfigCat.logger.error "threw exception #{e.class}:'#{e}'"
33
+ ConfigCat.logger.error "stacktrace: #{e.backtrace}"
34
+ end
35
+ end
36
+
37
+ def stop()
38
+ # pass
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,72 @@
1
+ require 'configcat/user'
2
+ require 'digest'
3
+
4
+ module ConfigCat
5
+ class RolloutEvaluator
6
+
7
+ def self.evaluate(key, user, default_value, config)
8
+ if !user.equal?(nil) && !user.class.equal?(User)
9
+ ConfigCat.logger.warn "User parameter is not an instance of User type."
10
+ user = nil
11
+ end
12
+ setting_descriptor = config.fetch(key, nil)
13
+ if setting_descriptor === nil
14
+ ConfigCat.logger.warn "Could not find setting by key, returning default value. Key: #{key}"
15
+ return default_value
16
+ end
17
+ if user === nil
18
+ return setting_descriptor.fetch("Value", default_value)
19
+ end
20
+ rollout_rules = setting_descriptor.fetch("RolloutRules", [])
21
+ for rollout_rule in rollout_rules
22
+ comparison_attribute = rollout_rule.fetch("ComparisonAttribute")
23
+ user_value = user.get_attribute(comparison_attribute)
24
+ if user_value === nil || !user_value
25
+ next
26
+ end
27
+ comparison_value = rollout_rule.fetch("ComparisonValue", nil)
28
+ comparator = rollout_rule.fetch("Comparator", nil)
29
+ value = rollout_rule.fetch("Value", nil)
30
+ if comparator == 0
31
+ if comparison_value.to_s.split(",").map { |x| x.strip() }.include?(user_value.to_s)
32
+ return value
33
+ end
34
+ else
35
+ if comparator == 1
36
+ if !comparison_value.to_s.split(",").map { |x| x.strip() }.include?(user_value.to_s)
37
+ return value
38
+ end
39
+ else
40
+ if comparator == 2
41
+ if user_value.to_s.include?(comparison_value.to_s)
42
+ return value
43
+ end
44
+ else
45
+ if comparator == 3
46
+ if !user_value.to_s.include?(comparison_value.to_s)
47
+ return value
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ rollout_percentage_items = setting_descriptor.fetch("RolloutPercentageItems", [])
55
+ if rollout_percentage_items.size > 0
56
+ user_key = user.get_identifier()
57
+ hash_candidate = ("%s%s" % [key, user_key]).encode("utf-8")
58
+ hash_val = Digest::SHA1.hexdigest(hash_candidate)[0...7].to_i(base=16) % 100
59
+ bucket = 0
60
+ for rollout_percentage_item in rollout_percentage_items || []
61
+ bucket += rollout_percentage_item.fetch("Percentage", 0)
62
+ if hash_val < bucket
63
+ return rollout_percentage_item.fetch("Value", nil)
64
+ end
65
+ end
66
+ end
67
+ return setting_descriptor.fetch("Value", default_value)
68
+ end
69
+ end
70
+
71
+ end
72
+
@@ -0,0 +1,37 @@
1
+ module ConfigCat
2
+
3
+ class User
4
+ #
5
+ # The user object for variation evaluation
6
+ #
7
+
8
+ PREDEFINED = ["identifier", "email", "country"]
9
+
10
+ def initialize(identifier, email: nil, country: nil, custom: nil)
11
+ @__identifier = identifier
12
+ @__data = {"identifier" => identifier, "email" => email, "country" => country}
13
+ @__custom = custom
14
+ end
15
+
16
+ def get_identifier()
17
+ return @__identifier
18
+ end
19
+
20
+ def get_attribute(attribute)
21
+ attribute = attribute.to_s.downcase()
22
+ if PREDEFINED.include?(attribute)
23
+ return @__data[attribute]
24
+ end
25
+
26
+ if !@__custom.equal?(nil)
27
+ @__custom.each do |customField, customValue|
28
+ if customField.to_s.downcase() == attribute
29
+ return customValue
30
+ end
31
+ end
32
+ end
33
+ return nil
34
+ end
35
+ end
36
+
37
+ end
@@ -0,0 +1,3 @@
1
+ module ConfigCat
2
+ VERSION = "0.0.1"
3
+ end
data/lib/configcat.rb ADDED
@@ -0,0 +1,80 @@
1
+ require 'configcat/interfaces'
2
+ require 'configcat/configcatclient'
3
+ require 'configcat/user'
4
+ require 'logger'
5
+
6
+ module ConfigCat
7
+
8
+ @logger = Logger.new(STDOUT, level: Logger::INFO)
9
+ class << self
10
+ attr_accessor :logger
11
+ end
12
+
13
+ def ConfigCat.create_client(api_key)
14
+ #
15
+ # Create an instance of ConfigCatClient and setup Auto Poll mode with default options
16
+ #
17
+ # :param api_key: ConfigCat ApiKey to access your configuration.
18
+ #
19
+ return create_client_with_auto_poll(api_key)
20
+ end
21
+
22
+ def ConfigCat.create_client_with_auto_poll(api_key, poll_interval_seconds: 60, max_init_wait_time_seconds: 5, on_configuration_changed_callback: nil, config_cache_class: nil, base_url: nil)
23
+ #
24
+ # Create an instance of ConfigCatClient and setup Auto Poll mode with custom options
25
+ #
26
+ # :param api_key: ConfigCat ApiKey to access your configuration.
27
+ # :param poll_interval_seconds: The client's poll interval in seconds. Default: 60 seconds.
28
+ # :param on_configuration_changed_callback: You can subscribe to configuration changes with this callback
29
+ # :param max_init_wait_time_seconds: maximum waiting time for first configuration fetch in polling mode.
30
+ # :param config_cache_class: If you want to use custom caching instead of the client's default InMemoryConfigCache,
31
+ # You can provide an implementation of ConfigCache.
32
+ # :param base_url: You can set a base_url if you want to use a proxy server between your application and ConfigCat
33
+ #
34
+ if api_key === nil
35
+ raise ConfigCatClientException, "API Key is required."
36
+ end
37
+ if poll_interval_seconds < 1
38
+ poll_interval_seconds = 1
39
+ end
40
+ if max_init_wait_time_seconds < 0
41
+ max_init_wait_time_seconds = 0
42
+ end
43
+ return ConfigCatClient.new(api_key, poll_interval_seconds, max_init_wait_time_seconds, on_configuration_changed_callback, 0, config_cache_class, base_url)
44
+ end
45
+
46
+ def ConfigCat.create_client_with_lazy_load(api_key, cache_time_to_live_seconds: 60, config_cache_class: nil, base_url: nil)
47
+ #
48
+ # Create an instance of ConfigCatClient and setup Lazy Load mode with custom options
49
+ #
50
+ # :param api_key: ConfigCat ApiKey to access your configuration.
51
+ # :param cache_time_to_live_seconds: The cache TTL.
52
+ # :param config_cache_class: If you want to use custom caching instead of the client's default InMemoryConfigCache,
53
+ # You can provide an implementation of ConfigCache.
54
+ # :param base_url: You can set a base_url if you want to use a proxy server between your application and ConfigCat
55
+ #
56
+ if api_key === nil
57
+ raise ConfigCatClientException, "API Key is required."
58
+ end
59
+ if cache_time_to_live_seconds < 1
60
+ cache_time_to_live_seconds = 1
61
+ end
62
+ return ConfigCatClient.new(api_key, 0, 0, nil, cache_time_to_live_seconds, config_cache_class, base_url)
63
+ end
64
+
65
+ def ConfigCat.create_client_with_manual_poll(api_key, config_cache_class: nil, base_url: nil)
66
+ #
67
+ # Create an instance of ConfigCatClient and setup Manual Poll mode with custom options
68
+ #
69
+ # :param api_key: ConfigCat ApiKey to access your configuration.
70
+ # :param config_cache_class: If you want to use custom caching instead of the client's default InMemoryConfigCache,
71
+ # You can provide an implementation of ConfigCache.
72
+ # :param base_url: You can set a base_url if you want to use a proxy server between your application and ConfigCat
73
+ #
74
+ if api_key === nil
75
+ raise ConfigCatClientException, "API Key is required."
76
+ end
77
+ return ConfigCatClient.new(api_key, 0, 0, nil, 0, config_cache_class, base_url)
78
+ end
79
+
80
+ end
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: configcat
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - ConfigCat
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-10-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: concurrent-ruby
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: ConfigCat is a configuration as a service that lets you manage your features
56
+ and configurations without actually deploying new code.
57
+ email:
58
+ - developer@configcat.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - lib/configcat.rb
64
+ - lib/configcat/autopollingcachepolicy.rb
65
+ - lib/configcat/configcache.rb
66
+ - lib/configcat/configcatclient.rb
67
+ - lib/configcat/configfetcher.rb
68
+ - lib/configcat/interfaces.rb
69
+ - lib/configcat/lazyloadingcachepolicy.rb
70
+ - lib/configcat/manualpollingcachepolicy.rb
71
+ - lib/configcat/rolloutevaluator.rb
72
+ - lib/configcat/user.rb
73
+ - lib/configcat/version.rb
74
+ homepage: https://configcat.com
75
+ licenses:
76
+ - MIT
77
+ metadata: {}
78
+ post_install_message:
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - "~>"
85
+ - !ruby/object:Gem::Version
86
+ version: '2.2'
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ requirements: []
93
+ rubyforge_project:
94
+ rubygems_version: 2.7.3
95
+ signing_key:
96
+ specification_version: 4
97
+ summary: ConfigCat SDK for Ruby.
98
+ test_files: []