slender_t 0.1.1

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.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 David Richards
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.
data/README.rdoc ADDED
@@ -0,0 +1,29 @@
1
+ = slender_t
2
+
3
+ SlenderT is a triples store It is simple for the simple jobs. I decided to replace a recommendation engine with a set of triples and some simple query tools, and this is the result. I didn't want to break out the bigger tools for something that will only have hundreds of triples.
4
+
5
+ The ideas started with of "Programming the Semantic Web" by Toby Segaran et al. Toby's code was in Python, and of course this is a Ruby version. I've made quite a few changes with the query tools and using my Ruby idioms, but this is still a very simple gem.
6
+
7
+ == Usage
8
+
9
+ From the command line:
10
+
11
+ slender_t
12
+ >> ...
13
+
14
+
15
+
16
+ == Note on Patches/Pull Requests
17
+
18
+ * Fork the project.
19
+ * Make your feature addition or bug fix.
20
+ * Add tests for it. This is important so I don't break it in a
21
+ future version unintentionally.
22
+ * Commit, do not mess with rakefile, version, or history.
23
+ (if you want to have your own version, that is fine but
24
+ bump version in a commit by itself I can ignore when I pull)
25
+ * Send me a pull request. Bonus points for topic branches.
26
+
27
+ == Copyright
28
+
29
+ Copyright (c) 2009 David Richards. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,49 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "slender_t"
8
+ gem.summary = %Q{Simple triples storage}
9
+ gem.description = %Q{Simple triples storage for projects too small to install an RDF database.}
10
+ gem.email = "david@fleetventures.com"
11
+ gem.homepage = "http://github.com/davidrichards/slender_t"
12
+ gem.authors = ["David Richards"]
13
+ gem.add_development_dependency "rspec"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
19
+ end
20
+
21
+ require 'spec/rake/spectask'
22
+ Spec::Rake::SpecTask.new(:spec) do |spec|
23
+ spec.libs << 'lib' << 'spec'
24
+ spec.spec_files = FileList['spec/**/*_spec.rb']
25
+ end
26
+
27
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
28
+ spec.libs << 'lib' << 'spec'
29
+ spec.pattern = 'spec/**/*_spec.rb'
30
+ spec.rcov = true
31
+ end
32
+
33
+ task :spec => :check_dependencies
34
+
35
+ task :default => :spec
36
+
37
+ require 'rake/rdoctask'
38
+ Rake::RDocTask.new do |rdoc|
39
+ if File.exist?('VERSION')
40
+ version = File.read('VERSION')
41
+ else
42
+ version = ""
43
+ end
44
+
45
+ rdoc.rdoc_dir = 'rdoc'
46
+ rdoc.title = "slender_t #{version}"
47
+ rdoc.rdoc_files.include('README*')
48
+ rdoc.rdoc_files.include('lib/**/*.rb')
49
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.1
data/bin/slender_t ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby -wKU
2
+ require 'yaml'
3
+
4
+ version = File.read(File.join(File.dirname(__FILE__), %w(.. VERSION)))
5
+ st_file = File.join(File.dirname(__FILE__), %w(.. lib slender_t))
6
+
7
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
8
+
9
+ require 'optparse'
10
+ options = { :irb => irb, :without_stored_procedures => false }
11
+ OptionParser.new do |opt|
12
+ opt.banner = "Usage: console [environment] [options]"
13
+ opt.on("--irb=[#{irb}]", 'Invoke a different irb.') { |v| options[:irb] = v }
14
+ opt.parse!(ARGV)
15
+ end
16
+
17
+ libs = " -r irb/completion -r #{st_file}"
18
+
19
+ puts "Loading SlenderT version: #{version}"
20
+
21
+ exec "#{options[:irb]} #{libs} --simple-prompt"
data/lib/slender_t.rb ADDED
@@ -0,0 +1,161 @@
1
+ class SlenderT
2
+
3
+ require 'rubygems'
4
+ require 'open-uri'
5
+ require 'fastercsv'
6
+
7
+ class << self
8
+ def load(source, opts={})
9
+ SlenderT.new(source, opts)
10
+ end
11
+ end
12
+
13
+ attr_reader :spo, :pos, :osp
14
+
15
+ def initialize(contents=nil, opts={})
16
+ @spo, @pos, @osp = {}, {}, {}
17
+ self.load(contents, opts) if contents
18
+ end
19
+
20
+ # Expects CSV, a filename, or a URL with triples in it
21
+ # If there is a header in the file, use load(contents, :header => true)
22
+ # so that we can ignore the first line
23
+ # If there are special converters needed for FasterCSV to work, include them
24
+ # in the options as well.
25
+ def load(contents, opts={})
26
+ table = infer_csv_contents(contents, opts)
27
+ return nil unless contents
28
+ table.each do |row|
29
+ self.add(*row)
30
+ end
31
+ end
32
+
33
+ def save(filename)
34
+ File.open(filename, 'wb') {|f| f.write self.to_csv}
35
+ end
36
+
37
+ def add(subject, predicate, object)
38
+ add_to_index(self.spo, subject, predicate, object)
39
+ add_to_index(self.pos, predicate, object, subject)
40
+ add_to_index(self.osp, object, subject, predicate)
41
+ [subject, predicate, object]
42
+ end
43
+
44
+ def remove(subject, predicate, object)
45
+ triples = find(subject, predicate, object)
46
+ return true unless triples
47
+ for s, p, o in triples do
48
+ self.remove_from_index(self.spo, s, p, o)
49
+ self.remove_from_index(self.pos, p, o, s)
50
+ self.remove_from_index(self.osp, o, s, p)
51
+ end
52
+ [subject, predicate, object]
53
+ end
54
+
55
+ def find(subject=nil, predicate=nil, object=nil)
56
+ begin
57
+ if subject and predicate and object
58
+ if self.spo[subject] and self.spo[subject][predicate] and self.spo[subject][predicate].include?(object)
59
+ return [[subject, predicate, object]]
60
+ else
61
+ return []
62
+ end
63
+ elsif subject and predicate and object.nil?
64
+ return self.spo[subject][predicate].map {|o| [subject, predicate, o]}
65
+ elsif subject and predicate.nil? and object
66
+ return self.osp[object][subject].map {|p| [subject, p, object]}
67
+ elsif subject and predicate.nil? and object.nil?
68
+ return self.spo[subject].inject([]) do |list, h|
69
+ p, objects = h.first, h.last
70
+ objects.each {|o| list << [subject, p, o]}
71
+ list
72
+ end
73
+ elsif subject.nil? and predicate and object
74
+ return self.pos[predicate][object].map {|s| [s, predicate, object]}
75
+ elsif subject.nil? and predicate and object.nil?
76
+ return self.pos[predicate].inject([]) do |list, h|
77
+ o, subjects = h.first, h.last
78
+ subjects.each {|s| list << [s, predicate, o]}
79
+ list
80
+ end
81
+ elsif subject.nil? and predicate.nil? and object
82
+ self.osp[object].inject([]) do |list, h|
83
+ s, predicates = h.first, h.last
84
+ predicates.each {|p| list << [s, p, object]}
85
+ list
86
+ end
87
+ elsif subject.nil? and predicate.nil? and object.nil?
88
+ list = []
89
+ self.spo.each do |s, predicates|
90
+ predicates.each do |p, objects|
91
+ objects.each {|o| list << [s, p, o]}
92
+ end
93
+ end
94
+ list
95
+ end
96
+ rescue
97
+ []
98
+ end
99
+ end
100
+
101
+ def inspect
102
+ self.spo.keys.size > 20 ? "#{self.class}: #{self.spo.keys.size} unique subjects" : "#{self.class}: #{self.spo.keys.inspect}"
103
+ end
104
+
105
+ # Just very basic for now
106
+ def to_csv
107
+ self.find.map {|row| row.join(',')}.join("\n")
108
+ end
109
+
110
+ def value(subject=nil, predicate=nil, object=nil)
111
+ s, p, o = find(subject, predicate, object).first
112
+ return s unless subject
113
+ return p unless predicate
114
+ return o unless object
115
+ nil
116
+ end
117
+
118
+ protected
119
+ def add_to_index(index, a, b, c)
120
+ if index.keys.include?(a) and index[a].keys.include?(b)
121
+ # TODO: May not be efficient enough
122
+ index[a][b] = index[a][b] | [c]
123
+ elsif index.keys.include?(a)
124
+ index[a][b] = [c]
125
+ else
126
+ index[a] = {}
127
+ index[a][b] = [c]
128
+ end
129
+ end
130
+
131
+ def remove_from_index(index, a, b, c)
132
+ bs = index[a]
133
+ cset = bs[b] if bs
134
+ cset.delete(c) if cset
135
+ bs.delete(b) if cset and cset.empty?
136
+ index.delete(a) if bs and bs.empty?
137
+ true
138
+ end
139
+
140
+ def infer_csv_contents(obj, opts={})
141
+ begin
142
+ contents = File.read(obj) if File.exist?(obj)
143
+ open(obj) {|f| contents = f.read} unless contents
144
+ rescue
145
+ nil
146
+ end
147
+ contents ||= obj if obj.is_a?(String)
148
+ return nil unless contents
149
+ table = FCSV.parse(contents, default_csv_opts.merge(opts))
150
+ labels = opts.fetch(:headers, false) ? table.shift : []
151
+ while table.last.empty?
152
+ table.pop
153
+ end
154
+ table
155
+ end
156
+
157
+ def default_csv_opts; {:converters => :all}; end
158
+
159
+ end
160
+
161
+ Dir.glob("#{File.dirname(__FILE__)}/slender_t/*.rb").each { |file| require file }