simple-feed 2.1.0 → 3.0.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of simple-feed might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.envrc +1 -0
- data/.github/workflows/ruby.yml +1 -0
- data/.gitignore +1 -1
- data/.travis.yml +3 -13
- data/CHANGELOG.md +93 -0
- data/README.adoc +168 -51
- data/Rakefile +1 -1
- data/codecov.yml +28 -0
- data/examples/shared/provider_example.rb +43 -25
- data/lib/simplefeed.rb +20 -15
- data/lib/simplefeed/activity/single_user.rb +2 -2
- data/lib/simplefeed/dsl/formatter.rb +58 -23
- data/lib/simplefeed/event.rb +47 -10
- data/lib/simplefeed/feed.rb +20 -9
- data/lib/simplefeed/providers/base/provider.rb +1 -1
- data/lib/simplefeed/providers/hash/provider.rb +26 -16
- data/lib/simplefeed/providers/key.rb +57 -42
- data/lib/simplefeed/providers/proxy.rb +20 -7
- data/lib/simplefeed/providers/redis/provider.rb +6 -6
- data/lib/simplefeed/response.rb +1 -1
- data/lib/simplefeed/version.rb +1 -1
- data/man/running-example-redis-debug.png +0 -0
- data/man/running-example.png +0 -0
- data/man/sf-color-dump.png +0 -0
- data/simple-feed.gemspec +5 -1
- metadata +51 -8
- data/lib/simplefeed/key/template.rb +0 -48
- data/lib/simplefeed/key/type.rb +0 -28
data/Rakefile
CHANGED
data/codecov.yml
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
codecov:
|
2
|
+
require_ci_to_pass: no
|
3
|
+
|
4
|
+
notify:
|
5
|
+
after_n_builds: 30
|
6
|
+
wait_for_ci: yes
|
7
|
+
|
8
|
+
parsers:
|
9
|
+
v1:
|
10
|
+
include_full_missed_files: true # To use with Ruby so we see files that have NO tests written
|
11
|
+
|
12
|
+
coverage:
|
13
|
+
precision: 1
|
14
|
+
status:
|
15
|
+
project:
|
16
|
+
default: off
|
17
|
+
simplefeed:
|
18
|
+
target: 90%
|
19
|
+
threshold: 100%
|
20
|
+
informational: true
|
21
|
+
if_not_found: success
|
22
|
+
if_ci_failed: error
|
23
|
+
paths:
|
24
|
+
- lib/
|
25
|
+
flags:
|
26
|
+
simplefeed:
|
27
|
+
paths:
|
28
|
+
- lib/
|
@@ -1,3 +1,6 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
1
4
|
$LOAD_PATH.unshift File.expand_path('../../../lib', __FILE__)
|
2
5
|
|
3
6
|
# Please set @feed in the enclosing context
|
@@ -14,8 +17,6 @@ srand(Time.now.to_i % 100003)
|
|
14
17
|
n % 2 == 0 ? UUID.generate : rand(100003)
|
15
18
|
end
|
16
19
|
|
17
|
-
pp @users
|
18
|
-
|
19
20
|
@activity = @feed.activity(@users)
|
20
21
|
@uid = @users.first
|
21
22
|
|
@@ -28,47 +29,64 @@ class Object
|
|
28
29
|
end
|
29
30
|
|
30
31
|
def p(*args)
|
31
|
-
printf "
|
32
|
+
printf "%40s -> %s\n", args[0].strip.blue.bold, args[1].bold.red
|
32
33
|
end
|
33
34
|
|
34
35
|
with_activity(@activity) do
|
35
|
-
header "#{@activity.feed.provider_type} provider example"
|
36
|
+
header "#{@activity.feed.provider_type} provider example".upcase,
|
37
|
+
"Starting with a blank feed, no items",
|
38
|
+
align: :center
|
36
39
|
|
37
40
|
wipe
|
38
41
|
|
39
|
-
store('value one')
|
40
|
-
store('value two')
|
41
|
-
store('value three')
|
42
|
-
|
43
|
-
hr
|
42
|
+
store('value one') { p 'storing new value', 'value one' }
|
43
|
+
store('value two') { p 'storing new value', 'value two' }
|
44
|
+
store('value three') { p 'storing new value', 'value three' }
|
44
45
|
|
45
|
-
total_count
|
46
|
-
unread_count
|
46
|
+
total_count { |r| p 'total_count is now', "#{r[@uid]._v}" }
|
47
|
+
unread_count { |r| p 'unread_count is now', "#{r[@uid]._v}" }
|
47
48
|
|
48
|
-
header 'paginate(page: 1, per_page: 2)'
|
49
|
+
header 'activity.paginate(page: 1, per_page: 2)'
|
49
50
|
paginate(page: 1, per_page: 2) { |r| puts r[@uid].map(&:to_color_s) }
|
50
|
-
|
51
|
+
|
52
|
+
header 'activity.paginate(page: 2, per_page: 2, reset_last_read: true)'
|
51
53
|
paginate(page: 2, per_page: 2, reset_last_read: true) { |r| puts r[@uid].map(&:to_color_s) }
|
52
54
|
|
53
|
-
|
55
|
+
total_count { |r| p 'total_count ', "#{r[@uid]._v}" }
|
56
|
+
unread_count { |r| p 'unread_count ', "#{r[@uid]._v}" }
|
54
57
|
|
55
|
-
|
56
|
-
unread_count { |r| p 'unread_count ', "#{r[@uid]._v}" }
|
57
|
-
|
58
|
-
hr
|
59
|
-
store('value four') { p 'storing', 'value four' }
|
58
|
+
store('value four') { p 'storing', 'value four' }
|
60
59
|
|
61
60
|
color_dump
|
62
61
|
|
63
62
|
header 'deleting'
|
64
63
|
|
65
|
-
delete('value three')
|
66
|
-
|
67
|
-
|
64
|
+
delete('value three') { p 'deleting', 'value three' }
|
65
|
+
|
66
|
+
total_count { |r| p 'total_count ', "#{r[@uid]._v}" }
|
67
|
+
unread_count { |r| p 'unread_count ', "#{r[@uid]._v}" }
|
68
|
+
|
68
69
|
hr
|
69
|
-
delete('value four') { p 'deleting', 'value four' }
|
70
|
-
total_count { |r| p 'total_count ', "#{r[@uid]._v}" }
|
71
|
-
unread_count { |r| p 'unread_count ', "#{r[@uid]._v}" }
|
72
70
|
|
71
|
+
delete('value four') { p 'deleting', 'value four' }
|
72
|
+
total_count { |r| p 'total_count ', "#{r[@uid]._v}" }
|
73
|
+
unread_count { |r| p 'unread_count ', "#{r[@uid]._v}" }
|
74
|
+
|
75
|
+
puts
|
76
|
+
end
|
77
|
+
|
78
|
+
notes = [
|
79
|
+
'Thanks for trying SimpleFeed!', 'For any questions, reach out to',
|
80
|
+
'kigster@gmail.com',
|
81
|
+
]
|
82
|
+
|
83
|
+
unless ENV['REDIS_DEBUG']
|
84
|
+
notes << [
|
85
|
+
'———',
|
86
|
+
'To see REDIS commands, set REDIS_DEBUG environment variable to true,',
|
87
|
+
'and re-run the example.'
|
88
|
+
]
|
73
89
|
end
|
74
90
|
|
91
|
+
header notes.flatten,
|
92
|
+
align: :center
|
data/lib/simplefeed.rb
CHANGED
@@ -11,43 +11,48 @@ Hashie.logger = Logger.new(nil)
|
|
11
11
|
require 'simplefeed/providers/redis'
|
12
12
|
require 'simplefeed/providers/hash'
|
13
13
|
require 'simplefeed/dsl'
|
14
|
+
require 'simplefeed/feed'
|
14
15
|
|
15
16
|
# Main namespace module for the SimpleFeed gem. It provides several shortcuts and entry
|
16
17
|
# points into the library, such as ability to define and fetch new feeds via +define+,
|
17
18
|
# and so on.
|
18
19
|
module SimpleFeed
|
20
|
+
TIME_FORMAT = '%Y-%m-%d %H:%M:%S.%L'
|
21
|
+
|
19
22
|
@registry = {}
|
20
23
|
|
21
24
|
class << self
|
22
|
-
# @return
|
25
|
+
# @return Hash<Symbol, Feed> the registry of the defined feeds
|
23
26
|
attr_reader :registry
|
24
27
|
|
25
|
-
# @param name
|
26
|
-
# @param options
|
28
|
+
# @param <Symbol> name of the feed
|
29
|
+
# @param <Hash> options any key-value pairs to set on the feed
|
27
30
|
#
|
28
|
-
# @return [Feed]
|
29
|
-
def define(name, **options
|
31
|
+
# @return [Feed] the feed with the given name, and defined via options and a block
|
32
|
+
def define(name, **options)
|
30
33
|
name = name.to_sym unless name.is_a?(Symbol)
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
+
|
35
|
+
registry[name] ||= Feed.new(name)
|
36
|
+
registry[name].tap do |feed|
|
37
|
+
feed.configure(options) do
|
38
|
+
yield(feed) if block_given?
|
39
|
+
end
|
34
40
|
end
|
35
|
-
registry[name] = feed
|
36
|
-
feed
|
37
41
|
end
|
38
42
|
|
39
|
-
# @
|
43
|
+
# @param [Symbol] name
|
44
|
+
# @return <Feed> the pre-defined feed with the given name
|
40
45
|
def get(name)
|
41
46
|
registry[name.to_sym]
|
42
47
|
end
|
43
48
|
|
44
49
|
# A factory method that constructs an instance of a provider based on the provider name and arguments.
|
45
50
|
#
|
46
|
-
# @param provider_name
|
47
|
-
# @
|
48
|
-
# @
|
51
|
+
# @param <Symbol> provider_name short name of the provider, eg, :redis, :hash, etc.
|
52
|
+
# @param <Array> args constructor array arguments of the provider
|
53
|
+
# @param <Hash, NilClass> opts constructor hash arguments of the provider
|
49
54
|
#
|
50
|
-
# @return
|
55
|
+
# @return <Provider>
|
51
56
|
def provider(provider_name, *args, **opts, &block)
|
52
57
|
provider_class = SimpleFeed::Providers.registry[provider_name]
|
53
58
|
raise ArgumentError, "No provider named #{provider_name} was found, #{SimpleFeed::Providers.registry.inspect}" unless provider_class
|
@@ -1,8 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'tty-box'
|
4
|
+
require 'tty-screen'
|
3
5
|
require 'simplefeed/dsl'
|
4
6
|
require 'simplefeed/activity/single_user'
|
5
7
|
require 'simplefeed/activity/multi_user'
|
8
|
+
|
6
9
|
module SimpleFeed
|
7
10
|
module DSL
|
8
11
|
# This module exports method #color_dump which receives an activity and
|
@@ -18,27 +21,30 @@ module SimpleFeed
|
|
18
21
|
this_activity.feed.activity([this_activity.user_id])
|
19
22
|
else
|
20
23
|
this_activity
|
21
|
-
|
24
|
+
end
|
22
25
|
_puts
|
23
26
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
27
|
+
feed_header(feed) do
|
28
|
+
[
|
29
|
+
field('Feed Name', feed.name, "\n"),
|
30
|
+
field('Provider', feed.provider.provider.class, "\n"),
|
31
|
+
field('Max Size', feed.max_size, "\n")
|
32
|
+
]
|
28
33
|
end
|
29
34
|
|
30
35
|
with_activity(this_activity) do
|
31
|
-
this_activity.
|
36
|
+
this_activity.each_with_index do |user_id, index|
|
32
37
|
this_last_event_at = nil
|
33
38
|
this_last_read = (last_read[user_id] || 0.0).to_f
|
34
39
|
|
40
|
+
fields = []
|
35
41
|
[['User ID', user_id, "\n"],
|
36
42
|
['Activities', sprintf('%d total, %d unread', total_count[user_id], unread_count[user_id]), "\n"],
|
37
43
|
['Last Read', this_last_read ? Time.at(this_last_read) : 'N/A'],].each do |field, value, *args|
|
38
|
-
field(field, value, *args)
|
44
|
+
fields << field(field, value, *args)
|
39
45
|
end
|
40
46
|
|
41
|
-
|
47
|
+
header(title: { top_center: " « User Activity #{index + 1} » " }) { fields.map(&:green) }
|
42
48
|
|
43
49
|
this_events = fetch[user_id]
|
44
50
|
this_events_count = this_events.size
|
@@ -50,7 +56,7 @@ module SimpleFeed
|
|
50
56
|
end
|
51
57
|
|
52
58
|
this_last_event_at = _event.at # float
|
53
|
-
|
59
|
+
output "[%2d] %16s %s\n", _index, _event.time.strftime(TIME_FORMAT).blue.bold, _event.value
|
54
60
|
if _index == this_events_count - 1 && this_last_read < _event.at
|
55
61
|
print_last_read_separator(this_last_read)
|
56
62
|
end
|
@@ -60,17 +66,18 @@ module SimpleFeed
|
|
60
66
|
end
|
61
67
|
|
62
68
|
def print_last_read_separator(lr)
|
63
|
-
|
69
|
+
output ">>>> %16s <<<< last read\n", Time.at(lr).strftime(TIME_FORMAT).red.bold
|
64
70
|
end
|
65
71
|
end
|
66
72
|
|
73
|
+
# This allows redirecting output in tests.
|
67
74
|
@print_method = :printf
|
68
75
|
|
69
76
|
class << self
|
70
77
|
attr_accessor :print_method
|
71
78
|
end
|
72
79
|
|
73
|
-
def
|
80
|
+
def output(*args, **opts, &block)
|
74
81
|
send(SimpleFeed::DSL.print_method, *args, **opts, &block)
|
75
82
|
end
|
76
83
|
|
@@ -82,8 +89,6 @@ module SimpleFeed
|
|
82
89
|
sprintf ' %20s ', text
|
83
90
|
end
|
84
91
|
|
85
|
-
TIME_FORMAT = '%Y-%m-%d %H:%M:%S.%L'
|
86
|
-
|
87
92
|
def field_value(value)
|
88
93
|
case value
|
89
94
|
when Numeric
|
@@ -95,22 +100,52 @@ module SimpleFeed
|
|
95
100
|
end
|
96
101
|
end
|
97
102
|
|
98
|
-
def field(label, value,
|
99
|
-
|
103
|
+
def field(label, value, _sep = '')
|
104
|
+
field_label(label) + ' -> ' + field_value(value)
|
105
|
+
end
|
106
|
+
|
107
|
+
def hr
|
108
|
+
output hr_string.magenta
|
109
|
+
end
|
110
|
+
|
111
|
+
def hr_string
|
112
|
+
'―' * width + "\n"
|
113
|
+
end
|
114
|
+
|
115
|
+
def width
|
116
|
+
@width ||= TTY::Screen.width - 5
|
100
117
|
end
|
101
118
|
|
102
|
-
def
|
103
|
-
|
119
|
+
def feed_header(feed, &block)
|
120
|
+
header title: { top_left: " « #{feed.name.capitalize} Feed » " },
|
121
|
+
border: :thick,
|
122
|
+
style: {
|
123
|
+
fg: :bright_red,
|
124
|
+
border: { fg: :white }
|
125
|
+
}, &block
|
104
126
|
end
|
105
127
|
|
106
|
-
def
|
107
|
-
|
128
|
+
def header(*args, **opts)
|
129
|
+
message = args.join("\n")
|
130
|
+
msg = block_given? ? (yield || message) : message + "\n"
|
131
|
+
box = TTY::Box.frame(box_config(**opts)) { Array(msg).join("\n") }
|
132
|
+
output "\n#{box}"
|
108
133
|
end
|
109
134
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
135
|
+
private
|
136
|
+
|
137
|
+
def box_config(**opts)
|
138
|
+
{
|
139
|
+
width: width,
|
140
|
+
align: :left,
|
141
|
+
padding: [0, 3],
|
142
|
+
style: {
|
143
|
+
fg: :bright_yellow,
|
144
|
+
border: {
|
145
|
+
fg: :bright_magenta,
|
146
|
+
}
|
147
|
+
}
|
148
|
+
}.merge(opts)
|
114
149
|
end
|
115
150
|
end
|
116
151
|
end
|
data/lib/simplefeed/event.rb
CHANGED
@@ -7,6 +7,21 @@ module SimpleFeed
|
|
7
7
|
attr_accessor :value, :at
|
8
8
|
include Comparable
|
9
9
|
|
10
|
+
class << self
|
11
|
+
attr_accessor :is_time
|
12
|
+
end
|
13
|
+
|
14
|
+
# This proc can be overridden in a configuration if needed.
|
15
|
+
# @example To always assume this is time, set it like so,
|
16
|
+
# before defining your feeds.
|
17
|
+
#
|
18
|
+
# SimpleFeed::Event.is_time = ->(*) { true }
|
19
|
+
#
|
20
|
+
self.is_time = ->(float) {
|
21
|
+
# assume it's time if epoch is > June 1974 and < December 2040.
|
22
|
+
float < 2_237_932_800.0 && float > 139_276_800.0
|
23
|
+
}
|
24
|
+
|
10
25
|
def initialize(*args, value: nil, at: Time.now)
|
11
26
|
if args && !args.empty?
|
12
27
|
self.value = args[0]
|
@@ -22,7 +37,11 @@ module SimpleFeed
|
|
22
37
|
end
|
23
38
|
|
24
39
|
def time
|
40
|
+
return nil unless Event.is_time[at]
|
41
|
+
|
25
42
|
Time.at(at)
|
43
|
+
rescue ArgumentError
|
44
|
+
nil
|
26
45
|
end
|
27
46
|
|
28
47
|
def <=>(other)
|
@@ -38,10 +57,6 @@ module SimpleFeed
|
|
38
57
|
self.value == other.value
|
39
58
|
end
|
40
59
|
|
41
|
-
def to_h
|
42
|
-
{ value: value, at: at, time: time }
|
43
|
-
end
|
44
|
-
|
45
60
|
def hash
|
46
61
|
self.value.hash
|
47
62
|
end
|
@@ -54,16 +69,38 @@ module SimpleFeed
|
|
54
69
|
YAML.dump(to_h)
|
55
70
|
end
|
56
71
|
|
72
|
+
def to_h
|
73
|
+
return @to_h if @to_h
|
74
|
+
|
75
|
+
@to_h ||= { value: value, at: at }
|
76
|
+
@to_h.merge!(time: time) if time
|
77
|
+
@to_h
|
78
|
+
end
|
79
|
+
|
57
80
|
def to_s
|
58
|
-
|
81
|
+
return @to_s if @to_s
|
82
|
+
|
83
|
+
output = StringIO.new
|
84
|
+
output.print "<SimpleFeed::Event: "
|
85
|
+
output.print(time.nil? ? "[#{at}]" : "[#{time&.strftime(::SimpleFeed::TIME_FORMAT)}]")
|
86
|
+
output.print " -> [#{value}] "
|
87
|
+
@to_s = output.string
|
59
88
|
end
|
60
89
|
|
90
|
+
COLOR_MAP = {
|
91
|
+
1 => ->(word) { word.green.bold },
|
92
|
+
3 => ->(word) { word.yellow.bold },
|
93
|
+
}.freeze
|
94
|
+
|
61
95
|
def to_color_s
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
96
|
+
return @to_color_s if @to_color_s
|
97
|
+
|
98
|
+
output = StringIO.new
|
99
|
+
to_s.split(/[\[\]]/).each_with_index do |word, index|
|
100
|
+
output.print(COLOR_MAP[index]&.call(word) || word.cyan)
|
101
|
+
end
|
102
|
+
output.print '>'
|
103
|
+
@to_color_s = output.string
|
67
104
|
end
|
68
105
|
|
69
106
|
def inspect
|