simple-feed 2.1.0 → 3.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.
data/Rakefile CHANGED
@@ -11,7 +11,7 @@ def shell(*args)
11
11
  end
12
12
 
13
13
  task :permissions do
14
- shell('rm -rf pkg/ tmp/ coverage/')
14
+ shell('rm -rf pkg/')
15
15
  shell("chmod -v o+r,g+r * */* */*/* */*/*/* */*/*/*/* */*/*/*/*/*")
16
16
  shell("find . -type d -exec chmod o+x,g+x {} \\;")
17
17
  end
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 "%-40s %s\n", args[0].blue, args[1].bold.red
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') { p 'storing new value', 'value one' }
40
- store('value two') { p 'storing new value', 'value two' }
41
- store('value three') { p 'storing new value', '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 { |r| p 'total_count is now', "#{r[@uid]._v}" }
46
- unread_count { |r| p 'unread_count is now', "#{r[@uid]._v}" }
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
- header 'paginate(page: 2, per_page: 2, reset_last_read: true)'
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
- hr
55
+ total_count { |r| p 'total_count ', "#{r[@uid]._v}" }
56
+ unread_count { |r| p 'unread_count ', "#{r[@uid]._v}" }
54
57
 
55
- total_count { |r| p 'total_count ', "#{r[@uid]._v}" }
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') { p 'deleting', 'value three' }
66
- total_count { |r| p 'total_count ', "#{r[@uid]._v}" }
67
- unread_count { |r| p 'unread_count ', "#{r[@uid]._v}" }
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
@@ -74,8 +74,8 @@ module SimpleFeed
74
74
  end
75
75
 
76
76
  def initialize(user_id:, feed:)
77
- @feed = feed
78
- @user_id = user_id
77
+ @feed = feed
78
+ @user_id = user_id
79
79
  self.user_activity = MultiUser.new(feed: feed, user_ids: [user_id])
80
80
  end
81
81
  end
@@ -1,8 +1,12 @@
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
+ require 'awesome_print'
9
+
6
10
  module SimpleFeed
7
11
  module DSL
8
12
  # This module exports method #color_dump which receives an activity and
@@ -18,40 +22,43 @@ module SimpleFeed
18
22
  this_activity.feed.activity([this_activity.user_id])
19
23
  else
20
24
  this_activity
21
- end
25
+ end
22
26
  _puts
23
27
 
24
- header do
25
- field('Feed Name', feed.name, "\n")
26
- field('Provider', feed.provider.provider.class, "\n")
27
- field('Max Size', feed.max_size, "\n")
28
+ feed_header(feed) do
29
+ [
30
+ field('Feed Name', feed.name, "\n"),
31
+ field('Provider', feed.provider.provider.class, "\n"),
32
+ field('Max Size', feed.max_size, "\n")
33
+ ]
28
34
  end
29
35
 
30
36
  with_activity(this_activity) do
31
- this_activity.each do |user_id|
37
+ this_activity.each_with_index do |user_id, index|
32
38
  this_last_event_at = nil
33
39
  this_last_read = (last_read[user_id] || 0.0).to_f
34
40
 
41
+ fields = []
35
42
  [['User ID', user_id, "\n"],
36
43
  ['Activities', sprintf('%d total, %d unread', total_count[user_id], unread_count[user_id]), "\n"],
37
44
  ['Last Read', this_last_read ? Time.at(this_last_read) : 'N/A'],].each do |field, value, *args|
38
- field(field, value, *args)
45
+ fields << field(field, value, *args)
39
46
  end
40
47
 
41
- _puts; hr '¨'
48
+ header(title: { top_center: " « User Activity #{index + 1} » " }, style: { fg: :green }) { fields }
42
49
 
43
50
  this_events = fetch[user_id]
44
51
  this_events_count = this_events.size
45
- this_events.each_with_index do |_event, _index|
46
- if this_last_event_at.nil? && _event.at < this_last_read
52
+ this_events.each_with_index do |evt, idx|
53
+ if this_last_event_at.nil? && evt.at < this_last_read
47
54
  print_last_read_separator(this_last_read)
48
- elsif this_last_event_at && this_last_read < this_last_event_at && this_last_read > _event.at
55
+ elsif this_last_event_at && this_last_read < this_last_event_at && this_last_read > evt.at
49
56
  print_last_read_separator(this_last_read)
50
57
  end
51
58
 
52
- this_last_event_at = _event.at # float
53
- _print "[%2d] %16s %s\n", _index, _event.time.strftime(TIME_FORMAT).blue.bold, _event.value
54
- if _index == this_events_count - 1 && this_last_read < _event.at
59
+ this_last_event_at = evt.at # float
60
+ output "[%2d] %16s %s\n", idx, evt.time.strftime(TIME_FORMAT).blue.bold, evt.value
61
+ if idx == this_events_count - 1 && this_last_read < evt.at
55
62
  print_last_read_separator(this_last_read)
56
63
  end
57
64
  end
@@ -60,17 +67,18 @@ module SimpleFeed
60
67
  end
61
68
 
62
69
  def print_last_read_separator(lr)
63
- _print ">>>> %16s <<<< last read\n", Time.at(lr).strftime(TIME_FORMAT).red.bold
70
+ output "———— %16s [last read] ———————————— \n", Time.at(lr).strftime(TIME_FORMAT).red.bold
64
71
  end
65
72
  end
66
73
 
74
+ # This allows redirecting output in tests.
67
75
  @print_method = :printf
68
76
 
69
77
  class << self
70
78
  attr_accessor :print_method
71
79
  end
72
80
 
73
- def _print(*args, **opts, &block)
81
+ def output(*args, **opts, &block)
74
82
  send(SimpleFeed::DSL.print_method, *args, **opts, &block)
75
83
  end
76
84
 
@@ -82,8 +90,6 @@ module SimpleFeed
82
90
  sprintf ' %20s ', text
83
91
  end
84
92
 
85
- TIME_FORMAT = '%Y-%m-%d %H:%M:%S.%L'
86
-
87
93
  def field_value(value)
88
94
  case value
89
95
  when Numeric
@@ -95,22 +101,55 @@ module SimpleFeed
95
101
  end
96
102
  end
97
103
 
98
- def field(label, value, sep = '')
99
- _print field_label(label).italic + field_value(value).cyan.bold + sep
104
+ def field(label, value, _sep = '')
105
+ field_label(label) + ' ❯ ' + field_value(value)
106
+ end
107
+
108
+ def hr
109
+ output hr_string.magenta
110
+ end
111
+
112
+ def hr_string
113
+ '―' * width + "\n"
114
+ end
115
+
116
+ def width
117
+ @width ||= [[TTY::Screen.width - 5, 60].max, 75].min
100
118
  end
101
119
 
102
- def hr(char = '—')
103
- _print(_hr(char).magenta)
120
+ def feed_header(feed, &block)
121
+ header title: { top_left: " « #{feed.name.capitalize} Feed » " },
122
+ border: :thick,
123
+ style: {
124
+ fg: :black,
125
+ bg: :green,
126
+ border: { fg: :bright_black, bg: :green }
127
+ }, &block
104
128
  end
105
129
 
106
- def _hr(char = '—')
107
- char * 75 + "\n"
130
+ def header(*args, **opts)
131
+ message = args.join("\n")
132
+ msg = block_given? ? (yield || message) : message + "\n"
133
+ config = box_config(**opts)
134
+ lines = Array(msg).flatten
135
+ box = TTY::Box.frame(*lines, **config)
136
+ output "\n#{box}"
108
137
  end
109
138
 
110
- def header(message = nil)
111
- _print(_hr.green.bold)
112
- block_given? ? yield : _print(message.green.italic + "\n")
113
- _print(_hr.green.bold)
139
+ private
140
+
141
+ def box_config(**opts)
142
+ {
143
+ width: width,
144
+ align: :left,
145
+ padding: [1, 3],
146
+ style: {
147
+ fg: :bright_yellow,
148
+ border: {
149
+ fg: :bright_magenta,
150
+ }
151
+ }
152
+ }.merge(opts)
114
153
  end
115
154
  end
116
155
  end
@@ -5,8 +5,24 @@ require 'json'
5
5
  module SimpleFeed
6
6
  class Event
7
7
  attr_accessor :value, :at
8
+
8
9
  include Comparable
9
10
 
11
+ class << self
12
+ attr_accessor :is_time
13
+ end
14
+
15
+ # This proc can be overridden in a configuration if needed.
16
+ # @example To always assume this is time, set it like so,
17
+ # before defining your feeds.
18
+ #
19
+ # SimpleFeed::Event.is_time = ->(*) { true }
20
+ #
21
+ self.is_time = ->(float) {
22
+ # assume it's time if epoch is > June 1974 and < December 2040.
23
+ float < 2_237_932_800.0 && float > 139_276_800.0
24
+ }
25
+
10
26
  def initialize(*args, value: nil, at: Time.now)
11
27
  if args && !args.empty?
12
28
  self.value = args[0]
@@ -22,7 +38,11 @@ module SimpleFeed
22
38
  end
23
39
 
24
40
  def time
41
+ return nil unless Event.is_time[at]
42
+
25
43
  Time.at(at)
44
+ rescue ArgumentError
45
+ nil
26
46
  end
27
47
 
28
48
  def <=>(other)
@@ -38,10 +58,6 @@ module SimpleFeed
38
58
  self.value == other.value
39
59
  end
40
60
 
41
- def to_h
42
- { value: value, at: at, time: time }
43
- end
44
-
45
61
  def hash
46
62
  self.value.hash
47
63
  end
@@ -54,16 +70,38 @@ module SimpleFeed
54
70
  YAML.dump(to_h)
55
71
  end
56
72
 
73
+ def to_h
74
+ return @to_h if @to_h
75
+
76
+ @to_h ||= { value: value, at: at }
77
+ @to_h.merge!(time: time) if time
78
+ @to_h
79
+ end
80
+
57
81
  def to_s
58
- "<SimpleFeed::Event: value='#{value}', at='#{at}', time='#{time}'>"
82
+ return @to_s if @to_s
83
+
84
+ output = StringIO.new
85
+ output.print "<SimpleFeed::Event: "
86
+ output.print(time.nil? ? "[#{at}]" : "[#{time&.strftime(::SimpleFeed::TIME_FORMAT)}]")
87
+ output.print ", [\"#{value}\"]"
88
+ @to_s = output.string
59
89
  end
60
90
 
91
+ COLOR_MAP = {
92
+ 1 => ->(word) { word.green.bold },
93
+ 3 => ->(word) { word.yellow.bold },
94
+ }.freeze
95
+
61
96
  def to_color_s
62
- counter = 0
63
- to_s.split(/[']/).map do |word|
64
- counter += 1
65
- counter.even? ? word.yellow.bold : word.blue
66
- end.join('')
97
+ return @to_color_s if @to_color_s
98
+
99
+ output = StringIO.new
100
+ to_s.split(/[\[\]]/).each_with_index do |word, index|
101
+ output.print(COLOR_MAP[index]&.call(word) || word.cyan)
102
+ end
103
+ output.print '>'
104
+ @to_color_s = output.string
67
105
  end
68
106
 
69
107
  def inspect
@@ -2,11 +2,17 @@
2
2
 
3
3
  require_relative 'providers'
4
4
  require_relative 'activity/base'
5
- require 'simplefeed/key/template'
5
+ require_relative 'providers/key'
6
6
 
7
7
  module SimpleFeed
8
8
  class Feed
9
- attr_accessor :per_page, :max_size, :batch_size, :meta, :namespace
9
+ attr_accessor :per_page,
10
+ :max_size,
11
+ :batch_size,
12
+ :namespace,
13
+ :data_key_transformer,
14
+ :meta_key_transformer
15
+
10
16
  attr_reader :name
11
17
 
12
18
  SimpleFeed::Providers.define_provider_methods(self) do |feed, method, opts, &block|
@@ -14,19 +20,21 @@ module SimpleFeed
14
20
  end
15
21
 
16
22
  def initialize(name)
17
- @name = name
18
- @name = name.underscore.to_sym unless name.is_a?(Symbol)
23
+ @name = name
24
+ @name = name.underscore.to_sym unless name.is_a?(Symbol)
19
25
  # set the defaults if not passed in
20
- @meta = {}
21
- @namespace = nil
26
+ @meta = {}
27
+ @namespace = nil
22
28
  @per_page ||= 50
23
29
  @max_size ||= 1000
24
30
  @batch_size ||= 10
31
+ @meta_key_transformer = nil
32
+ @data_key_transformer = nil
25
33
  @proxy = nil
26
34
  end
27
35
 
28
36
  def provider=(definition)
29
- @proxy = Providers::Proxy.from(definition)
37
+ @proxy = Providers::Proxy.from(definition)
30
38
  @proxy.feed = self
31
39
  @proxy
32
40
  end
@@ -63,12 +71,15 @@ module SimpleFeed
63
71
  end
64
72
 
65
73
  def key(user_id)
66
- SimpleFeed::Providers::Key.new(user_id, key_template)
74
+ SimpleFeed::Providers::Key.new(user_id,
75
+ namespace: namespace,
76
+ data_key_transformer: data_key_transformer,
77
+ meta_key_transformer: meta_key_transformer)
67
78
  end
68
79
 
69
80
  def eql?(other)
70
81
  other.class == self.class &&
71
- %i(per_page max_size name).all? { |m| send(m).equal?(other.send(m)) } &&
82
+ %i(per_page max_size name namespace data_key_transformer meta_key_transformer).all? { |m| send(m).equal?(other.send(m)) } &&
72
83
  provider.provider.class == other.provider.provider.class
73
84
  end
74
85
 
@@ -58,7 +58,7 @@ module SimpleFeed
58
58
  def with_response_batched(user_ids, external_response = nil)
59
59
  with_response(external_response) do |response|
60
60
  batch(user_ids) do |key|
61
- response.for(key.user_id) { yield(key, response) }
61
+ response.for(key.consumer) { yield(key, response) }
62
62
  end
63
63
  end
64
64
  end
@@ -2,10 +2,6 @@
2
2
 
3
3
  require 'base62-rb'
4
4
  require 'hashie/mash'
5
- require 'simplefeed/key/template'
6
- require 'simplefeed/key/type'
7
-
8
- require 'forwardable'
9
5
 
10
6
  module SimpleFeed
11
7
  module Providers
@@ -15,66 +11,85 @@ module SimpleFeed
15
11
  # ↓ ↓
16
12
  # "ff|u.f23098.m"
17
13
  # ↑ ↑
18
- # namespace user_id(base62)
14
+ # namespace consumer(base62)
19
15
  #
20
16
  class Key
21
- attr_accessor :user_id, :key_template
17
+ class << self
18
+ def rot13(value)
19
+ value.tr('abcdefghijklmnopqrstuvwxyz',
20
+ 'nopqrstuvwxyzabcdefghijklm')
21
+ end
22
+ end
22
23
 
23
- extend Forwardable
24
- def_delegators :@key_template, :key_names, :key_types
24
+ SERIALIZED_DATA_TEMPLATE = '{{namespace}}u.{{data_id}}.d'
25
+ SERIALIZED_META_TEMPLATE = '{{namespace}}u.{{meta_id}}.m'
25
26
 
26
- def initialize(user_id, key_template)
27
- self.user_id = user_id
28
- self.key_template = key_template
27
+ attr_reader :consumer, :namespace, :data_key_transformer, :meta_key_transformer
29
28
 
30
- define_key_methods
29
+ def initialize(consumer,
30
+ namespace: nil,
31
+ data_key_transformer: nil,
32
+ meta_key_transformer: nil)
33
+ @consumer = consumer
34
+ @namespace = namespace
35
+ @data_key_transformer = data_key_transformer
36
+ @meta_key_transformer = meta_key_transformer
31
37
  end
32
38
 
33
- # Defines #data and #meta methods.
34
- def define_key_methods
35
- key_template.key_types.each do |type|
36
- key_name = type.name
37
- next if respond_to?(key_name)
38
-
39
- self.class.send(:define_method, key_name) do
40
- instance_variable_get("@#{key_name}") ||
41
- instance_variable_set("@#{key_name}", type.render(render_options))
42
- end
43
- end
39
+ def data
40
+ @data ||= render(SERIALIZED_DATA_TEMPLATE)
44
41
  end
45
42
 
46
- def base62_user_id
47
- @base62_user_id ||= if user_id.is_a?(Numeric)
48
- ::Base62.encode(user_id)
49
- else
50
- rot13(user_id.to_s)
51
- end
43
+ def meta
44
+ @meta ||= render(SERIALIZED_META_TEMPLATE)
52
45
  end
53
46
 
54
47
  def keys
55
- key_names.map { |name| send(name) }
56
- end
57
-
58
- def render_options
59
- key_template.render_options.merge!({
60
- 'user_id' => user_id,
61
- 'base62_user_id' => base62_user_id
62
- })
48
+ [data, meta]
63
49
  end
64
50
 
65
51
  def to_s
66
- super + { user_id: user_id, base62_user_id: base62_user_id, keys: keys }.to_s
52
+ super + key_params.to_s
67
53
  end
68
54
 
69
55
  def inspect
70
- render_options.inspect
56
+ super + key_params.inspect
71
57
  end
72
58
 
73
59
  private
74
60
 
75
- def rot13(value)
76
- value.tr('abcdefghijklmnopqrstuvwxyz',
77
- 'nopqrstuvwxyzabcdefghijklm')
61
+ def render(template)
62
+ template.dup.tap do |output|
63
+ key_params.each_pair do |key, value|
64
+ output.gsub!(/{{#{key}}}/, value.to_s)
65
+ end
66
+ end
67
+ end
68
+
69
+ def obscure_value(id)
70
+ id = id.to_i if id.is_a?(String) && id =~ /^\d+$/
71
+
72
+ if id.is_a?(Numeric)
73
+ ::Base62.encode(id)
74
+ else
75
+ self.class.rot13(id.to_s)
76
+ end
77
+ end
78
+
79
+ def key_params
80
+ @key_params ||= Hashie::Mash.new(
81
+ namespace: namespace ? "#{namespace}|" : '',
82
+ data_id: obscure_value(data_id),
83
+ meta_id: obscure_value(meta_id)
84
+ )
85
+ end
86
+
87
+ def meta_id
88
+ meta_key_transformer&.call(consumer) || consumer
89
+ end
90
+
91
+ def data_id
92
+ data_key_transformer&.call(consumer) || consumer
78
93
  end
79
94
  end
80
95
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  module SimpleFeed
4
4
  module Providers
5
+ RUBY_MAJOR_VERSION = RUBY_VERSION.split('.')[0..1].join.to_i
6
+
5
7
  class Proxy
6
8
  attr_accessor :provider
7
9
 
@@ -15,7 +17,7 @@ module SimpleFeed
15
17
  end
16
18
 
17
19
  def initialize(provider_or_klass, *args, **options)
18
- self.provider = if provider_or_klass.is_a?(::String)
20
+ self.provider = if provider_or_klass.is_a?(::String) || provider_or_klass.is_a?(::Symbol)
19
21
  ::Object.const_get(provider_or_klass).new(*args, **options)
20
22
  else
21
23
  provider_or_klass
@@ -26,12 +28,23 @@ module SimpleFeed
26
28
  end
27
29
  end
28
30
 
29
- # Forward all other method calls to Provider
30
- def method_missing(name, *args, **opts, &block)
31
- if provider&.respond_to?(name)
32
- provider.send(name, *args, **opts, &block)
33
- else
34
- super(name, *args, **opts, &block)
31
+ if RUBY_MAJOR_VERSION >= 27
32
+ # Forward all other method calls to Provider
33
+ def method_missing(name, *args, **opts, &block)
34
+ if provider&.respond_to?(name)
35
+ provider.send(name, *args, **opts, &block)
36
+ else
37
+ super(name, *args, **opts, &block)
38
+ end
39
+ end
40
+ else
41
+ # Forward all other method calls to Provider
42
+ def method_missing(name, *args, &block)
43
+ if provider&.respond_to?(name)
44
+ provider.send(name, *args, &block)
45
+ else
46
+ super(name, *args, &block)
47
+ end
35
48
  end
36
49
  end
37
50
  end