state 0.4.2

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/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
+