stupa 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.
@@ -0,0 +1,2 @@
1
+ README.markdown
2
+ LICENSE
@@ -0,0 +1,14 @@
1
+ .DS_Store
2
+
3
+ *~
4
+ \#*
5
+ .\#*
6
+
7
+ coverage
8
+ rdoc
9
+ pkg
10
+ *.gem
11
+ .yardopts
12
+ .yardoc
13
+ .bundle
14
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem 'shoulda-context'
7
+ end
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+
2
+ The Stupa Ruby client library is covered by the following license :
3
+
4
+ /*
5
+ * Copyright (c) 2011
6
+ * Frank Denis <j at pureftpd dot org>
7
+ *
8
+ * Permission to use, copy, modify, and distribute this software for any
9
+ * purpose with or without fee is hereby granted, provided that the above
10
+ * copyright notice and this permission notice appear in all copies.
11
+ *
12
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19
+ */
@@ -0,0 +1,54 @@
1
+ Stupa client library for Ruby
2
+ =============================
3
+
4
+ A Ruby client for [Stupa](http://code.google.com/p/stupa/).
5
+
6
+ "Stupa is an associative search engine.
7
+ You can search related documents with high performance and high precision.
8
+ Since document data and inverted indexes are kept in memory,
9
+ Stupa reflects updates of documents in search results in real time."
10
+
11
+ The current SVN code of Stupa contains a fast HTTP API built on libevent.
12
+ This is what this library connects to.
13
+
14
+ Usage
15
+ =====
16
+
17
+ require 'stupa'
18
+
19
+ client = Stupa::Client.new(host: "localhost", port: 22122)
20
+
21
+ # delete all entries
22
+ client.clear
23
+
24
+ # get the number of entries in the database
25
+ size = client.size
26
+
27
+ # record a couple key/features tuples
28
+ client.add('key1' => 'feat1')
29
+ client.add('key2' => 'feat2', 'key3' => 'feat3')
30
+ client.add('key4' => ['feat4', 'feat5', 'feat6'])
31
+
32
+ # delete keys
33
+ client.delete('key3')
34
+ client.delete(['key1', 'key2'])
35
+
36
+ # dump the database
37
+ client.save('/var/db/stupa.db')
38
+
39
+ # load from a dump
40
+ client.load('/var/db/stupa.db')
41
+
42
+ # look for entries similar to key1
43
+ results = client.dsearch(query: 'key1')
44
+ results = client.dsearch(query: 'key1', max: 50)
45
+
46
+ # look for keys with matching features
47
+ results = client.fsearch(query: 'feat1')
48
+ results = client.fsearch(query: ['feat2', 'feat3'])
49
+ results = client.fsearch(query: ['feat2', 'feat3'], max: 50)
50
+
51
+ Copyright
52
+ =========
53
+
54
+ See LICENSE for details.
@@ -0,0 +1,14 @@
1
+ require 'rake'
2
+ require 'shoulda/tasks'
3
+ require 'rake/testtask'
4
+ require 'bundler'
5
+
6
+ Bundler::GemHelper.install_tasks
7
+
8
+ task :default => :test
9
+
10
+ Rake::TestTask.new(:test) do |test|
11
+ test.ruby_opts = ["-rubygems"] if defined?(Gem)
12
+ test.libs << 'lib' << 'test'
13
+ test.pattern = 'test/**/*_test.rb'
14
+ end
@@ -0,0 +1,12 @@
1
+
2
+ module Stupa
3
+ def self.configure
4
+ yield self
5
+ true
6
+ end
7
+ end
8
+
9
+ require 'stupa/errors'
10
+ require 'stupa/client'
11
+
12
+
@@ -0,0 +1,111 @@
1
+
2
+ require 'faraday'
3
+ require 'stupa/tsv'
4
+
5
+ module Stupa
6
+ class Client
7
+ SEARCH_DEFAULT_MAX_RESULTS = 20
8
+ DEFAULT_HOST = 'localhost'
9
+ DEFAULT_PORT = 22122
10
+
11
+ attr_accessor :host, :port, :adapter
12
+
13
+ def base_url
14
+ "http://#{@host}:#{@port}"
15
+ end
16
+
17
+ def initialize(params = { })
18
+ @host = params[:host] || DEFAULT_HOST
19
+ @port = params[:port] || DEFAULT_PORT
20
+ @adapter = params[:adapter] || :net_http
21
+ end
22
+
23
+ def close; end
24
+
25
+ def load(file)
26
+ resp = _conn.post '/load' do |req|
27
+ req.body = { file: file }
28
+ end
29
+ _raise_errors(resp)
30
+ end
31
+
32
+ def save(file)
33
+ resp = _conn.post '/save' do |req|
34
+ req.body = { file: file }
35
+ end
36
+ _raise_errors(resp)
37
+ end
38
+
39
+ def clear
40
+ resp = _conn.post '/clear'
41
+ _raise_errors(resp)
42
+ end
43
+
44
+ def size
45
+ resp = _conn.get '/size'
46
+ _raise_errors(resp)
47
+ resp.body.to_i
48
+ end
49
+
50
+ def add(params)
51
+ conn = _conn
52
+ params.each_pair do |_key, features|
53
+ key = _key.to_s
54
+ features = [ features ] unless features.kind_of?(Array)
55
+ features = features.collect { |feature| feature.to_s }
56
+ resp = conn.post '/add' do |req|
57
+ req.body = { id: _key, feature: TSV.join(features) }
58
+ end
59
+ _raise_errors(resp)
60
+ end
61
+ end
62
+
63
+ def delete(params)
64
+ params = [ params ] unless params.respond_to?('each')
65
+ conn = _conn
66
+ params.each do |_key|
67
+ key = _key.to_s
68
+ resp = conn.post '/delete' do |req|
69
+ req.body = { id: key }
70
+ end
71
+ _raise_errors(resp)
72
+ end
73
+ end
74
+
75
+ def fsearch(params)
76
+ _search('/fsearch', params)
77
+ end
78
+
79
+ def dsearch(params)
80
+ _search('/dsearch', params)
81
+ end
82
+
83
+ private
84
+
85
+ def _conn(&block)
86
+ Faraday.new(url: base_url) do |builder|
87
+ builder.adapter @adapter
88
+ yield builder if block
89
+ end
90
+ end
91
+
92
+ def _raise_errors(resp)
93
+ status = resp.status
94
+ raise StupaServerError, "(#{status})" if status >= 500
95
+ raise StupaClientError, "(#{status})" if status >= 400
96
+ end
97
+
98
+ def _search(url_part, params)
99
+ max = params[:max] || SEARCH_DEFAULT_MAX_RESULTS
100
+ query = params[:query]
101
+ query = [ query ] unless query.kind_of?(Array)
102
+ query = query.collect { |part| part.to_s }
103
+
104
+ resp = _conn.post url_part do |req|
105
+ req.body = { query: TSV.join(query), max: max }
106
+ end
107
+ _raise_errors(resp)
108
+ TSV.parse_rows_key_and_score(resp.body)
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,14 @@
1
+
2
+ module Stupa
3
+ class StupaError < StandardError
4
+ attr_reader :data
5
+
6
+ def initialize(data)
7
+ @data = data
8
+ super
9
+ end
10
+ end
11
+
12
+ class StupaServerError < StupaError; end
13
+ class StupaClientError < StupaError; end
14
+ end
@@ -0,0 +1,42 @@
1
+
2
+ module TSV
3
+ class << self
4
+ def parse_rows(str)
5
+ str.split("\n").collect { |row| row.split("\t") }
6
+ end
7
+
8
+ def parse_rows_key_and_score(str)
9
+ rows = parse_rows(str)
10
+ h = rows.collect do |row|
11
+ [ row.first, row.last.to_f ]
12
+ end
13
+ Hash[*h.flatten]
14
+ end
15
+
16
+ def parse_rows_key_and_values(str)
17
+ rows = parse_rows(str)
18
+ h = rows.collect do |row|
19
+ [ row.first, row[1..-1] ]
20
+ end
21
+ Hash[*h.flatten(1)]
22
+ end
23
+
24
+ def join(values)
25
+ raise TSVError, values unless joinable?(values) && ! values.empty?
26
+ if values.respond_to?('join')
27
+ values.join("\t")
28
+ else
29
+ values
30
+ end
31
+ end
32
+
33
+ def joinable?(values)
34
+ values = [ values ] unless values.kind_of?(Array)
35
+ ! values.detect do |value|
36
+ value.include?("\t") || value.include?("\n")
37
+ end
38
+ end
39
+ end
40
+
41
+ class TSVError < StandardError; end
42
+ end
@@ -0,0 +1,10 @@
1
+
2
+ module Stupa
3
+ module VERSION #:nodoc:
4
+ MAJOR = 0
5
+ MINOR = 1
6
+ TINY = 0
7
+
8
+ STRING = [MAJOR, MINOR, TINY].join('.')
9
+ end
10
+ end
@@ -0,0 +1,34 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/stupa/version', __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = %q{stupa}
6
+ s.version = Stupa::VERSION::STRING
7
+ s.platform = Gem::Platform::RUBY
8
+
9
+ s.authors = ['Frank Denis']
10
+ s.email = ['j at pureftpd dot org']
11
+
12
+ s.homepage = %q{http://github.com/jedisct1/stupa-rb}
13
+ s.summary = %q{Ruby client library for Stupa}
14
+ s.description = %q{Ruby client library for Stupa}
15
+
16
+ s.extra_rdoc_files = ['README.markdown']
17
+
18
+ s.add_runtime_dependency 'faraday', '~> 0.5.3'
19
+
20
+ s.add_development_dependency 'bundler', '~> 1.0'
21
+ s.add_development_dependency 'rake', '~> 0.8'
22
+ s.add_development_dependency 'shoulda', '~> 2.11'
23
+ s.add_development_dependency 'test-unit', '~> 2.1'
24
+
25
+ s.required_rubygems_version = Gem::Requirement.new('>= 1.3.6') \
26
+ if s.respond_to? :required_rubygems_version=
27
+
28
+ s.platform = Gem::Platform::RUBY
29
+ s.require_paths = ['lib']
30
+
31
+ s.files = `git ls-files`.split("\n")
32
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
33
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
34
+ end
@@ -0,0 +1,51 @@
1
+
2
+ require 'helper'
3
+
4
+ class ClientTest < Test::Unit::TestCase
5
+ context "a Stupa client" do
6
+ setup do
7
+ @client = Stupa::Client.new
8
+ @client.clear
9
+ end
10
+
11
+ should "build base_url" do
12
+ assert_equal(@client.base_url, 'http://localhost:22122')
13
+ end
14
+
15
+ should "empty the initial database" do
16
+ assert_equal(@client.size, 0)
17
+ end
18
+
19
+ should "add a single item" do
20
+ @client.add('key1' => 'feat1')
21
+ assert_equal(@client.size, 1)
22
+ end
23
+
24
+ should "add a couple more items" do
25
+ @client.add('key2' => 'feat2', 'key3' => 'feat3')
26
+ assert_equal(@client.size, 2)
27
+ end
28
+
29
+ should "add multiple features to a single item" do
30
+ @client.add('key4' => ['feat4', 'feat5'])
31
+ assert_equal(@client.size, 1)
32
+ end
33
+
34
+ should "add multiple features to a multiple item" do
35
+ @client.add('key5' => ['feat6', 'feat7'], 'key6' => ['feat8', 'feat9'])
36
+ assert_equal(@client.size, 2)
37
+ end
38
+
39
+ should "add two items and delete one" do
40
+ @client.add('key5' => ['feat6', 'feat7'], 'key6' => ['feat8', 'feat9'])
41
+ @client.delete('key5')
42
+ assert_equal(@client.size, 1)
43
+ end
44
+
45
+ should "add two items and delete two" do
46
+ @client.add('key5' => ['feat6', 'feat7'], 'key6' => ['feat8', 'feat9'])
47
+ @client.delete(['key5', 'key6'])
48
+ assert_equal(@client.size, 0)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,11 @@
1
+
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ begin require 'redgreen'; rescue LoadError; end
6
+
7
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
8
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
9
+
10
+ require 'stupa'
11
+
@@ -0,0 +1,23 @@
1
+
2
+ require 'helper'
3
+ require 'tempfile'
4
+
5
+ class PersistenceTest < Test::Unit::TestCase
6
+ context "a Stupa client with some data on the server" do
7
+ setup do
8
+ @client = Stupa::Client.new
9
+ @client.clear
10
+ @client.add('key1' => ['feature1', 'feature2', 'feature3'],
11
+ 'key2' => ['feature2', 'feature3', 'feature4'])
12
+ end
13
+
14
+ should "backup and reload the database" do
15
+ tmpfile = Tempfile.new('stupa-test')
16
+ @client.save(tmpfile)
17
+ @client.clear
18
+ assert_equal(@client.size, 0)
19
+ @client.load(tmpfile)
20
+ assert_equal(@client.size, 2)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,59 @@
1
+
2
+ require 'helper'
3
+
4
+ class SearchTest < Test::Unit::TestCase
5
+ context "a Stupa client with some data on the server" do
6
+ setup do
7
+ @client = Stupa::Client.new
8
+ @client.clear
9
+ @client.add('key1' => ['feature1', 'feature2', 'feature3'],
10
+ 'key2' => ['feature2', 'feature3', 'feature4'])
11
+ end
12
+
13
+ should "look for similar documents" do
14
+ result = @client.dsearch(query: 'key1')
15
+ assert_not_nil(result)
16
+ assert_nil(result[:key1])
17
+ assert_not_nil(result['key1'])
18
+ assert_equal(result['key1'], 1.0)
19
+ assert_not_nil(result['key2'])
20
+ assert(result['key2'] < 1.0)
21
+ assert(result['key2'] > 0.0)
22
+ end
23
+
24
+ should "lookup a nonexistent document" do
25
+ result = @client.dsearch(query: 'nonexistent_key')
26
+ assert_not_nil(result)
27
+ assert(result.empty?)
28
+ end
29
+
30
+ should "search for a feature" do
31
+ result = @client.fsearch(query: 'feature1')
32
+ assert_not_nil(result['key1'])
33
+ assert(result['key1'] < 1.0)
34
+ assert(result['key1'] > 0.0)
35
+ assert_nil(result['key2'])
36
+ end
37
+
38
+ should "search for features" do
39
+ result = @client.fsearch(query: ['feature3', 'feature4'])
40
+ assert_not_nil(result['key1'])
41
+ assert(result['key1'] < 1.0)
42
+ assert(result['key1'] > 0.0)
43
+ assert_not_nil(result['key2'])
44
+ assert(result['key2'] < 1.0)
45
+ assert(result['key2'] > 0.0)
46
+ end
47
+
48
+ should "search for nonexistent features" do
49
+ result = @client.fsearch(query: 'nonexistent_feature')
50
+ assert_not_nil(result)
51
+ assert(result.empty?)
52
+ end
53
+
54
+ should "check for partial results" do
55
+ result = @client.fsearch(query: 'feature3', max: 1)
56
+ assert(result.size == 1)
57
+ end
58
+ end
59
+ end
metadata ADDED
@@ -0,0 +1,155 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stupa
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Frank Denis
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-03-06 00:00:00 +01:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: faraday
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ - 5
31
+ - 3
32
+ version: 0.5.3
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: bundler
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ segments:
44
+ - 1
45
+ - 0
46
+ version: "1.0"
47
+ type: :development
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: rake
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ~>
56
+ - !ruby/object:Gem::Version
57
+ segments:
58
+ - 0
59
+ - 8
60
+ version: "0.8"
61
+ type: :development
62
+ version_requirements: *id003
63
+ - !ruby/object:Gem::Dependency
64
+ name: shoulda
65
+ prerelease: false
66
+ requirement: &id004 !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ~>
70
+ - !ruby/object:Gem::Version
71
+ segments:
72
+ - 2
73
+ - 11
74
+ version: "2.11"
75
+ type: :development
76
+ version_requirements: *id004
77
+ - !ruby/object:Gem::Dependency
78
+ name: test-unit
79
+ prerelease: false
80
+ requirement: &id005 !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ segments:
86
+ - 2
87
+ - 1
88
+ version: "2.1"
89
+ type: :development
90
+ version_requirements: *id005
91
+ description: Ruby client library for Stupa
92
+ email:
93
+ - j at pureftpd dot org
94
+ executables: []
95
+
96
+ extensions: []
97
+
98
+ extra_rdoc_files:
99
+ - README.markdown
100
+ files:
101
+ - .document
102
+ - .gitignore
103
+ - Gemfile
104
+ - LICENSE
105
+ - README.markdown
106
+ - Rakefile
107
+ - lib/stupa.rb
108
+ - lib/stupa/client.rb
109
+ - lib/stupa/errors.rb
110
+ - lib/stupa/tsv.rb
111
+ - lib/stupa/version.rb
112
+ - stupa.gemspec
113
+ - test/client_test.rb
114
+ - test/helper.rb
115
+ - test/persistence_test.rb
116
+ - test/search_test.rb
117
+ has_rdoc: true
118
+ homepage: http://github.com/jedisct1/stupa-rb
119
+ licenses: []
120
+
121
+ post_install_message:
122
+ rdoc_options: []
123
+
124
+ require_paths:
125
+ - lib
126
+ required_ruby_version: !ruby/object:Gem::Requirement
127
+ none: false
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ segments:
132
+ - 0
133
+ version: "0"
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
135
+ none: false
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ segments:
140
+ - 1
141
+ - 3
142
+ - 6
143
+ version: 1.3.6
144
+ requirements: []
145
+
146
+ rubyforge_project:
147
+ rubygems_version: 1.3.7
148
+ signing_key:
149
+ specification_version: 3
150
+ summary: Ruby client library for Stupa
151
+ test_files:
152
+ - test/client_test.rb
153
+ - test/helper.rb
154
+ - test/persistence_test.rb
155
+ - test/search_test.rb