simple-feed 2.1.0 → 3.0.1
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.
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
|