redis-prescription 1.0.0 → 2.2.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e4c70dac256ca079b9af76b03cefc189db30507860c0a6816ddd519da7bdd75a
4
- data.tar.gz: a1c5e2fe3645bd592f26a00a16accf2be1d0340468c4f824e06c7da51f1d29eb
3
+ metadata.gz: c503c680688ac288e5d3b9e59314c20055c10239b31733de6855f6a78ed90d3e
4
+ data.tar.gz: f0a2373d51deb922ac02bb8283e05cb583dc3578f95ea8f4923bba2d1a3058e1
5
5
  SHA512:
6
- metadata.gz: a1f3988eb35db89865af75881b7bd0e33cb4a922fa468589f8f9de3396ac88118405b400ac8999995648ed92a224f768f2fc88a19d3a75d6c54145c9f01488b1
7
- data.tar.gz: 19a72258d2adf1bef91b436965ebcea477a886cc4c9e269e81f5cffe489b406811f9fc7c46088bfc02071c720fecc016c819b48e297bd8b602e6ce11d21d31fd
6
+ metadata.gz: 516e413b8da8d7dace049f9169c307d3a87b1ee52ecdc759368f3012d2d51e6b44732d38a2980635bc5acb1015436960c48fe17239edd734101feda83d18c503
7
+ data.tar.gz: 2c07386fbc7fb53a0a8093df08a35ed6d366a37084df8823cc49685cf95034e5599f7aeacb2fd502ebef6d4f0654f244988e86bd7bd26bfd30c3f0f7741d079b
data/LICENSE.txt CHANGED
@@ -1,6 +1,7 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2018 Alexey Zapparov
3
+ Copyright (c) 2020-2023 Alexey Zapparov
4
+ Copyright (c) 2018 SensorTower Inc.
4
5
 
5
6
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
7
  of this software and associated documentation files (the "Software"), to deal
@@ -1,48 +1,56 @@
1
- # Redis::Prescription
1
+ = Redis::Prescription
2
2
 
3
3
  Redis LUA stored procedure runner. Preloads (and reloads when needed, e.g. when
4
4
  scripts were flushed away) script and then runs it with `EVALSHA`.
5
5
 
6
6
 
7
- ## Installation
7
+ == Installation
8
8
 
9
9
  Add this line to your application's Gemfile:
10
10
 
11
- ```ruby
12
- gem "redis-prescription"
13
- ```
14
-
15
- And then execute:
16
-
17
- $ bundle
11
+ $ bundle add redis-prescription
18
12
 
19
13
  Or install it yourself as:
20
14
 
21
15
  $ gem install redis-prescription
22
16
 
23
17
 
24
- ## Usage
18
+ == Usage
25
19
 
26
- ``` ruby
27
- script = Redis::Prescription.new <<~LUA
20
+ [source,ruby]
21
+ ----
22
+ script = RedisPrescription.new <<~LUA
28
23
  return tonumber(redis.call('GET', KEYS[1]) or 42)
29
24
  LUA
30
25
 
31
- script.eval(Redis.current, :xxx) # => 42
26
+ redis = Redis.new
27
+ script.call(redis, keys: [:xxx]) # => 42
32
28
 
33
- Redis.current.set(:xxx, 123)
34
- script.eval(Redis.current, :xxx) # => 123
35
- ```
29
+ redis.set(:xxx, 123)
30
+ script.call(redis, keys: [:xxx]) # => 123
31
+ ----
36
32
 
37
33
 
38
- ## Supported Ruby Versions
34
+ == Supported Ruby Versions
39
35
 
40
- This library aims to support and is [tested against][1] the following Ruby
41
- versions:
36
+ This library aims to support and is tested against:
42
37
 
43
- * Ruby 2.3.x
44
- * Ruby 2.4.x
45
- * Ruby 2.5.x
38
+ * https://www.ruby-lang.org[Ruby]
39
+ ** MRI 3.0.x
40
+ ** MRI 3.1.x
41
+ * https://redis.io[Redis Server]
42
+ ** 6.2.x
43
+ ** 7.0.x
44
+ * https://github.com/redis/redis-rb[redis-rb]
45
+ ** 4.7.x
46
+ ** 4.8.x
47
+ ** 5.0.x
48
+ * https://github.com/resque/redis-namespace[redis-namespace]
49
+ ** 1.10.x
50
+ * https://github.com/redis-rb/redis-client[redis-client]
51
+ ** 0.12.x
52
+ ** 0.13.x
53
+ ** 0.14.x
46
54
 
47
55
  If something doesn't work on one of these versions, it's a bug.
48
56
 
@@ -57,33 +65,27 @@ patches in a timely fashion. If critical issues for a particular implementation
57
65
  exist at the time of a major release, support for that Ruby version may be
58
66
  dropped.
59
67
 
68
+ The same applies to *Redis Server*, *redis-rb*, *redis-namespace*,
69
+ and *redis-client* support.
70
+
71
+
72
+ == Similar Projects
73
+
74
+ * https://github.com/Shopify/wolverine
60
75
 
61
- ## Development
62
76
 
63
- After checking out the repo, run `bundle install` to install dependencies.
64
- Then, run `bundle exec rake spec` to run the tests with ruby-rb client.
77
+ == Development
65
78
 
66
- To install this gem onto your local machine, run `bundle exec rake install`.
67
- To release a new version, update the version number in `version.rb`, and then
68
- run `bundle exec rake release`, which will create a git tag for the version,
69
- push git commits and tags, and push the `.gem` file to [rubygems.org][2].
79
+ scripts/update-gemfiles
80
+ scripts/run-rspec
81
+ bundle exec rubocop
70
82
 
71
83
 
72
- ## Contributing
84
+ == Contributing
73
85
 
74
- * Fork sidekiq-throttled on GitHub
86
+ * Fork redis-prescription
75
87
  * Make your changes
76
88
  * Ensure all tests pass (`bundle exec rake`)
77
- * Send a pull request
89
+ * Send a merge request
78
90
  * If we like them we'll merge them
79
91
  * If we've accepted a patch, feel free to ask for commit access!
80
-
81
-
82
- ## Copyright
83
-
84
- Copyright (c) 2018 SensorTower Inc.
85
- See LICENSE.md for further details.
86
-
87
-
88
- [1]: http://travis-ci.org/sensortower/redis-prescription
89
- [2]: https://rubygems.org
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "redis_prescription"
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../errors"
4
+
5
+ class RedisPrescription
6
+ module Adapters
7
+ # redis-rb adapter
8
+ class Redis
9
+ def self.adapts?(redis)
10
+ defined?(::Redis) && redis.is_a?(::Redis)
11
+ end
12
+
13
+ def initialize(redis)
14
+ @redis = redis
15
+ end
16
+
17
+ def adapter_name
18
+ "redis"
19
+ end
20
+
21
+ def eval(script, keys, argv)
22
+ @redis.eval(script, keys, argv)
23
+ rescue ::Redis::CommandError => e
24
+ raise CommandError, e.message
25
+ end
26
+
27
+ def evalsha(digest, keys, argv)
28
+ @redis.evalsha(digest, keys, argv)
29
+ rescue ::Redis::CommandError => e
30
+ raise CommandError, e.message
31
+ end
32
+
33
+ def purge!
34
+ @redis.script("flush")
35
+ @redis.flushdb
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../errors"
4
+
5
+ class RedisPrescription
6
+ module Adapters
7
+ # redis-client adapter
8
+ class RedisClient
9
+ def self.adapts?(redis)
10
+ return true if defined?(::RedisClient) && redis.is_a?(::RedisClient)
11
+ return true if defined?(::RedisClient::Decorator::Client) && redis.is_a?(::RedisClient::Decorator::Client)
12
+
13
+ false
14
+ end
15
+
16
+ def initialize(redis)
17
+ @redis = redis
18
+ end
19
+
20
+ def adapter_name
21
+ "redis-client"
22
+ end
23
+
24
+ def eval(script, keys, argv)
25
+ @redis.call("EVAL", script, keys.size, *keys, *argv)
26
+ rescue ::RedisClient::CommandError => e
27
+ raise CommandError, e.message
28
+ end
29
+
30
+ def evalsha(digest, keys, argv)
31
+ @redis.call("EVALSHA", digest, keys.size, *keys, *argv)
32
+ rescue ::RedisClient::CommandError => e
33
+ raise CommandError, e.message
34
+ end
35
+
36
+ def purge!
37
+ @redis.call("SCRIPT", "FLUSH")
38
+ @redis.call("FLUSHDB")
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../errors"
4
+
5
+ class RedisPrescription
6
+ module Adapters
7
+ # redis-namespace adapter
8
+ class RedisNamespace
9
+ def self.adapts?(redis)
10
+ defined?(::Redis::Namespace) && redis.is_a?(::Redis::Namespace)
11
+ end
12
+
13
+ def initialize(redis)
14
+ @redis = redis
15
+ end
16
+
17
+ def adapter_name
18
+ "redis-namespace"
19
+ end
20
+
21
+ def eval(script, keys, argv)
22
+ @redis.eval(script, keys, argv)
23
+ rescue ::Redis::CommandError => e
24
+ raise CommandError, e.message
25
+ end
26
+
27
+ def evalsha(digest, keys, argv)
28
+ @redis.evalsha(digest, keys, argv)
29
+ rescue ::Redis::CommandError => e
30
+ raise CommandError, e.message
31
+ end
32
+
33
+ def purge!
34
+ @redis.redis.script("flush")
35
+ @redis.redis.flushdb
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./adapters/redis"
4
+ require_relative "./adapters/redis_client"
5
+ require_relative "./adapters/redis_namespace"
6
+
7
+ class RedisPrescription
8
+ # @api internal
9
+ module Adapters
10
+ class << self
11
+ def [](redis)
12
+ return Adapters::Redis.new(redis) if Adapters::Redis.adapts?(redis)
13
+ return Adapters::RedisClient.new(redis) if Adapters::RedisClient.adapts?(redis)
14
+ return Adapters::RedisNamespace.new(redis) if Adapters::RedisNamespace.adapts?(redis)
15
+
16
+ raise TypeError, "Unsupported redis client: #{redis.class}"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RedisPrescription
4
+ # Top-level error class for all RedisPrescription errors
5
+ class Error < StandardError; end
6
+
7
+ # Redis command error wrapper, `#cause` will be `Redis::CommandError` or
8
+ # `RedisClient::CommandError`.
9
+ class CommandError < Error; end
10
+
11
+ # Lua script eval/evalsha failure
12
+ class ScriptError < Error
13
+ # rubocop:disable Layout/LineLength
14
+ LUA_ERROR_MESSAGE = %r{
15
+ # * Error compiling script:
16
+ # ** Redis 6.0, 6.2, 7.0
17
+ # *** ERR Error compiling script (new function): user_script:7: unexpected symbol near '!'
18
+ # * Error running script:
19
+ # ** Redis 6.0, 6.2
20
+ # *** ERR Error running script (call to f_64203334c42d5690c2d008a78aa7789f5b83e5bb): @user_script:4: user_script:4: attempt to perform arithmetic on a string value
21
+ # ** Redis 7.0
22
+ # *** ERR user_script:4: attempt to perform arithmetic on a string value script: 64203334c42d5690c2d008a78aa7789f5b83e5bb, on @user_script:4.
23
+ \A
24
+ ERR\s
25
+ (?:
26
+ Error\scompiling\sscript\s\([^)]+\):\s # Redis 6.0, 6.2, 7.0
27
+ .+:(?<loc>\d+):\s # Redis 6.0, 6.2, 7.0
28
+ (?<message>.+) # Redis 6.0, 6.2, 7.0
29
+ |
30
+ (?:Error\srunning\sscript\s\([^)]+\):\s@\S+\s)? # Redis 6.0, 6.2
31
+ .+:(?<loc>\d+):\s # Redis 6.0, 6.2, 7.0
32
+ (?<message>.+?) # Redis 6.0, 6.2, 7.0
33
+ (?::\s\h+,\son\s@[^:]+:\d+\.)? # Redis 7.0
34
+ )
35
+ \z
36
+ }x
37
+ private_constant :LUA_ERROR_MESSAGE
38
+ # rubocop:enable Layout/LineLength
39
+
40
+ # Lua script source
41
+ #
42
+ # @return [String]
43
+ attr_reader :source
44
+
45
+ # Line of code where error was encountered
46
+ #
47
+ # @return [Integer?]
48
+ attr_reader :loc
49
+
50
+ # @param message [String]
51
+ # @param source [#to_s]
52
+ def initialize(message, source)
53
+ @source = -source.to_s
54
+
55
+ if (parsed = LUA_ERROR_MESSAGE.match(message))
56
+ @loc = parsed[:loc].to_i
57
+ message = [parsed[:message], excerpt(@source, @loc)].compact.join("\n\n")
58
+ end
59
+
60
+ super(message)
61
+ end
62
+
63
+ private
64
+
65
+ def excerpt(source, loc)
66
+ lines = excerpt_lines(source, loc)
67
+ gutter = lines.map(&:first).max.to_s.length
68
+
69
+ lines.map! do |(pos, line)|
70
+ format(pos == loc ? "\t%#{gutter}d > %s" : "\t%#{gutter}d | %s", pos, line).rstrip
71
+ end
72
+
73
+ lines.join("\n")
74
+ rescue => e
75
+ warn "Failed extracting source excerpt: #{e.message}"
76
+ nil
77
+ end
78
+
79
+ def excerpt_lines(source, loc)
80
+ lines = source.lines
81
+ pos = loc - 1 # reported line of code is 1-offset
82
+ min = pos - 2 # 2 lines of head context
83
+ max = pos + 2 # 2 lines of tail context
84
+
85
+ min = 0 if min.negative?
86
+ max = lines.size - 1 if lines.size <= max
87
+
88
+ (min..max).map { |i| [i.succ, lines[i]] }
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RedisPrescription
4
+ # Gem version.
5
+ VERSION = "2.2.0"
6
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./redis_prescription/adapters"
4
+ require_relative "./redis_prescription/errors"
5
+ require_relative "./redis_prescription/version"
6
+
7
+ # Lua script executor for redis.
8
+ #
9
+ # Instead of executing script with `EVAL` everytime - loads script once
10
+ # and then runs it with `EVALSHA`.
11
+ #
12
+ # @example Usage
13
+ #
14
+ # redis = Redis.new
15
+ # script = RedisPrescription.new("return ARGV[1] + ARGV[2]")
16
+ # script.call(redis, argv: [2, 2]) # => 4
17
+ class RedisPrescription
18
+ # Redis error fired when script ID is unkown.
19
+ NOSCRIPT = "NOSCRIPT"
20
+ private_constant :NOSCRIPT
21
+
22
+ EMPTY_LIST = [].freeze
23
+ private_constant :EMPTY_LIST
24
+
25
+ # Lua script source.
26
+ # @return [String]
27
+ attr_reader :source
28
+
29
+ # Lua script SHA1 digest.
30
+ # @return [String]
31
+ attr_reader :digest
32
+
33
+ # @param source [#to_s] Lua script
34
+ def initialize(source)
35
+ @source = -source.to_s
36
+ @digest = Digest::SHA1.hexdigest(@source).freeze
37
+ end
38
+
39
+ # Executes script and return result of execution.
40
+ # @param redis [Redis, RedisClient]
41
+ # @param keys [Array] keys to pass to the script
42
+ # @param argv [Array] arguments to pass to the script
43
+ # @raise [TypeError] if given redis client is not supported
44
+ # @raise [ScriptError] if script execution failed
45
+ # @return depends on the script
46
+ def call(redis, keys: EMPTY_LIST, argv: EMPTY_LIST)
47
+ evalsha_with_fallback(Adapters[redis], keys, argv)
48
+ rescue CommandError => e
49
+ raise ScriptError.new(e.message, @source)
50
+ end
51
+
52
+ private
53
+
54
+ def evalsha_with_fallback(redis, keys, argv)
55
+ redis.evalsha(@digest, keys, argv)
56
+ rescue CommandError => e
57
+ raise unless e.message.include?(NOSCRIPT)
58
+
59
+ redis.eval(@source, keys, argv)
60
+ end
61
+ end
metadata CHANGED
@@ -1,56 +1,44 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis-prescription
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexey Zapparov
8
- autorequire:
9
- bindir: bin
8
+ autorequire:
9
+ bindir: exe
10
10
  cert_chain: []
11
- date: 2018-02-11 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: bundler
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '1.16'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '1.16'
11
+ date: 2023-04-05 00:00:00.000000000 Z
12
+ dependencies: []
27
13
  description: |
28
- Preloads (and reloads when needed, e.g. when scripts
29
- were flushed away) script and then runs it with `EVALSHA`.
14
+ Preloads (and reloads when needed, e.g. when scripts were flushed away)
15
+ script and then runs it with `EVALSHA`.
30
16
  email:
31
- - ixti@member.fsf.org
17
+ - alexey@zapparov.com
32
18
  executables: []
33
19
  extensions: []
34
20
  extra_rdoc_files: []
35
21
  files:
36
- - ".gitignore"
37
- - ".rspec"
38
- - ".rubocop.yml"
39
- - ".travis.yml"
40
- - ".yardopts"
41
- - Gemfile
42
- - Guardfile
43
22
  - LICENSE.txt
44
- - README.md
45
- - Rakefile
46
- - lib/redis/prescription.rb
47
- - lib/redis/prescription/version.rb
48
- - redis-prescription.gemspec
49
- homepage: https://github.com/ixti/redis-prescription
23
+ - README.adoc
24
+ - lib/redis-prescription.rb
25
+ - lib/redis_prescription.rb
26
+ - lib/redis_prescription/adapters.rb
27
+ - lib/redis_prescription/adapters/redis.rb
28
+ - lib/redis_prescription/adapters/redis_client.rb
29
+ - lib/redis_prescription/adapters/redis_namespace.rb
30
+ - lib/redis_prescription/errors.rb
31
+ - lib/redis_prescription/version.rb
32
+ homepage: https://gitlab.com/ixti/redis-prescription
50
33
  licenses:
51
34
  - MIT
52
- metadata: {}
53
- post_install_message:
35
+ metadata:
36
+ homepage_uri: https://gitlab.com/ixti/redis-prescription
37
+ source_code_uri: https://gitlab.com/ixti/redis-prescription
38
+ bug_tracker_uri: https://gitlab.com/ixti/redis-prescription/issues
39
+ changelog_uri: https://gitlab.com/ixti/redis-prescription/blob/v2.2.0/CHANGES.md
40
+ rubygems_mfa_required: 'true'
41
+ post_install_message:
54
42
  rdoc_options: []
55
43
  require_paths:
56
44
  - lib
@@ -58,16 +46,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
58
46
  requirements:
59
47
  - - ">="
60
48
  - !ruby/object:Gem::Version
61
- version: '0'
49
+ version: '3.0'
62
50
  required_rubygems_version: !ruby/object:Gem::Requirement
63
51
  requirements:
64
52
  - - ">="
65
53
  - !ruby/object:Gem::Version
66
54
  version: '0'
67
55
  requirements: []
68
- rubyforge_project:
69
- rubygems_version: 2.7.3
70
- signing_key:
56
+ rubygems_version: 3.3.26
57
+ signing_key:
71
58
  specification_version: 4
72
59
  summary: Redis LUA stored procedure runner.
73
60
  test_files: []
data/.gitignore DELETED
@@ -1,10 +0,0 @@
1
- /Gemfile.lock
2
-
3
- /.autoenv.zsh
4
- /.bundle
5
- /.yardoc
6
- /_yardoc
7
- /coverage
8
- /doc
9
- /pkg
10
- /tmp
data/.rspec DELETED
@@ -1 +0,0 @@
1
- --require spec_helper
data/.rubocop.yml DELETED
@@ -1,40 +0,0 @@
1
- AllCops:
2
- TargetRubyVersion: 2.3
3
- DisplayCopNames: true
4
-
5
-
6
- ## Layout ######################################################################
7
-
8
- Layout/DotPosition:
9
- EnforcedStyle: trailing
10
-
11
- Layout/IndentArray:
12
- EnforcedStyle: consistent
13
-
14
-
15
- ## Metrics #####################################################################
16
-
17
- Metrics/BlockLength:
18
- Exclude:
19
- - "spec/**/*"
20
-
21
-
22
- ## Style #######################################################################
23
-
24
- Style/HashSyntax:
25
- EnforcedStyle: hash_rockets
26
-
27
- Style/RegexpLiteral:
28
- EnforcedStyle: percent_r
29
-
30
- Style/RescueStandardError:
31
- EnforcedStyle: implicit
32
-
33
- Style/SafeNavigation:
34
- Enabled: false
35
-
36
- Style/StringLiterals:
37
- EnforcedStyle: double_quotes
38
-
39
- Style/YodaCondition:
40
- Enabled: false
data/.travis.yml DELETED
@@ -1,28 +0,0 @@
1
- language: ruby
2
- sudo: false
3
-
4
- services:
5
- - redis-server
6
-
7
- cache: bundler
8
-
9
- rvm:
10
- - 2.3
11
- - 2.4
12
- - 2.5
13
-
14
- matrix:
15
- fast_finish: true
16
- include:
17
- - rvm: 2.4
18
- env: TEST_SUITE="rubocop"
19
-
20
- before_install:
21
- - gem update --system
22
- - gem --version
23
- - gem install bundler --no-rdoc --no-ri
24
- - bundle --version
25
-
26
- install: bundle install --without development doc
27
-
28
- script: bundle exec rake $TEST_SUITE
data/.yardopts DELETED
@@ -1,2 +0,0 @@
1
- --markup-provider=redcarpet
2
- --markup=markdown
data/Gemfile DELETED
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source "https://rubygems.org"
4
- ruby RUBY_VERSION
5
-
6
- gem "rake"
7
- gem "rspec"
8
- gem "rubocop", "~> 0.52.0", :require => false
9
-
10
- gem "redis"
11
- gem "redis-namespace"
12
-
13
- group :development do
14
- gem "guard", :require => false
15
- gem "guard-rspec", :require => false
16
- gem "guard-rubocop", :require => false
17
- gem "pry", :require => false
18
- end
19
-
20
- group :test do
21
- gem "codecov", :require => false
22
- gem "simplecov", :require => false
23
- end
24
-
25
- group :doc do
26
- gem "redcarpet"
27
- gem "yard"
28
- end
29
-
30
- # Specify your gem's dependencies in redis-prescription.gemspec
31
- gemspec
data/Guardfile DELETED
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- guard :rspec, :cmd => "bundle exec rspec" do
4
- require "guard/rspec/dsl"
5
- dsl = Guard::RSpec::Dsl.new(self)
6
-
7
- # RSpec files
8
- rspec = dsl.rspec
9
- watch(rspec.spec_helper) { rspec.spec_dir }
10
- watch(rspec.spec_support) { rspec.spec_dir }
11
- watch(rspec.spec_files)
12
-
13
- # Ruby files
14
- ruby = dsl.ruby
15
- dsl.watch_spec_files_for(ruby.lib_files)
16
- end
17
-
18
- guard :rubocop do
19
- watch(%r{.+\.rb$})
20
- watch(%r{(?:.+/)?\.rubocop(?:_todo)?\.yml$}) { |m| File.dirname(m[0]) }
21
- end
data/Rakefile DELETED
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler/gem_tasks"
4
-
5
- require "rspec/core/rake_task"
6
- RSpec::Core::RakeTask.new
7
-
8
- require "rubocop/rake_task"
9
- RuboCop::RakeTask.new
10
-
11
- if ENV["CI"]
12
- task :default => :spec
13
- else
14
- task :default => %i[rubocop spec]
15
- end
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Redis
4
- class Prescription
5
- # Gem version.
6
- VERSION = "1.0.0"
7
- end
8
- end
@@ -1,90 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "prescription/version"
4
-
5
- # @see https://github.com/redis/redis-rb
6
- class Redis
7
- # Lua script executor for redis.
8
- #
9
- # Instead of executing script with `EVAL` everytime - loads script once
10
- # and then runs it with `EVALSHA`.
11
- #
12
- # @example Usage
13
- #
14
- # script = Redis::Prescription.new("return ARGV[1] + ARGV[2]")
15
- # script.eval(Redis.current, :argv => [2, 2]) # => 2
16
- class Prescription
17
- # Script load command.
18
- LOAD = "load"
19
- private_constant :LOAD
20
-
21
- # Redis error fired when script ID is unkown.
22
- NOSCRIPT = "NOSCRIPT"
23
- private_constant :NOSCRIPT
24
-
25
- # LUA script source.
26
- # @return [String]
27
- attr_reader :source
28
-
29
- # LUA script SHA1 digest.
30
- # @return [String]
31
- attr_reader :digest
32
-
33
- # @param source [#to_s] Lua script.
34
- def initialize(source)
35
- @source = source.to_s.strip.freeze
36
- @digest = Digest::SHA1.hexdigest(@source).freeze
37
- end
38
-
39
- # Loads script to redis.
40
- # @param redis (see #namespaceless)
41
- # @return [void]
42
- def bootstrap!(redis)
43
- digest = namespaceless(redis).script(LOAD, @source)
44
- return if @digest == digest
45
-
46
- # XXX: this may happen **ONLY** if script digesting will be
47
- # changed in redis, which is not likely gonna happen.
48
- warn "[#{self.class}] Unexpected digest: " \
49
- "#{digest.inspect} (expected: #{@digest.inspect})"
50
-
51
- @digest = digest.freeze
52
- end
53
-
54
- # Executes script and returns result of execution.
55
- # @param redis (see #namespaceless)
56
- # @param keys [Array] keys to pass to the script
57
- # @param argv [Array] arguments to pass to the script
58
- # @return depends on the script
59
- def eval(redis, keys: [], argv: [])
60
- redis.evalsha(@digest, keys, argv)
61
- rescue => e
62
- raise unless e.message.include? NOSCRIPT
63
-
64
- bootstrap!(redis)
65
- redis.evalsha(@digest, keys, argv)
66
- end
67
-
68
- # Reads given file and returns new {Prescription} with its contents.
69
- # @param file [String]
70
- # @return [Prescription]
71
- def self.read(file)
72
- new File.read file
73
- end
74
-
75
- private
76
-
77
- # Yields real namespace-less redis client.
78
- # @param redis [Redis, Redis::Namespace]
79
- # @return [Redis]
80
- def namespaceless(redis)
81
- if redis.is_a?(Redis)
82
- redis
83
- elsif defined?(Redis::Namespace) && redis.is_a?(Redis::Namespace)
84
- redis.redis
85
- else
86
- raise TypeError, "Unsupported redis client type: #{redis.class}"
87
- end
88
- end
89
- end
90
- end
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- lib = File.expand_path("../lib", __FILE__)
4
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
-
6
- require "redis/prescription/version"
7
-
8
- Gem::Specification.new do |spec|
9
- spec.name = "redis-prescription"
10
- spec.version = Redis::Prescription::VERSION
11
- spec.authors = ["Alexey Zapparov"]
12
- spec.email = ["ixti@member.fsf.org"]
13
-
14
- spec.summary = "Redis LUA stored procedure runner."
15
- spec.description = <<~DESCRIPTION
16
- Preloads (and reloads when needed, e.g. when scripts
17
- were flushed away) script and then runs it with `EVALSHA`.
18
- DESCRIPTION
19
-
20
- spec.homepage = "https://github.com/ixti/redis-prescription"
21
- spec.license = "MIT"
22
-
23
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
24
- f.match(%r{^(test|spec|features)/})
25
- end
26
-
27
- spec.require_paths = ["lib"]
28
-
29
- spec.add_development_dependency "bundler", "~> 1.16"
30
- end