hit_counter 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -13
- checksums.yaml.gz.sig +0 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +23 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +17 -0
- data/.github/ISSUE_TEMPLATE/task.md +21 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +31 -0
- data/.github/dependabot.yml +11 -0
- data/.github/workflows/test.yml +35 -0
- data/.gitignore +2 -1
- data/.simplecov +15 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/CONTRIBUTING.md +34 -0
- data/Gemfile +4 -2
- data/LICENSE.txt +17 -11
- data/README.md +121 -0
- data/Rakefile +3 -1
- data/SECURITY.md +16 -0
- data/certs/ivanoblomov.pem +25 -0
- data/config/initializers/mime_types.rb +3 -1
- data/config/mongoid.yml +2 -2
- data/hit_counter.gemspec +29 -16
- data/lib/hit_counter.rb +77 -78
- data/lib/version.rb +4 -1
- data/public/images/digits/celtic/0.png +0 -0
- data/public/images/digits/celtic/1.png +0 -0
- data/public/images/digits/celtic/2.png +0 -0
- data/public/images/digits/celtic/3.png +0 -0
- data/public/images/digits/celtic/4.png +0 -0
- data/public/images/digits/celtic/5.png +0 -0
- data/public/images/digits/celtic/6.png +0 -0
- data/public/images/digits/celtic/7.png +0 -0
- data/public/images/digits/celtic/8.png +0 -0
- data/public/images/digits/celtic/9.png +0 -0
- data/public/images/digits/odometer/0.png +0 -0
- data/public/images/digits/odometer/1.png +0 -0
- data/public/images/digits/odometer/2.png +0 -0
- data/public/images/digits/odometer/3.png +0 -0
- data/public/images/digits/odometer/4.png +0 -0
- data/public/images/digits/odometer/5.png +0 -0
- data/public/images/digits/odometer/6.png +0 -0
- data/public/images/digits/odometer/7.png +0 -0
- data/public/images/digits/odometer/8.png +0 -0
- data/public/images/digits/odometer/9.png +0 -0
- data/public/images/digits/scout/0.png +0 -0
- data/public/images/digits/scout/1.png +0 -0
- data/public/images/digits/scout/2.png +0 -0
- data/public/images/digits/scout/3.png +0 -0
- data/public/images/digits/scout/4.png +0 -0
- data/public/images/digits/scout/5.png +0 -0
- data/public/images/digits/scout/6.png +0 -0
- data/public/images/digits/scout/7.png +0 -0
- data/public/images/digits/scout/8.png +0 -0
- data/public/images/digits/scout/9.png +0 -0
- data/spec/lib/hit_counter_spec.rb +97 -72
- data/spec/spec_helper.rb +5 -6
- data.tar.gz.sig +0 -0
- metadata +175 -74
- metadata.gz.sig +0 -0
- data/.ruby-version +0 -1
- data/.travis.yml +0 -4
- data/Gemfile.lock +0 -74
- data/README.rdoc +0 -84
- data/public/images/digits/celtic/0.gif +0 -0
- data/public/images/digits/celtic/1.gif +0 -0
- data/public/images/digits/celtic/2.gif +0 -0
- data/public/images/digits/celtic/3.gif +0 -0
- data/public/images/digits/celtic/4.gif +0 -0
- data/public/images/digits/celtic/5.gif +0 -0
- data/public/images/digits/celtic/6.gif +0 -0
- data/public/images/digits/celtic/7.gif +0 -0
- data/public/images/digits/celtic/8.gif +0 -0
- data/public/images/digits/celtic/9.gif +0 -0
- data/public/images/digits/odometer/0.gif +0 -0
- data/public/images/digits/odometer/1.gif +0 -0
- data/public/images/digits/odometer/2.gif +0 -0
- data/public/images/digits/odometer/3.gif +0 -0
- data/public/images/digits/odometer/4.gif +0 -0
- data/public/images/digits/odometer/5.gif +0 -0
- data/public/images/digits/odometer/6.gif +0 -0
- data/public/images/digits/odometer/7.gif +0 -0
- data/public/images/digits/odometer/8.gif +0 -0
- data/public/images/digits/odometer/9.gif +0 -0
- data/public/images/digits/scout/0.gif +0 -0
- data/public/images/digits/scout/1.gif +0 -0
- data/public/images/digits/scout/2.gif +0 -0
- data/public/images/digits/scout/3.gif +0 -0
- data/public/images/digits/scout/4.gif +0 -0
- data/public/images/digits/scout/5.gif +0 -0
- data/public/images/digits/scout/6.gif +0 -0
- data/public/images/digits/scout/7.gif +0 -0
- data/public/images/digits/scout/8.gif +0 -0
- data/public/images/digits/scout/9.gif +0 -0
data/lib/hit_counter.rb
CHANGED
@@ -1,148 +1,147 @@
|
|
1
|
-
# Copyright
|
2
|
-
#
|
3
|
-
#
|
4
|
-
|
5
|
-
# the Free Software Foundation, either version 3 of the License, or
|
6
|
-
# (at your option) any later version.
|
7
|
-
#
|
8
|
-
# This program is distributed in the hope that it will be useful,
|
9
|
-
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
-
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
-
# GNU General Public License for more details.
|
12
|
-
#
|
13
|
-
# You should have received a copy of the GNU General Public License
|
14
|
-
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
1
|
+
# Copyright the HitCounter contributors.
|
2
|
+
# SPDX-License-Identifier: MIT
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
15
5
|
require 'rubygems'
|
16
6
|
require 'bundler/setup'
|
17
7
|
Bundler.require :default
|
18
8
|
|
9
|
+
# Ruby version of that old 90s chestnut, <BLINK>the web-site hit counter</BLINK>
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# hc = HitCounter.get 'cnn.com'
|
13
|
+
# hc.increment
|
19
14
|
class HitCounter
|
20
15
|
include Mongoid::Document
|
21
16
|
include Mongoid::Timestamps
|
22
17
|
|
23
|
-
# Mongo Config
|
18
|
+
# Mongo Config ===============================================================
|
19
|
+
field :hits, type: Integer, default: 0
|
24
20
|
field :url
|
25
|
-
field :hits, :type => Integer, :default => 0
|
26
21
|
|
27
22
|
# Validates the <code>HitCounter</code>'s URL.
|
28
23
|
#
|
29
|
-
# If the URI library can parse the value and the scheme is valid, then we
|
24
|
+
# If the URI library can parse the value and the scheme is valid, then we
|
25
|
+
# assume the url is valid.
|
30
26
|
class UrlValidator < ActiveModel::EachValidator
|
31
|
-
def validate_each
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
rescue Addressable::URI::InvalidURIError
|
39
|
-
record.errors[attribute] << 'Invalid URL'
|
40
|
-
end
|
27
|
+
def validate_each(record, attribute, value)
|
28
|
+
uri = Addressable::URI.parse value
|
29
|
+
raise Addressable::URI::InvalidURIError unless %w[http https].include?(
|
30
|
+
uri.scheme
|
31
|
+
)
|
32
|
+
rescue Addressable::URI::InvalidURIError
|
33
|
+
record.errors.add attribute, 'Invalid URL'
|
41
34
|
end
|
42
35
|
end
|
43
36
|
|
44
|
-
validates :
|
37
|
+
validates :hits, numericality: { only_integer: true }
|
38
|
+
validates :url, presence: true, url: true, uniqueness: true
|
45
39
|
|
46
|
-
# Class methods
|
40
|
+
# Class methods ==============================================================
|
47
41
|
|
48
|
-
# Returns a <code>HitCounter</code> matching the specified URL. The HitCounter
|
49
|
-
# matching one is found. In the latter case, the hits
|
42
|
+
# Returns a <code>HitCounter</code> matching the specified URL. The HitCounter
|
43
|
+
# is created if no matching one is found. In the latter case, the hits
|
44
|
+
# argument specifies the starting count.
|
50
45
|
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
#
|
56
|
-
|
57
|
-
|
58
|
-
args
|
59
|
-
|
46
|
+
# @param url [String] the URL for the site being counted
|
47
|
+
# @param hits [Integer] the number of hits to start with
|
48
|
+
# @return [HitCounter] the site's HitCounter
|
49
|
+
# @example
|
50
|
+
# hc = HitCounter.get 'cnn.com'
|
51
|
+
# hc = HitCounter.get 'cnn.com', 100
|
52
|
+
def self.get(url, hits = 0)
|
53
|
+
args = { url: normalize_url(url) }
|
54
|
+
args[:hits] = hits unless where(conditions: args).exists?
|
55
|
+
find_or_create_by args
|
60
56
|
end
|
61
57
|
|
62
|
-
# Instance methods: Overrides
|
58
|
+
# Instance methods: Overrides ================================================
|
63
59
|
|
64
60
|
# Sets the number of hits.
|
65
61
|
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
def hits=
|
62
|
+
# @param value [Integer] the initial number of hits
|
63
|
+
# @return [Integer] the number of hits
|
64
|
+
# @example
|
65
|
+
# hc.hits = 100
|
66
|
+
def hits=(value)
|
71
67
|
self[:hits] = value.to_i
|
72
68
|
end
|
73
69
|
|
74
70
|
# Sets the URL to be tracked. The http prefix is optional.
|
75
71
|
#
|
76
|
-
#
|
77
|
-
#
|
78
|
-
#
|
79
|
-
#
|
80
|
-
def url=
|
81
|
-
self[:url] = HitCounter.normalize_url value
|
72
|
+
# @param value [String] the URL for the site being counted
|
73
|
+
# @return [String] the normalized URL
|
74
|
+
# @example
|
75
|
+
# hc.url = 'cnn.com'
|
76
|
+
def url=(value)
|
77
|
+
self[:url] = HitCounter.send(:normalize_url, value)
|
82
78
|
end
|
83
79
|
|
84
|
-
# Instance methods
|
80
|
+
# Instance methods ===========================================================
|
85
81
|
|
86
82
|
# Returns the hit count as a PNG image, using the specified style:
|
87
83
|
# 1 odometer
|
88
84
|
# 2 scout
|
89
85
|
# 3 Celtic
|
90
86
|
#
|
91
|
-
#
|
92
|
-
#
|
93
|
-
#
|
94
|
-
#
|
95
|
-
def image
|
96
|
-
image = HitCounter.cat_image
|
87
|
+
# @param style_number [String] the image style
|
88
|
+
# @return [Magick::Image] the current hit count as a PNG image
|
89
|
+
# @example
|
90
|
+
# hc.image = 1
|
91
|
+
def image(style_number)
|
92
|
+
image = HitCounter.send(:cat_image, hits.to_s, HitCounter.send(:normalize_style_number, style_number))
|
97
93
|
image.format = 'png'
|
98
94
|
image
|
99
95
|
end
|
100
96
|
|
101
97
|
# Increments the hit count and saves the HitCounter.
|
102
98
|
#
|
103
|
-
#
|
104
|
-
#
|
99
|
+
# @return [true] if the save was successful
|
100
|
+
# @example
|
101
|
+
# hc.increment
|
105
102
|
def increment
|
106
103
|
self.hits += 1
|
107
|
-
|
104
|
+
save
|
108
105
|
end
|
109
106
|
|
110
|
-
|
111
|
-
|
112
|
-
STYLES = ['odometer', 'scout', 'celtic']
|
107
|
+
STYLES = %w[odometer scout celtic].freeze
|
113
108
|
|
114
|
-
def self.cat_image
|
109
|
+
private_class_method def self.cat_image(number, style_index, images = Magick::ImageList.new)
|
115
110
|
return images.append(false) if number.blank?
|
116
|
-
|
111
|
+
|
112
|
+
cat_image(number[1..-1], style_index, images <<
|
113
|
+
Magick::Image.read("#{Rails.root}/public/images/digits/"\
|
114
|
+
"#{STYLES[style_index]}/#{number[0..0]}.png").first)
|
117
115
|
end
|
118
116
|
|
119
|
-
def self.normalize_style_number
|
117
|
+
private_class_method def self.normalize_style_number(value)
|
120
118
|
value = value.to_i
|
121
|
-
value -= 1 if value
|
119
|
+
value -= 1 if value.positive?
|
122
120
|
|
123
|
-
if value >= 0 && value < STYLES.size
|
124
|
-
|
125
|
-
|
126
|
-
value % 3
|
127
|
-
end
|
121
|
+
return value if value >= 0 && value < STYLES.size
|
122
|
+
|
123
|
+
value % 3
|
128
124
|
end
|
129
125
|
|
130
|
-
def self.normalize_url
|
126
|
+
private_class_method def self.normalize_url(value)
|
131
127
|
value !~ %r{^http://} ? "http://#{value}" : value
|
132
128
|
end
|
133
129
|
end
|
134
130
|
|
135
131
|
if defined? Rails
|
132
|
+
# Overriding Rails to include install task
|
136
133
|
class Railtie < Rails::Railtie
|
137
134
|
rake_tasks do
|
138
135
|
namespace :hit_counter do
|
139
136
|
desc 'Install HitCounter into your app.'
|
140
137
|
task :install do
|
141
138
|
puts 'Installing required image files...'
|
142
|
-
system "rsync -ruv #{Gem.searcher.find('hit_counter').full_gem_path}
|
143
|
-
|
139
|
+
system "rsync -ruv #{Gem.searcher.find('hit_counter').full_gem_path}"\
|
140
|
+
'/config .'
|
141
|
+
system "rsync -ruv #{Gem.searcher.find('hit_counter').full_gem_path}"\
|
142
|
+
'/public .'
|
144
143
|
end
|
145
144
|
end
|
146
145
|
end
|
147
146
|
end
|
148
|
-
end
|
147
|
+
end
|
data/lib/version.rb
CHANGED
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -1,118 +1,143 @@
|
|
1
|
-
|
1
|
+
# Copyright the HitCounter contributors.
|
2
|
+
# SPDX-License-Identifier: MIT
|
3
|
+
# frozen_string_literal: true
|
2
4
|
|
5
|
+
require "#{File.dirname(__FILE__)}/../spec_helper"
|
6
|
+
|
7
|
+
JAVASCRIPT_HACK = %q(eval("console.log('All your base belong to us!')"))
|
8
|
+
# rubocop:disable Metrics/BlockLength
|
3
9
|
describe HitCounter do
|
4
|
-
before
|
5
|
-
|
6
|
-
|
7
|
-
end
|
10
|
+
before(:all) { described_class.delete_all }
|
11
|
+
|
12
|
+
subject { described_class.get 'www.google.com' }
|
8
13
|
|
9
|
-
describe '
|
10
|
-
context '
|
11
|
-
|
14
|
+
describe '.get' do
|
15
|
+
context 'with existing URL' do
|
16
|
+
let!(:hit_counter2) { described_class.get subject.url, 10 }
|
12
17
|
|
13
|
-
describe '
|
14
|
-
it { expect(
|
18
|
+
describe '.count' do
|
19
|
+
it { expect(described_class.count).to eq 1 }
|
15
20
|
end
|
16
21
|
|
17
22
|
context 'with starting count 10' do
|
18
|
-
describe 'hits' do
|
19
|
-
it { expect(
|
23
|
+
describe '#hits' do
|
24
|
+
it { expect(hit_counter2.hits).to eq 10 }
|
20
25
|
end
|
21
26
|
end
|
22
27
|
end
|
28
|
+
context 'with new URL' do
|
29
|
+
before { described_class.get 'www.cnn.com' }
|
23
30
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
describe 'document count' do
|
28
|
-
it { expect(HitCounter.count).to eq 2 }
|
31
|
+
describe '.count' do
|
32
|
+
it { expect(described_class.count).to eq 2 }
|
29
33
|
end
|
30
34
|
|
31
35
|
context 'with blank starting count' do
|
32
36
|
describe '#hits' do
|
33
|
-
it
|
37
|
+
it do
|
38
|
+
expect(described_class.get('www.nytimes.com', nil).hits).to eq 0
|
39
|
+
end
|
34
40
|
end
|
35
41
|
end
|
36
|
-
|
37
42
|
context 'with starting count 10' do
|
38
43
|
describe '#hits' do
|
39
|
-
it { expect(
|
44
|
+
it { expect(described_class.get('online.wsj.com', 10).hits).to eq 10 }
|
40
45
|
end
|
41
46
|
end
|
42
47
|
end
|
43
|
-
|
48
|
+
context 'with JavaScript' do
|
49
|
+
before { described_class.delete_all }
|
44
50
|
|
45
|
-
|
46
|
-
subject { @hit_counter.hits }
|
51
|
+
subject { described_class.get(JAVASCRIPT_HACK) }
|
47
52
|
|
48
|
-
|
53
|
+
it do
|
54
|
+
subject.save
|
55
|
+
expect(subject).to be_invalid
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
describe '#hits' do
|
60
|
+
it { expect(subject.hits).to eq 0 }
|
49
61
|
|
50
62
|
context 'when incremented' do
|
51
|
-
before {
|
63
|
+
before { subject.increment }
|
52
64
|
|
53
|
-
it { expect(
|
65
|
+
it { expect(subject.hits).to eq 1 }
|
54
66
|
end
|
55
67
|
end
|
68
|
+
describe '#hits=' do
|
69
|
+
context 'with JavaScript' do
|
70
|
+
before { subject.hits = JAVASCRIPT_HACK }
|
56
71
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
it { expect(subject).to be_a Magick::Image }
|
72
|
+
it { expect(subject.hits).to eq 0 }
|
73
|
+
end
|
61
74
|
end
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
it { expect(HitCounter.normalize_style_number(nil)).to be_zero }
|
75
|
+
describe '#image' do
|
76
|
+
context 'with a valid :style_number' do
|
77
|
+
it { expect(subject.image('1').filename).to eq "./public/images/digits/odometer/#{subject.hits}.png" }
|
66
78
|
end
|
67
|
-
|
68
|
-
|
69
|
-
|
79
|
+
context 'with JavaScript' do
|
80
|
+
it {
|
81
|
+
expect(subject.image(JAVASCRIPT_HACK).filename).to eq "./public/images/digits/odometer/#{subject.hits}.png"
|
82
|
+
}
|
70
83
|
end
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
84
|
+
end
|
85
|
+
describe '#url=' do
|
86
|
+
context 'with JavaScript' do
|
87
|
+
before do
|
88
|
+
subject.url = JAVASCRIPT_HACK
|
89
|
+
subject.save
|
75
90
|
end
|
76
91
|
|
77
|
-
it
|
78
|
-
expect(HitCounter.normalize_style_number('3')).to eq 2
|
79
|
-
end
|
92
|
+
it { expect(subject).to be_invalid }
|
80
93
|
end
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
94
|
+
end
|
95
|
+
describe '.normalize_style_number' do
|
96
|
+
context 'with nil' do
|
97
|
+
it { expect(described_class.send(:normalize_style_number, nil)).to be_zero }
|
98
|
+
end
|
99
|
+
context 'with empty string' do
|
100
|
+
it { expect(described_class.send(:normalize_style_number, '')).to be_zero }
|
101
|
+
end
|
102
|
+
context 'with JavaScript' do
|
103
|
+
it { expect(described_class.send(:normalize_style_number, JAVASCRIPT_HACK)).to be_zero }
|
104
|
+
end
|
105
|
+
context 'with number in range' do
|
106
|
+
{
|
107
|
+
'1' => 0,
|
108
|
+
'3' => 2
|
109
|
+
}.each do |input, output|
|
110
|
+
context "with '#{input}'" do
|
111
|
+
it do
|
112
|
+
expect(described_class.send(:normalize_style_number, input)).to eq output
|
91
113
|
end
|
92
114
|
end
|
93
115
|
end
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
116
|
+
end
|
117
|
+
context 'with number outside range' do
|
118
|
+
{
|
119
|
+
'-31' => 2,
|
120
|
+
'-1' => 2,
|
121
|
+
'4' => 0,
|
122
|
+
'34' => 0
|
123
|
+
}.each do |input, output|
|
124
|
+
context "with '#{input}'" do
|
125
|
+
it do
|
126
|
+
expect(described_class.send(:normalize_style_number, input)).to eq output
|
103
127
|
end
|
104
128
|
end
|
105
129
|
end
|
106
130
|
end
|
107
131
|
end
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
132
|
+
describe '.normalize_url' do
|
133
|
+
{
|
134
|
+
'cnn.com' => 'http://cnn.com',
|
135
|
+
'http://www.nytimes.com' => 'http://www.nytimes.com'
|
136
|
+
}.each do |input, output|
|
137
|
+
context "with '#{input}'" do
|
138
|
+
it { expect(described_class.send(:normalize_url, input)).to eq output }
|
139
|
+
end
|
116
140
|
end
|
117
141
|
end
|
118
|
-
end
|
142
|
+
end
|
143
|
+
# rubocop:enable Metrics/BlockLength
|
data/spec/spec_helper.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
|
-
|
2
|
-
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
3
|
require 'simplecov'
|
5
|
-
|
6
|
-
|
4
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
5
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
7
6
|
require 'addressable/uri'
|
8
7
|
require 'mongoid'
|
9
8
|
require 'RMagick'
|
@@ -12,11 +11,11 @@ require 'rspec'
|
|
12
11
|
require 'hit_counter'
|
13
12
|
|
14
13
|
Mongoid.load! 'config/mongoid.yml', :test
|
15
|
-
|
14
|
+
Mongo::Logger.logger.level = ::Logger::WARN
|
16
15
|
|
17
16
|
# Stub Rails root during tests.
|
18
17
|
module Rails
|
19
18
|
def self.root
|
20
19
|
'.'
|
21
20
|
end
|
22
|
-
end
|
21
|
+
end
|
data.tar.gz.sig
ADDED
Binary file
|