pager-ultrasphinx 1.0.20080510

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.
@@ -0,0 +1,199 @@
1
+
2
+ module Ultrasphinx
3
+
4
+ class Error < ::StandardError #:nodoc:
5
+ end
6
+ class ConfigurationError < Error #:nodoc:
7
+ end
8
+ class DaemonError < Error #:nodoc:
9
+ end
10
+ class UsageError < Error #:nodoc:
11
+ end
12
+
13
+ # Internal file paths
14
+
15
+ SUBDIR = "config/ultrasphinx"
16
+
17
+ DIR = "#{RAILS_ROOT}/#{SUBDIR}"
18
+
19
+ THIS_DIR = File.expand_path(File.dirname(__FILE__))
20
+
21
+ CONF_PATH = "#{DIR}/#{RAILS_ENV}.conf"
22
+
23
+ ENV_BASE_PATH = "#{DIR}/#{RAILS_ENV}.base"
24
+
25
+ GENERIC_BASE_PATH = "#{DIR}/default.base"
26
+
27
+ BASE_PATH = (File.exist?(ENV_BASE_PATH) ? ENV_BASE_PATH : GENERIC_BASE_PATH)
28
+
29
+ raise ConfigurationError, "Please create a '#{SUBDIR}/#{RAILS_ENV}.base' or '#{SUBDIR}/default.base' file in order to use Ultrasphinx in your #{RAILS_ENV} environment." unless File.exist? BASE_PATH # XXX lame
30
+
31
+ # Some miscellaneous constants
32
+
33
+ MAX_INT = 2**32-1
34
+
35
+ MAX_WORDS = 2**16 # The maximum number of stopwords built
36
+
37
+ MAIN_INDEX = "main"
38
+
39
+ DELTA_INDEX = "delta"
40
+
41
+ INDEXES = [MAIN_INDEX, DELTA_INDEX]
42
+
43
+ CONFIG_MAP = {
44
+ # These must be symbols for key mapping against Rails itself.
45
+ :username => 'sql_user',
46
+ :password => 'sql_pass',
47
+ :host => 'sql_host',
48
+ :database => 'sql_db',
49
+ :port => 'sql_port',
50
+ :socket => 'sql_sock'
51
+ }
52
+
53
+ CONNECTION_DEFAULTS = {
54
+ :host => 'localhost',
55
+ :password => '',
56
+ :username => 'root'
57
+ }
58
+
59
+ mattr_accessor :with_rake
60
+
61
+ def self.load_stored_procedure(name)
62
+ open("#{THIS_DIR}/postgresql/#{name}.sql").read.gsub(/\s+/, ' ')
63
+ end
64
+
65
+ SQL_FUNCTIONS = {
66
+ 'mysql' => {
67
+ 'group_concat' => "CAST(GROUP_CONCAT(DISTINCT ? ? SEPARATOR ' ') AS CHAR)",
68
+ 'delta' => "DATE_SUB(NOW(), INTERVAL ? SECOND)",
69
+ 'hash' => "CAST(CRC32(?) AS unsigned)",
70
+ 'range_cast' => "?"
71
+ },
72
+ 'postgresql' => {
73
+ 'group_concat' => "GROUP_CONCAT(?)",
74
+ 'delta' => "(NOW() - '? SECOND'::interval)",
75
+ 'range_cast' => "cast(coalesce(?,1) AS integer)",
76
+ 'hash' => "CRC32(?)"
77
+ }
78
+ }
79
+
80
+ DEFAULTS = {
81
+ 'mysql' => %(
82
+ type = mysql
83
+ sql_query_pre = SET SESSION group_concat_max_len = 65535
84
+ sql_query_pre = SET NAMES utf8
85
+ ),
86
+ 'postgresql' => %(
87
+ type = pgsql
88
+ sql_query_pre =
89
+ )
90
+ }
91
+
92
+ ADAPTER = ActiveRecord::Base.connection.instance_variable_get("@config")[:adapter] rescue 'mysql'
93
+
94
+ # Warn-mode logger. Also called from rake tasks.
95
+ def self.say msg
96
+ # XXX Method name is stupid.
97
+ if with_rake
98
+ puts msg[0..0].upcase + msg[1..-1]
99
+ else
100
+ msg = "** ultrasphinx: #{msg}"
101
+ if defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER
102
+ RAILS_DEFAULT_LOGGER.warn msg
103
+ else
104
+ STDERR.puts msg
105
+ end
106
+ end
107
+ nil # Explicitly return nil
108
+ end
109
+
110
+ # Debug-mode logger.
111
+ def self.log msg
112
+ # XXX Method name is stupid.
113
+ if defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER
114
+ RAILS_DEFAULT_LOGGER.debug msg
115
+ else
116
+ STDERR.puts msg
117
+ end
118
+ end
119
+
120
+ # Configuration file parser.
121
+ def self.options_for(heading, path)
122
+ # Evaluate ERB
123
+ template = ERB.new(File.open(path) {|f| f.read})
124
+ contents = template.result(binding)
125
+
126
+ # Find the correct heading.
127
+ section = contents[/^#{heading.gsub('/', '__')}\s*?\{(.*?)\}/m, 1]
128
+
129
+ if section
130
+ # Convert to a hash
131
+ options = section.split("\n").map do |line|
132
+ line =~ /\s*(.*?)\s*=\s*([^\#]*)/
133
+ $1 ? [$1, $2.strip] : []
134
+ end
135
+ Hash[*options.flatten]
136
+ else
137
+ # XXX Is it safe to raise here?
138
+ Ultrasphinx.say "warning; heading #{heading} not found in #{path}; it may be corrupted. "
139
+ {}
140
+ end
141
+ end
142
+
143
+ def self.get_models_to_class_ids #:nodoc:
144
+ # Reading the conf file makes sure that we are in sync with the actual Sphinx index, not
145
+ # whatever you happened to change your models to most recently.
146
+ if File.exist? CONF_PATH
147
+ lines, hash = open(CONF_PATH).readlines, {}
148
+ msg = "#{CONF_PATH} file is corrupted. Please run 'rake ultrasphinx:configure'."
149
+
150
+ lines.each_with_index do |line, index|
151
+ # Find the main sources
152
+ if line =~ /^source ([\w\d_-]*)_#{MAIN_INDEX}/
153
+ # Derive the model name
154
+ model = $1.gsub('__', '/').classify
155
+
156
+ # Get the id modulus out of the adjacent sql_query
157
+ query = lines[index..-1].detect do |query_line|
158
+ query_line =~ /^sql_query /
159
+ end
160
+ raise ConfigurationError, msg unless query
161
+ hash[model] = query[/(\d*) AS class_id/, 1].to_i
162
+ end
163
+ end
164
+ raise ConfigurationError, msg unless hash.values.size == hash.values.uniq.size
165
+ hash
166
+ else
167
+ # We can't raise here because you may be generating the configuration for the first time
168
+ Ultrasphinx.say "configuration file not found for #{RAILS_ENV.inspect} environment"
169
+ Ultrasphinx.say "please run 'rake ultrasphinx:configure'"
170
+ end
171
+ end
172
+
173
+ # Introspect on the existing generated conf files.
174
+ INDEXER_SETTINGS = options_for('indexer', BASE_PATH)
175
+ CLIENT_SETTINGS = options_for('client', BASE_PATH)
176
+ DAEMON_SETTINGS = options_for('searchd', BASE_PATH)
177
+ SOURCE_SETTINGS = options_for('source', BASE_PATH)
178
+ INDEX_SETTINGS = options_for('index', BASE_PATH)
179
+
180
+ # Make sure there's a trailing slash.
181
+ INDEX_SETTINGS['path'] = INDEX_SETTINGS['path'].chomp("/") + "/"
182
+
183
+ DICTIONARY = CLIENT_SETTINGS['dictionary_name'] || 'ap'
184
+ raise ConfigurationError, "Aspell does not support dictionary names longer than two letters" if DICTIONARY.size > 2
185
+
186
+ STOPWORDS_PATH = "#{Ultrasphinx::INDEX_SETTINGS['path']}/#{DICTIONARY}-stopwords.txt"
187
+
188
+ MODEL_CONFIGURATION = {}
189
+
190
+ # See if a delta index was defined.
191
+ def self.delta_index_present?
192
+ if File.exist?(CONF_PATH)
193
+ File.open(CONF_PATH).readlines.detect do |line|
194
+ line =~ /^index delta/
195
+ end
196
+ end
197
+ end
198
+
199
+ end
@@ -0,0 +1,36 @@
1
+
2
+ require 'fileutils'
3
+ require 'chronic'
4
+ require 'singleton'
5
+
6
+ if defined? RAILS_ENV and RAILS_ENV == "development"
7
+ if ENV['USER'] == 'eweaver'
8
+ require 'ruby-debug'
9
+ Debugger.start
10
+ end
11
+ end
12
+
13
+ $LOAD_PATH << "#{File.dirname(__FILE__)}/../vendor/riddle/lib"
14
+ require 'riddle'
15
+ require 'ultrasphinx/ultrasphinx'
16
+ require 'ultrasphinx/associations'
17
+ require 'ultrasphinx/core_extensions'
18
+ require 'ultrasphinx/is_indexed'
19
+
20
+ if (ActiveRecord::Base.connection rescue nil) # XXX Not sure why this needed to be wrapped.
21
+ require 'ultrasphinx/configure'
22
+ require 'ultrasphinx/autoload'
23
+ require 'ultrasphinx/fields'
24
+
25
+ require 'ultrasphinx/search/internals'
26
+ require 'ultrasphinx/search/parser'
27
+ require 'ultrasphinx/search'
28
+
29
+ begin
30
+ require 'raspell'
31
+ rescue Object => e
32
+ end
33
+
34
+ require 'ultrasphinx/spell'
35
+ end
36
+
data/rails/init.rb ADDED
@@ -0,0 +1,2 @@
1
+
2
+ require 'ultrasphinx'
@@ -0,0 +1,206 @@
1
+
2
+ ENV['RAILS_ENV'] ||= "development"
3
+
4
+ namespace :ultrasphinx do
5
+
6
+ task :_environment => [:environment] do
7
+ # We can't just chain :environment because we want to make
8
+ # sure it's set only for known Sphinx tasks
9
+ Ultrasphinx.with_rake = true
10
+ end
11
+
12
+ desc "Bootstrap a full Sphinx environment"
13
+ task :bootstrap => [:_environment, :configure, :index, :"daemon:restart"] do
14
+ say "done"
15
+ say "please restart your application containers"
16
+ end
17
+
18
+ desc "Rebuild the configuration file for this particular environment."
19
+ task :configure => [:_environment] do
20
+ Ultrasphinx::Configure.run
21
+ end
22
+
23
+ namespace :index do
24
+ desc "Reindex and rotate the main index."
25
+ task :main => [:_environment] do
26
+ ultrasphinx_index(Ultrasphinx::MAIN_INDEX)
27
+ end
28
+
29
+ desc "Reindex and rotate the delta index."
30
+ task :delta => [:_environment] do
31
+ ultrasphinx_index(Ultrasphinx::DELTA_INDEX)
32
+ end
33
+
34
+ desc "Merge the delta index into the main index."
35
+ task :merge => [:_environment] do
36
+ ultrasphinx_merge
37
+ end
38
+
39
+ end
40
+
41
+ desc "Reindex and rotate all indexes."
42
+ task :index => [:_environment] do
43
+ ultrasphinx_index("--all")
44
+ end
45
+
46
+ namespace :daemon do
47
+ desc "Start the search daemon"
48
+ task :start => [:_environment] do
49
+ FileUtils.mkdir_p File.dirname(Ultrasphinx::DAEMON_SETTINGS["log"]) rescue nil
50
+ raise Ultrasphinx::DaemonError, "Already running" if ultrasphinx_daemon_running?
51
+ system "searchd --config '#{Ultrasphinx::CONF_PATH}'"
52
+ sleep(4) # give daemon a chance to write the pid file
53
+ if ultrasphinx_daemon_running?
54
+ say "started successfully"
55
+ else
56
+ say "failed to start"
57
+ end
58
+ end
59
+
60
+ desc "Stop the search daemon"
61
+ task :stop => [:_environment] do
62
+ raise Ultrasphinx::DaemonError, "Doesn't seem to be running" unless ultrasphinx_daemon_running?
63
+ system "kill #{pid = ultrasphinx_daemon_pid}"
64
+ sleep(1)
65
+ if ultrasphinx_daemon_running?
66
+ system "kill -9 #{pid}"
67
+ sleep(1)
68
+ end
69
+ if ultrasphinx_daemon_running?
70
+ say "#{pid} could not be stopped"
71
+ else
72
+ say "stopped #{pid}"
73
+ end
74
+ end
75
+
76
+ desc "Restart the search daemon"
77
+ task :restart => [:_environment] do
78
+ Rake::Task["ultrasphinx:daemon:stop"].invoke if ultrasphinx_daemon_running?
79
+ sleep(3)
80
+ Rake::Task["ultrasphinx:daemon:start"].invoke
81
+ end
82
+
83
+ desc "Check if the search daemon is running"
84
+ task :status => [:_environment] do
85
+ if ultrasphinx_daemon_running?
86
+ say "daemon is running."
87
+ else
88
+ say "daemon is stopped."
89
+ end
90
+ end
91
+ end
92
+
93
+
94
+ namespace :spelling do
95
+ desc "Rebuild the custom spelling dictionary. You may need to use 'sudo' if your Aspell folder is not writable by the app user."
96
+ task :build => [:_environment] do
97
+ ENV['OPTS'] = "--buildstops #{Ultrasphinx::STOPWORDS_PATH} #{Ultrasphinx::MAX_WORDS} --buildfreqs"
98
+ Rake::Task["ultrasphinx:index"].invoke
99
+ tmpfile = "/tmp/ultrasphinx-stopwords.txt"
100
+ words = []
101
+ say "filtering"
102
+ File.open(Ultrasphinx::STOPWORDS_PATH).each do |line|
103
+ if line =~ /^([^\s\d_]{4,}) (\d+)/
104
+ # XXX should be configurable
105
+ words << $1 if $2.to_i > 40
106
+ # ideally we would also skip words within X edit distance of a correction
107
+ # by aspell-en, in order to not add typos to the dictionary
108
+ end
109
+ end
110
+ say "writing #{words.size} words"
111
+ File.open(tmpfile, 'w').write(words.join("\n"))
112
+ say "loading dictionary '#{Ultrasphinx::DICTIONARY}' into aspell"
113
+ system("aspell --lang=en create master #{Ultrasphinx::DICTIONARY}.rws < #{tmpfile}")
114
+ end
115
+ end
116
+
117
+ end
118
+
119
+ # task shortcuts
120
+ namespace :us do
121
+ task :start => ["ultrasphinx:daemon:start"]
122
+ task :restart => ["ultrasphinx:daemon:restart"]
123
+ task :stop => ["ultrasphinx:daemon:stop"]
124
+ task :stat => ["ultrasphinx:daemon:status"]
125
+ task :index => ["ultrasphinx:index"]
126
+ task :in => ["ultrasphinx:index"]
127
+ task :main => ["ultrasphinx:index:main"]
128
+ task :delta => ["ultrasphinx:index:delta"]
129
+ task :merge => ["ultrasphinx:index:merge"]
130
+ task :spell => ["ultrasphinx:spelling:build"]
131
+ task :conf => ["ultrasphinx:configure"]
132
+ task :boot => ["ultrasphinx:bootstrap"]
133
+ end
134
+
135
+ # Support methods
136
+
137
+ def ultrasphinx_daemon_pid
138
+ open(Ultrasphinx::DAEMON_SETTINGS['pid_file']).readline.chomp rescue nil
139
+ end
140
+
141
+ def ultrasphinx_daemon_running?
142
+ if ultrasphinx_daemon_pid and `ps -p#{ultrasphinx_daemon_pid} | wc`.to_i > 1
143
+ true
144
+ else
145
+ # Remove bogus lockfiles.
146
+ Dir[Ultrasphinx::INDEX_SETTINGS["path"] + "*spl"].each {|file| File.delete(file)}
147
+ false
148
+ end
149
+ end
150
+
151
+ def ultrasphinx_index(index)
152
+ rotate = ultrasphinx_daemon_running?
153
+ ultrasphinx_create_index_path
154
+
155
+ cmd = "indexer --config '#{Ultrasphinx::CONF_PATH}'"
156
+ cmd << " #{ENV['OPTS']} " if ENV['OPTS']
157
+ cmd << " --rotate" if rotate
158
+ cmd << " #{index}"
159
+
160
+ say "$ #{cmd}"
161
+ system cmd
162
+
163
+ ultrasphinx_check_rotate if rotate
164
+ end
165
+
166
+ def ultrasphinx_merge
167
+ rotate = ultrasphinx_daemon_running?
168
+
169
+ indexes = [Ultrasphinx::MAIN_INDEX, Ultrasphinx::DELTA_INDEX]
170
+ indexes.each do |index|
171
+ raise "#{index} index is missing" unless File.exist? "#{Ultrasphinx::INDEX_SETTINGS['path']}/sphinx_index_#{index}.spa"
172
+ end
173
+
174
+ cmd = "indexer --config '#{Ultrasphinx::CONF_PATH}'"
175
+ cmd << " #{ENV['OPTS']} " if ENV['OPTS']
176
+ cmd << " --rotate" if rotate
177
+ cmd << " --merge #{indexes.join(' ')}"
178
+
179
+ say "$ #{cmd}"
180
+ system cmd
181
+
182
+ ultrasphinx_check_rotate if rotate
183
+ end
184
+
185
+ def ultrasphinx_check_rotate
186
+ sleep(4)
187
+ failed = Dir[Ultrasphinx::INDEX_SETTINGS['path'] + "/*.new.*"]
188
+ if failed.any?
189
+ say "warning; index failed to rotate! Deleting new indexes"
190
+ say "try 'killall searchd' and then 'rake ultrasphinx:daemon:start'"
191
+ failed.each {|f| File.delete f }
192
+ else
193
+ say "index rotated ok"
194
+ end
195
+ end
196
+
197
+ def ultrasphinx_create_index_path
198
+ unless File.directory? Ultrasphinx::INDEX_SETTINGS['path']
199
+ mkdir_p Ultrasphinx::INDEX_SETTINGS['path']
200
+ end
201
+ end
202
+
203
+ def say msg
204
+ Ultrasphinx.say msg
205
+ end
206
+
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2007 Pat Allan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,74 @@
1
+ This client has been written to interface with Sphinx[http://sphinxsearch.com/]. It is written by
2
+ {Pat Allan}[http://freelancing-gods.com], and has been influenced by both Dmytro Shteflyuk's Ruby
3
+ client and the original PHP client - credit where credit's due, after all.
4
+
5
+ It does not follow the same syntax as those two, though (not much point writing this otherwise) -
6
+ opting for a more Ruby-like structure.
7
+
8
+ The easiest way to install is to grab the gem (available since 0.9.8r1112 only):
9
+
10
+ sudo gem install riddle
11
+
12
+ However, if you're so inclined, you can grab sourcecode via subversion. If you
13
+ are after a specific release, use the tag as follows:
14
+
15
+ svn co http://rails-oceania.googlecode.com/svn/patallan/riddle/tags/0.9.8-rc2 riddle
16
+
17
+ Or for the most current, just use trunk:
18
+
19
+ svn co http://rails-oceania.googlecode.com/svn/patallan/riddle/trunk riddle
20
+
21
+ Please note that at the time of writing, the following versions are supported (if you get the appropriate tag):
22
+
23
+ * 0.9.8-r871
24
+ * 0.9.8-r909
25
+ * 0.9.8-r985
26
+ * 0.9.8-r1065
27
+ * 0.9.8-r1112
28
+ * 0.9.8-rc1 (gem version: 0.9.8.1198)
29
+ * 0.9.8-rc2 (gem version: 0.9.8.1231)
30
+
31
+ To get started, just instantiate a Client object:
32
+
33
+ client = Riddle::Client.new # defaults to localhost and port 3312
34
+ client = Riddle::Client.new "sphinxserver.domain.tld", 3333 # custom settings
35
+
36
+ And then set the parameters to what you want, before running a query:
37
+
38
+ client.match_mode = :extended
39
+ client.query "Pat Allan @state Victoria"
40
+
41
+ The results from a query are similar to the other clients - but here's the details. It's a hash with
42
+ the following keys:
43
+
44
+ * :matches
45
+ * :fields
46
+ * :attributes
47
+ * :attribute_names
48
+ * :words
49
+ * :total
50
+ * :total_found
51
+ * :time
52
+ * :status
53
+ * :warning (if appropriate)
54
+ * :error (if appropriate)
55
+
56
+ The key <tt>:matches</tt> returns an array of hashes - the actual search results. Each hash has the
57
+ document id (<tt>:doc</tt>), the result weighting (<tt>:weight</tt>), and a hash of the attributes for
58
+ the document (<tt>:attributes</tt>).
59
+
60
+ The <tt>:fields</tt> and <tt>:attribute_names</tt> keys return list of fields and attributes for the
61
+ documents. The key <tt>:attributes</tt> will return a hash of attribute name and type pairs, and
62
+ <tt>:words</tt> returns a hash of hashes representing the words from the search, with the number of
63
+ documents and hits for each, along the lines of:
64
+
65
+ results[:words]["Pat"] #=> {:docs => 12, :hits => 15}
66
+
67
+ <tt>:total</tt>, <tt>:total_found</tt> and <tt>:time</tt> return the number of matches available, the
68
+ total number of matches (which may be greater than the maximum available), and the time in milliseconds
69
+ that the query took to run.
70
+
71
+ <tt>:status</tt> is the error code for the query - and if there was a related warning, it will be under
72
+ the <tt>:warning</tt> key. Fatal errors will be described under <tt>:error</tt>.
73
+
74
+ If you've installed the gem and wondering why there's no tests - check out the svn version. I've kept the specs out of the gem as I have a decent amount of test data in there, which really isn't needed unless you want to submit patches.
@@ -0,0 +1,117 @@
1
+ require 'rake'
2
+ require 'rake/packagetask'
3
+ require 'rake/rdoctask'
4
+ require 'spec/rake/spectask'
5
+ require 'rdoc/rdoc'
6
+ require 'rdoc/generators/html_generator'
7
+ require 'rdoc/generators/template/html/html'
8
+
9
+ module Generators
10
+ class HtmlFile < ContextUser
11
+ alias_method :core_attribute_values, :file_attribute_values
12
+
13
+ def file_attribute_values
14
+ core_attribute_values
15
+
16
+ @values["analytics"] = @options.analytics if @options.analytics
17
+ end
18
+ end
19
+
20
+ class HtmlClass < ContextUser
21
+ alias_method :core_attribute_values, :class_attribute_values
22
+
23
+ def class_attribute_values
24
+ core_attribute_values
25
+
26
+ @values["analytics"] = @options.analytics if @options.analytics
27
+ end
28
+ end
29
+ end
30
+
31
+ class Options
32
+ attr_accessor :analytics
33
+
34
+ module OptionList
35
+ OPTION_LIST << [
36
+ "--analytics", "-y", "code", "Google Analytics Code"
37
+ ]
38
+ end
39
+
40
+ alias_method :core_parse, :parse
41
+
42
+ def parse(argv, generators)
43
+ core_parse(argv, generators)
44
+
45
+ old_args = ARGV.dup
46
+ ARGV.replace(argv)
47
+
48
+ go = GetoptLong.new(*OptionList.options)
49
+ go.quiet = true
50
+
51
+ go.each do |opt, arg|
52
+ case opt
53
+ when "--analytics"
54
+ @analytics = arg.strip
55
+ end
56
+ end
57
+
58
+ ARGV.replace(old_args)
59
+ end
60
+
61
+ end
62
+
63
+ module RDoc
64
+ module Page
65
+ remove_const :FOOTER
66
+ const_set :FOOTER, %{
67
+ <div id="validator-badges">
68
+ <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
69
+ </div>
70
+ <script src="http://www.google-analytics.com/urchin.js" type="text/javascript">
71
+ </script>
72
+ <script type="text/javascript">
73
+ _uacct = "%analytics%";
74
+ urchinTracker();
75
+ </script>
76
+
77
+ </body>
78
+ </html>
79
+ }
80
+
81
+ remove_const :BODY
82
+ const_set :BODY, HEADER + %{
83
+
84
+ !INCLUDE! <!-- banner header -->
85
+
86
+ <div id="bodyContent">
87
+
88
+ } + METHOD_LIST + %{
89
+
90
+ </div>
91
+
92
+ } + FOOTER
93
+ end
94
+ end
95
+
96
+ desc 'Generate documentation'
97
+ Rake::RDocTask.new(:rdoc) do |rdoc|
98
+ rdoc.rdoc_dir = 'rdoc'
99
+ rdoc.title = 'Riddle - Ruby Sphinx Client'
100
+ rdoc.options << '--line-numbers' << '--inline-source'
101
+ rdoc.options << '-y US-2475317-6'
102
+ rdoc.rdoc_files.include('README')
103
+ rdoc.rdoc_files.include('lib/**/*.rb')
104
+ end
105
+
106
+ desc "Run Riddle's specs"
107
+ Spec::Rake::SpecTask.new do |t|
108
+ t.libs << 'lib'
109
+ t.spec_files = FileList['spec/**/*_spec.rb']
110
+ end
111
+
112
+ Spec::Rake::SpecTask.new(:rcov) do |t|
113
+ t.libs << 'lib'
114
+ t.spec_files = FileList['spec/**/*_spec.rb']
115
+ t.rcov = true
116
+ t.rcov_opts = ['--exclude', 'spec', '--exclude', 'gems']
117
+ end
@@ -0,0 +1,44 @@
1
+ module Riddle
2
+ class Client
3
+ # Used for querying Sphinx.
4
+ class Filter
5
+ attr_accessor :attribute, :values, :exclude
6
+
7
+ # Attribute name, values (which can be an array or a range), and whether
8
+ # the filter should be exclusive.
9
+ def initialize(attribute, values, exclude=false)
10
+ @attribute, @values, @exclude = attribute, values, exclude
11
+ end
12
+
13
+ def exclude?
14
+ self.exclude
15
+ end
16
+
17
+ # Returns the message for this filter to send to the Sphinx service
18
+ def query_message
19
+ message = Message.new
20
+
21
+ message.append_string self.attribute
22
+ case self.values
23
+ when Range
24
+ if self.values.first.is_a?(Float) && self.values.last.is_a?(Float)
25
+ message.append_int FilterTypes[:float_range]
26
+ message.append_floats self.values.first, self.values.last
27
+ else
28
+ message.append_int FilterTypes[:range]
29
+ message.append_ints self.values.first, self.values.last
30
+ end
31
+ when Array
32
+ message.append_int FilterTypes[:values]
33
+ message.append_int self.values.length
34
+ # using to_f is a hack from the php client - to workaround 32bit
35
+ # signed ints on x32 platforms
36
+ message.append_ints *self.values.collect { |val| val.to_f }
37
+ end
38
+ message.append_int self.exclude? ? 1 : 0
39
+
40
+ message.to_s
41
+ end
42
+ end
43
+ end
44
+ end