frame_tree 0.0.1.pre
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.txt +21 -0
- data/README.md +87 -0
- data/lib/frame_tree.rb +13 -0
- data/lib/frame_tree/branch.rb +74 -0
- data/lib/frame_tree/config.rb +27 -0
- data/lib/frame_tree/counter.rb +37 -0
- data/lib/frame_tree/error.rb +20 -0
- data/lib/frame_tree/frame.rb +120 -0
- data/lib/frame_tree/helper.rb +21 -0
- data/lib/frame_tree/tree.rb +117 -0
- data/lib/frame_tree/version.rb +3 -0
- metadata +110 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8eef4bf9e7de2f6ff90c1998e0d9a9c8b5c1e70bbfa4beef5b7d79aaf70f1cfb
|
4
|
+
data.tar.gz: 6d0187ce44f22ed81c33d35c856211ce029dab995ae22adf3ec8c69a2508d8a1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e67540bd4fc3a030ad06d5b51fe88624e85f71a514902cb8050d762c3eb3446c7356d4b29f31ffb80fa00425f44a5d86a399ca4e3ad4c275aae806a1300e2abc
|
7
|
+
data.tar.gz: e468c128b8e0574583adfc9ccc534712dcb5344edb8f69bde8bd2eda30e14477683aa85ed8d0bf7bbe3ed4627d64db30cf85031d5e7646c182ec36f94be34164
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2021 creadone
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# FrameTree
|
2
|
+
|
3
|
+
It's a prototype of a fast in-memory tree-like structure to store and retrieve values from the counters through a rolling time-frame/window. FrameTree was created to reduce unnecessary database queries and to provide data to a sparkline from ui-component dataGrid in one of my projects. At the moment some features have been hardcoded, in particular the Frame, but in the future this is planned to rewrite more flexible.
|
4
|
+
|
5
|
+
## Features:
|
6
|
+
|
7
|
+
* FT is fast. Batch insert of 50 000 ordered items by 100 in batch took 425 ms and mixed 390 ms., for batch get 390 and 170 ms. accordingly. Update by previously inserted keys (no need to create new objects) took 150 ms. for both.
|
8
|
+
* FT is simple. Only built-in Ruby objects, limited set of base operations: get/set and get_batch/set_batch, data can be dumped to well-known format MessagePack.
|
9
|
+
|
10
|
+
## Structure:
|
11
|
+
|
12
|
+
The FrameTree consists of four levels:
|
13
|
+
* Tree — starting point and set of management methods. The Tree keeps a list of existing branches:
|
14
|
+
```
|
15
|
+
+-----------+------+
|
16
|
+
| Branch ID | Size |
|
17
|
+
+-----------+------+
|
18
|
+
| 0 | 12 |
|
19
|
+
| 1 | 10 |
|
20
|
+
| 2 | 10 |
|
21
|
+
| 3 | 10 |
|
22
|
+
| 4 | 10 |
|
23
|
+
| 5 | 10 |
|
24
|
+
| 6 | 10 |
|
25
|
+
| 7 | 10 |
|
26
|
+
| 8 | 10 |
|
27
|
+
| 9 | 10 |
|
28
|
+
| 10 | 10 |
|
29
|
+
+-----------+------+
|
30
|
+
```
|
31
|
+
|
32
|
+
* Branch — section like partition in DB to store number of frames. The main role of the branchs is to increase the lookup speed. Testing has shown that it is most efficient to set the maximum number of branches to 1/5 of the total amount of data. Each branch keeps a list of existing frames:
|
33
|
+
|
34
|
+
```
|
35
|
+
+----------+---+---+---+---+---+---+---+---+
|
36
|
+
| Frame ID | T | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
|
37
|
+
+----------+---+---+---+---+---+---+---+---+
|
38
|
+
| 40000 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 2 |
|
39
|
+
| 0 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 2 |
|
40
|
+
| 30000 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 2 |
|
41
|
+
| 10000 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 2 |
|
42
|
+
| 50000 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 2 |
|
43
|
+
| 20000 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 2 |
|
44
|
+
+----------+---+---+---+---+---+---+---+---+
|
45
|
+
```
|
46
|
+
|
47
|
+
* Frame — group of counters like a table
|
48
|
+
* Counter — simple class with increment, decrement and replace operations.
|
49
|
+
|
50
|
+
## Install
|
51
|
+
```
|
52
|
+
gem install frame_tree --pre
|
53
|
+
```
|
54
|
+
|
55
|
+
## Usage
|
56
|
+
|
57
|
+
```Ruby
|
58
|
+
require 'frame_tree'
|
59
|
+
|
60
|
+
FrameTree.setup do |s|
|
61
|
+
s.max_branches = 2_000 # Max possible branches, better 1/5 of the total
|
62
|
+
s.frame_length = 6 # Number of days for which values are stored
|
63
|
+
s.data_path = '/tmp' # Path to directory to drop dumps
|
64
|
+
end
|
65
|
+
|
66
|
+
# Init
|
67
|
+
storage = FrameTree::Tree.new :db_name
|
68
|
+
|
69
|
+
# Insert only key (will be considered an increment)
|
70
|
+
storage.set 13441946 # => 1
|
71
|
+
|
72
|
+
# Output values will be in the format [total, sum of 7 day, sum of 6 day, sum of 5 day, ..., sum of current day]
|
73
|
+
# total — is not a sum of 7 days, it's just value that will be incrementing.
|
74
|
+
storage.get 13441946 # => [1, 0, 0, 0, 0, 0, 0, 1]
|
75
|
+
|
76
|
+
# Will return ordered arrays of counters value
|
77
|
+
storage.get_batch [key1, key2, key3, ...]
|
78
|
+
|
79
|
+
# will return array of total of each key
|
80
|
+
storage.set_batch [key1, key2, key3, ...]
|
81
|
+
|
82
|
+
# Dump data to file
|
83
|
+
storage.dump('path_to_file')
|
84
|
+
|
85
|
+
# Load data from file
|
86
|
+
storage.restore('path_to_file')
|
87
|
+
```
|
data/lib/frame_tree.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'msgpack'
|
4
|
+
require_relative 'frame_tree/config'
|
5
|
+
require_relative 'frame_tree/helper'
|
6
|
+
require_relative 'frame_tree/counter'
|
7
|
+
require_relative 'frame_tree/branch'
|
8
|
+
require_relative 'frame_tree/error'
|
9
|
+
require_relative 'frame_tree/frame'
|
10
|
+
require_relative 'frame_tree/tree'
|
11
|
+
|
12
|
+
module FrameTree
|
13
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module FrameTree
|
2
|
+
class Branch
|
3
|
+
attr_reader :size
|
4
|
+
attr_accessor :frames
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def from_hash id, data
|
8
|
+
branch = Branch.new id
|
9
|
+
frames = data.each_with_object({}) do |(idx, val), acc|
|
10
|
+
acc[idx] = Frame.from_hash idx, val
|
11
|
+
end
|
12
|
+
branch.frames = frames
|
13
|
+
branch
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize id
|
18
|
+
@id = id
|
19
|
+
@size = 0
|
20
|
+
@frames = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def get(key)
|
24
|
+
if @frames.has_key?(key)
|
25
|
+
@frames[key].get
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def set(key, val = nil)
|
30
|
+
if @frames.key? key
|
31
|
+
@frames[key].set val
|
32
|
+
else
|
33
|
+
@frames[key] = Frame.new @frames.size + 1
|
34
|
+
@frames[key].set val
|
35
|
+
end
|
36
|
+
@size += 1
|
37
|
+
end
|
38
|
+
|
39
|
+
# def [](key)
|
40
|
+
# get key
|
41
|
+
# end
|
42
|
+
|
43
|
+
def frame(id)
|
44
|
+
@frames[id]
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_hash
|
48
|
+
@frames.each_with_object({}) do |(k,v), acc|
|
49
|
+
acc[k] = v.to_h
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_s
|
54
|
+
"<FrameTree::Branch @id=#{@id},
|
55
|
+
@size=#{@size},
|
56
|
+
@frames=[#{@frames.keys.first(3).join(', ')}, ...]>"
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_table
|
60
|
+
Helper.render do |rows, headings|
|
61
|
+
headings << ['Frame ID', 'T', 0, 1, 2, 3, 4, 5, 6]
|
62
|
+
@frames.each do |(id, frame)|
|
63
|
+
rows << [id, frame.values].flatten
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def each
|
69
|
+
@frames.each do |frame|
|
70
|
+
yield frame
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module FrameTree
|
2
|
+
class << self
|
3
|
+
attr_accessor :config
|
4
|
+
end
|
5
|
+
|
6
|
+
class Config
|
7
|
+
attr_accessor :frame_length
|
8
|
+
attr_accessor :max_branches
|
9
|
+
attr_accessor :data_path
|
10
|
+
attr_accessor :tree
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@frame_length = 6
|
14
|
+
@max_branches = 1
|
15
|
+
@data_path = '/tmp'
|
16
|
+
@tree = nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.config
|
21
|
+
@config ||= Config.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.setup
|
25
|
+
yield(config)
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module FrameTree
|
2
|
+
class Counter
|
3
|
+
def initialize id
|
4
|
+
@id = id
|
5
|
+
@storage = 0
|
6
|
+
end
|
7
|
+
|
8
|
+
def get
|
9
|
+
@storage
|
10
|
+
end
|
11
|
+
|
12
|
+
def set(val)
|
13
|
+
@storage = if val.nil?
|
14
|
+
@storage += 1
|
15
|
+
else
|
16
|
+
@storage += val
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def incr
|
21
|
+
@storage = @storage + 1
|
22
|
+
end
|
23
|
+
|
24
|
+
def decr
|
25
|
+
@storage = @storage - 1
|
26
|
+
end
|
27
|
+
|
28
|
+
def clear
|
29
|
+
@storage = 0
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
"<FrameTree::Counter @id=#{@id}, @value=#{@storage}>"
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module FrameTree
|
2
|
+
class WrongInputType < ArgumentError
|
3
|
+
def initialize(msg = 'Key or Value must be Integer')
|
4
|
+
super(msg)
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class WrongDataLength < ArgumentError
|
9
|
+
def initialize(msg = 'Data must be the same length as Frame')
|
10
|
+
super(msg)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class MissingOption < ArgumentError
|
15
|
+
def initialize(option)
|
16
|
+
@msg = "Missing config option #{option}"
|
17
|
+
super(@msg)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module FrameTree
|
2
|
+
class Frame
|
3
|
+
attr_reader :size
|
4
|
+
attr_accessor :total
|
5
|
+
attr_accessor :config
|
6
|
+
attr_accessor :counters
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def from_hash id, data
|
10
|
+
frame = Frame.new id
|
11
|
+
frame.total = [data.shift].to_h['total']
|
12
|
+
frame.counters = data.each_with_object({}) do |(idx, val), acc|
|
13
|
+
acc[idx] = Counter.new idx
|
14
|
+
acc[idx].set val
|
15
|
+
end
|
16
|
+
frame
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(id, config: FrameTree.config)
|
21
|
+
@config = config
|
22
|
+
@id = id
|
23
|
+
@wday = Time.now.wday
|
24
|
+
@size = 0
|
25
|
+
@total = Counter.new :total
|
26
|
+
@count_num = @config.frame_length
|
27
|
+
@counters = setup_counters
|
28
|
+
@last_access = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def set val = nil
|
32
|
+
update { val }
|
33
|
+
end
|
34
|
+
|
35
|
+
def get
|
36
|
+
[@total.get] + @counters.values.map(&:get)
|
37
|
+
end
|
38
|
+
|
39
|
+
def [](idx)
|
40
|
+
@counters[idx]
|
41
|
+
end
|
42
|
+
|
43
|
+
def values
|
44
|
+
get
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_h
|
48
|
+
(['total'] + @counters.keys).zip(values).to_h
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_table
|
52
|
+
Helper.render do |rows, headers|
|
53
|
+
headers << ['ID', 'Value']
|
54
|
+
rows << ['T', @total.get]
|
55
|
+
@counters.each do |(idx, value)|
|
56
|
+
rows << [idx, value]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_s
|
62
|
+
"<FrameTree::Frame @id=#{@id}, @size=#{@size}, @counters=[#{values.join(', ')}]>"
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def update
|
68
|
+
time_now = Time.now
|
69
|
+
current_wday = time_now.wday
|
70
|
+
rotate_counters if @wday != current_wday
|
71
|
+
data = yield
|
72
|
+
if data.is_a?(Hash) || data.is_a?(Array)
|
73
|
+
update_counters data
|
74
|
+
else
|
75
|
+
active_counter.incr
|
76
|
+
@total.incr
|
77
|
+
end
|
78
|
+
@wday = current_wday
|
79
|
+
@last_access = time_now.to_i
|
80
|
+
values
|
81
|
+
end
|
82
|
+
|
83
|
+
def update_counters data
|
84
|
+
data = data.values if data.is_a? Hash
|
85
|
+
raise WrongDataLength.new unless data.size == @count_num
|
86
|
+
raise WrongInputType.new unless data.all?{ |val| val.is_a?(Integer) }
|
87
|
+
|
88
|
+
@total.set data.shift
|
89
|
+
data.each_with_index do |val, idx|
|
90
|
+
@counters[idx].set val
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def rotate_counters
|
95
|
+
@counters.shift
|
96
|
+
tmp_hash = @counters.each_with_object({}) do |(pos, val), acc|
|
97
|
+
acc[pos - 1] = val
|
98
|
+
end
|
99
|
+
tmp_hash[tmp_hash.size] = Counter.new tmp_hash.size
|
100
|
+
@counters = tmp_hash
|
101
|
+
end
|
102
|
+
|
103
|
+
def setup_counters
|
104
|
+
unless @count_num.is_a?(Integer)
|
105
|
+
raise MissingOption.new('frame_length')
|
106
|
+
end
|
107
|
+
tmp_hash = {}
|
108
|
+
(0..@count_num).each do |idx|
|
109
|
+
tmp_hash[idx] = Counter.new idx
|
110
|
+
@size = idx
|
111
|
+
end
|
112
|
+
tmp_hash
|
113
|
+
end
|
114
|
+
|
115
|
+
def active_counter
|
116
|
+
counters[@count_num]
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'terminal-table'
|
2
|
+
|
3
|
+
module FrameTree
|
4
|
+
module Helper
|
5
|
+
class << self
|
6
|
+
def fetch_obj(oid)
|
7
|
+
ObjectSpace._id2ref oid
|
8
|
+
end
|
9
|
+
|
10
|
+
def render
|
11
|
+
rows = []
|
12
|
+
headings = []
|
13
|
+
yield rows, headings
|
14
|
+
table = Terminal::Table.new rows: rows
|
15
|
+
table.align_column(0, :right)
|
16
|
+
table.headings = headings
|
17
|
+
table
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module FrameTree
|
2
|
+
class Tree
|
3
|
+
attr_reader :size
|
4
|
+
attr_reader :name
|
5
|
+
attr_reader :statistics
|
6
|
+
attr_accessor :branches
|
7
|
+
attr_accessor :config
|
8
|
+
|
9
|
+
def initialize(name, config = FrameTree.config)
|
10
|
+
@config = config
|
11
|
+
@config.tree = self
|
12
|
+
@name = name
|
13
|
+
@size = 0
|
14
|
+
@today = Time.now.wday
|
15
|
+
@branches = {}
|
16
|
+
@max_branches = @config.max_branches
|
17
|
+
@statistics = {
|
18
|
+
get: 0, set: 0, lookup: 0,
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
# Select and insert data
|
23
|
+
def set(key, val = nil)
|
24
|
+
@statistics[:set] += 1
|
25
|
+
with_branch_for(key) do |branch|
|
26
|
+
branch.set key, val
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def get(key)
|
31
|
+
@statistics[:get] += 1
|
32
|
+
with_branch_for(key) do |branch|
|
33
|
+
branch.get key
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def set_batch(items)
|
38
|
+
items.map do |(key, val)|
|
39
|
+
set key, val
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_batch(keys)
|
44
|
+
keys.map do |key|
|
45
|
+
get key
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def branch(id)
|
50
|
+
@branches[id]
|
51
|
+
end
|
52
|
+
|
53
|
+
# Explore structure
|
54
|
+
def to_table
|
55
|
+
Helper.render do |rows, headings|
|
56
|
+
headings << ['Branch ID', 'Size']
|
57
|
+
@branches.each do |(id, branch)|
|
58
|
+
rows << [id, branch.size]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def each
|
64
|
+
@branches.each do |branch|
|
65
|
+
yield branch
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_hash
|
70
|
+
@branches.each_with_object({}) do |(k,v), acc|
|
71
|
+
acc[k] = v.to_hash
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def from_hash data
|
76
|
+
@branches = data.each_with_object({}) do |(idx, values), acc|
|
77
|
+
acc[idx] = Branch.from_hash idx, values
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Dump and restore data
|
82
|
+
def dump(file_path = nil)
|
83
|
+
file_path ||= File.join(@config.data_path, "#{Time.now.to_i.to_s}.bin")
|
84
|
+
File.open(file_path, 'wb') do |io|
|
85
|
+
io << MessagePack.pack(to_hash)
|
86
|
+
end
|
87
|
+
file_path
|
88
|
+
end
|
89
|
+
|
90
|
+
def restore(file_path = nil)
|
91
|
+
data = nil
|
92
|
+
file_path ||= Dir.glob(File.join(@config.data_path, '*.bin')).sort.last
|
93
|
+
File.open(file_path, 'rb') do |io|
|
94
|
+
data = MessagePack.unpack(io.read)
|
95
|
+
end
|
96
|
+
from_hash data
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def hash(key)
|
102
|
+
@statistics[:lookup] += 1
|
103
|
+
key % @max_branches
|
104
|
+
end
|
105
|
+
|
106
|
+
def with_branch_for(key)
|
107
|
+
index = hash key
|
108
|
+
if @branches.has_key? index
|
109
|
+
@branches[index]
|
110
|
+
else
|
111
|
+
@size += 1
|
112
|
+
@branches[index] = Branch.new index
|
113
|
+
end
|
114
|
+
yield @branches[index]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: frame_tree
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.pre
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sergey Fedorov
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-02-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: terminal-table
|
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: msgpack
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
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
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description:
|
70
|
+
email:
|
71
|
+
- creadone@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- LICENSE.txt
|
77
|
+
- README.md
|
78
|
+
- lib/frame_tree.rb
|
79
|
+
- lib/frame_tree/branch.rb
|
80
|
+
- lib/frame_tree/config.rb
|
81
|
+
- lib/frame_tree/counter.rb
|
82
|
+
- lib/frame_tree/error.rb
|
83
|
+
- lib/frame_tree/frame.rb
|
84
|
+
- lib/frame_tree/helper.rb
|
85
|
+
- lib/frame_tree/tree.rb
|
86
|
+
- lib/frame_tree/version.rb
|
87
|
+
homepage: https://github.com/creadone/frame_tree
|
88
|
+
licenses:
|
89
|
+
- MIT
|
90
|
+
metadata: {}
|
91
|
+
post_install_message:
|
92
|
+
rdoc_options: []
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: 2.3.0
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">"
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: 1.3.1
|
105
|
+
requirements: []
|
106
|
+
rubygems_version: 3.1.4
|
107
|
+
signing_key:
|
108
|
+
specification_version: 4
|
109
|
+
summary: In-memory Tree-like structure to store counters with rolling frame/window.
|
110
|
+
test_files: []
|