state 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,160 @@
1
+ NAME
2
+ state
3
+
4
+ SYNOPSIS
5
+ platform agnostic persistent state for ruby processes based on sqlite
6
+
7
+ DESCRIPTION
8
+ state provides an extremely simple to use state mechanism for ruby programs
9
+ which require state between invocations. the storage is based on sqlite and
10
+ is therefore platform agnostic. using state is no more difficult that using
11
+ a hash - only that hash will persist between invocations your ruby program.
12
+ see the samples and specs for details.
13
+
14
+ INSTALL
15
+ gem install state
16
+
17
+ URIS
18
+ http://codeforpeople.com/lib/ruby/
19
+ http://rubyforge.org/projects/codeforpeople/
20
+
21
+ SAMPLES
22
+
23
+ <========< sample/a.rb >========>
24
+
25
+ ~ > cat sample/a.rb
26
+
27
+ require 'state'
28
+
29
+ # state provides persistent state for processes. usage requires simply
30
+ # accesses the state in a hash-like way.
31
+ #
32
+
33
+ # one process can create state
34
+ #
35
+ child do
36
+ State.clear
37
+ State['key'] = 'value'
38
+ end
39
+
40
+ # and later processes can have access to it
41
+ #
42
+ 2.times do |i|
43
+ child do
44
+ value = State['key']
45
+ puts "child[#{ i }] => #{ value.inspect }"
46
+ end
47
+ end
48
+
49
+
50
+ BEGIN {
51
+ # we use fork just for demonstation, but this works on windows too ;-)
52
+ #
53
+ def child &block
54
+ Process.waitpid fork(&block)
55
+ end
56
+ }
57
+
58
+
59
+ ~ > ruby sample/a.rb
60
+
61
+ child[0] => "value"
62
+ child[1] => "value"
63
+
64
+
65
+ <========< sample/b.rb >========>
66
+
67
+ ~ > cat sample/b.rb
68
+
69
+ require 'state'
70
+
71
+ # state will store it's db in a subdirectory (.state) of your home directory,
72
+ # the default database is ~/.state/global, but you may specify a name to
73
+ # create a new database
74
+ #
75
+
76
+ db = State.for 'foobar'
77
+
78
+ db.clear
79
+
80
+ puts db.path
81
+
82
+ 10.times{|i| db[i] = i}
83
+
84
+ puts db.keys.inspect
85
+
86
+ ~ > ruby sample/b.rb
87
+
88
+ /Users/ahoward/.state/foobar
89
+ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
90
+
91
+
92
+ <========< sample/c.rb >========>
93
+
94
+ ~ > cat sample/c.rb
95
+
96
+ require 'state'
97
+
98
+ # of course you can specify and absolute path
99
+ #
100
+
101
+ db = State.for :path => '/tmp/state'
102
+
103
+ db.clear
104
+
105
+ puts db.path
106
+
107
+ ~ > ruby sample/c.rb
108
+
109
+ /tmp/state
110
+
111
+
112
+ <========< sample/d.rb >========>
113
+
114
+ ~ > cat sample/d.rb
115
+
116
+ require 'state'
117
+
118
+ # in general the interface for state is like that of a hash, see the specs for
119
+ # more details
120
+
121
+ db = State.for 'foobar'
122
+
123
+ db.clear
124
+
125
+ 10.times{|i| db[i] = i**2}
126
+ 5.times{|i| db.delete i}
127
+
128
+ p db.keys
129
+ p db.values
130
+
131
+ # use the update method for atomic read-update of a key/val pair
132
+
133
+ db['key'] = 42
134
+
135
+ p :current => db['key']
136
+
137
+ db.update 'key' do |old|
138
+ p :old => old
139
+ new = 42.0
140
+ end
141
+
142
+ p :update => db['key']
143
+
144
+
145
+
146
+ ~ > ruby sample/d.rb
147
+
148
+ [5, 6, 7, 8, 9]
149
+ [25, 36, 49, 64, 81]
150
+ {:current=>42}
151
+ {:old=>42}
152
+ {:update=>42.0}
153
+
154
+
155
+ HISTORY
156
+ 0.4.2
157
+ initial version
158
+
159
+ AUTHORS
160
+ ara.t.howard
data/db ADDED
File without changes
@@ -0,0 +1,39 @@
1
+ #! /usr/bin/env gem build
2
+
3
+ lib, version = File::basename(File::dirname(File::expand_path(__FILE__))).split %r/-/, 2
4
+
5
+ require 'rubygems'
6
+
7
+ Gem::Specification::new do |spec|
8
+ $VERBOSE = nil
9
+
10
+ shiteless = lambda do |list|
11
+ list.delete_if do |file|
12
+ file =~ %r/\.svn/ or
13
+ file =~ %r/\.tmp/
14
+ end
15
+ end
16
+
17
+ spec.name = lib
18
+ spec.version = version
19
+ spec.platform = Gem::Platform::RUBY
20
+ spec.summary = lib
21
+
22
+ spec.files = shiteless[Dir::glob("**/**")]
23
+ spec.executables = shiteless[Dir::glob("bin/*")].map{|exe| File::basename exe}
24
+
25
+ spec.require_path = "lib"
26
+
27
+ spec.has_rdoc = File::exist? "doc"
28
+ spec.test_suite_file = "test/#{ lib }.rb" if File::directory? "test"
29
+ #spec.add_dependency 'lib', '>= version'
30
+ spec.add_dependency 'sqlite3'
31
+ spec.add_dependency 'fattr'
32
+
33
+ spec.extensions << "extconf.rb" if File::exists? "extconf.rb"
34
+
35
+ spec.rubyforge_project = 'codeforpeople'
36
+ spec.author = "Ara T. Howard"
37
+ spec.email = "ara.t.howard@gmail.com"
38
+ spec.homepage = "http://codeforpeople.com/lib/ruby/#{ lib }/"
39
+ end
@@ -0,0 +1,35 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require 'pathname'
4
+
5
+ $VERBOSE=nil
6
+
7
+ def indent s, n = 2
8
+ ws = ' ' * n
9
+ s.gsub %r/^/, ws
10
+ end
11
+
12
+ template = IO::read 'README.tmpl'
13
+
14
+ samples = ''
15
+ prompt = '~ > '
16
+
17
+ Dir['sample*/*'].sort.each do |sample|
18
+ samples << "\n" << " <========< #{ sample } >========>" << "\n\n"
19
+
20
+ cmd = "cat #{ sample }"
21
+ samples << indent(prompt + cmd, 2) << "\n\n"
22
+ samples << indent(`#{ cmd }`, 4) << "\n"
23
+
24
+ cmd = "ruby #{ sample }"
25
+ samples << indent(prompt + cmd, 2) << "\n\n"
26
+
27
+ cmd = "ruby -e'STDOUT.sync=true; exec %(ruby -Ilib #{ sample })'"
28
+ #cmd = "ruby -Ilib #{ sample }"
29
+ samples << indent(`#{ cmd } 2>&1`, 4) << "\n"
30
+ end
31
+
32
+ #samples.gsub! %r/^/, ' '
33
+
34
+ readme = template.gsub %r/^\s*@samples\s*$/, samples
35
+ print readme
@@ -0,0 +1,214 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rbconfig'
3
+ require 'find'
4
+ require 'ftools'
5
+ require 'tempfile'
6
+ include Config
7
+
8
+ LIBDIR = "lib"
9
+ LIBDIR_MODE = 0644
10
+
11
+ BINDIR = "bin"
12
+ BINDIR_MODE = 0755
13
+
14
+
15
+ $srcdir = CONFIG["srcdir"]
16
+ $version = CONFIG["MAJOR"]+"."+CONFIG["MINOR"]
17
+ $libdir = File.join(CONFIG["libdir"], "ruby", $version)
18
+ $archdir = File.join($libdir, CONFIG["arch"])
19
+ $site_libdir = $:.find {|x| x =~ /site_ruby$/}
20
+ $bindir = CONFIG["bindir"] || CONFIG['BINDIR']
21
+ $ruby_install_name = CONFIG['ruby_install_name'] || CONFIG['RUBY_INSTALL_NAME'] || 'ruby'
22
+ $ruby_ext = CONFIG['EXEEXT'] || ''
23
+ $ruby = File.join($bindir, ($ruby_install_name + $ruby_ext))
24
+
25
+ if !$site_libdir
26
+ $site_libdir = File.join($libdir, "site_ruby")
27
+ elsif $site_libdir !~ %r/#{Regexp.quote($version)}/
28
+ $site_libdir = File.join($site_libdir, $version)
29
+ end
30
+
31
+ def install_rb(srcdir=nil, destdir=nil, mode=nil, bin=nil)
32
+ #{{{
33
+ path = []
34
+ dir = []
35
+ Find.find(srcdir) do |f|
36
+ next unless FileTest.file?(f)
37
+ next if (f = f[srcdir.length+1..-1]) == nil
38
+ next if (/CVS$/ =~ File.dirname(f))
39
+ next if (/\.svn/ =~ File.dirname(f))
40
+ next if f =~ %r/\.lnk/
41
+ next if f =~ %r/\.svn/
42
+ next if f =~ %r/\.swp/
43
+ next if f =~ %r/\.svn/
44
+ path.push f
45
+ dir |= [File.dirname(f)]
46
+ end
47
+ for f in dir
48
+ next if f == "."
49
+ next if f == "CVS"
50
+ File::makedirs(File.join(destdir, f))
51
+ end
52
+ for f in path
53
+ next if (/\~$/ =~ f)
54
+ next if (/^\./ =~ File.basename(f))
55
+ unless bin
56
+ File::install(File.join(srcdir, f), File.join(destdir, f), mode, true)
57
+ else
58
+ from = File.join(srcdir, f)
59
+ to = File.join(destdir, f)
60
+ shebangify(from) do |sf|
61
+ $deferr.print from, " -> ", File::catname(from, to), "\n"
62
+ $deferr.printf "chmod %04o %s\n", mode, to
63
+ File::install(sf, to, mode, false)
64
+ end
65
+ end
66
+ end
67
+ #}}}
68
+ end
69
+ def shebangify f
70
+ #{{{
71
+ open(f) do |fd|
72
+ buf = fd.read 42
73
+ if buf =~ %r/^\s*#\s*!.*ruby/o
74
+ ftmp = Tempfile::new("#{ $$ }_#{ File::basename(f) }")
75
+ begin
76
+ fd.rewind
77
+ ftmp.puts "#!#{ $ruby }"
78
+ while((buf = fd.read(8192)))
79
+ ftmp.write buf
80
+ end
81
+ ftmp.close
82
+ yield ftmp.path
83
+ ensure
84
+ ftmp.close!
85
+ end
86
+ else
87
+ yield f
88
+ end
89
+ end
90
+ #}}}
91
+ end
92
+ def ARGV.switch
93
+ #{{{
94
+ return nil if self.empty?
95
+ arg = self.shift
96
+ return nil if arg == '--'
97
+ if arg =~ /^-(.)(.*)/
98
+ return arg if $1 == '-'
99
+ raise 'unknown switch "-"' if $2.index('-')
100
+ self.unshift "-#{$2}" if $2.size > 0
101
+ "-#{$1}"
102
+ else
103
+ self.unshift arg
104
+ nil
105
+ end
106
+ #}}}
107
+ end
108
+ def ARGV.req_arg
109
+ #{{{
110
+ self.shift || raise('missing argument')
111
+ #}}}
112
+ end
113
+ def linkify d, linked = []
114
+ #--{{{
115
+ if test ?d, d
116
+ versioned = Dir[ File::join(d, "*-[0-9].[0-9].[0-9].rb") ]
117
+ versioned.each do |v|
118
+ src, dst = v, v.gsub(%r/\-[\d\.]+\.rb$/, '.rb')
119
+ lnk = nil
120
+ begin
121
+ if test ?l, dst
122
+ lnk = "#{ dst }.lnk"
123
+ puts "#{ dst } -> #{ lnk }"
124
+ File::rename dst, lnk
125
+ end
126
+ unless test ?e, dst
127
+ puts "#{ src } -> #{ dst }"
128
+ File::copy src, dst
129
+ linked << dst
130
+ end
131
+ ensure
132
+ if lnk
133
+ at_exit do
134
+ puts "#{ lnk } -> #{ dst }"
135
+ File::rename lnk, dst
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
141
+ linked
142
+ #--}}}
143
+ end
144
+
145
+
146
+ #
147
+ # main program
148
+ #
149
+
150
+ libdir = $site_libdir
151
+ bindir = $bindir
152
+ no_linkify = false
153
+ linked = nil
154
+ help = false
155
+
156
+ usage = <<-usage
157
+ #{ File::basename $0 }
158
+ -d, --destdir <destdir>
159
+ -l, --libdir <libdir>
160
+ -b, --bindir <bindir>
161
+ -r, --ruby <ruby>
162
+ -n, --no_linkify
163
+ -s, --sudo
164
+ -h, --help
165
+ usage
166
+
167
+ begin
168
+ while switch = ARGV.switch
169
+ case switch
170
+ when '-d', '--destdir'
171
+ libdir = ARGV.req_arg
172
+ when '-l', '--libdir'
173
+ libdir = ARGV.req_arg
174
+ when '-b', '--bindir'
175
+ bindir = ARGV.req_arg
176
+ when '-r', '--ruby'
177
+ $ruby = ARGV.req_arg
178
+ when '-n', '--no_linkify'
179
+ no_linkify = true
180
+ when '-s', '--sudo'
181
+ sudo = 'sudo'
182
+ when '-h', '--help'
183
+ help = true
184
+ else
185
+ raise "unknown switch #{switch.dump}"
186
+ end
187
+ end
188
+ rescue
189
+ STDERR.puts $!.to_s
190
+ STDERR.puts usage
191
+ exit 1
192
+ end
193
+
194
+ if help
195
+ STDOUT.puts usage
196
+ exit
197
+ end
198
+
199
+ system "#{ sudo } #{ $ruby } pre-install.rb" if test(?s, 'pre-install.rb')
200
+
201
+ unless no_linkify
202
+ linked = linkify('lib') + linkify('bin')
203
+ end
204
+
205
+ system "#{ $ruby } extconf.rb && make && #{ sudo } make install" if test(?s, 'extconf.rb')
206
+
207
+ install_rb(LIBDIR, libdir, LIBDIR_MODE)
208
+ install_rb(BINDIR, bindir, BINDIR_MODE, bin=true)
209
+
210
+ if linked
211
+ linked.each{|path| File::rm_f path}
212
+ end
213
+
214
+ system "#{ sudo } #{ $ruby } post-install.rb" if test(?s, 'post-install.rb')
@@ -0,0 +1,213 @@
1
+ require 'fileutils'
2
+
3
+ require 'rubygems'
4
+ require 'sqlite3'
5
+ require 'fattr'
6
+
7
+ module State
8
+ def for *argv, &block
9
+ options = (Hash === argv.last ? argv.pop : {})
10
+ name = argv.first
11
+ name ||= 'global' if(options.empty? and name.nil?)
12
+
13
+ default_path = File.join(rootdir, clean(name)) if name
14
+ path = getopt(:path, options, default_path)
15
+
16
+ db.new path, options
17
+ end
18
+
19
+ alias_method 'new', 'for'
20
+
21
+ fattr :home do
22
+ home =
23
+ catch :home do
24
+ ["HOME", "USERPROFILE"].each do |key|
25
+ throw(:home, ENV[key]) if ENV[key]
26
+ end
27
+ if ENV["HOMEDRIVE"] and ENV["HOMEPATH"]
28
+ throw(:home, "#{ ENV['HOMEDRIVE'] }:#{ ENV['HOMEPATH'] }")
29
+ end
30
+ File.expand_path("~") rescue(File::ALT_SEPARATOR ? "C:/" : "/")
31
+ end
32
+ File.expand_path home
33
+ end
34
+
35
+ fattr :dotdir do
36
+ '.state'
37
+ end
38
+
39
+ fattr :rootdir do
40
+ root = File.join home, dotdir
41
+ FileUtils.mkdir_p root
42
+ root
43
+ end
44
+
45
+ def clean name
46
+ fugly = %r/[^a-zA-Z0-9_-]/
47
+ name.gsub fugly, '_'
48
+ end
49
+
50
+ def getopt key, hash, default = nil
51
+ return hash.fetch(key) if hash.has_key?(key)
52
+ key = key.to_s
53
+ return hash.fetch(key) if hash.has_key?(key)
54
+ key = key.to_sym
55
+ return hash.fetch(key) if hash.has_key?(key)
56
+ default
57
+ end
58
+
59
+ class Db
60
+ fattr('path'){ }
61
+ fattr('db'){ SQLite3::Database.new(path) }
62
+ fattr('table'){ 'state' }
63
+ fattr('schema'){
64
+ <<-sql
65
+ create table #{ table }(
66
+ key blob,
67
+ val blob,
68
+ primary key(key)
69
+ )
70
+ sql
71
+ }
72
+
73
+ def initialize path, options = {}
74
+ self.path = path
75
+ db!
76
+ migrate!
77
+ end
78
+
79
+ def clear
80
+ db.execute "delete from #{ table }"
81
+ end
82
+
83
+ def delete key
84
+ db.execute "delete from #{ table } where key=?", blob(key)
85
+ end
86
+
87
+ def size
88
+ db.query("select count(*) from #{ table }") do |result|
89
+ return result.each{|row| break Integer(row.first)}
90
+ end
91
+ end
92
+
93
+ def each
94
+ db.query("select * from #{ table }") do |result|
95
+ result.each do |row|
96
+ key, val, *ignored = row
97
+ yield load(key), load(val)
98
+ end
99
+ end
100
+ end
101
+ include ::Enumerable
102
+
103
+ def keys
104
+ map{|pair| pair.first}
105
+ end
106
+
107
+ def values
108
+ map{|pair| pair.last}
109
+ end
110
+
111
+ def [] key
112
+ db.query("select val from #{ table } where key=?", blob(key)) do |result|
113
+ val = result.each{|row| break row.first}
114
+ return( val ? load(val) : val )
115
+ end
116
+ end
117
+ alias_method 'get', '[]'
118
+
119
+ def []= key, val
120
+ key, val = blob(key), blob(val)
121
+
122
+ transaction do
123
+ begin
124
+ statement = "insert into #{ table }(key, val) values (?, ?)"
125
+ db.execute statement, key, val
126
+ return val
127
+ rescue Object
128
+ begin
129
+ statement = "update #{ table } set val=? where key=?"
130
+ db.execute statement, val, key
131
+ return val
132
+ rescue Object
133
+ begin
134
+ statement = "delete from #{ table } where key=?"
135
+ db.execute statement, key
136
+ statement = "insert into #{ table }(key, val) values (?, ?)"
137
+ db.execute statement, key, val
138
+ return val
139
+ rescue Object
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+ alias_method 'set', '[]='
146
+
147
+ def update key, *val, &block
148
+ if block and not val.empty?
149
+ raise ArgumentError, 'both block and value'
150
+ end
151
+
152
+ if val.empty? and block.nil?
153
+ raise ArgumentError, 'no update value'
154
+ end
155
+
156
+ transaction do
157
+ old = get key
158
+ val = (block ? block.call(old) : val.first)
159
+ set key, val
160
+ end
161
+ end
162
+
163
+ def transaction &block
164
+ if defined?(@transaction) and @transaction
165
+ block.call db
166
+ else
167
+ @transaction = true
168
+ begin
169
+ db.transaction{ block.call db }
170
+ ensure
171
+ @transaction = false
172
+ end
173
+ end
174
+ end
175
+
176
+ def blob val
177
+ SQLite3::Blob.new dump(val)
178
+ end
179
+
180
+ def dump value
181
+ Marshal.dump(value)
182
+ end
183
+
184
+ def load value
185
+ Marshal.load value
186
+ end
187
+
188
+ def migrate!
189
+ begin
190
+ db.execute "select count(*) from #{ table }"
191
+ rescue Object
192
+ db.execute schema
193
+ end
194
+ end
195
+ end
196
+
197
+ def db
198
+ Db
199
+ end
200
+
201
+ fattr(:global){ State.for('global') }
202
+
203
+ def method_missing m, *a, &b
204
+ super unless global.respond_to?(m)
205
+ global.send(m, *a, &b)
206
+ end
207
+
208
+ extend self
209
+ end
210
+
211
+ def State *a, &b
212
+ State.for *a, &b
213
+ end
@@ -0,0 +1,85 @@
1
+
2
+ require 'stringio'
3
+
4
+ require 'rubygems'
5
+ require 'amalgalite'
6
+ require 'sqlite'
7
+ require 'fattr'
8
+
9
+ module State
10
+ def for program = __FILE__
11
+ end
12
+
13
+ class Db
14
+ fattr('path'){ }
15
+ fattr('db'){ Amalgalite::Database.new(path) }
16
+ fattr('table'){ 'state' }
17
+ fattr('schema'){
18
+ <<-sql
19
+ create table #{ table }(
20
+ key blob,
21
+ value blob,
22
+ primary key(key)
23
+ )
24
+ sql
25
+ }
26
+
27
+ def initialize path
28
+ self.path = path
29
+ db!
30
+ migrate!
31
+ end
32
+
33
+ def []
34
+ end
35
+
36
+ def []= key, value
37
+ db.execute "insert into #{ table }(key, value) values ($key, $value)",
38
+ blob_pair(key, value)
39
+ end
40
+
41
+ def blob_pair key, value
42
+ { "$key" => blob_key(key), "$value" => blob_value(value) }
43
+ end
44
+
45
+ def blob_key value
46
+ blob value, 'key'
47
+ end
48
+
49
+ def blob_value value
50
+ blob value, 'value'
51
+ end
52
+
53
+ def blob value, column
54
+ Amalgalite::Blob.new(
55
+ :io => io(dump(value)),
56
+ :column => column(column)
57
+ )
58
+ end
59
+
60
+ def io value
61
+ StringIO.new value
62
+ end
63
+
64
+ def dump value
65
+ Marshal.dump value
66
+ end
67
+
68
+ def column column
69
+ db.schema.tables[table].columns[column]
70
+ end
71
+
72
+ def migrate!
73
+ unless db.schema.tables[table]
74
+ db.execute schema
75
+ db.reload_schema!
76
+ end
77
+ end
78
+ end
79
+
80
+ extend self
81
+ end
82
+
83
+ def State *a, &b
84
+ State.for *a, &b
85
+ end
@@ -0,0 +1,31 @@
1
+ require 'state'
2
+
3
+ # state provides persistent state for processes. usage requires simply
4
+ # accesses the state in a hash-like way.
5
+ #
6
+
7
+ # one process can create state
8
+ #
9
+ child do
10
+ State.clear
11
+ State['key'] = 'value'
12
+ end
13
+
14
+ # and later processes can have access to it
15
+ #
16
+ 2.times do |i|
17
+ child do
18
+ value = State['key']
19
+ puts "child[#{ i }] => #{ value.inspect }"
20
+ end
21
+ end
22
+
23
+
24
+ BEGIN {
25
+ # we use fork just for demonstation, but this works on windows too ;-)
26
+ #
27
+ def child &block
28
+ Process.waitpid fork(&block)
29
+ end
30
+ }
31
+
@@ -0,0 +1,16 @@
1
+ require 'state'
2
+
3
+ # state will store it's db in a subdirectory (.state) of your home directory,
4
+ # the default database is ~/.state/global, but you may specify a name to
5
+ # create a new database
6
+ #
7
+
8
+ db = State.for 'foobar'
9
+
10
+ db.clear
11
+
12
+ puts db.path
13
+
14
+ 10.times{|i| db[i] = i}
15
+
16
+ puts db.keys.inspect
@@ -0,0 +1,10 @@
1
+ require 'state'
2
+
3
+ # of course you can specify and absolute path
4
+ #
5
+
6
+ db = State.for :path => '/tmp/state'
7
+
8
+ db.clear
9
+
10
+ puts db.path
@@ -0,0 +1,29 @@
1
+ require 'state'
2
+
3
+ # in general the interface for state is like that of a hash, see the specs for
4
+ # more details
5
+
6
+ db = State.for 'foobar'
7
+
8
+ db.clear
9
+
10
+ 10.times{|i| db[i] = i**2}
11
+ 5.times{|i| db.delete i}
12
+
13
+ p db.keys
14
+ p db.values
15
+
16
+ # use the update method for atomic read-update of a key/val pair
17
+
18
+ db['key'] = 42
19
+
20
+ p :current => db['key']
21
+
22
+ db.update 'key' do |old|
23
+ p :old => old
24
+ new = 42.0
25
+ end
26
+
27
+ p :update => db['key']
28
+
29
+
@@ -0,0 +1,51 @@
1
+ module Spec
2
+ require 'fileutils'
3
+
4
+ @rootdir ||= File.expand_path(File.dirname(File.dirname(__FILE__)))
5
+ @libdir ||= File.join(@rootdir, 'lib')
6
+ @bindir ||= File.join(@rootdir, 'bin')
7
+ @specdir ||= File.join(@rootdir, 'spec')
8
+
9
+ def dir which, *parts
10
+ ivar = "@#{ which }dir"
11
+ dir = instance_variable_get(ivar)
12
+ if parts.empty?
13
+ dir
14
+ else
15
+ parts = parts.flatten.compacet.map{|part| part.to_s}
16
+ File.join(dir, *parts)
17
+ end
18
+ end
19
+
20
+ %w( root lib bin spec ).each do |which|
21
+ define_method("#{ which }dir"){|*parts| dir which, *parts}
22
+ end
23
+
24
+ def pid
25
+ @pid ||= Process.pid
26
+ end
27
+
28
+ def fu
29
+ FileUtils
30
+ end
31
+
32
+ def tmpdir &block
33
+ path = File.join specdir, 'tmp', pid.to_s
34
+ fu.mkdir_p path
35
+ at_exit{ fu.rm_rf path}
36
+ if block
37
+ begin
38
+ Dir.chdir(path, &block)
39
+ ensure
40
+ fu.rm_rf path
41
+ end
42
+ else
43
+ path
44
+ end
45
+ end
46
+
47
+ extend self
48
+
49
+ STDOUT.sync = true
50
+ STDERR.sync = true
51
+ end
@@ -0,0 +1,274 @@
1
+ #! /usr/bin/env bacon
2
+
3
+ require 'rubygems'
4
+ require 'bacon'
5
+
6
+ describe 'state db' do
7
+ it 'should initialize a new db when none exists' do
8
+ Spec.tmpdir do
9
+ path = 'state.db'
10
+ db = State::Db.new path
11
+ db.path.should == path
12
+ exists = test(?s, db.path) ? true : false
13
+ exists.should == true
14
+ end
15
+ end
16
+
17
+ it 'should re-use an existing db' do
18
+ path = 'state.db'
19
+ buf = nil
20
+
21
+ Spec.tmpdir do
22
+ db = State::Db.new path
23
+ buf = IO.read db.path
24
+ end
25
+
26
+ Spec.tmpdir do
27
+ open(path,'wb+'){|f| f.write buf}
28
+
29
+ before = File.stat path
30
+
31
+ db = State::Db.new path
32
+ db.path.should == path
33
+ exists = test(?s, db.path) ? true : false
34
+ exists.should == true
35
+
36
+ after = File.stat path
37
+
38
+ before.should == after
39
+ end
40
+ end
41
+
42
+ it 'should store key/value pairs' do
43
+ Spec.tmpdir do
44
+ db = State::Db.new 'state.db'
45
+
46
+ hash = {
47
+ 'a' => 'a',
48
+ 'b' => 0,
49
+ 'c' => [],
50
+ 'd' => hash
51
+ }
52
+
53
+ hash.keys.sort.each do |k|
54
+ v = hash[k]
55
+ raised =
56
+ begin
57
+ db[k] = v
58
+ false
59
+ rescue Object
60
+ true
61
+ end
62
+ raised.should == false
63
+ end
64
+ end
65
+ end
66
+
67
+ it 'should read key/value pairs' do
68
+ Spec.tmpdir do
69
+ db = State::Db.new 'state.db'
70
+
71
+ hash = {
72
+ 'a' => 'a',
73
+ 'b' => 0,
74
+ 'c' => [],
75
+ 'd' => hash
76
+ }
77
+
78
+ hash.keys.sort.each do |k|
79
+ v = hash[k]
80
+ db[k] = v
81
+ v.should == db[k]
82
+ end
83
+
84
+ hash.keys.sort.each do |k|
85
+ v = hash[k]
86
+ v.should == db[k]
87
+ end
88
+ end
89
+ end
90
+
91
+ it 'should clobber key/value pairs' do
92
+ Spec.tmpdir do
93
+ db = State::Db.new 'state.db'
94
+
95
+ hash = {
96
+ 'a' => 'a',
97
+ 'b' => 0,
98
+ 'c' => [],
99
+ 'd' => hash
100
+ }
101
+
102
+ hash.keys.sort.each do |k|
103
+ 2.times do |i|
104
+ v = "#{ hash[k] }_#{ i }"
105
+ db[k] = v
106
+ db[k].should == v
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+ it 'should know how many key/value pairs it has' do
113
+ Spec.tmpdir do
114
+ db = State::Db.new 'state.db'
115
+
116
+ hash = {
117
+ 'a' => 'a',
118
+ 'b' => 0,
119
+ 'c' => [],
120
+ 'd' => hash
121
+ }
122
+
123
+ hash.keys.sort.each do |k|
124
+ db[k] = hash[k]
125
+ end
126
+
127
+ db.size.should == hash.size
128
+ end
129
+ end
130
+
131
+ it 'should be able to clear all key/value pairs' do
132
+ Spec.tmpdir do
133
+ db = State::Db.new 'state.db'
134
+
135
+ hash = {
136
+ 'a' => 'a',
137
+ 'b' => 0,
138
+ 'c' => [],
139
+ 'd' => hash
140
+ }
141
+
142
+ hash.keys.sort.each do |k|
143
+ db[k] = hash[k]
144
+ end
145
+
146
+ db.size.should == hash.size
147
+
148
+ db.clear
149
+
150
+ db.size.should == 0
151
+ end
152
+ end
153
+
154
+ it 'should be able to delete specific key/value pairs' do
155
+ Spec.tmpdir do
156
+ db = State::Db.new 'state.db'
157
+
158
+ hash = {
159
+ 'a' => 'a',
160
+ 'b' => 0,
161
+ 'c' => [],
162
+ 'd' => hash
163
+ }
164
+
165
+ hash.keys.sort.each do |k|
166
+ db[k] = hash[k]
167
+ end
168
+
169
+ db.size.should == hash.size
170
+
171
+ hash.keys.sort.each do |k|
172
+ hash.delete k
173
+ db.delete k
174
+ db.size.should == hash.size
175
+ end
176
+ end
177
+ end
178
+
179
+ it 'should iterate with an each method, map, and be enumerable' do
180
+ Spec.tmpdir do
181
+ db = State::Db.new 'state.db'
182
+
183
+ (Enumerable === db).should == true
184
+
185
+ hash = {
186
+ 'a' => 'a',
187
+ 'b' => 0,
188
+ 'c' => [],
189
+ 'd' => hash
190
+ }
191
+
192
+ hash.keys.sort.each do |k|
193
+ db[k] = hash[k]
194
+ end
195
+
196
+ result = {}
197
+ db.each do |k,v|
198
+ result[k] = v
199
+ end
200
+ result.should == hash
201
+
202
+ db.map.should == hash.map
203
+ end
204
+ end
205
+
206
+ it 'should support atomic update of a key/value pair' do
207
+ Spec.tmpdir do
208
+ db = State::Db.new 'state.db'
209
+
210
+ hash = {
211
+ 'a' => 'a',
212
+ 'b' => 0,
213
+ 'c' => [],
214
+ 'd' => hash
215
+ }
216
+
217
+ hash.keys.sort.each do |k|
218
+ db.update k, hash[k]
219
+ db[k].should == hash[k]
220
+ end
221
+
222
+ hash.keys.sort.each do |k|
223
+ db.update k, hash[k]
224
+ db[k].should == hash[k]
225
+ end
226
+
227
+ hash.keys.sort.each do |k|
228
+ db.update(k){ hash[k] }
229
+ db[k].should == hash[k]
230
+ end
231
+
232
+ hash.keys.sort.each do |k|
233
+ v = hash[k].to_s + '!'
234
+ db.update(k){ v }
235
+ db[k].should == v
236
+ end
237
+ end
238
+ end
239
+ end
240
+
241
+ describe 'state' do
242
+ it 'can find a writable home directory' do
243
+ found = test(?w, State.home) ? true : false
244
+ found.should == true
245
+ end
246
+
247
+ it 'can bootstrap a db into a subdir of the home directory based on a name' do
248
+ db = State 'foobar'
249
+ exists = test(?s, db.path) ? true : false
250
+ exists.should == true
251
+ File.dirname(db.path).should == State.rootdir
252
+ end
253
+
254
+ it 'can bootstrap a db into a named path' do
255
+ Spec.tmpdir do
256
+ db = State :path => 'foobar'
257
+ exists = test(?s, db.path) ? true : false
258
+ exists.should == true
259
+ File.expand_path(db.path).should == File.expand_path('./foobar')
260
+ end
261
+ end
262
+ end
263
+
264
+
265
+ BEGIN {
266
+ rootdir = File.expand_path(File.dirname(File.dirname(__FILE__)))
267
+
268
+ libdir = File.join(rootdir, 'lib')
269
+ glob = File.join(libdir, '*.rb')
270
+ Dir[glob].each{|lib| require lib}
271
+
272
+ specdir = File.join(rootdir, 'spec')
273
+ require(File.join(specdir, 'helper'))
274
+ }
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: state
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.2
5
+ platform: ruby
6
+ authors:
7
+ - Ara T. Howard
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-09-10 00:00:00 -06:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: sqlite3
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: fattr
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ description:
36
+ email: ara.t.howard@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - bin
45
+ - db
46
+ - gemspec.rb
47
+ - gen_readme.rb
48
+ - install.rb
49
+ - lib
50
+ - lib/state.rb
51
+ - lib/state.rb.bak
52
+ - README
53
+ - sample
54
+ - sample/a.rb
55
+ - sample/b.rb
56
+ - sample/c.rb
57
+ - sample/d.rb
58
+ - spec
59
+ - spec/helper.rb
60
+ - spec/state.rb
61
+ - spec/tmp
62
+ has_rdoc: false
63
+ homepage: http://codeforpeople.com/lib/ruby/state/
64
+ post_install_message:
65
+ rdoc_options: []
66
+
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: "0"
74
+ version:
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: "0"
80
+ version:
81
+ requirements: []
82
+
83
+ rubyforge_project: codeforpeople
84
+ rubygems_version: 1.2.0
85
+ signing_key:
86
+ specification_version: 2
87
+ summary: state
88
+ test_files: []
89
+