highscore 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,10 @@
1
+ == 1.1.0 / 2013-04-
2
+
3
+ * added support for custom word ignore handlers using lambda functions
4
+ * fixed some performance issues with unnecessary ranking by unused criteria (consonants & vowels)
5
+ * added possibility to use bloomfilters for faster performance instead of Array#include?
6
+ * removed compatibility with Ruby 1.8.x
7
+
1
8
  == 1.0.0 / 2013-02-02
2
9
 
3
10
  * added per-language support for black- and whitelists (thanks to bobjflong)
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Easily find and rank keywords in long texts.
4
4
 
5
- [![Build Status](https://secure.travis-ci.org/domnikl/highscore.png?branch=develop)](http://travis-ci.org/domnikl/highscore) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/domnikl/highscore)
5
+ [![Build Status](https://secure.travis-ci.org/domnikl/highscore.png?branch=develop)](http://travis-ci.org/domnikl/highscore) [![Code Climate](https://codeclimate.com/github/domnikl/highscore.png)](https://codeclimate.com/github/domnikl/highscore)
6
6
 
7
7
  ## Features
8
8
 
@@ -11,9 +11,23 @@ Easily find and rank keywords in long texts.
11
11
  * directly get keywords from String objects
12
12
  * blacklist words via a plain text file, String or an Array of words (per language if needed)
13
13
  * optionally, configure a whitelist and only words from that list will get ranked
14
- * use word stemming (requires the `fast-stemmer` gem, or the `stemmer` gem)
14
+ * uses word stemming if necessary (requires the `fast-stemmer` or `stemmer` gem)
15
15
  * merge together Keywords from multiple sources
16
16
  * contains a CLI tool that operates on STDIN/OUT and is configurable via parameters
17
+ * can use `bloomfilter-rb` gem for better performance (optional)
18
+
19
+ ## Installation
20
+
21
+ * `[sudo] gem install highscore`
22
+
23
+ For better blacklist perfomance, use the `bloomfilter-rb` gem:
24
+
25
+ * `[sudo] gem install bloomfilter-rb`
26
+
27
+ To use word stemming, you need to have the `fast-stemmer` (C extension) or `stemmer` gem installed:
28
+
29
+ * `[sudo] gem install fast-stemmer`
30
+ * `[sudo] gem install stemmer`
17
31
 
18
32
  ## Examples
19
33
 
@@ -37,7 +51,7 @@ text.keywords.top(50).each do |keyword|
37
51
  keyword.weight # => rank weight (float)
38
52
  end
39
53
 
40
- # you could just use a string
54
+ # you could also just use a string
41
55
  keywords = "Foo bar baz".keywords(blacklist) do
42
56
  set :multiplier, 10
43
57
  end
@@ -59,9 +73,11 @@ end
59
73
  # not 1.0
60
74
  ```
61
75
 
62
- Have a look at bin/highscore, you can run highscore on your CLI and feed it with text on STDIN.
76
+ Have a look at `bin/highscore`, you can run highscore on your CLI and feed it with text on STDIN.
63
77
 
64
- ## Using a custom blacklist to ignore keywords
78
+ ## Blacklisting and Whitelisting
79
+
80
+ ### Using a custom blacklist to ignore keywords
65
81
 
66
82
  ```ruby
67
83
  # setting single words
@@ -82,7 +98,7 @@ blacklist = Highscore::Blacklist.load_default_file
82
98
  content = Highscore::Content.new "a string", blacklist
83
99
  ```
84
100
 
85
- ## Using a whitelist instead of ranking all words
101
+ ### Using a whitelist instead of ranking all words
86
102
 
87
103
  ```ruby
88
104
  # construct and inject it just like a blacklist
@@ -90,7 +106,7 @@ whitelist = Highscore::Whitelist.load %w{these are valid keywords}
90
106
  content = Highscore::Content.new "invalid words", whitelist
91
107
  ```
92
108
 
93
- ## Multiple languages
109
+ ## I18n
94
110
 
95
111
  ```ruby
96
112
  # Load a default blacklist
@@ -98,34 +114,25 @@ blacklist_default = Highscore::Blacklist.load "mister"
98
114
  text = Highscore::Content.new "oui mister interesting", blacklist_default
99
115
  text.keywords.top(3).join " "
100
116
 
101
- # Prints "interesting oui"
117
+ # => prints "interesting oui"
102
118
 
103
119
  # Load a rudimentary blacklist for French
104
120
  blacklist_francais = Highscore::Blacklist.load "oui"
105
- text.add_wordlist blacklist_francais, "fr"
121
+ text.add_wordlist blacklist_francais, :french
106
122
  text.keywords(:lang => :fr).top(3).join " "
107
123
 
108
- # Prints "interesting mister"
124
+ # => prints "interesting mister"
109
125
  ```
110
126
 
111
- ## Install
112
-
113
- * `[sudo] gem install highscore`
114
-
115
- To use word stemming, you need to have the `fast-stemmer` (C extension) or `stemmer` gem installed:
116
-
117
- * `[sudo] gem install fast-stemmer`
118
- * `[sudo] gem install stemmer`
119
-
120
127
  ## Author
121
128
 
122
- Original author: Dominik Liebler <liebler.dominik@googlemail.com>
129
+ Original author: Dominik Liebler <liebler.dominik@gmail.com>
123
130
 
124
131
  ## License
125
132
 
126
133
  (The MIT License)
127
134
 
128
- Copyright (c) 2012 Dominik Liebler
135
+ Copyright (c) 2013 Dominik Liebler
129
136
 
130
137
  Permission is hereby granted, free of charge, to any person obtaining
131
138
  a copy of this software and associated documentation files (the
data/Rakefile CHANGED
@@ -9,18 +9,22 @@ require "highscore"
9
9
  gem_name = "highscore-#{Highscore::VERSION}.gem"
10
10
 
11
11
  namespace :gem do
12
+ desc "clean previously generated gems"
12
13
  task :clean do
13
14
  system "rm -f *.gem"
14
15
  end
15
-
16
+
17
+ desc "build gem"
16
18
  task :build => [:clean, :test] do
17
19
  system "gem build highscore.gemspec"
18
20
  end
19
21
 
22
+ desc "install gem"
20
23
  task :install => :build do
21
24
  system "gem install #{gem_name}"
22
25
  end
23
26
 
27
+ desc "release to rubygems.org"
24
28
  task :release => :build do
25
29
  system "gem push #{gem_name}"
26
30
  end
@@ -1,5 +1,3 @@
1
- require 'rubygems' if RUBY_VERSION !~ /^1\.9/
2
-
3
1
  module Highscore
4
2
  # :stopdoc:
5
3
  LIBPATH = ::File.expand_path('..', __FILE__) + ::File::SEPARATOR
@@ -14,5 +14,17 @@ module Highscore
14
14
  file_path = File.join(File.dirname(__FILE__), %w{.. blacklist.txt})
15
15
  self.load_file(file_path)
16
16
  end
17
+
18
+ # filters a given keywords array
19
+ #
20
+ # @param Array keywords
21
+ # @return Array
22
+ def filter(keywords)
23
+ keywords.delete_if do |key, value|
24
+ include?(key.downcase)
25
+ end
26
+ end
27
+
28
+
17
29
  end
18
30
  end
@@ -1,9 +1,6 @@
1
1
  $:.unshift(File.dirname(__FILE__))
2
2
  require 'keywords'
3
3
 
4
- # external gems
5
- require 'rubygems'
6
-
7
4
  module Highscore
8
5
  class Content
9
6
  attr_reader :content
@@ -33,13 +30,10 @@ module Highscore
33
30
  :consonants => 0,
34
31
  :ignore_short_words => true,
35
32
  :ignore_case => false,
33
+ :ignore => nil,
36
34
  :word_pattern => /\p{Word}+/u,
37
35
  :stemming => false
38
36
  }
39
-
40
- if RUBY_VERSION =~ /^1\.8/
41
- @emphasis[:word_pattern] = /\w+/
42
- end
43
37
  end
44
38
 
45
39
  # configure ranking
@@ -71,6 +65,7 @@ module Highscore
71
65
  # @return Highscore::Keywords
72
66
  def keywords(opts = {})
73
67
  used_wordlist = nil
68
+
74
69
  if opts[:lang]
75
70
  used_wordlist = language_wordlists[opts[:lang].to_sym]
76
71
  else
@@ -80,16 +75,9 @@ module Highscore
80
75
  @emphasis[:stemming] = use_stemming?
81
76
 
82
77
  keywords = Keywords.new
83
-
84
- Keywords.find_keywords(processed_content, used_wordlist, word_pattern).each do |text|
85
- text = text.to_s
86
- text = text.stem if @emphasis[:stemming]
87
-
88
- if not (text.match(/^[\d]+(\.[\d]+){0,1}$/) or text.length <= 2)
89
- keywords << Highscore::Keyword.new(text, weight(text))
90
- elsif allow_short_words
91
- keywords << Highscore::Keyword.new(text, weight(text))
92
- end
78
+ Keywords.find_keywords(processed_content, used_wordlist, word_pattern).each do |word|
79
+ keyword = extract_keyword(word)
80
+ keywords << keyword unless keyword.nil?
93
81
  end
94
82
 
95
83
  keywords
@@ -99,10 +87,10 @@ module Highscore
99
87
  #
100
88
  # @return Highscore::Wordlist
101
89
  def wordlist
102
- unless @whitelist.nil?
103
- @whitelist
104
- else
90
+ if @whitelist.nil?
105
91
  @blacklist
92
+ else
93
+ @whitelist
106
94
  end
107
95
  end
108
96
 
@@ -116,6 +104,35 @@ module Highscore
116
104
 
117
105
  private
118
106
 
107
+ # extracts a single keyword from a single word
108
+ #
109
+ # @return Highscore::Keyword
110
+ def extract_keyword word
111
+ word = word.to_s
112
+ word = word.stem if @emphasis[:stemming]
113
+
114
+ unless ignore?(word)
115
+ Highscore::Keyword.new(word, weight(word))
116
+ end
117
+ end
118
+
119
+ # Internal: should the word be ignored or not?
120
+ #
121
+ # @return TrueClass FalseClass
122
+ def ignore? word
123
+ ignore = word.short?
124
+
125
+ # exception: allow short words
126
+ ignore = (not allow_short_words?) if ignore
127
+
128
+ # exception: custom handler
129
+ unless @emphasis[:ignore].nil?
130
+ ignore = @emphasis[:ignore].call(word)
131
+ end
132
+
133
+ ignore
134
+ end
135
+
119
136
  # processes the text content applying any necessary transformations
120
137
  #
121
138
  # @return String
@@ -128,8 +145,8 @@ module Highscore
128
145
 
129
146
  # allow short words to be rated
130
147
  #
131
- # @return TrueClass FalseClass
132
- def allow_short_words
148
+ # @return TrueClass|FalseClass
149
+ def allow_short_words?
133
150
  not @emphasis[:ignore_short_words]
134
151
  end
135
152
 
@@ -156,8 +173,7 @@ module Highscore
156
173
  end
157
174
 
158
175
  weight += vowels(text)
159
- weight += consonants(text)
160
- weight
176
+ weight + consonants(text)
161
177
  end
162
178
 
163
179
  # weight the vowels on a text
@@ -165,6 +181,8 @@ module Highscore
165
181
  # @param text String
166
182
  # @return Float
167
183
  def vowels(text)
184
+ return 0 if @emphasis[:vowels] == 0
185
+
168
186
  percent = text.vowels.length / text.length.to_f
169
187
  percent * @emphasis[:vowels]
170
188
  end
@@ -174,28 +192,31 @@ module Highscore
174
192
  # @param text String
175
193
  # @return Float
176
194
  def consonants(text)
195
+ return 0 if @emphasis[:consonants] == 0
196
+
177
197
  percent = text.consonants.length / text.length.to_f
178
198
  percent * @emphasis[:consonants]
179
199
  end
180
200
 
181
- # using stemming is only possible, if fast-stemmer is installed
182
- # doesn't work for JRuby
201
+ # Internal: using stemming is only possible if fast-stemmer is installed
202
+ # doesn't work for JRuby
203
+ #
204
+ # @return TrueClass|FalseClass
183
205
  def use_stemming?
184
- if @emphasis[:stemming]
206
+ return false unless @emphasis[:stemming]
207
+
208
+ gems = %w(fast_stemmer stemmer)
209
+
210
+ gems.each do |gem|
185
211
  begin
186
- require 'fast_stemmer'
187
- true
212
+ require gem
213
+ return true
188
214
  rescue LoadError
189
- begin
190
- require 'stemmer'
191
- true
192
- rescue LoadError
193
- false
194
- end
215
+ false
195
216
  end
196
- else
197
- false
198
217
  end
199
218
  end
219
+
220
+
200
221
  end
201
- end
222
+ end
@@ -3,7 +3,7 @@ module Highscore
3
3
  # keywords read from the content
4
4
  #
5
5
  class Keyword
6
- attr_accessor :weight, :text
6
+ attr_accessor :weight, :text, :percent
7
7
 
8
8
  # init a keyword
9
9
  #
@@ -14,6 +14,13 @@ module Highscore
14
14
  @weight = weight.to_f
15
15
  end
16
16
 
17
+ # sets a percent value (in the keywords context)
18
+ #
19
+ # @param percent Float
20
+ def percent=(percent)
21
+ @percent = percent.to_f
22
+ end
23
+
17
24
  # sort keywords
18
25
  #
19
26
  # @param other Highscore::Keyword
@@ -1,6 +1,3 @@
1
- # external
2
- require 'digest/sha1'
3
-
4
1
  module Highscore
5
2
 
6
3
  # keywords that were found in content
@@ -16,14 +13,11 @@ module Highscore
16
13
  # @return Highscore::Keywords
17
14
  def self.find_keywords content, wordlist, pattern=/\w+/
18
15
  keywords = content.to_s.scan(pattern).flatten
19
- keywords.delete_if do |key, value|
20
- if wordlist.kind_of? Highscore::Blacklist
21
- wordlist.include?(key.downcase)
22
- elsif wordlist.kind_of? Highscore::Whitelist
23
- not wordlist.include?(key.downcase)
24
- end
16
+
17
+ if not wordlist.nil? and wordlist.respond_to? :filter
18
+ keywords = wordlist.filter(keywords)
25
19
  end
26
-
20
+
27
21
  keywords.sort
28
22
  end
29
23
 
@@ -33,8 +27,7 @@ module Highscore
33
27
  @keywords = {}
34
28
  end
35
29
 
36
- # ranks the keywords and removes keywords that have a low ranking
37
- # or are blacklisted
30
+ # ranks the keywords
38
31
  #
39
32
  # @return Array
40
33
  def rank
@@ -46,20 +39,26 @@ module Highscore
46
39
  # @param n Fixnum
47
40
  # @return Array
48
41
  def top n = 10
49
- rank[0..(n - 1)]
42
+ tops = rank[0..(n - 1)]
43
+ sum_all = sum(n)
44
+
45
+ # set percentage values
46
+ tops.each do |keyword|
47
+ keyword.percent = keyword.weight * 100 / sum_all
48
+ end
49
+
50
+ tops
50
51
  end
51
52
 
52
53
  # add new keywords
53
54
  #
54
- # @param keyword String
55
+ # @param keyword Highscore::Keyword
55
56
  # @return Highscore::Keywords
56
57
  def <<(keyword)
57
- key = Digest::SHA1.hexdigest(keyword.text)
58
-
59
- if @keywords.has_key?(key)
60
- @keywords[key].weight += keyword.weight
58
+ if @keywords.has_key?(keyword.text)
59
+ @keywords[keyword.text].weight += keyword.weight
61
60
  else
62
- @keywords[key] = keyword
61
+ @keywords[keyword.text] = keyword
63
62
  end
64
63
 
65
64
  @keywords
@@ -102,6 +101,14 @@ module Highscore
102
101
  sort.reverse.first
103
102
  end
104
103
 
104
+ # Returns the sum of the weight of the top n keywords
105
+ #
106
+ # @return Float
107
+ def sum(n)
108
+ top = rank[0..(n - 1)]
109
+ top.map(&:weight).inject { |sum,weight| sum + weight }
110
+ end
111
+
105
112
  # merge in another keyword list, operates on self
106
113
  #
107
114
  # @return Highscore::Keywords
@@ -34,4 +34,11 @@ class String
34
34
  def consonants
35
35
  gsub(/[aeiou]/, '')
36
36
  end
37
+
38
+ # is this a short word?
39
+ #
40
+ # @return TrueClass|FalseClass
41
+ def short?
42
+ match(/^[\d]+(\.[\d]+){0,1}$/) or length <= 2
43
+ end
37
44
  end
@@ -5,6 +5,15 @@ module Highscore
5
5
  # whitelisted words
6
6
  #
7
7
  class Whitelist < Wordlist
8
-
8
+ # filters a given keywords array
9
+ #
10
+ # @param Array keywords
11
+ # @return Array
12
+ def filter(keywords)
13
+ keywords.delete_if do |key, value|
14
+ not include?(key.downcase)
15
+ end
16
+ end
17
+
9
18
  end
10
19
  end
@@ -8,7 +8,7 @@ module Highscore
8
8
  #
9
9
  # @param file_path String
10
10
  # @return Highscore::Wordlist
11
- def self.load_file file_path
11
+ def self.load_file(file_path, use_bloom_filter = true)
12
12
  words = File.read(file_path).split(' ')
13
13
  self.load(words)
14
14
  end
@@ -34,8 +34,11 @@ module Highscore
34
34
  attr_reader :words
35
35
 
36
36
  # @param words Array
37
- def initialize(words = [])
37
+ def initialize(words = [], use_bloom_filter = true)
38
38
  @words = words
39
+ @bloom_filter = nil
40
+
41
+ init_bloom_filter
39
42
  end
40
43
 
41
44
  # iterate over words
@@ -62,8 +65,12 @@ module Highscore
62
65
  #
63
66
  # @param keyword String
64
67
  # @return true/false
65
- def include? keyword
66
- @words.include? keyword
68
+ def include?(keyword)
69
+ unless @bloom_filter.nil?
70
+ @bloom_filter.include? keyword
71
+ else
72
+ @words.include? keyword
73
+ end
67
74
  end
68
75
 
69
76
  # add a new word to the blacklist
@@ -71,6 +78,40 @@ module Highscore
71
78
  # @param word String
72
79
  def <<(word)
73
80
  @words << word
81
+ @bloom_filter << word unless @bloom_filter.nil?
82
+ end
83
+
84
+ private
85
+
86
+ # determine whether bloom filters should be used
87
+ def use_bloom_filter
88
+ begin
89
+ require 'bloomfilter-rb'
90
+ true
91
+ rescue LoadError
92
+ false
93
+ end
94
+ end
95
+
96
+ # build a bloom filter out of this wordlist to determine faster
97
+ # if words should be black- or whitelisted
98
+ #
99
+ def init_bloom_filter
100
+ return unless use_bloom_filter
101
+
102
+ n = length # number of filter elements
103
+
104
+ if n > 0
105
+ b = 4 # bits per bucket
106
+ m = n * b * 10 # number of filter buckets
107
+
108
+ k = (0.7 * (m / n)).to_i # number of hash functions
109
+ k = 1 if k <= 1
110
+
111
+ @bloom_filter = BloomFilter::Native.new(:size => m, :bucket => b, :raise => true, :hashes => k)
112
+ each { |w| @bloom_filter.insert(w) }
113
+ end
74
114
  end
115
+
75
116
  end
76
117
  end
@@ -37,13 +37,7 @@ class TestContent < Highscore::TestCase
37
37
  content = 'Schöne Grüße, caractères, русский'
38
38
 
39
39
  content = Highscore::Content.new content
40
-
41
- if RUBY_VERSION =~ /^1\.8/
42
- # Ruby 1.8 doesn't support correct tokenization
43
- assert_equal 3, content.keywords.length
44
- else
45
- assert_equal 4, content.keywords.length
46
- end
40
+ assert_equal 4, content.keywords.length
47
41
  end
48
42
 
49
43
  def test_vowels_and_consonants
@@ -66,6 +60,14 @@ class TestContent < Highscore::TestCase
66
60
  assert_equal 4, keywords.length
67
61
  end
68
62
 
63
+ def test_ignore_custom
64
+ keywords = 'foobar a3832'.keywords do
65
+ set :ignore, lambda { |w| w.gsub(/[^0-9]/, '').length > 2 }
66
+ end
67
+
68
+ assert_equal 1, keywords.length
69
+ end
70
+
69
71
  def test_word_pattern
70
72
  keywords = 'foo Ruby foo Ruby'.keywords do
71
73
  set :word_pattern, /(?=(\b\w+\s\w+\b))/
@@ -30,4 +30,12 @@ class TestKeyword < Highscore::TestCase
30
30
  @keyword.weight = 10.123
31
31
  assert_equal 10.123, @keyword.weight
32
32
  end
33
+
34
+ def test_percent
35
+ # per default, percent is not used => nil
36
+ assert_nil @keyword.percent
37
+
38
+ @keyword.percent = 50.1
39
+ assert_equal 50.1, @keyword.percent
40
+ end
33
41
  end
@@ -51,8 +51,24 @@ class TestKeywords < Highscore::TestCase
51
51
  def test_top
52
52
  top = @keywords.top(1)
53
53
 
54
+ assert_equal(1, top.length)
54
55
  assert_equal('the', top[0].text)
55
56
  assert_equal(10.0, top[0].weight)
57
+ assert_equal(100.0, top[0].percent)
58
+ end
59
+
60
+ def test_percent
61
+ top = @keywords.top(10)
62
+
63
+ assert_equal 4, top.length
64
+ assert_equal 62.5, top[0].percent
65
+ assert_equal 18.75, top[1].percent
66
+ assert_equal 12.5, top[2].percent
67
+ assert_equal 6.25, top[3].percent
68
+ end
69
+
70
+ def test_sum
71
+ assert_equal(16, @keywords.sum(50))
56
72
  end
57
73
 
58
74
  def test_top_empty
@@ -41,4 +41,9 @@ class TestString < Highscore::TestCase
41
41
  assert_equal 90, keywords.first.weight
42
42
  assert_equal 30, keywords.last.weight
43
43
  end
44
+
45
+ def test_short?
46
+ assert "to".short?
47
+ assert (not "this ain't no short word".short?)
48
+ end
44
49
  end
@@ -54,4 +54,12 @@ class TestBlacklist < Highscore::TestCase
54
54
  assert blacklist.include?("foobar")
55
55
  assert !blacklist.include?("bla")
56
56
  end
57
+
58
+ def test_each
59
+ blacklist = Highscore::Wordlist.load "foo bar baz"
60
+
61
+ a = []
62
+ blacklist.each { |x| a << x }
63
+ assert_equal ['foo', 'bar', 'baz'], a
64
+ end
57
65
  end
@@ -1 +1 @@
1
- 1.0.0
1
+ 1.1.0
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: highscore
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -13,7 +13,7 @@ date: 2012-12-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: simplecov
16
- requirement: &70317557633680 !ruby/object:Gem::Requirement
16
+ requirement: &70314466936760 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 0.6.4
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70317557633680
24
+ version_requirements: *70314466936760
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: whatlanguage
27
- requirement: &70317557633200 !ruby/object:Gem::Requirement
27
+ requirement: &70314466936180 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,7 +32,7 @@ dependencies:
32
32
  version: 1.0.0
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70317557633200
35
+ version_requirements: *70314466936180
36
36
  description: Find and rank keywords in text.
37
37
  email: liebler.dominik@googlemail.com
38
38
  executables:
@@ -66,7 +66,7 @@ files:
66
66
  - History.txt
67
67
  - Rakefile
68
68
  - version.txt
69
- homepage: https://domnikl.github.com/highscore
69
+ homepage: http://domnikl.github.com/highscore
70
70
  licenses: []
71
71
  post_install_message:
72
72
  rdoc_options: