obuf 1.1.0 → 1.2.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.
@@ -1,4 +1,8 @@
1
1
  rvm:
2
2
  - 1.8.7
3
- - 1.9.2
4
- - 1.9.3
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.1
6
+ - 2.2.0
7
+ - jruby
8
+ - ruby-head
data/Gemfile CHANGED
@@ -1,7 +1,8 @@
1
- source :rubygems
1
+ source 'https://rubygems.org'
2
2
 
3
3
  group :development do
4
- gem "jeweler"
4
+ gem "bundler"
5
+ gem "flexmock", "~>0.8"
5
6
  gem "rake"
6
- gem "flexmock", "~> 0.8"
7
+ gem "jeweler", '1.8.4' # Last one without the stupid nokogiri dependency
7
8
  end
@@ -1,3 +1,7 @@
1
+ === 1.2.0 / 2015-04-17
2
+
3
+ * Add Obuf::Lens to provide access to externally managed IO objects and files
4
+
1
5
  === 1.1.0 / 2011-08-02
2
6
 
3
7
  * Stores every item from the Enumerable passed to the constructor
@@ -1,37 +1,42 @@
1
- = obuf
2
-
3
- * http://github.com/julik/obuf
4
-
5
- == DESCRIPTION:
1
+ # obuf
6
2
 
7
3
  A simple Ruby object buffer. Use this if you need to temporarily store alot of serializable Ruby objects.
8
4
 
9
- == FEATURES/PROBLEMS:
10
-
11
- * Thread safe (for as far as I can gauge it)
12
- * Gives both read and write access to the storage
13
-
14
- == SYNOPSIS:
15
-
16
5
  obuf = Obuf.new
17
6
  5_000_000.times{ obuf.push(compute_some_object) } # no memory inflation
18
7
  obuf.each do | stored_object | # objects are restored one by one
19
8
  # do something with stored_object
20
9
  end
21
10
 
22
- == REQUIREMENTS:
11
+ You can also write objects in one process, and recover them in another, using `Obuf::Lens`:
12
+
13
+ # In fork process
14
+ File.open('/tmp/output.bin', 'a') do |f|
15
+ Obuf::Lens.new(f) << my_object
16
+ end
17
+
18
+ # In join process after forks completed
19
+ File.open('/tmp/output.bin', 'r') do |f|
20
+ Obuf::Lens.new(f).each do | object_written_by_fork |
21
+ puts object_written_by_fork.inspect
22
+ end
23
+ end
24
+
25
+ The `Lens` is what is used under the hood in the main Obuf as well.
26
+
27
+ ## Requirements
23
28
 
24
29
  * Ruby 1.8.6+
25
30
 
26
- == INSTALL:
31
+ ## Installation
27
32
 
28
33
  * gem install obuf
29
34
 
30
- == LICENSE:
35
+ ## License
31
36
 
32
37
  (The MIT License)
33
38
 
34
- Copyright (c) 2011 Julik Tarkhanov <me@julik.nl>
39
+ Copyright (c) 2011-2015 Julik Tarkhanov <me@julik.nl>
35
40
 
36
41
  Permission is hereby granted, free of charge, to any person obtaining
37
42
  a copy of this software and associated documentation files (the
@@ -19,13 +19,12 @@ require "thread" # required for ruby 18
19
19
  #
20
20
  # Both reading and writing aim to be threadsafe
21
21
  class Obuf
22
- VERSION = "1.1.0"
22
+ VERSION = "1.2.0"
23
+ require File.dirname(__FILE__) + "/obuf/lens"
24
+ require File.dirname(__FILE__) + "/obuf/protected_lens"
23
25
 
24
26
  include Enumerable
25
27
 
26
- DELIM = "\t"
27
- END_RECORD = "\n"
28
-
29
28
  # Returns the number of objects stored so far
30
29
  attr_reader :size
31
30
 
@@ -34,13 +33,15 @@ class Obuf
34
33
  def initialize(enumerable = [])
35
34
  @sem = Mutex.new
36
35
  @store = Tempfile.new("obuf")
37
- @store.set_encoding(Encoding::BINARY) if @store.respond_to?(:set_encoding)
38
36
  @store.binmode
39
37
  @size = 0
40
38
 
39
+ @lens = Obuf::ProtectedLens.new(@store)
40
+
41
41
  # Store everything from the enumerable in self
42
- enumerable.each(&method(:push))
42
+ enumerable.each { |e| push(e) }
43
43
 
44
+ # ...and yield self for any configuration
44
45
  yield self if block_given?
45
46
  end
46
47
 
@@ -51,14 +52,10 @@ class Obuf
51
52
 
52
53
  # Store an object
53
54
  def push(object_to_store)
54
- blob = marshal_object(object_to_store)
55
- @sem.synchronize do
56
- @store.write(blob.size)
57
- @store.write(DELIM)
58
- @store.write(blob)
59
- @store.write(END_RECORD)
55
+ @sem.synchronize {
56
+ @lens << object_to_store
60
57
  @size += 1
61
- end
58
+ }
62
59
  object_to_store
63
60
  end
64
61
 
@@ -68,7 +65,8 @@ class Obuf
68
65
  # methods are also available (but be careful with Enumerable#map and to_a)
69
66
  def each
70
67
  with_separate_read_io do | iterable |
71
- @size.times { yield(recover_object_from(iterable)) }
68
+ reading_lens = Obuf::Lens.new(iterable)
69
+ @size.times { yield(reading_lens.recover_object) }
72
70
  end
73
71
  end
74
72
 
@@ -89,15 +87,8 @@ class Obuf
89
87
 
90
88
  def recover_at(idx)
91
89
  with_separate_read_io do | iterable |
92
- iterable.seek(0)
93
-
94
- # Do not unmarshal anything but wind the IO in fixed offsets
95
- idx.times do
96
- skip_bytes = iterable.gets("\t").to_i
97
- iterable.seek(iterable.pos + skip_bytes)
98
- end
99
-
100
- recover_object_from(iterable)
90
+ reading_lens = Obuf::Lens.new(iterable)
91
+ reading_lens.recover_at(idx)
101
92
  end
102
93
  end
103
94
 
@@ -105,38 +96,15 @@ class Obuf
105
96
  # and iterate through that (we will have one IO handle per loop nest)
106
97
  def with_separate_read_io
107
98
  # Ensure all data is written before we read it
108
- @sem.synchronize { @store.flush }
99
+ iterable = @sem.synchronize do
100
+ @store.flush
101
+ File.open(@store.path, "rb")
102
+ end
109
103
 
110
- iterable = File.open(@store.path, "rb")
111
104
  begin
112
105
  yield(iterable)
113
106
  ensure
114
107
  iterable.close
115
108
  end
116
109
  end
117
-
118
- def recover_object_from(io)
119
- # Up to the tab is the amount of bytes to read
120
- demarshal_bytes = io.gets("\t").to_i
121
-
122
- # When at end of IO return nil
123
- return nil if demarshal_bytes == 0
124
-
125
- blob = io.read(demarshal_bytes)
126
- demarshal_object(blob)
127
- end
128
-
129
- # This method is only used internally.
130
- # Override this if you need non-default marshalling
131
- # (don't forget to also override demarshal_object)
132
- def marshal_object(object_to_store)
133
- d = Marshal.dump(object_to_store)
134
- end
135
-
136
- # This method is only used internally.
137
- # Override this if you need non-default demarshalling
138
- # (don't forget to also override marshal_object)
139
- def demarshal_object(blob)
140
- Marshal.load(blob)
141
- end
142
110
  end
@@ -0,0 +1,70 @@
1
+ require 'thread'
2
+
3
+ # Provides a per-object iterator on top of any IO object or pipe
4
+ class Obuf::Lens
5
+ include Enumerable
6
+
7
+ DELIM = "\t"
8
+ END_RECORD = "\n"
9
+
10
+ # Creates a new Lens on top of a File object, IO object or pipe.
11
+ # The object given should support +seek+, +gets+, and +read+ to recover
12
+ # objects, and +write+ to dump objects.
13
+ def initialize(io_or_pipe)
14
+ @io = io_or_pipe
15
+ end
16
+
17
+ # Store an object
18
+ def <<(object_to_store)
19
+ blob = marshal_object(object_to_store)
20
+ @io.write(blob.size)
21
+ @io.write(DELIM)
22
+ @io.write(blob)
23
+ @io.write(END_RECORD)
24
+ object_to_store
25
+ end
26
+
27
+ # Recover Nth object
28
+ def recover_at(idx)
29
+ @io.seek(0)
30
+ # Do not unmarshal anything but wind the IO in fixed offsets
31
+ idx.times do
32
+ skip_bytes = @io.gets("\t").to_i
33
+ @io.seek(@io.pos + skip_bytes + 1)
34
+ end
35
+ recover_object
36
+ end
37
+
38
+ # Recover the object at the current position in the IO. Returns +nil+
39
+ # if there is nothing to recover or the backing buffer is empty.
40
+ def recover_object
41
+ # Up to the tab is the amount of bytes to read
42
+ demarshal_bytes = @io.gets("\t").to_i
43
+
44
+ # When at end of IO return nil
45
+ return nil if demarshal_bytes == 0
46
+
47
+ blob = @io.read(demarshal_bytes)
48
+ demarshal_object(blob)
49
+ end
50
+
51
+ def each
52
+ yield(recover_object) until @io.eof?
53
+ end
54
+
55
+ private
56
+
57
+ # This method is only used internally.
58
+ # Override this if you need non-default marshalling
59
+ # (don't forget to also override demarshal_object)
60
+ def marshal_object(object_to_store)
61
+ Marshal.dump(object_to_store)
62
+ end
63
+
64
+ # This method is only used internally.
65
+ # Override this if you need non-default demarshalling
66
+ # (don't forget to also override marshal_object)
67
+ def demarshal_object(blob)
68
+ Marshal.load(blob)
69
+ end
70
+ end
@@ -0,0 +1,23 @@
1
+ require 'thread'
2
+
3
+ # Similar to Obuf::Lens but protects all the operations that change the IO offset
4
+ # with a Mutex.
5
+ class Obuf::ProtectedLens < Obuf::Lens
6
+ def initialize(io)
7
+ super
8
+ @mutex = Mutex.new
9
+ end
10
+
11
+ # Store an object
12
+ def <<(object_to_store)
13
+ @mutex.synchronize { super }
14
+ end
15
+
16
+ def recover_at(idx)
17
+ @mutex.synchronize { super }
18
+ end
19
+
20
+ def recover_object
21
+ @mutex.synchronize { super }
22
+ end
23
+ end
@@ -5,49 +5,54 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "obuf"
8
- s.version = "1.1.0"
8
+ s.version = "1.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Julik Tarkhanov"]
12
- s.date = "2012-02-27"
12
+ s.date = "2015-04-17"
13
13
  s.description = "Stores marshaled temporary objects on-disk in a simple Enumerable"
14
14
  s.email = "me@julik.nl"
15
15
  s.extra_rdoc_files = [
16
- "README.rdoc"
16
+ "README.md"
17
17
  ]
18
18
  s.files = [
19
19
  ".autotest",
20
20
  ".travis.yml",
21
21
  "Gemfile",
22
22
  "History.txt",
23
- "README.rdoc",
23
+ "README.md",
24
24
  "Rakefile",
25
25
  "lib/obuf.rb",
26
+ "lib/obuf/lens.rb",
27
+ "lib/obuf/protected_lens.rb",
26
28
  "obuf.gemspec",
27
29
  "test/test_obuf.rb"
28
30
  ]
29
31
  s.homepage = "http://github.com/julik/obuf"
30
32
  s.licenses = ["MIT"]
31
33
  s.require_paths = ["lib"]
32
- s.rubygems_version = "1.8.15"
34
+ s.rubygems_version = "1.8.23.2"
33
35
  s.summary = "Ruby disk-backed object buffer"
34
36
 
35
37
  if s.respond_to? :specification_version then
36
38
  s.specification_version = 3
37
39
 
38
40
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
39
- s.add_development_dependency(%q<jeweler>, [">= 0"])
40
- s.add_development_dependency(%q<rake>, [">= 0"])
41
+ s.add_development_dependency(%q<bundler>, [">= 0"])
41
42
  s.add_development_dependency(%q<flexmock>, ["~> 0.8"])
43
+ s.add_development_dependency(%q<rake>, [">= 0"])
44
+ s.add_development_dependency(%q<jeweler>, ["= 1.8.4"])
42
45
  else
43
- s.add_dependency(%q<jeweler>, [">= 0"])
44
- s.add_dependency(%q<rake>, [">= 0"])
46
+ s.add_dependency(%q<bundler>, [">= 0"])
45
47
  s.add_dependency(%q<flexmock>, ["~> 0.8"])
48
+ s.add_dependency(%q<rake>, [">= 0"])
49
+ s.add_dependency(%q<jeweler>, ["= 1.8.4"])
46
50
  end
47
51
  else
48
- s.add_dependency(%q<jeweler>, [">= 0"])
49
- s.add_dependency(%q<rake>, [">= 0"])
52
+ s.add_dependency(%q<bundler>, [">= 0"])
50
53
  s.add_dependency(%q<flexmock>, ["~> 0.8"])
54
+ s.add_dependency(%q<rake>, [">= 0"])
55
+ s.add_dependency(%q<jeweler>, ["= 1.8.4"])
51
56
  end
52
57
  end
53
58
 
@@ -4,6 +4,7 @@ require "flexmock"
4
4
  require "flexmock/test_unit"
5
5
  require "stringio"
6
6
 
7
+ # We are limited to flexmock 0.8 on Ruby 1.8
7
8
  # http://redmine.ruby-lang.org/issues/4882
8
9
  # https://github.com/jimweirich/flexmock/issues/4
9
10
  # https://github.com/julik/flexmock/commit/4acea00677e7b558bd564ec7c7630f0b27d368ca
@@ -87,15 +88,6 @@ class TestObuf < Test::Unit::TestCase
87
88
  assert_equal "E\r\nWow!", a[4]
88
89
  end
89
90
 
90
- def test_random_access
91
- a = Obuf.new
92
- letters = ("A".."Z").map{|e| "#{e}\r\nWow!"}.to_a
93
- letters.map(&a.method(:push))
94
-
95
- assert_equal "B\r\nWow!", a[1]
96
- assert_equal "E\r\nWow!", a[4]
97
- end
98
-
99
91
  def test_random_access_out_of_bounds
100
92
  a = Obuf.new
101
93
  letters = ("A".."Z").map{|e| "#{e}\r\nWow!"}.to_a
@@ -119,3 +111,74 @@ class TestObuf < Test::Unit::TestCase
119
111
  assert_equal 0, a.size
120
112
  end
121
113
  end
114
+
115
+ class TestLens < Test::Unit::TestCase
116
+ def test_lens_write
117
+ mock_io = flexmock()
118
+ lens = Obuf::Lens.new(mock_io)
119
+ flexmock(mock_io).should_receive(:write){|written|
120
+ assert_kind_of Fixnum, written
121
+ }
122
+ flexmock(mock_io).should_receive(:write).with("\t")
123
+ flexmock(mock_io).should_receive(:write){|byte_blob|
124
+ assert_kind_of String, byte_blob
125
+ }
126
+ flexmock(mock_io).should_receive(:write).with("\n")
127
+ lens << "Hi there!"
128
+ end
129
+
130
+ def test_lens_recover_object_after_write
131
+ mock_io = StringIO.new
132
+ lens = Obuf::Lens.new(mock_io)
133
+ lens << "Hi there!"
134
+
135
+ recovered = lens.recover_object
136
+ assert_nil recovered, "The IO is at the end now"
137
+
138
+ mock_io.rewind
139
+ recovered = lens.recover_object
140
+ assert_equal "Hi there!", recovered
141
+ end
142
+
143
+ def test_lens_each
144
+ mock_io = StringIO.new
145
+
146
+ writing_lens = Obuf::Lens.new(mock_io)
147
+ writing_lens << "Hi there!"
148
+ writing_lens << 98121
149
+ writing_lens << [123, :a]
150
+
151
+ mock_io.rewind
152
+
153
+ reading_lens = Obuf::Lens.new(mock_io)
154
+ items = []
155
+ reading_lens.each do | item |
156
+ items << item
157
+ end
158
+
159
+ assert_equal "Hi there!", items[0]
160
+ assert_equal 98121, items[1]
161
+ assert_equal [123, :a], items[2]
162
+ end
163
+
164
+ def test_lens_recover_at
165
+ mock_io = StringIO.new
166
+
167
+ writing_lens = Obuf::Lens.new(mock_io)
168
+ writing_lens << "Hi there!"
169
+ writing_lens << 98121
170
+ writing_lens << [123, :a]
171
+
172
+ reading_lens = Obuf::Lens.new(mock_io)
173
+ # Also try to recover object at index 3, which does not exist in the underlying buffer
174
+ objects_at_positions = (0..3).to_a.reverse.map do | i |
175
+ reading_lens.recover_at(i)
176
+ end
177
+
178
+ assert_nil objects_at_positions[0]
179
+ assert_equal [123, :a], objects_at_positions[1]
180
+ assert_equal 98121, objects_at_positions[2]
181
+ assert_equal "Hi there!", objects_at_positions[3]
182
+ end
183
+
184
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: obuf
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,22 +9,43 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-02-27 00:00:00.000000000 Z
12
+ date: 2015-04-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: jeweler
16
- requirement: &4139570 !ruby/object:Gem::Requirement
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
17
25
  none: false
18
26
  requirements:
19
27
  - - ! '>='
20
28
  - !ruby/object:Gem::Version
21
29
  version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: flexmock
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '0.8'
22
38
  type: :development
23
39
  prerelease: false
24
- version_requirements: *4139570
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '0.8'
25
46
  - !ruby/object:Gem::Dependency
26
47
  name: rake
27
- requirement: &4139080 !ruby/object:Gem::Requirement
48
+ requirement: !ruby/object:Gem::Requirement
28
49
  none: false
29
50
  requirements:
30
51
  - - ! '>='
@@ -32,32 +53,44 @@ dependencies:
32
53
  version: '0'
33
54
  type: :development
34
55
  prerelease: false
35
- version_requirements: *4139080
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
36
62
  - !ruby/object:Gem::Dependency
37
- name: flexmock
38
- requirement: &4138610 !ruby/object:Gem::Requirement
63
+ name: jeweler
64
+ requirement: !ruby/object:Gem::Requirement
39
65
  none: false
40
66
  requirements:
41
- - - ~>
67
+ - - '='
42
68
  - !ruby/object:Gem::Version
43
- version: '0.8'
69
+ version: 1.8.4
44
70
  type: :development
45
71
  prerelease: false
46
- version_requirements: *4138610
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - '='
76
+ - !ruby/object:Gem::Version
77
+ version: 1.8.4
47
78
  description: Stores marshaled temporary objects on-disk in a simple Enumerable
48
79
  email: me@julik.nl
49
80
  executables: []
50
81
  extensions: []
51
82
  extra_rdoc_files:
52
- - README.rdoc
83
+ - README.md
53
84
  files:
54
85
  - .autotest
55
86
  - .travis.yml
56
87
  - Gemfile
57
88
  - History.txt
58
- - README.rdoc
89
+ - README.md
59
90
  - Rakefile
60
91
  - lib/obuf.rb
92
+ - lib/obuf/lens.rb
93
+ - lib/obuf/protected_lens.rb
61
94
  - obuf.gemspec
62
95
  - test/test_obuf.rb
63
96
  homepage: http://github.com/julik/obuf
@@ -73,6 +106,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
73
106
  - - ! '>='
74
107
  - !ruby/object:Gem::Version
75
108
  version: '0'
109
+ segments:
110
+ - 0
111
+ hash: 4056659205467737583
76
112
  required_rubygems_version: !ruby/object:Gem::Requirement
77
113
  none: false
78
114
  requirements:
@@ -81,7 +117,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
81
117
  version: '0'
82
118
  requirements: []
83
119
  rubyforge_project:
84
- rubygems_version: 1.8.15
120
+ rubygems_version: 1.8.23.2
85
121
  signing_key:
86
122
  specification_version: 3
87
123
  summary: Ruby disk-backed object buffer