# frozen_string_literal: true require_relative "objects/version" require 'redis-client' require 'json' require 'ruby-duration' require 'amatch' require 'ap' module VK @@XX = {} def self.included(x) x.extend VK end def self.extended(x) ## # Example: # class ExmpleObject # include VK # ## Add Object Containers by Value Type: # value :myValue # counter :myCounter # hashkey :myHashKey # sortedset :mySortedSet # set :mySet # queue :myQueue # place :myPlace # toggle :myToggle # ticker :myTicker # entry :myEntry # ## Add an Object Container With an Implicit Expiration: # value :myExpiringValue, ttl: (seconds to live without interaction) # # def initialize k # @id = k # end # end ## # Create the Object: # @obj = ExampleObject.new('object id...') ## # For all object methods: # @obj.myObjectContainer.expire(seconds) # @obj.myObjectContainer.delete! # xx = x.name.gsub("::", "-") ## # Object Method Types: ## # A String Value ## # value :myValue # @obj.myValue # @obj.myValue.exist? # @obj.myValue.value = "my value" # @obj.myValue.value => "my value" define_method(:value) { |k, h={}| define_method(k.to_sym) { VALUE.new(%[#{xx}:value:#{k}:#{@id}], h) } }; ## # A Number Value ## # counter :myCounter # @obj.myCounter # @obj.myCounter.exist? # @obj.myCounter.value = number # @obj.myCounter.value => number # @obj.myCounter.incr number # @obj.myCounter.decr number # define_method(:counter) { |k, h={}| define_method(k.to_sym) { COUNTER.new(%[#{xx}:counter:#{k}:#{@id}], h); } }; ## # An Epoch Value ## # timestamp :myTimestamp # @obj.myTimestamp # @obj.myTimestamp.exist? # @obj.myTimestamp.value! # @obj.myTimestamp.value => epoch # @obj.myTimestamp.ago => Seconds since epoch # @obj.myTimestamp.to_time => Time object define_method(:timestamp) { |k, h={}| define_method(k.to_sym) { TIMESTAMP.new(%[#{xx}:timestamp:#{k}:#{@id}], h) } } ## # A Hash Value ## # hashkey :myHashKey # @obj.myHashKey # @obj.myHashKey[:key] = value # @obj.myHashKey[:key] => "value" # define_method(:hashkey) { |k, h={}| define_method(k.to_sym) { HASH.new(%[#{xx}:hashkey:#{k}:#{@id}], h); } }; ## # A Sorted Set Value ## # sortedset :mySortedSet # @obj.mySortedSet # @obj.mySortedSet[:key] = value # @obj.mySortedSet[:key] => ... # @obj.mySortedSet.value { |key, i| ... } # @obj.mySortedSet.poke key, number # define_method(:sortedset) { |k, h={}| define_method(k.to_sym) { SORTEDSET.new(%[#{xx}:sortedset:#{k}:#{@id}], h); } }; ## # A Collection of Values ## # set :mySet # @obj.mySet # @obj.mySet << "x" # @obj.myset.rm "x" # @obj.mySet & @obj.otherSet # @obj.mySet | @obj.otherSet # @obj.myset["pattern"] # @obj.mySet.value { |key, i| ... } # define_method(:set) { |k, h={}| define_method(k.to_sym) { SET.new(%[#{xx}:set:#{k}:#{@id}], h); } }; ## # A List of Values ## # queue :myQueue # @obj.myQueue # @obj.myQueue << "x" # @obj.myQueue.front => "x" and pop # @obj.myQueue.value { |key, i| ... } # define_method(:queue) { |k, h={}| define_method(k.to_sym) { QUEUE.new(%[#{xx}:queue:#{k}:#{@id}], h); } }; ## # A Collection of Places ## # place :myPlace # @obj.myPlace # @obj.myPlace.add "key", longitude, latitude # @obj.myPlace["key"] => { longitude: xx, latitude: yy } # @obj.myPlace.distance "key", "other key" # @obj.myPlace.radius longitude, latitude, distance # @obj.myPlace.value { |key, i| ... } # define_method(:place) { |k, h={}| define_method(k.to_sym) { PLACE.new(%[#{xx}:place:#{k}:#{@id}], h); } }; ## # A Boolean Value ## # toggle :myToggle # @obj.myToggle # @obj.myToggle.exist? # @obj.myToggle.value = bool # @obj.myToggle.value => ... # @obj.myToggle.value! => value = !value # define_method(:toggle) { |k, h={}| define_method(k.to_sym) { TOGGLE.new(%[#{xx}:toggle:#{k}:#{@id}], h); } }; ## # A Sorted Hash of Values ## # ticker :myTicker # @obj.myTicker # @obj.myTicker[:key] = value # @obj.myTicker[:key] => "value" # @obj.myticker.value { |i,e| ... } define_method(:ticker) { |k, h={}| define_method(k.to_sym) { SORTEDHASH.new(%[#{xx}:ticker:#{k}:#{@id}], h); } }; ## # A List of Hashes ## # entry :myEntry # @obj.myEntry # @obj.myEntry << { key: 'value', ... } # @obj.myEntry.value { |i,e| ... } define_method(:entry) { |k, h={}| define_method(k.to_sym) { HASHLIST.new(%[#{xx}:entry:#{k}:#{@id}], h); } }; ## # A list of Strings ## # vector :myVector # @obj.myVector # @obj.myVector << "An Entry of Text." # @obj.myVector.value { |i,e| ... } # @obj.myvector[0] = "An Entry of Text." define_method(:vector) { |k, h={}| define_method(k.to_sym) { VECTOR.new(%[#{xx}:vector:#{k}:#{@id}], h); } }; end def id @id end module AGO class Error < StandardError; end class Clock def initialize t @t = AGO.now @s = @t.to_i - t.to_i @t = Time.new(t.to_i).utc @d = Duration.new(@s.abs) end def to_i @s end def to_s *s if s[0] @d.format(s[0]) else @d.format('%w %~w %d %~d %H:%M:%S') end end end def self.now Time.now.utc end def self.[] k Clock.new(k) end end def self.at epoch Time.at epoch end def self.clock *t if t[0] AGO[t[0]] else AGO.now end end def self.redis RedisClient.config(host: "127.0.0.1", port: 6379, db: 0).new_client end class O attr_reader :key def initialize k, h={} @key = k @opts = h if @opts.has_key?(:ttl) expire @opts[:ttl] end end def delete! VK.redis.call("DEL", key); end def expire sec VK.redis.call("EXPIRE", key, sec); end end class TIMESTAMP < O def value x = VK.redis.call("GET", key).to_i; if @opts.has_key?(:flush) == true delete! end return x end def value! VK.redis.call("SET", key, "#{VK.clock.to_i}"); end def exist? VK.redis.call("GET", key) ? true : false end def ago VK.clock.to_i - value; end def to_time Time.at(value); end end class TOGGLE < O def value VK.redis.call("GET", key) == 'true' ? true : false if @opts.has_key?(:flush) == true delete! end end def exist? VK.redis.call("GET", key) ? true : false end def value= x VK.redis.call("SET", key, "#{x.to_s}") end def value! if self.value self.value = false else self.value = true end end end class VALUE < O def value VK.redis.call("GET", key) if @opts.has_key?(:flush) == true delete! end end def value= x VK.redis.call("SET", key, x) end def exist? VK.redis.call("GET", key) ? true : false end def match r, &b m = Regexp.new(r).match(value) if block_given? b.call(m) else return m end end end class VECTOR < O include Amatch def value &b a = [] VK.redis.call("LRANGE", key, 0, -1).each_with_index { |e, i| if block_given? a << b.call(i, VK.redis.call("GET", e)) else a << VK.redis.call("GET", e) end if @opts.has_key?(:flush) == true VK.redis.call("DEL", e); end } if @opts.has_key?(:flush) == true delete! end return a end def [] k VK.redis.call("GET", "#{@key}-#{k}"); end def << i kk = %[#{@key}-#{VK.redis.call("LLEN",@key)}] VK.redis.call("SET", kk, i); VK.redis.call("RPUSH", key, kk) end def nearest p h = {} value { |i,v| h[i] = { value: v, levenshtein: p.levenshtein_similar(v), damerau: p.damerau_levenshtein_similar(v), hamming: p.hamming_similar(v), distance: p.pair_distance_similar(v), subsequence: p.longest_subsequence_similar(v), substring: p.longest_substring_similar(v), jaro: p.jaro_similar(v), winkler: p.jarowinkler_similar(v) } } return h end end class COUNTER < O def incr n VK.redis.call("SET", key, value + n.to_f) end def decr n VK.redis.call("SET", key, value + n.to_f) end def value VK.redis.call("GET", key).to_f if @opts.has_key?(:flush) == true delete! end end def value= n VK.redis.call("SET", key, n.to_f) end def exist? VK.redis.call("GET", key) ? true : false end end class HASH < O def [] k VK.redis.call("HGET", key, k); end def []= k,v VK.redis.call("HSET", key, k, v); end def to_h VK.redis.call("HGETALL", key); end end class QUEUE < O def value &b VK.redis.call("LRANGE", key, 0, -1).each_with_index { |e, i| b.call(i, e) } if @opts.has_key?(:flush) == true delete! end end def length VK.redis.call("LLEN", key) end def << i VK.redis.call("RPUSH", key, i) end def front VK.redis.call("LPOP", key) end end class SORTEDSET < O def value &b VK.redis.call("ZREVRANGE", key, 0, -1, 'WITHSCORES').each_with_index { |e, i| b.call(i, e) } if @opts.has_key?(:flush) == true delete! end end def [] k VK.redis.call("ZSCORE", key, k).to_f; end def []= k,v VK.redis.call("ZADD", key, v, k).to_f; end def poke k, n VK.redis.call("ZINCRBY", key, n.to_f, k); end end class SET < O def value &b a = Set.new VK.redis.call("SMEMBERS", key).each_with_index { |e, i| if block_given? a << b.call(i, e) else a << e end } if @opts.has_key?(:flush) == true delete! end return a end def include? k if VK.redis.call("SMISMEMBER", key, k)[0] == 0 return false else return true end end def length VK.redis.call("SCARD", key) end def << i VK.redis.call("SADD", key, i) end def rm i VK.redis.call("SREM", key, i) end def & k VK.redis.call("SINTER", key, k.key) end def | k VK.redis.call("SUNION", key, k.key) end def [] k r, h = Regexp.new(k), {} VK.redis.call("SMEMBERS", key).each { |e| if m = r.match(e); h[e] = m; end; } return h end end class PLACE < O def value &b a = [] VK.redis.call("ZRANGE", key, 0, -1).each_with_index { |e, i| if block_given? a << b.call(i, e) else a << e end }; if @opts.has_key?(:flush) == true delete! end return a end def add i, lon, lat VK.redis.call("GEOADD", key, lon, lat, i) end def [] i x = VK.redis.call("GEOPOS", key, i)[0]; return { longitude: x[0], latitude: x[1] } end def distance a, b VK.redis.call("GEODIST", key, a, b, 'm').to_f; end def radius lon, lat, r h = {} VK.redis.call("GEORADIUS", key, lon, lat, r, 'm', 'WITHDIST').each { |e| h[e[0]] = e[1].to_f }; return h end end class SORTEDHASH < O def value &b VK.redis.call("ZREVRANGE", key, 0, -1, 'WITHSCORES').each_with_index { |e, i| kx = %[#{@key}-#{e[0]}] a = [] if block_given? b.call(i, { key: e[0], value: VK.redis.call("GET", kx), score: e[1] } ) else a << { key: e[0], value: VK.redis.call("GET", kx), score: e[1] } end if @opts.has_key?(:flush) == true VK.redis.call("DEL", kx) end } if @opts.has_key?(:flush) == true delete! end return a end def [] k kx = %[#{@key}-#{k}] VK.redis.call("GET", kx) end def []= k, v kx = %[#{@key}-#{k}] VK.redis.call("SET", kx, v) VK.redis.call("ZINCRBY", key, 1, k) end end class HASHLIST < O def value &b a = [] VK.redis.call("LRANGE", key, 0, -1).each_with_index { |e, i| if block_given? a << b.call(i, JSON.parse(VK.redis.call("GET", e))) else a << JSON.parse(VK.redis.call("GET", e)) end if @opts.has_key?(:flush) == true VK.redis.call("DEL", e) end } if @opts.has_key?(:flush) == true delete! end return a end def length VK.redis.call("LLEN", key) end def [] k hx = %[#{key}-#{k}] JSON.parse(VK.redis.call("GET", hx)); end def push h={} hx = %[#{key}-#{length}] VK.redis.call("SET", hx, JSON.generate(h)); VK.redis.call("RPUSH", key, hx) end end def self.flushdb! VK.redis.call("FLUSHDB") end def self.[] k VK.redis.call("KEYS", k) end end