bandicoot 0.0.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.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in bandicoot.gemspec
4
+ gemspec
@@ -0,0 +1,87 @@
1
+ Bandicoot
2
+ =========
3
+
4
+ "I get knocked down, but I get up again. You're never gonna keep me down" --
5
+ Chumbawumba
6
+
7
+ Wouldn't video games suck without levels and save points? I can't even tell
8
+ you the number of times I've been playing Pokemon, forgetting to save
9
+ regularly, only to get in a trainer battle right when my mom calls me down to
10
+ dinner. I was angry for the rest of the day thinking about how all that
11
+ effort getting the Magikarp enough XP to evolve was wasted in that one flick
12
+ of the power switch.
13
+
14
+ I feel similarly about my long running, data intensive tasks in Ruby. If I'm
15
+ loading in a file with 200,000 records, some jackass has probably put a
16
+ Windows-1252 character in record 195,095 or so. Restarting this process from
17
+ the beginning would throw me into a rage.
18
+
19
+ Bandicoot lets you set save points from which future computation can resume.
20
+ Have a boss battle with some complicated data 5 hours into a process? No
21
+ problem! Bandicoot will let you try and fix the bugs and start again.
22
+
23
+ Warning
24
+ -------
25
+
26
+ Bandicoot is a work in progress. Bandicoot is not currently thread (or Fiber)
27
+ safe. I'm working on a way to make it work while maintaining decent
28
+ semantics, but it is not there yet. It may destroy your computer and force
29
+ you to lightly blow on your cartridges.
30
+
31
+ Usage
32
+ -----
33
+
34
+ Basic usage involves setting Bandicoot up and defining save_points
35
+
36
+ Bandicoot.start do
37
+ 5.times do |i|
38
+ the_meaning_of_life = 0
39
+ the_meaning_of_life += Bandicoot.save_point(["outer", i]) do
40
+ result = 0
41
+ 10.times do |j|
42
+ result += Bandicoot.save_point(["inner", j]) do
43
+ expensive_computation(j)
44
+ end
45
+ end
46
+ result
47
+ end
48
+ the_meaning_of_life
49
+ end
50
+ end
51
+
52
+ Save points have a key and take a block. The return value of
53
+ Bandicoot.save_point is the return of the block. Save points can be nested
54
+ arbitrarily and proper scoping will be maintained.
55
+
56
+ This example will always run all of the code, writing its progress out to a
57
+ save file. If the program crashes, you can continue from where it left off by
58
+ changing the first line to
59
+
60
+ Bandicoot.start(:continue => "path_to_save_file.save") do
61
+
62
+ It will then load in that file and whenever a save_point is encountered, it
63
+ will check whether that save_point was completed. If it has been, the return
64
+ value it gave last time will be returned and the block will not be run. If it
65
+ has not been, it will be run and upon successful completion that save point
66
+ will be recorded as well. In this manner, you can keep trying until you get
67
+ it right.
68
+
69
+ Caveats and Considerations
70
+ --------------------------
71
+
72
+ 1) Bandicoot uses msgpack for serialization of keys and return values;
73
+ therefore, keys and return values must be primitives where item ==
74
+ deserialize(serialize(item)). Practically, this means primitives and NO
75
+ SYMBOLS. Arbitrarily nested arrays/hashes, numbers, and strings will work
76
+ fine.
77
+
78
+ 2) The whole point of Bandicoot is to skip over already run blocks, so
79
+ obviously any side effects in that block are not guaranteed to occur.
80
+ Oftentimes this is fine and desired (e.g. inserting a row into a database),
81
+ but if later code depends on side effects from earlier code, you may have a
82
+ bad day.
83
+
84
+ 3) Your code could fail at any point in the save point block. Bandicoot will
85
+ rerun the entire failing block, a bit of idempotence would probably be a good
86
+ idea.
87
+
@@ -0,0 +1,10 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rake/testtask'
5
+ Rake::TestTask.new(:test) do |test|
6
+ test.libs << 'lib' << 'test'
7
+ test.pattern = 'test/**/*_test.rb'
8
+ test.verbose = true
9
+ end
10
+
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "bandicoot/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "bandicoot"
7
+ s.version = Bandicoot::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Ben Hughes"]
10
+ s.email = ["ben@nabewise.com"]
11
+ s.homepage = ""
12
+ s.summary = %q{Easy resume/save point lib}
13
+ s.description = %q{Doesn't it suck when a long running task crashes? Wouldn't it be great to resume from more or less where you left off.}
14
+
15
+ s.rubyforge_project = "bandicoot"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency "msgpack"
23
+
24
+ s.add_development_dependency "mocha"
25
+ s.add_development_dependency "fakefs"
26
+ end
@@ -0,0 +1,43 @@
1
+ require 'bandicoot/save_file'
2
+ require 'bandicoot/save_point_hash'
3
+ require 'bandicoot/context'
4
+
5
+ module Bandicoot
6
+ @@current = nil
7
+
8
+ def self.start(opts={})
9
+ raise AlreadyStartedError if Bandicoot.current
10
+ Bandicoot.push_context(opts)
11
+ begin
12
+ yield
13
+ ensure
14
+ Bandicoot.current.save_file.close
15
+ Bandicoot.pop_context
16
+ end
17
+ end
18
+
19
+ def self.current
20
+ @@current
21
+ end
22
+
23
+ def self.save_point(key, &blk)
24
+ raise NotStartedError unless Bandicoot.current
25
+ Bandicoot.push_context(:key => key)
26
+ begin
27
+ Bandicoot.current.run(blk)
28
+ ensure
29
+ Bandicoot.pop_context
30
+ end
31
+ end
32
+
33
+ def self.push_context(opts={})
34
+ @@current = Bandicoot::Context.new(opts.merge(:parent => Bandicoot.current))
35
+ end
36
+
37
+ def self.pop_context
38
+ @@current = Bandicoot.current.parent
39
+ end
40
+
41
+ class AlreadyStartedError < RuntimeError; end
42
+ class NotStartedError < RuntimeError; end
43
+ end
@@ -0,0 +1,60 @@
1
+ module Bandicoot
2
+ class Context
3
+ attr_reader :parent, :save_points
4
+
5
+ def initialize(opts={})
6
+ @parent = opts[:parent]
7
+ @key = opts[:key]
8
+
9
+ # if this is the top level context
10
+ if !parent
11
+ @key ||= "__main__"
12
+ @continuing = !!opts[:continue]
13
+ if continuing?
14
+ @save_file = SaveFile.continue(opts[:continue])
15
+ @save_points = @save_file.save_points[@key] || SavePointHash.new
16
+ else
17
+ @save_file = SaveFile.create(opts[:save_file] || default_save_filename)
18
+ @save_points = SavePointHash.new
19
+ end
20
+ else
21
+ @save_points = parent.save_points[@key] || SavePointHash.new
22
+ end
23
+ end
24
+
25
+ def key
26
+ @m_key ||= ((parent && parent.key) || []) + [@key]
27
+ end
28
+
29
+ def save_file
30
+ @save_file ||= (parent && parent.save_file)
31
+ end
32
+
33
+ def continuing?
34
+ @continuing ||= (parent && parent.continuing?)
35
+ end
36
+
37
+ # makes things a little prettier
38
+ def save_point
39
+ @save_points
40
+ end
41
+
42
+ def run(blk)
43
+ if continuing? && save_point.completed?
44
+ save_point.ret_val
45
+ else
46
+ finish! blk.call
47
+ end
48
+ end
49
+
50
+ def finish!(retval=nil)
51
+ save_file.write(key, retval) if save_file
52
+ retval
53
+ end
54
+
55
+ protected
56
+ def default_save_filename
57
+ "bandicoot-#{Time.now.to_i}-#{rand(65535)}.save"
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,47 @@
1
+ require 'msgpack'
2
+
3
+ module Bandicoot
4
+ class SaveFile
5
+ attr_reader :file, :save_points
6
+
7
+ def self.create(filename)
8
+ new(File.open(filename, "w"))
9
+ end
10
+
11
+ def self.continue(filename)
12
+ file = File.open(filename, "r+")
13
+ save_points = read_save_points(file)
14
+ file.seek(0, IO::SEEK_END)
15
+ new(file, save_points)
16
+ end
17
+
18
+ def initialize(file, save_points=nil)
19
+ @file = file
20
+ @save_points = save_points
21
+ end
22
+
23
+ def write(key, retval)
24
+ file.write([key, retval].to_msgpack)
25
+ end
26
+
27
+ def close
28
+ file.close
29
+ end
30
+
31
+ protected
32
+ def self.read_save_points(file)
33
+ hash = SavePointHash.new
34
+ MessagePack::Unpacker.new(file).each do |key, retval|
35
+ c = hash
36
+ key.each do |x|
37
+ c[x] ||= SavePointHash.new
38
+ c = c[x]
39
+ end
40
+ c.complete = true
41
+ c.ret_val = retval
42
+ end
43
+ p hash
44
+ hash
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,9 @@
1
+ module Bandicoot
2
+ class SavePointHash < Hash
3
+ attr_accessor :ret_val, :complete
4
+
5
+ def completed?
6
+ !!complete
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module Bandicoot
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,57 @@
1
+ require File.join(File.dirname(__FILE__), "helper")
2
+
3
+ class BandicootTest < Test::Unit::TestCase
4
+
5
+ def teardown
6
+ FakeFS::FileSystem.clear
7
+ end
8
+
9
+ def test_start
10
+ started = false
11
+ Bandicoot.start do
12
+ started = true
13
+ assert Bandicoot.current
14
+ end
15
+ assert started
16
+ end
17
+
18
+ def test_start_with_custom_path
19
+ Bandicoot.start(:save_file => "blah") do
20
+ 1+1
21
+ end
22
+ assert File.exists?("blah")
23
+ end
24
+
25
+ def test_start_with_continue
26
+ File.open("blah", "w").close
27
+ Bandicoot.start(:continue => false) do
28
+ assert !Bandicoot.current.continuing?
29
+ end
30
+
31
+ Bandicoot.start(:continue => "blah") do
32
+ assert Bandicoot.current.continuing?
33
+ end
34
+ end
35
+
36
+ def test_save_point
37
+ run_count = 0
38
+ Bandicoot.start(:save_file => "blah") do
39
+ x = Bandicoot.save_point(:incr) do
40
+ run_count += 1
41
+ 42
42
+ end
43
+ assert_equal 42, x
44
+ end
45
+
46
+ Bandicoot.start(:continue => "blah") do
47
+ x = Bandicoot.save_point("incr") do
48
+ run_count += 1
49
+ 42
50
+ end
51
+ assert_equal 42, x
52
+ end
53
+
54
+ assert_equal 1, run_count
55
+ end
56
+ end
57
+
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ require 'redgreen'
3
+ require 'bundler/setup'
4
+
5
+ require 'bandicoot'
6
+
7
+ require 'test/unit'
8
+ require 'fakefs'
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bandicoot
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Ben Hughes
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-07-15 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: msgpack
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: mocha
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :development
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: fakefs
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: 3
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ type: :development
62
+ version_requirements: *id003
63
+ description: Doesn't it suck when a long running task crashes? Wouldn't it be great to resume from more or less where you left off.
64
+ email:
65
+ - ben@nabewise.com
66
+ executables: []
67
+
68
+ extensions: []
69
+
70
+ extra_rdoc_files: []
71
+
72
+ files:
73
+ - .gitignore
74
+ - Gemfile
75
+ - README.md
76
+ - Rakefile
77
+ - bandicoot.gemspec
78
+ - lib/bandicoot.rb
79
+ - lib/bandicoot/context.rb
80
+ - lib/bandicoot/save_file.rb
81
+ - lib/bandicoot/save_point_hash.rb
82
+ - lib/bandicoot/version.rb
83
+ - test/bandicoot_test.rb
84
+ - test/helper.rb
85
+ has_rdoc: true
86
+ homepage: ""
87
+ licenses: []
88
+
89
+ post_install_message:
90
+ rdoc_options: []
91
+
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ hash: 3
100
+ segments:
101
+ - 0
102
+ version: "0"
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ hash: 3
109
+ segments:
110
+ - 0
111
+ version: "0"
112
+ requirements: []
113
+
114
+ rubyforge_project: bandicoot
115
+ rubygems_version: 1.4.1
116
+ signing_key:
117
+ specification_version: 3
118
+ summary: Easy resume/save point lib
119
+ test_files:
120
+ - test/bandicoot_test.rb
121
+ - test/helper.rb