fc-reminder 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/Gemfile +3 -0
- data/LICENSE +27 -0
- data/README.md +10 -0
- data/Rakefile +13 -0
- data/bin/fc-reminder +90 -0
- data/fc-reminder.gemspec +31 -0
- data/lib/fc-reminder.rb +13 -0
- data/lib/fc-reminder/gateways/base.rb +10 -0
- data/lib/fc-reminder/gateways/twilio.rb +30 -0
- data/lib/fc-reminder/providers/base.rb +23 -0
- data/lib/fc-reminder/providers/livescore.rb +59 -0
- data/lib/fc-reminder/reminder.rb +41 -0
- data/lib/fc-reminder/runner.rb +68 -0
- data/lib/fc-reminder/version.rb +3 -0
- data/spec/fc-reminder/gateways/base_spec.rb +21 -0
- data/spec/fc-reminder/gateways/twilio_spec.rb +11 -0
- data/spec/fc-reminder/providers/base_spec.rb +30 -0
- data/spec/fc-reminder/providers/livescore_spec.rb +44 -0
- data/spec/fc-reminder/reminder_spec.rb +117 -0
- data/spec/fc-reminder/runner_spec.rb +64 -0
- data/spec/fc-reminder/version_spec.rb +7 -0
- data/spec/fc-reminder_spec.rb +10 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/support/fakeweb/fake_page_with_match.html +41 -0
- data/spec/support/fakeweb/fake_page_without_match.html +52 -0
- data/spec/support/helpers.rb +18 -0
- metadata +210 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b5ce7bf3d6d953a9e54908ae34a2bd2396baf817
|
4
|
+
data.tar.gz: bcdc3d656757ae1435d8d0ffe91ef6994727cc0b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 534d323b3629151c2f241ce61db8bd44df904192f587023dfcbf654f23f3fb21a7eafc872ebefb77a808ab4813e83039c0996e409ea40c4c17c0a6fe2c8ead6d
|
7
|
+
data.tar.gz: 41f6f401684ace0fae51de6e0aa4b7bb53cb188a9293447cbc481e26dd9c88f86a246db0d4962c80d13b752de5c8d3ea86c198241710008abe38c2d4571c3992
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
Copyright (c) 2013, Kamil Kieliszczyk
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without modification,
|
5
|
+
are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright notice, this
|
8
|
+
list of conditions and the following disclaimer.
|
9
|
+
|
10
|
+
* Redistributions in binary form must reproduce the above copyright notice, this
|
11
|
+
list of conditions and the following disclaimer in the documentation and/or
|
12
|
+
other materials provided with the distribution.
|
13
|
+
|
14
|
+
* Neither the name of the {organization} nor the names of its
|
15
|
+
contributors may be used to endorse or promote products derived from
|
16
|
+
this software without specific prior written permission.
|
17
|
+
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
19
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
20
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
22
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
23
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
24
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
25
|
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
26
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
27
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
|
6
|
+
require 'rspec/core'
|
7
|
+
require 'rspec/core/rake_task'
|
8
|
+
|
9
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
10
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
11
|
+
end
|
12
|
+
|
13
|
+
task default: :spec
|
data/bin/fc-reminder
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'mixlib/cli'
|
5
|
+
require 'fc-reminder'
|
6
|
+
|
7
|
+
class FCReminderCLI
|
8
|
+
include Mixlib::CLI
|
9
|
+
|
10
|
+
BIN_BASENAME = File.basename(__FILE__)
|
11
|
+
HOME_DIR = '~/fcreminder'
|
12
|
+
|
13
|
+
banner "Usage: #{BIN_BASENAME} [options] start|stop|restart|run"
|
14
|
+
|
15
|
+
option :team,
|
16
|
+
short: '-t STRING',
|
17
|
+
long: '--team STRING',
|
18
|
+
required: true,
|
19
|
+
on: :head,
|
20
|
+
description: 'Team name'
|
21
|
+
|
22
|
+
option :recipient,
|
23
|
+
short: '-r STRING',
|
24
|
+
long: '--recipient STRING',
|
25
|
+
required: true,
|
26
|
+
on: :head,
|
27
|
+
description: 'Recipient data'
|
28
|
+
|
29
|
+
option :check,
|
30
|
+
short: '-c HH:MM',
|
31
|
+
long: '--check HH:MM',
|
32
|
+
default: '12:00',
|
33
|
+
description: 'Time at which presence of a match will be checked (default: 12:00)'
|
34
|
+
|
35
|
+
option :daemon,
|
36
|
+
short: '-D',
|
37
|
+
long: '--daemon',
|
38
|
+
default: false,
|
39
|
+
boolean: true,
|
40
|
+
description: 'Daemonize process (default: false)'
|
41
|
+
|
42
|
+
option :dir,
|
43
|
+
short: '-d',
|
44
|
+
long: '--dir DIR',
|
45
|
+
default: File.expand_path(HOME_DIR),
|
46
|
+
description: "Directory to change to once the process starts (default: #{File.expand_path(HOME_DIR)})",
|
47
|
+
proc: lambda { |dir| File.expand_path(dir) }
|
48
|
+
|
49
|
+
option :monitor,
|
50
|
+
short: '-m',
|
51
|
+
long: '--monitor',
|
52
|
+
default: false,
|
53
|
+
boolean: true,
|
54
|
+
description: 'Start monitor process (default: false)'
|
55
|
+
|
56
|
+
option :identifier,
|
57
|
+
short: '-i STRING',
|
58
|
+
long: '--identifier STRING',
|
59
|
+
description: 'An identifier for the process (default: value of --team)'
|
60
|
+
|
61
|
+
option :log,
|
62
|
+
short: '-l',
|
63
|
+
long: '--log',
|
64
|
+
default: false,
|
65
|
+
boolean: true,
|
66
|
+
description: 'Redirect both STDOUT and STDERR to a logfile named FCReminder.<team>.output in the directory defined in --log-dir (default: false)'
|
67
|
+
|
68
|
+
option :logdir,
|
69
|
+
long: '--log-dir DIR',
|
70
|
+
default: File.expand_path("#{HOME_DIR}/log"),
|
71
|
+
description: "A specific directory to put the log files into (default: #{File.expand_path("#{HOME_DIR}/log")}",
|
72
|
+
proc: lambda { |dir| File.expand_path(dir) }
|
73
|
+
|
74
|
+
def run
|
75
|
+
parse_options
|
76
|
+
config[:argv] = opt_parser.parse!(ARGV)
|
77
|
+
create_dir(config[:dir])
|
78
|
+
create_dir(config[:logdir])
|
79
|
+
config
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def create_dir(dir)
|
85
|
+
Dir.mkdir(dir) unless File.exists?(dir)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
FCReminder::Runner.new(FCReminderCLI.new.run).start
|
90
|
+
|
data/fc-reminder.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
$:.push File.expand_path('../lib', __FILE__)
|
2
|
+
require 'fc-reminder/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |spec|
|
5
|
+
spec.name = 'fc-reminder'
|
6
|
+
spec.version = FCReminder::VERSION
|
7
|
+
spec.authors = ['Kamil Kieliszczyk']
|
8
|
+
spec.email = ['kamil@kieliszczyk.net']
|
9
|
+
spec.description = 'Simple reminder about an upcoming football game'
|
10
|
+
spec.summary = 'Simple reminder about an upcoming football game'
|
11
|
+
spec.homepage = 'https://github.com/kiela/fc-reminder'
|
12
|
+
|
13
|
+
spec.platform = Gem::Platform::RUBY
|
14
|
+
spec.required_rubygems_version = '>= 1.3.6'
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_dependency 'mechanize', '~> 2.7.3'
|
22
|
+
spec.add_dependency 'twilio-ruby', '~> 3.11.4'
|
23
|
+
spec.add_dependency 'mixlib-cli', '~> 1.3.0'
|
24
|
+
spec.add_dependency 'daemons', '~> 1.1.9'
|
25
|
+
spec.add_dependency 'clockwork', '~> 0.7.0'
|
26
|
+
|
27
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
28
|
+
spec.add_development_dependency 'rake'
|
29
|
+
spec.add_development_dependency 'rspec', '~> 3.0.0.beta1'
|
30
|
+
spec.add_development_dependency 'fakeweb'
|
31
|
+
end
|
data/lib/fc-reminder.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'fc-reminder/version'
|
2
|
+
require 'fc-reminder/reminder'
|
3
|
+
require 'fc-reminder/gateways/base'
|
4
|
+
require 'fc-reminder/gateways/twilio'
|
5
|
+
require 'fc-reminder/providers/base'
|
6
|
+
require 'fc-reminder/providers/livescore'
|
7
|
+
require 'fc-reminder/runner'
|
8
|
+
|
9
|
+
module FCReminder
|
10
|
+
def self.build(&customization_block)
|
11
|
+
Reminder.new(&customization_block)
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'twilio-ruby'
|
2
|
+
|
3
|
+
module FCReminder
|
4
|
+
module Gateways
|
5
|
+
class Twilio < Base
|
6
|
+
def send(recipient:, data:)
|
7
|
+
client = ::Twilio::REST::Client.new(
|
8
|
+
config[:account_sid],
|
9
|
+
config[:auth_token]
|
10
|
+
)
|
11
|
+
client.account.messages.create(
|
12
|
+
from: config[:phone_number],
|
13
|
+
to: recipient,
|
14
|
+
body: message(data)
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def message(data)
|
21
|
+
if data.empty?
|
22
|
+
"No match for today"
|
23
|
+
else
|
24
|
+
"Reminder of today's match in #{data[:country]} (#{data[:league]})"\
|
25
|
+
" between #{data[:team1]} and #{data[:team2]} at #{data[:time]}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'mechanize'
|
2
|
+
|
3
|
+
module FCReminder
|
4
|
+
module Providers
|
5
|
+
class Base
|
6
|
+
attr_reader :client
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@client = Mechanize.new do |agent|
|
10
|
+
agent.user_agent_alias = "Linux Firefox"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def url
|
15
|
+
""
|
16
|
+
end
|
17
|
+
|
18
|
+
def run(team_name:)
|
19
|
+
Hash.new
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module FCReminder
|
2
|
+
module Providers
|
3
|
+
class LiveScore < Base
|
4
|
+
BASE_URL = "http://www.livescore.com"
|
5
|
+
|
6
|
+
def url
|
7
|
+
"#{BASE_URL}/soccer/#{Time.now.strftime("%Y-%m-%d")}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def run(team_name:)
|
11
|
+
cell = search(page: client.get(url), team_name: team_name.downcase)
|
12
|
+
cell.nil? ? super : prepare_output_from(cell)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def search(page:, team_name:)
|
18
|
+
page.search(".content table.league-table td").find do |td|
|
19
|
+
has_team?(td) and team_match?(td, team_name)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def has_team?(cell)
|
24
|
+
%w(fh fa).include? cell.attributes["class"].value
|
25
|
+
end
|
26
|
+
|
27
|
+
def team_match?(cell, team_name)
|
28
|
+
cell.text.strip.downcase == team_name
|
29
|
+
end
|
30
|
+
|
31
|
+
def prepare_output_from(cell)
|
32
|
+
r = Hash.new
|
33
|
+
r.merge!(fetch(attrs: league_attrs, source: cell.parent.parent))
|
34
|
+
r.merge!(fetch(attrs: match_attrs, source: cell.parent))
|
35
|
+
r
|
36
|
+
end
|
37
|
+
|
38
|
+
def fetch(attrs:, source:)
|
39
|
+
attrs.inject({}) { |h,(k,v)| h[k] = source.search(v).text.strip; h }
|
40
|
+
end
|
41
|
+
|
42
|
+
def league_attrs
|
43
|
+
{
|
44
|
+
country: 'span.league > a',
|
45
|
+
league: 'span.league > span > a',
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
def match_attrs
|
50
|
+
{
|
51
|
+
time: 'td.fd',
|
52
|
+
team1: 'td.fh',
|
53
|
+
team2: 'td.fa'
|
54
|
+
}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module FCReminder
|
2
|
+
class Reminder
|
3
|
+
attr_reader :provider, :gateway
|
4
|
+
attr_accessor :team_name, :recipient
|
5
|
+
|
6
|
+
def initialize(&customization_block)
|
7
|
+
@provider = Providers::LiveScore.new
|
8
|
+
@gateway = Gateways::Twilio.new
|
9
|
+
eval_block &customization_block
|
10
|
+
end
|
11
|
+
|
12
|
+
def provider=(obj)
|
13
|
+
if obj.kind_of? Providers::Base
|
14
|
+
@provider = obj
|
15
|
+
else
|
16
|
+
raise 'Provider should inherits from FCReminder::Providers::Base'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def gateway=(obj)
|
21
|
+
if obj.kind_of? Gateways::Base
|
22
|
+
@gateway = obj
|
23
|
+
else
|
24
|
+
raise 'Gateway should inherits from FCReminder::Gateways::Base'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def run(&customization_block)
|
29
|
+
eval_block &customization_block
|
30
|
+
result = provider.run(team_name: team_name)
|
31
|
+
gateway.send(recipient: recipient, data: result) unless result.empty?
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def eval_block(&block)
|
37
|
+
instance_eval &block if block_given?
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'daemons'
|
2
|
+
require 'clockwork'
|
3
|
+
|
4
|
+
module FCReminder
|
5
|
+
class Runner
|
6
|
+
PROCESS_NAME = 'FCReminder'
|
7
|
+
attr_reader :options, :reminder
|
8
|
+
|
9
|
+
def initialize(options)
|
10
|
+
@options = options
|
11
|
+
@reminder = initialize_reminder
|
12
|
+
end
|
13
|
+
|
14
|
+
def start
|
15
|
+
set_reminder(@reminder, @options)
|
16
|
+
@options[:daemon] ? daemonize(@options) : run
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def initialize_reminder
|
22
|
+
FCReminder.build do |config|
|
23
|
+
config.gateway.config = {
|
24
|
+
account_sid: ENV['TWILIO_ACCOUNT_SID'],
|
25
|
+
auth_token: ENV['TWILIO_AUTH_TOKEN'],
|
26
|
+
phone_number: ENV['TWILIO_PHONE_NUMBER']
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_reminder(reminer, options)
|
32
|
+
Clockwork.every(1.day, 'fc-reminder.check', at: options[:check]) do
|
33
|
+
reminder.run do |conf|
|
34
|
+
conf.team_name = options[:team]
|
35
|
+
conf.recipient = options[:recipient]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def run
|
41
|
+
Clockwork::run
|
42
|
+
end
|
43
|
+
|
44
|
+
def daemonize(options)
|
45
|
+
name = process_name(options)
|
46
|
+
opts = daemonize_options(options)
|
47
|
+
Daemons.run_proc(name, opts) { run }
|
48
|
+
end
|
49
|
+
|
50
|
+
def process_name(options)
|
51
|
+
arr = [PROCESS_NAME]
|
52
|
+
arr.push(options[:identifier] ? options[:identifier] : options[:team])
|
53
|
+
arr.join('.')
|
54
|
+
end
|
55
|
+
|
56
|
+
def daemonize_options(options)
|
57
|
+
{
|
58
|
+
ARGV: options[:argv],
|
59
|
+
dir_mode: :normal,
|
60
|
+
dir: options[:dir],
|
61
|
+
monitor: options[:monitor],
|
62
|
+
log_dir: options[:logdir],
|
63
|
+
log_output: options[:log]
|
64
|
+
}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FCReminder::Gateways::Base do
|
4
|
+
subject(:gateway) { FCReminder::Gateways::Base.new }
|
5
|
+
|
6
|
+
context "setters" do
|
7
|
+
it "allows to set config attribute" do
|
8
|
+
config = {foo: 'bar'}
|
9
|
+
expect(gateway.config).to be_nil
|
10
|
+
gateway.config = config
|
11
|
+
expect(gateway.config).to eq(config)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context "#send" do
|
16
|
+
it "responds to send method" do
|
17
|
+
expect(gateway).to respond_to(:send)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FCReminder::Gateways::Twilio do
|
4
|
+
subject(:gateway) { FCReminder::Gateways::Twilio.new }
|
5
|
+
|
6
|
+
context "#initialize" do
|
7
|
+
it "should be instance of FCReminder::Gateways::Base" do
|
8
|
+
expect(gateway).to be_kind_of(FCReminder::Gateways::Base)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FCReminder::Providers::Base do
|
4
|
+
subject(:provider) { FCReminder::Providers::Base.new }
|
5
|
+
let(:team_name) { "Barcelona" }
|
6
|
+
|
7
|
+
context "#initialize" do
|
8
|
+
it "initializes HTTP client" do
|
9
|
+
expect(provider.client).not_to be_nil
|
10
|
+
end
|
11
|
+
|
12
|
+
it "disallows to change client" do
|
13
|
+
expect(provider).not_to respond_to(:client=)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "#url" do
|
18
|
+
it "returns empty string" do
|
19
|
+
expect(provider.url).to be_empty
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "#run" do
|
24
|
+
it "returns empty instance of Hash" do
|
25
|
+
expect(provider.run(team_name: team_name)).to be_instance_of(Hash)
|
26
|
+
expect(provider.run(team_name: team_name)).to be_empty
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FCReminder::Providers::LiveScore do
|
4
|
+
subject(:provider) { FCReminder::Providers::LiveScore.new }
|
5
|
+
let(:team_name) { "Barcelona" }
|
6
|
+
|
7
|
+
context "#initialize" do
|
8
|
+
it "should be instance of FCReminder::Providers::Base" do
|
9
|
+
expect(provider).to be_kind_of(FCReminder::Providers::Base)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context "#url" do
|
14
|
+
it "returns valid url" do
|
15
|
+
expect(provider.url).to match(/^#{URI::regexp}$/)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "#run" do
|
20
|
+
context "there is no match that day" do
|
21
|
+
before(:each) { fake_page_without_match(provider.url) }
|
22
|
+
|
23
|
+
it "returns default value" do
|
24
|
+
default_value = FCReminder::Providers::Base.new.run(team_name: team_name)
|
25
|
+
expect(provider.run(team_name: team_name)).to eq(default_value)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "there is a match that day" do
|
30
|
+
before(:each) { fake_page_with_match(provider.url) }
|
31
|
+
|
32
|
+
it "returns non-empty instance of Hash" do
|
33
|
+
expect(provider.run(team_name: team_name)).to be_an_instance_of(Hash)
|
34
|
+
expect(provider.run(team_name: team_name)).not_to be_empty
|
35
|
+
end
|
36
|
+
|
37
|
+
%w(country league time team1 team2).each do |attr|
|
38
|
+
it "has return value which includes #{attr}" do
|
39
|
+
expect(provider.run(team_name: team_name)).to have_key(attr.to_sym)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class TestProvider < FCReminder::Providers::Base
|
4
|
+
end
|
5
|
+
|
6
|
+
class TestGateway < FCReminder::Gateways::Base
|
7
|
+
end
|
8
|
+
|
9
|
+
describe FCReminder::Reminder do
|
10
|
+
let(:team_name) { "Team Name" }
|
11
|
+
let(:recipient) { "+1234567890" }
|
12
|
+
|
13
|
+
context "#initialize" do
|
14
|
+
it "sets default values" do
|
15
|
+
reminder = FCReminder.build
|
16
|
+
expect(reminder.provider).to be_kind_of(FCReminder::Providers::Base)
|
17
|
+
expect(reminder.gateway).to be_kind_of(FCReminder::Gateways::Base)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "allows to configure attributes by using a block" do
|
21
|
+
team, receiver = team_name, recipient
|
22
|
+
|
23
|
+
reminder = FCReminder.build do |config|
|
24
|
+
config.team_name = team
|
25
|
+
config.recipient = receiver
|
26
|
+
end
|
27
|
+
|
28
|
+
expect(reminder.team_name).to eq(team)
|
29
|
+
expect(reminder.recipient).to eq(receiver)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "setters" do
|
34
|
+
subject(:reminder) { FCReminder.build }
|
35
|
+
|
36
|
+
it "allows to set team_name attribute" do
|
37
|
+
expect(reminder.team_name).to be_nil
|
38
|
+
reminder.team_name = team_name
|
39
|
+
expect(reminder.team_name).to eq(team_name)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "allows to set recipient attribute" do
|
43
|
+
expect(reminder.recipient).to be_nil
|
44
|
+
reminder.recipient = recipient
|
45
|
+
expect(reminder.recipient).to eq(recipient)
|
46
|
+
end
|
47
|
+
|
48
|
+
context "provider" do
|
49
|
+
it "disallows to set when instance doesn't inherit from FCReminder::Providers::Base" do
|
50
|
+
expect{ reminder.provider = Object.new }.to raise_error
|
51
|
+
end
|
52
|
+
|
53
|
+
it "allows to set when instance inherits from FCReminder::Providers::Base" do
|
54
|
+
obj = TestProvider.new
|
55
|
+
expect(reminder.provider).not_to eq(obj)
|
56
|
+
reminder.provider = obj
|
57
|
+
expect(reminder.provider).to eq(obj)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "gateway" do
|
62
|
+
it "disallows to set when instance doesn't inherit from FCReminder::Gateways::Base" do
|
63
|
+
expect{ reminder.gateway = Object.new }.to raise_error
|
64
|
+
end
|
65
|
+
|
66
|
+
it "allows to set when instance inherits from FCReminder::Gateways::Base" do
|
67
|
+
obj = TestGateway.new
|
68
|
+
expect(reminder.gateway).not_to eq(obj)
|
69
|
+
reminder.gateway = obj
|
70
|
+
expect(reminder.gateway).to eq(obj)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context "#run" do
|
76
|
+
subject(:reminder) { FCReminder.build }
|
77
|
+
before(:each) { fake_page_with_match(reminder.provider.url) }
|
78
|
+
before(:each) { reminder.gateway.stub(:send) {} }
|
79
|
+
|
80
|
+
it "allows to set consumer data by using a block" do
|
81
|
+
expect(reminder.team_name).to be_nil
|
82
|
+
expect(reminder.recipient).to be_nil
|
83
|
+
|
84
|
+
team, receiver = team_name, recipient
|
85
|
+
reminder.run do |config|
|
86
|
+
config.team_name = team
|
87
|
+
config.recipient = receiver
|
88
|
+
end
|
89
|
+
|
90
|
+
expect(reminder.team_name).to eq(team)
|
91
|
+
expect(reminder.recipient).to eq(receiver)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "calls provider for data" do
|
95
|
+
reminder.provider.stub(:run) { Hash.new }
|
96
|
+
expect(reminder.provider).to receive(:run).with(team_name: team_name)
|
97
|
+
|
98
|
+
reminder.team_name = team_name
|
99
|
+
reminder.recipient = recipient
|
100
|
+
reminder.run
|
101
|
+
end
|
102
|
+
|
103
|
+
it "calls gateway for sending message" do
|
104
|
+
result = {foo: 'bar'}
|
105
|
+
reminder.provider.stub(:run) { result }
|
106
|
+
expect(reminder.gateway).to receive(:send).with(
|
107
|
+
recipient: recipient,
|
108
|
+
data: result
|
109
|
+
)
|
110
|
+
|
111
|
+
reminder.team_name = team_name
|
112
|
+
reminder.recipient = recipient
|
113
|
+
reminder.run
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe FCReminder::Runner do
|
4
|
+
let(:options) { {foo: "bar"} }
|
5
|
+
|
6
|
+
context "#initialize" do
|
7
|
+
subject(:runner) { FCReminder::Runner.new(options) }
|
8
|
+
|
9
|
+
it "sets options" do
|
10
|
+
expect(runner.options).to eq(options)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "initializes reminder" do
|
14
|
+
expect(runner.reminder).to be_an_instance_of(FCReminder::Reminder)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context "setters" do
|
19
|
+
subject(:runner) { FCReminder::Runner.new(options) }
|
20
|
+
|
21
|
+
it "disallows to change options after initializing" do
|
22
|
+
expect(runner).not_to respond_to(:options=)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "disallows to change reminder after initializing" do
|
26
|
+
expect(runner).not_to respond_to(:reminder=)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "#start" do
|
31
|
+
it "sets reminder" do
|
32
|
+
options = {daemon: false}
|
33
|
+
runner = FCReminder::Runner.new(options)
|
34
|
+
|
35
|
+
Clockwork.stub(:every) { |&block| block.call }
|
36
|
+
runner.reminder.stub(:run) {}
|
37
|
+
runner.stub(:run) {}
|
38
|
+
|
39
|
+
expect(runner.reminder).to receive(:run)
|
40
|
+
runner.start
|
41
|
+
end
|
42
|
+
|
43
|
+
it "starts normal process" do
|
44
|
+
options = {daemon: false}
|
45
|
+
runner = FCReminder::Runner.new(options)
|
46
|
+
|
47
|
+
runner.stub(:run) {}
|
48
|
+
|
49
|
+
expect(runner).to receive(:run)
|
50
|
+
runner.start
|
51
|
+
end
|
52
|
+
|
53
|
+
it "starts daemon process" do
|
54
|
+
options = {daemon: true}
|
55
|
+
runner = FCReminder::Runner.new(options)
|
56
|
+
|
57
|
+
runner.stub(:daemonize) {}
|
58
|
+
|
59
|
+
expect(runner).to receive(:daemonize)
|
60
|
+
runner.start
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
<div class="content">
|
2
|
+
<table class="league-table">
|
3
|
+
<colgroup><col class="cold">
|
4
|
+
<col class="colh">
|
5
|
+
<col class="cols">
|
6
|
+
<col class="cola">
|
7
|
+
</colgroup><tbody><tr>
|
8
|
+
<th colspan="4">
|
9
|
+
<span class="league">
|
10
|
+
<a href="/soccer/spain/"><strong>Spain</strong></a> - <span><a href="/soccer/spain/primera-division/">Liga BBVA</a></span>
|
11
|
+
</span>
|
12
|
+
<span class="date">December 1</span>
|
13
|
+
</th>
|
14
|
+
</tr>
|
15
|
+
<tr class="even">
|
16
|
+
<td class="fd"> 12:00 </td>
|
17
|
+
<td class="fh"> Real Betis </td>
|
18
|
+
<td class="fs"> ? - ? </td>
|
19
|
+
<td class="fa"> Rayo Vallecano </td>
|
20
|
+
</tr>
|
21
|
+
<tr>
|
22
|
+
<td class="fd"> 17:00 </td>
|
23
|
+
<td class="fh"> Granada </td>
|
24
|
+
<td class="fs"> ? - ? </td>
|
25
|
+
<td class="fa"> Sevilla </td>
|
26
|
+
</tr>
|
27
|
+
<tr class="even">
|
28
|
+
<td class="fd"> 19:00 </td>
|
29
|
+
<td class="fh"> Valencia </td>
|
30
|
+
<td class="fs"> ? - ? </td>
|
31
|
+
<td class="fa"> Osasuna </td>
|
32
|
+
</tr>
|
33
|
+
<tr>
|
34
|
+
<td class="fd"> 21:00 </td>
|
35
|
+
<td class="fh"> Athletic Bilbao </td>
|
36
|
+
<td class="fs"> ? - ? </td>
|
37
|
+
<td class="fa"> Barcelona </td>
|
38
|
+
</tr>
|
39
|
+
</tbody></table>
|
40
|
+
</div>
|
41
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
<div class="content">
|
2
|
+
<table class="league-table">
|
3
|
+
<colgroup><col class="cold">
|
4
|
+
<col class="colh">
|
5
|
+
<col class="cols">
|
6
|
+
<col class="cola">
|
7
|
+
</colgroup><tbody><tr>
|
8
|
+
<th colspan="4">
|
9
|
+
<span class="league">
|
10
|
+
<a href="/soccer/england/"><strong>England</strong></a> - <span><a href="/soccer/england/premier-league/">Premier League</a></span>
|
11
|
+
</span>
|
12
|
+
<span class="date">November 30</span>
|
13
|
+
</th>
|
14
|
+
</tr>
|
15
|
+
<tr class="even">
|
16
|
+
<td class="fd"> 16:00 </td>
|
17
|
+
<td class="fh"> Aston Villa </td>
|
18
|
+
<td class="fs"> ? - ? </td>
|
19
|
+
<td class="fa"> Sunderland </td>
|
20
|
+
</tr>
|
21
|
+
<tr>
|
22
|
+
<td class="fd"> 16:00 </td>
|
23
|
+
<td class="fh"> Cardiff City </td>
|
24
|
+
<td class="fs"> ? - ? </td>
|
25
|
+
<td class="fa"> Arsenal </td>
|
26
|
+
</tr>
|
27
|
+
<tr class="even">
|
28
|
+
<td class="fd"> 16:00 </td>
|
29
|
+
<td class="fh"> Everton </td>
|
30
|
+
<td class="fs"> ? - ? </td>
|
31
|
+
<td class="fa"> Stoke City </td>
|
32
|
+
</tr>
|
33
|
+
<tr>
|
34
|
+
<td class="fd"> 16:00 </td>
|
35
|
+
<td class="fh"> Norwich City </td>
|
36
|
+
<td class="fs"> ? - ? </td>
|
37
|
+
<td class="fa"> Crystal Palace </td>
|
38
|
+
</tr>
|
39
|
+
<tr class="even">
|
40
|
+
<td class="fd"> 16:00 </td>
|
41
|
+
<td class="fh"> West Ham United </td>
|
42
|
+
<td class="fs"> ? - ? </td>
|
43
|
+
<td class="fa"> Fulham </td>
|
44
|
+
</tr>
|
45
|
+
<tr>
|
46
|
+
<td class="fd"> 18:30 </td>
|
47
|
+
<td class="fh"> Newcastle United </td>
|
48
|
+
<td class="fs"> ? - ? </td>
|
49
|
+
<td class="fa"> West Bromwich Albion </td>
|
50
|
+
</tr>
|
51
|
+
</tbody></table>
|
52
|
+
</div>
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'fakeweb'
|
2
|
+
|
3
|
+
FakeWeb.allow_net_connect = false
|
4
|
+
|
5
|
+
def fake_page_without_match(url)
|
6
|
+
file = File.join(GEM_ROOT, "spec", "support", "fakeweb", "fake_page_without_match.html")
|
7
|
+
fake_html_page url, File.read(file)
|
8
|
+
end
|
9
|
+
|
10
|
+
def fake_page_with_match(url)
|
11
|
+
file = File.join(GEM_ROOT, "spec", "support", "fakeweb", "fake_page_with_match.html")
|
12
|
+
fake_html_page url, File.read(file)
|
13
|
+
end
|
14
|
+
|
15
|
+
def fake_html_page(url, body)
|
16
|
+
FakeWeb.register_uri(:get, url, body: body, content_type: "text/html")
|
17
|
+
end
|
18
|
+
|
metadata
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fc-reminder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kamil Kieliszczyk
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-12-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: mechanize
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 2.7.3
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 2.7.3
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: twilio-ruby
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 3.11.4
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 3.11.4
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: mixlib-cli
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.3.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.3.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: daemons
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.1.9
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.1.9
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: clockwork
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.7.0
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.7.0
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: bundler
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.3'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.3'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rake
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rspec
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 3.0.0.beta1
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 3.0.0.beta1
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: fakeweb
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
description: Simple reminder about an upcoming football game
|
140
|
+
email:
|
141
|
+
- kamil@kieliszczyk.net
|
142
|
+
executables:
|
143
|
+
- fc-reminder
|
144
|
+
extensions: []
|
145
|
+
extra_rdoc_files: []
|
146
|
+
files:
|
147
|
+
- ".gitignore"
|
148
|
+
- ".rspec"
|
149
|
+
- Gemfile
|
150
|
+
- LICENSE
|
151
|
+
- README.md
|
152
|
+
- Rakefile
|
153
|
+
- bin/fc-reminder
|
154
|
+
- fc-reminder.gemspec
|
155
|
+
- lib/fc-reminder.rb
|
156
|
+
- lib/fc-reminder/gateways/base.rb
|
157
|
+
- lib/fc-reminder/gateways/twilio.rb
|
158
|
+
- lib/fc-reminder/providers/base.rb
|
159
|
+
- lib/fc-reminder/providers/livescore.rb
|
160
|
+
- lib/fc-reminder/reminder.rb
|
161
|
+
- lib/fc-reminder/runner.rb
|
162
|
+
- lib/fc-reminder/version.rb
|
163
|
+
- spec/fc-reminder/gateways/base_spec.rb
|
164
|
+
- spec/fc-reminder/gateways/twilio_spec.rb
|
165
|
+
- spec/fc-reminder/providers/base_spec.rb
|
166
|
+
- spec/fc-reminder/providers/livescore_spec.rb
|
167
|
+
- spec/fc-reminder/reminder_spec.rb
|
168
|
+
- spec/fc-reminder/runner_spec.rb
|
169
|
+
- spec/fc-reminder/version_spec.rb
|
170
|
+
- spec/fc-reminder_spec.rb
|
171
|
+
- spec/spec_helper.rb
|
172
|
+
- spec/support/fakeweb/fake_page_with_match.html
|
173
|
+
- spec/support/fakeweb/fake_page_without_match.html
|
174
|
+
- spec/support/helpers.rb
|
175
|
+
homepage: https://github.com/kiela/fc-reminder
|
176
|
+
licenses: []
|
177
|
+
metadata: {}
|
178
|
+
post_install_message:
|
179
|
+
rdoc_options: []
|
180
|
+
require_paths:
|
181
|
+
- lib
|
182
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
183
|
+
requirements:
|
184
|
+
- - ">="
|
185
|
+
- !ruby/object:Gem::Version
|
186
|
+
version: '0'
|
187
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
188
|
+
requirements:
|
189
|
+
- - ">="
|
190
|
+
- !ruby/object:Gem::Version
|
191
|
+
version: 1.3.6
|
192
|
+
requirements: []
|
193
|
+
rubyforge_project:
|
194
|
+
rubygems_version: 2.1.11
|
195
|
+
signing_key:
|
196
|
+
specification_version: 4
|
197
|
+
summary: Simple reminder about an upcoming football game
|
198
|
+
test_files:
|
199
|
+
- spec/fc-reminder/gateways/base_spec.rb
|
200
|
+
- spec/fc-reminder/gateways/twilio_spec.rb
|
201
|
+
- spec/fc-reminder/providers/base_spec.rb
|
202
|
+
- spec/fc-reminder/providers/livescore_spec.rb
|
203
|
+
- spec/fc-reminder/reminder_spec.rb
|
204
|
+
- spec/fc-reminder/runner_spec.rb
|
205
|
+
- spec/fc-reminder/version_spec.rb
|
206
|
+
- spec/fc-reminder_spec.rb
|
207
|
+
- spec/spec_helper.rb
|
208
|
+
- spec/support/fakeweb/fake_page_with_match.html
|
209
|
+
- spec/support/fakeweb/fake_page_without_match.html
|
210
|
+
- spec/support/helpers.rb
|