acts_as_ferret 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +29 -6
- data/config/ferret_server.yml +12 -0
- data/install.rb +19 -0
- data/lib/act_methods.rb +194 -0
- data/lib/acts_as_ferret.rb +74 -52
- data/lib/class_methods.rb +222 -482
- data/lib/ferret_result.rb +36 -0
- data/lib/ferret_server.rb +89 -0
- data/lib/index.rb +31 -0
- data/lib/instance_methods.rb +112 -143
- data/lib/local_index.rb +257 -0
- data/lib/more_like_this.rb +47 -41
- data/lib/multi_index.rb +8 -11
- data/lib/remote_index.rb +50 -0
- data/lib/shared_index.rb +14 -0
- data/lib/shared_index_class_methods.rb +90 -0
- data/rakefile +88 -147
- data/script/ferret_server +18 -0
- data/script/ferret_start +67 -0
- data/script/ferret_stop +22 -0
- metadata +23 -11
- data/.init.rb.swp +0 -0
- data/.rakefile.swp +0 -0
- data/lib/.acts_as_ferret.rb.swp +0 -0
- data/lib/.class_methods.rb.swo +0 -0
- data/lib/.class_methods.rb.swp +0 -0
data/lib/more_like_this.rb
CHANGED
@@ -1,15 +1,8 @@
|
|
1
|
-
module
|
2
|
-
module Acts #:nodoc:
|
3
|
-
module ARFerret #:nodoc:
|
1
|
+
module ActsAsFerret #:nodoc:
|
4
2
|
|
5
|
-
|
3
|
+
module MoreLikeThis
|
6
4
|
|
7
|
-
|
8
|
-
def idf(doc_freq, num_docs)
|
9
|
-
return 0.0 if num_docs == 0
|
10
|
-
return Math.log(num_docs.to_f/(doc_freq+1)) + 1.0
|
11
|
-
end
|
12
|
-
end
|
5
|
+
module InstanceMethods
|
13
6
|
|
14
7
|
# returns other instances of this class, which have similar contents
|
15
8
|
# like this one. Basically works like this: find out n most interesting
|
@@ -25,18 +18,16 @@ module FerretMixin
|
|
25
18
|
# :field_names : Array of field names to use for similarity search (mandatory)
|
26
19
|
# :min_term_freq => 2, # Ignore terms with less than this frequency in the source doc.
|
27
20
|
# :min_doc_freq => 5, # Ignore words which do not occur in at least this many docs
|
28
|
-
# :min_word_length => nil, # Ignore words
|
29
|
-
#
|
21
|
+
# :min_word_length => nil, # Ignore words shorter than this length (longer words tend to
|
22
|
+
# be more characteristic for the document they occur in).
|
30
23
|
# :max_word_length => nil, # Ignore words if greater than this len.
|
31
24
|
# :max_query_terms => 25, # maximum number of terms in the query built
|
32
|
-
# :max_num_tokens => 5000, # maximum number of tokens to examine in a
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
# :analyzer => Ferret::Analysis::StandardAnalyzer.new # the analyzer to
|
39
|
-
# use
|
25
|
+
# :max_num_tokens => 5000, # maximum number of tokens to examine in a single field
|
26
|
+
# :boost => false, # when true, a boost according to the relative score of
|
27
|
+
# a term is applied to this Term's TermQuery.
|
28
|
+
# :similarity => 'DefaultAAFSimilarity' # the similarity implementation to use (the default
|
29
|
+
# equals Ferret's internal similarity implementation)
|
30
|
+
# :analyzer => 'Ferret::Analysis::StandardAnalyzer' # class name of the analyzer to use
|
40
31
|
# :append_to_query => nil # proc taking a query object as argument, which will be called after generating the query. can be used to further manipulate the query used to find related documents, i.e. to constrain the search to a given class in single table inheritance scenarios
|
41
32
|
# find_options : options handed over to find_by_contents
|
42
33
|
def more_like_this(options = {}, find_options = {})
|
@@ -49,30 +40,39 @@ module FerretMixin
|
|
49
40
|
:max_query_terms => 25, # maximum number of terms in the query built
|
50
41
|
:max_num_tokens => 5000, # maximum number of tokens to analyze when analyzing contents
|
51
42
|
:boost => false,
|
52
|
-
:similarity => DefaultAAFSimilarity
|
53
|
-
:analyzer => Ferret::Analysis::StandardAnalyzer
|
43
|
+
:similarity => 'ActsAsFerret::MoreLikeThis::DefaultAAFSimilarity', # class name of the similarity implementation to use
|
44
|
+
:analyzer => 'Ferret::Analysis::StandardAnalyzer', # class name of the analyzer to use
|
54
45
|
:append_to_query => nil,
|
55
46
|
:base_class => self.class # base class to use for querying, useful in STI scenarios where BaseClass.find_by_contents can be used to retrieve results from other classes, too
|
56
47
|
}.update(options)
|
57
|
-
index = self.class.ferret_index
|
58
48
|
#index.search_each('id:*') do |doc, score|
|
59
49
|
# puts "#{doc} == #{index[doc][:description]}"
|
60
50
|
#end
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
51
|
+
clazz = options[:base_class]
|
52
|
+
options[:base_class] = clazz.name
|
53
|
+
query = clazz.aaf_index.build_more_like_this_query(self.id, self.class.name, options)
|
54
|
+
options[:append_to_query].call(query) if options[:append_to_query]
|
55
|
+
clazz.find_by_contents(query, find_options)
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
module IndexMethods
|
61
|
+
|
62
|
+
def build_more_like_this_query(id, class_name, options)
|
63
|
+
[:similarity, :analyzer].each { |sym| options[sym] = options[sym].constantize.new }
|
64
|
+
ferret_index.synchronize do # avoid that concurrent writes close our reader
|
65
|
+
ferret_index.send(:ensure_reader_open)
|
66
|
+
reader = ferret_index.send(:reader)
|
67
|
+
term_freq_map = retrieve_terms(id, class_name, reader, options)
|
66
68
|
priority_queue = create_queue(term_freq_map, reader, options)
|
67
|
-
|
68
|
-
logger.debug "morelikethis-query: #{query}"
|
69
|
-
options[:append_to_query].call(query) if options[:append_to_query]
|
70
|
-
options[:base_class].find_by_contents(query, find_options)
|
69
|
+
create_query(id, class_name, priority_queue, options)
|
71
70
|
end
|
72
71
|
end
|
73
72
|
|
73
|
+
protected
|
74
74
|
|
75
|
-
def create_query(priority_queue, options={})
|
75
|
+
def create_query(id, class_name, priority_queue, options={})
|
76
76
|
query = Ferret::Search::BooleanQuery.new
|
77
77
|
qterms = 0
|
78
78
|
best_score = nil
|
@@ -93,8 +93,8 @@ module FerretMixin
|
|
93
93
|
qterms += 1
|
94
94
|
break if options[:max_query_terms] > 0 && qterms >= options[:max_query_terms]
|
95
95
|
end
|
96
|
-
# exclude
|
97
|
-
query.add_query(
|
96
|
+
# exclude the original record
|
97
|
+
query.add_query(query_for_record(id, class_name), :must_not)
|
98
98
|
return query
|
99
99
|
end
|
100
100
|
|
@@ -102,11 +102,13 @@ module FerretMixin
|
|
102
102
|
|
103
103
|
# creates a term/term_frequency map for terms from the fields
|
104
104
|
# given in options[:field_names]
|
105
|
-
def retrieve_terms(
|
105
|
+
def retrieve_terms(id, class_name, reader, options)
|
106
|
+
document_number = document_number(id, class_name)
|
106
107
|
field_names = options[:field_names]
|
107
108
|
max_num_tokens = options[:max_num_tokens]
|
108
109
|
term_freq_map = Hash.new(0)
|
109
110
|
doc = nil
|
111
|
+
record = nil
|
110
112
|
field_names.each do |field|
|
111
113
|
#puts "field: #{field}"
|
112
114
|
term_freq_vector = reader.term_vector(document_number, field)
|
@@ -125,7 +127,8 @@ module FerretMixin
|
|
125
127
|
content = doc[field]
|
126
128
|
unless content
|
127
129
|
# no term vector, no stored content, so try content from this instance
|
128
|
-
|
130
|
+
record ||= options[:base_class].constantize.find(id)
|
131
|
+
content = record.content_for_field_name(field.to_s)
|
129
132
|
end
|
130
133
|
puts "have doc: #{doc[:id]} with #{field} == #{content}"
|
131
134
|
token_count = 0
|
@@ -184,12 +187,16 @@ module FerretMixin
|
|
184
187
|
)
|
185
188
|
end
|
186
189
|
|
187
|
-
|
188
|
-
self[field] || self.instance_variable_get("@#{field.to_s}".to_sym) || self.send(field.to_sym)
|
189
|
-
end
|
190
|
+
end
|
190
191
|
|
192
|
+
class DefaultAAFSimilarity
|
193
|
+
def idf(doc_freq, num_docs)
|
194
|
+
return 0.0 if num_docs == 0
|
195
|
+
return Math.log(num_docs.to_f/(doc_freq+1)) + 1.0
|
196
|
+
end
|
191
197
|
end
|
192
198
|
|
199
|
+
|
193
200
|
class FrequencyQueueItem
|
194
201
|
attr_reader :word, :field, :score
|
195
202
|
def initialize(word, field, score)
|
@@ -198,6 +205,5 @@ module FerretMixin
|
|
198
205
|
end
|
199
206
|
|
200
207
|
end
|
201
|
-
end
|
202
208
|
end
|
203
209
|
|
data/lib/multi_index.rb
CHANGED
@@ -1,15 +1,14 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
|
4
|
-
# not threadsafe
|
1
|
+
module ActsAsFerret #:nodoc:
|
2
|
+
|
3
|
+
# this class is not threadsafe
|
5
4
|
class MultiIndex
|
6
5
|
|
7
|
-
# todo: check for necessary index rebuilds in this place, too
|
8
|
-
# idea - each class gets a create_reader method that does this
|
9
6
|
def initialize(model_classes, options = {})
|
10
7
|
@model_classes = model_classes
|
8
|
+
# ensure all models indexes exist
|
9
|
+
@model_classes.each { |m| m.aaf_index.ensure_index_exists }
|
11
10
|
default_fields = @model_classes.inject([]) do |fields, c|
|
12
|
-
fields + c.
|
11
|
+
fields + c.aaf_configuration[:ferret][:default_field]
|
13
12
|
end
|
14
13
|
@options = {
|
15
14
|
:default_field => default_fields
|
@@ -68,9 +67,9 @@ module FerretMixin
|
|
68
67
|
unless latest?
|
69
68
|
@sub_readers = @model_classes.map { |clazz|
|
70
69
|
begin
|
71
|
-
reader = Ferret::Index::IndexReader.new(clazz.
|
70
|
+
reader = Ferret::Index::IndexReader.new(clazz.aaf_configuration[:index_dir])
|
72
71
|
rescue Exception
|
73
|
-
puts "error opening #{clazz.
|
72
|
+
puts "error opening #{clazz.aaf_configuration[:index_dir]}: #{$!}"
|
74
73
|
end
|
75
74
|
reader
|
76
75
|
}
|
@@ -82,6 +81,4 @@ module FerretMixin
|
|
82
81
|
|
83
82
|
end # of class MultiIndex
|
84
83
|
|
85
|
-
end
|
86
|
-
end
|
87
84
|
end
|
data/lib/remote_index.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'drb'
|
2
|
+
module ActsAsFerret
|
3
|
+
|
4
|
+
# This index implementation connects to a remote ferret server instance. It
|
5
|
+
# basically forwards all calls to the remote server.
|
6
|
+
class RemoteIndex < AbstractIndex
|
7
|
+
|
8
|
+
def initialize(config)
|
9
|
+
@config = config
|
10
|
+
@ferret_config = config[:ferret]
|
11
|
+
@server = DRbObject.new(nil, config[:remote])
|
12
|
+
end
|
13
|
+
|
14
|
+
def method_missing(method_name, *args)
|
15
|
+
args.unshift model_class_name
|
16
|
+
@server.send(method_name, *args)
|
17
|
+
end
|
18
|
+
|
19
|
+
def find_id_by_contents(q, options = {}, &proc)
|
20
|
+
total_hits, results = @server.find_id_by_contents(model_class_name, q, options)
|
21
|
+
block_given? ? yield_results(total_hits, results, &proc) : [ total_hits, results ]
|
22
|
+
end
|
23
|
+
|
24
|
+
def id_multi_search(query, models, options, &proc)
|
25
|
+
total_hits, results = @server.id_multi_search(model_class_name, query, models, options)
|
26
|
+
block_given? ? yield_results(total_hits, results, &proc) : [ total_hits, results ]
|
27
|
+
end
|
28
|
+
|
29
|
+
# add record to index
|
30
|
+
def add(record)
|
31
|
+
@server.add record.class.name, record.to_doc
|
32
|
+
end
|
33
|
+
alias << add
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def yield_results(total_hits, results)
|
38
|
+
results.each do |result|
|
39
|
+
yield result[:model], result[:id], result[:score], result[:data]
|
40
|
+
end
|
41
|
+
total_hits
|
42
|
+
end
|
43
|
+
|
44
|
+
def model_class_name
|
45
|
+
@config[:class_name]
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
data/lib/shared_index.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
module ActsAsFerret
|
2
|
+
|
3
|
+
class SharedIndex < LocalIndex
|
4
|
+
|
5
|
+
# build a ferret query matching only the record with the given id and class
|
6
|
+
def query_for_record(id, class_name)
|
7
|
+
returning bq = Ferret::Search::BooleanQuery.new do
|
8
|
+
bq.add_query(Ferret::Search::TermQuery.new(:id, id.to_s), :must)
|
9
|
+
bq.add_query(Ferret::Search::TermQuery.new(:class_name, class_name), :must)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module ActsAsFerret
|
2
|
+
|
3
|
+
# class methods for classes using acts_as_ferret :single_index => true
|
4
|
+
module SharedIndexClassMethods
|
5
|
+
|
6
|
+
def find_id_by_contents(q, options = {}, &block)
|
7
|
+
# add class name scoping to query if necessary
|
8
|
+
unless options[:models] == :all # search needs to be restricted by one or more class names
|
9
|
+
options[:models] ||= []
|
10
|
+
# add this class to the list of given models
|
11
|
+
options[:models] << self unless options[:models].include?(self)
|
12
|
+
# keep original query
|
13
|
+
original_query = q
|
14
|
+
|
15
|
+
if original_query.is_a? String
|
16
|
+
model_query = options[:models].map(&:name).join '|'
|
17
|
+
q << %{ +class_name:"#{model_query}"}
|
18
|
+
else
|
19
|
+
q = Ferret::Search::BooleanQuery.new
|
20
|
+
q.add_query(original_query, :must)
|
21
|
+
model_query = Ferret::Search::BooleanQuery.new
|
22
|
+
options[:models].each do |model|
|
23
|
+
model_query.add_query(Ferret::Search::TermQuery.new(:class_name, model.name), :should)
|
24
|
+
end
|
25
|
+
q.add_query(model_query, :must)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
options.delete :models
|
29
|
+
|
30
|
+
super(q, options, &block)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Overrides the standard find_by_contents for searching a shared index.
|
34
|
+
#
|
35
|
+
# please note that records from different models will be fetched in
|
36
|
+
# separate sql calls, so any sql order_by clause given with
|
37
|
+
# find_options[:order] will be ignored.
|
38
|
+
def find_by_contents(q, options = {}, find_options = {})
|
39
|
+
if order = find_options.delete(:order)
|
40
|
+
logger.warn "using a shared index, so ignoring order_by clause #{order}"
|
41
|
+
end
|
42
|
+
total_hits, result = find_records_lazy_or_not q, options, find_options
|
43
|
+
# sort so results have the same order they had when originally retrieved
|
44
|
+
# from ferret
|
45
|
+
return SearchResults.new(result, total_hits)
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
def ar_find_by_contents(q, options = {}, find_options = {})
|
51
|
+
total_hits, id_arrays = collect_results(q, options)
|
52
|
+
result = retrieve_records(id_arrays, find_options)
|
53
|
+
result.sort! { |a, b| id_arrays[a.class.name][a.id.to_s].first <=> id_arrays[b.class.name][b.id.to_s].first }
|
54
|
+
[ total_hits, result ]
|
55
|
+
end
|
56
|
+
|
57
|
+
def collect_results(q, options = {})
|
58
|
+
id_arrays = {}
|
59
|
+
# get object ids for index hits
|
60
|
+
rank = 0
|
61
|
+
total_hits = find_id_by_contents(q, options) do |model, id, score, data|
|
62
|
+
id_arrays[model] ||= {}
|
63
|
+
# store result rank and score
|
64
|
+
id_arrays[model][id] = [ rank += 1, score ]
|
65
|
+
end
|
66
|
+
[ total_hits, id_arrays ]
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
# determine all field names in the shared index
|
71
|
+
# TODO unused
|
72
|
+
# def single_index_field_names(models)
|
73
|
+
# @single_index_field_names ||= (
|
74
|
+
# searcher = Ferret::Search::Searcher.new(class_index_dir)
|
75
|
+
# if searcher.reader.respond_to?(:get_field_names)
|
76
|
+
# (searcher.reader.send(:get_field_names) - ['id', 'class_name']).to_a
|
77
|
+
# else
|
78
|
+
# puts <<-END
|
79
|
+
#unable to retrieve field names for class #{self.name}, please
|
80
|
+
#consider naming all indexed fields in your call to acts_as_ferret!
|
81
|
+
# END
|
82
|
+
# models.map { |m| m.content_columns.map { |col| col.name } }.flatten
|
83
|
+
# end
|
84
|
+
# )
|
85
|
+
#
|
86
|
+
# end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
data/rakefile
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
-
# rakefile for acts_as_ferret.
|
1
|
+
# rakefile for acts_as_ferret.
|
2
2
|
# use to create a gem or generate rdoc api documentation.
|
3
3
|
#
|
4
|
-
#
|
4
|
+
# RELEASE creation:
|
5
|
+
# rake release REL=x.y.z
|
5
6
|
|
6
7
|
require 'rake'
|
7
8
|
require 'rake/rdoctask'
|
@@ -10,16 +11,21 @@ require 'rake/gempackagetask'
|
|
10
11
|
require 'rake/testtask'
|
11
12
|
require 'rake/contrib/rubyforgepublisher'
|
12
13
|
|
14
|
+
def announce(msg='')
|
15
|
+
STDERR.puts msg
|
16
|
+
end
|
17
|
+
|
18
|
+
|
13
19
|
PKG_NAME = 'acts_as_ferret'
|
14
|
-
PKG_VERSION = '
|
20
|
+
PKG_VERSION = ENV['REL']
|
15
21
|
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
16
|
-
|
17
|
-
|
22
|
+
RUBYFORGE_PROJECT = 'actsasferret'
|
23
|
+
RUBYFORGE_USER = 'jkraemer'
|
18
24
|
|
19
25
|
desc 'Default: run unit tests.'
|
20
26
|
task :default => :test
|
21
27
|
|
22
|
-
desc 'Test the
|
28
|
+
desc 'Test the acts_as_ferret plugin.'
|
23
29
|
Rake::TestTask.new(:test) do |t|
|
24
30
|
t.libs << 'lib'
|
25
31
|
t.pattern = 'test/**/*_test.rb'
|
@@ -37,155 +43,90 @@ Rake::RDocTask.new(:rdoc) do |rdoc|
|
|
37
43
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
38
44
|
end
|
39
45
|
|
40
|
-
spec = Gem::Specification.new do |s|
|
41
|
-
s.name = PKG_NAME
|
42
|
-
s.version = PKG_VERSION
|
43
|
-
s.platform = Gem::Platform::RUBY
|
44
|
-
s.summary = "acts_as_ferret - Ferret based full text search for any ActiveRecord model"
|
45
|
-
s.files = Dir.glob('**/*', File::FNM_DOTMATCH).reject do |f|
|
46
|
-
[ /\.$/, /sqlite$/, /\.log$/, /^pkg/, /\.svn/,
|
47
|
-
/\~$/, /\/\._/, /\/#/ ].any? {|regex| f =~ regex }
|
48
|
-
end
|
49
|
-
#s.files = FileList["{lib,test}/**/*"].to_a + %w(README MIT-LICENSE CHANGELOG)
|
50
|
-
# s.files.delete ...
|
51
|
-
s.require_path = 'lib'
|
52
|
-
s.autorequire = 'acts_as_ferret'
|
53
|
-
s.has_rdoc = true
|
54
|
-
# s.test_files = Dir['test/**/*_test.rb']
|
55
|
-
s.author = "Jens Kraemer"
|
56
|
-
s.email = "jk@jkraemer.net"
|
57
|
-
s.homepage = "http://projects.jkraemer.net/acts_as_ferret"
|
58
|
-
end
|
59
|
-
|
60
|
-
Rake::GemPackageTask.new(spec) do |pkg|
|
61
|
-
pkg.need_tar = true
|
62
|
-
end
|
63
|
-
|
64
46
|
desc "Publish the API documentation"
|
65
47
|
task :pdoc => [:rdoc] do
|
66
|
-
Rake::RubyForgePublisher.new(
|
48
|
+
Rake::RubyForgePublisher.new(RUBYFORGE_PROJECT, RUBYFORGE_USER).upload
|
67
49
|
end
|
68
50
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
require 'net/http'
|
78
|
-
require 'open-uri'
|
79
|
-
|
80
|
-
project_uri = "http://rubyforge.org/projects/#{RUBY_FORGE_PROJECT}/"
|
81
|
-
project_data = open(project_uri) { |data| data.read }
|
82
|
-
group_id = project_data[/[?&]group_id=(\d+)/, 1]
|
83
|
-
raise "Couldn't get group id" unless group_id
|
84
|
-
|
85
|
-
# This echos password to shell which is a bit sucky
|
86
|
-
if ENV["RUBY_FORGE_PASSWORD"]
|
87
|
-
password = ENV["RUBY_FORGE_PASSWORD"]
|
88
|
-
else
|
89
|
-
print "#{RUBY_FORGE_USER}@rubyforge.org's password: "
|
90
|
-
password = STDIN.gets.chomp
|
51
|
+
if PKG_VERSION
|
52
|
+
spec = Gem::Specification.new do |s|
|
53
|
+
s.name = PKG_NAME
|
54
|
+
s.version = PKG_VERSION
|
55
|
+
s.platform = Gem::Platform::RUBY
|
56
|
+
s.summary = "acts_as_ferret - Ferret based full text search for any ActiveRecord model"
|
57
|
+
s.files = Dir.glob('**/*', File::FNM_DOTMATCH).reject do |f|
|
58
|
+
[ /\.$/, /sqlite$/, /\.log$/, /^pkg/, /\.svn/, /\.swp$/, /^html/, /\~$/, /\/\._/, /\/#/ ].any? {|regex| f =~ regex }
|
91
59
|
end
|
60
|
+
#s.files = FileList["{lib,test}/**/*"].to_a + %w(README MIT-LICENSE CHANGELOG)
|
61
|
+
# s.files.delete ...
|
62
|
+
s.require_path = 'lib'
|
63
|
+
s.autorequire = 'acts_as_ferret'
|
64
|
+
s.has_rdoc = true
|
65
|
+
# s.test_files = Dir['test/**/*_test.rb']
|
66
|
+
s.author = "Jens Kraemer"
|
67
|
+
s.email = "jk@jkraemer.net"
|
68
|
+
s.homepage = "http://projects.jkraemer.net/acts_as_ferret"
|
69
|
+
end
|
92
70
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
"form_loginname=#{RUBY_FORGE_USER}",
|
97
|
-
"form_pw=#{password}"
|
98
|
-
].join("&")
|
99
|
-
|
100
|
-
headers = { 'Content-Type' => 'application/x-www-form-urlencoded' }
|
101
|
-
|
102
|
-
http.post("/account/login.php", data, headers)
|
103
|
-
end
|
104
|
-
|
105
|
-
cookie = login_response["set-cookie"]
|
106
|
-
raise "Login failed" unless cookie
|
107
|
-
headers = { "Cookie" => cookie }
|
108
|
-
|
109
|
-
release_uri = "http://rubyforge.org/frs/admin/?group_id=#{group_id}"
|
110
|
-
release_data = open(release_uri, headers) { |data| data.read }
|
111
|
-
package_id = release_data[/[?&]package_id=(\d+)/, 1]
|
112
|
-
raise "Couldn't get package id" unless package_id
|
113
|
-
|
114
|
-
first_file = true
|
115
|
-
release_id = ""
|
116
|
-
|
117
|
-
files.each do |filename|
|
118
|
-
basename = File.basename(filename)
|
119
|
-
file_ext = File.extname(filename)
|
120
|
-
file_data = File.open(filename, "rb") { |file| file.read }
|
121
|
-
|
122
|
-
puts "Releasing #{basename}..."
|
123
|
-
|
124
|
-
release_response = Net::HTTP.start("rubyforge.org", 80) do |http|
|
125
|
-
release_date = Time.now.strftime("%Y-%m-%d %H:%M")
|
126
|
-
type_map = {
|
127
|
-
".zip" => "3000",
|
128
|
-
".tgz" => "3110",
|
129
|
-
".gz" => "3110",
|
130
|
-
".gem" => "1400"
|
131
|
-
}; type_map.default = "9999"
|
132
|
-
type = type_map[file_ext]
|
133
|
-
boundary = "rubyqMY6QN9bp6e4kS21H4y0zxcvoor"
|
134
|
-
|
135
|
-
query_hash = if first_file then
|
136
|
-
{
|
137
|
-
"group_id" => group_id,
|
138
|
-
"package_id" => package_id,
|
139
|
-
"release_name" => PKG_FILE_NAME,
|
140
|
-
"release_date" => release_date,
|
141
|
-
"type_id" => type,
|
142
|
-
"processor_id" => "8000", # Any
|
143
|
-
"release_notes" => "",
|
144
|
-
"release_changes" => "",
|
145
|
-
"preformatted" => "1",
|
146
|
-
"submit" => "1"
|
147
|
-
}
|
148
|
-
else
|
149
|
-
{
|
150
|
-
"group_id" => group_id,
|
151
|
-
"release_id" => release_id,
|
152
|
-
"package_id" => package_id,
|
153
|
-
"step2" => "1",
|
154
|
-
"type_id" => type,
|
155
|
-
"processor_id" => "8000", # Any
|
156
|
-
"submit" => "Add This File"
|
157
|
-
}
|
158
|
-
end
|
159
|
-
|
160
|
-
data = [
|
161
|
-
"--" + boundary,
|
162
|
-
"Content-Disposition: form-data; name=\"userfile\"; filename=\"#{basename}\"",
|
163
|
-
"Content-Type: application/octet-stream",
|
164
|
-
"Content-Transfer-Encoding: binary",
|
165
|
-
"", file_data, "",
|
166
|
-
query_hash.collect do |name, value|
|
167
|
-
[ "--" + boundary,
|
168
|
-
"Content-Disposition: form-data; name='#{name}'",
|
169
|
-
"", value, "" ]
|
170
|
-
end
|
171
|
-
].flatten.join("\x0D\x0A")
|
172
|
-
|
173
|
-
release_headers = headers.merge(
|
174
|
-
"Content-Type" => "multipart/form-data; boundary=#{boundary}"
|
175
|
-
)
|
176
|
-
|
177
|
-
target = first_file ? "/frs/admin/qrs.php" : "/frs/admin/editrelease.php"
|
178
|
-
http.post(target, data, release_headers)
|
179
|
-
end
|
71
|
+
package_task = Rake::GemPackageTask.new(spec) do |pkg|
|
72
|
+
pkg.need_tar = true
|
73
|
+
end
|
180
74
|
|
181
|
-
|
182
|
-
|
183
|
-
|
75
|
+
# Validate that everything is ready to go for a release.
|
76
|
+
task :prerelease do
|
77
|
+
announce
|
78
|
+
announce "**************************************************************"
|
79
|
+
announce "* Making RubyGem Release #{PKG_VERSION}"
|
80
|
+
announce "**************************************************************"
|
81
|
+
announce
|
82
|
+
# Are all source files checked in?
|
83
|
+
if ENV['RELTEST']
|
84
|
+
announce "Release Task Testing, skipping checked-in file test"
|
85
|
+
else
|
86
|
+
announce "Pulling in svn..."
|
87
|
+
`svk pull .`
|
88
|
+
announce "Checking for unchecked-in files..."
|
89
|
+
data = `svk st`
|
90
|
+
unless data =~ /^$/
|
91
|
+
fail "SVK status is not clean ... do you have unchecked-in files?"
|
184
92
|
end
|
185
|
-
|
186
|
-
|
93
|
+
announce "No outstanding checkins found ... OK"
|
94
|
+
announce "Pushing to svn..."
|
95
|
+
`svk push .`
|
187
96
|
end
|
188
97
|
end
|
98
|
+
|
99
|
+
|
100
|
+
desc "tag the new release"
|
101
|
+
task :tag => [ :prerelease ] do
|
102
|
+
reltag = "REL_#{PKG_VERSION.gsub(/\./, '_')}"
|
103
|
+
reltag << ENV['REUSE'].gsub(/\./, '_') if ENV['REUSE']
|
104
|
+
announce "Tagging with [#{PKG_VERSION}]"
|
105
|
+
if ENV['RELTEST']
|
106
|
+
announce "Release Task Testing, skipping tagging"
|
107
|
+
else
|
108
|
+
`svn copy -m 'tagging version #{PKG_VERSION}' svn://projects.jkraemer.net/acts_as_ferret/trunk/plugin svn://projects.jkraemer.net/acts_as_ferret/tags/#{PKG_VERSION}`
|
109
|
+
`svn del -m 'remove old stable' svn://projects.jkraemer.net/acts_as_ferret/tags/stable`
|
110
|
+
`svn copy -m 'tagging version #{PKG_VERSION} as stable' svn://projects.jkraemer.net/acts_as_ferret/tags/#{PKG_VERSION} svn://projects.jkraemer.net/acts_as_ferret/tags/stable`
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Upload release to rubyforge
|
115
|
+
desc "Upload release to rubyforge"
|
116
|
+
#task :prel => [ :tag, :prerelease, :package ] do
|
117
|
+
task :prel => [ :package ] do
|
118
|
+
#task :prel do
|
119
|
+
`rubyforge login`
|
120
|
+
release_command = "rubyforge add_release #{RUBYFORGE_PROJECT} #{PKG_NAME} '#{PKG_VERSION}' pkg/#{PKG_NAME}-#{PKG_VERSION}.gem"
|
121
|
+
puts release_command
|
122
|
+
system(release_command)
|
123
|
+
`rubyforge config #{RUBYFORGE_PROJECT}`
|
124
|
+
release_command = "rubyforge add_file #{RUBYFORGE_PROJECT} #{PKG_NAME} '#{PKG_VERSION}' pkg/#{PKG_NAME}-#{PKG_VERSION}.tgz"
|
125
|
+
puts release_command
|
126
|
+
system(release_command)
|
127
|
+
end
|
128
|
+
|
129
|
+
desc 'Publish the gem and API docs'
|
130
|
+
task :release => [:pdoc, :prel ]
|
131
|
+
|
189
132
|
end
|
190
|
-
|
191
|
-
|