persistent_blocks 0.1.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.
- checksums.yaml +15 -0
- data/.gitignore +17 -0
- data/LICENSE.txt +22 -0
- data/README.md +49 -0
- data/lib/persistent_blocks.rb +142 -0
- data/persistent_blocks.gemspec +22 -0
- data/rakefile.rb +10 -0
- data/test/persistent_blocks_test.rb +76 -0
- data/test/test_rakefile.rb +65 -0
- metadata +68 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YWVjNDQ5ZGQ1Nzk0OTc2N2UyNjg5ODQwMTM5ZjJjZTUwMjhjOTY4ZA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ODg5MTcxYzNmN2U3YTZiZTY0MzJiOThkN2NjOGQyNDljMDM0OTBkYg==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MGMzY2QxMDU5ODA5M2UxMDhhMWZmNjhiNDQyZTY5OTViZGM5MmJmMWEyMTY0
|
10
|
+
ODM4YmMzM2RmMjdmYWE2YWNjZDYxYjEyZDE1MzI2ZDhlZjhkZDEzY2U2NDhj
|
11
|
+
M2MyMjdkM2QzZjdkMDI4NjYzMWM3YzRjNDJjZDE2NjQ2OTMxZTI=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
YmZjOWUxYWExMzZhZjhkOGM3NjFjMDUwODhiODY2NjI3MmI1OTM3ZjBhNjdh
|
14
|
+
OWRiYzY0NmE3ODA3MzY3NmRmNzk2MmUyZGJiNWYyOGMzNWIzOWQyNzgyZmIw
|
15
|
+
MTQ5Y2E2YzA0MDBlNmEwNTJjM2E3MmI3OGZjZWEzNzI0NmNmZTY=
|
data/.gitignore
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Morris Feldman
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# PersistentBlocks
|
2
|
+
|
3
|
+
Persist the output of ruby blocks
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'persistent_blocks'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install persistent_blocks
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
## put the following in a `rakefile.rb`
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
require 'persistent_blocks'
|
24
|
+
extend PersistentBlocks
|
25
|
+
|
26
|
+
persist :some_persistent_data do
|
27
|
+
# Do a long running process to generate some data called :some_persistent_data
|
28
|
+
# You don't want to repeat the calculation unecessarily, so persist will
|
29
|
+
# automatically persist it to disk for you.
|
30
|
+
sleep(1)
|
31
|
+
puts "I will return an array that will be automatically persisted to disk"
|
32
|
+
puts "Next time you run the rakefile I won't be run."
|
33
|
+
["First persisted element", "Second persisted element"]
|
34
|
+
end
|
35
|
+
|
36
|
+
persist do |some_persistent_data|
|
37
|
+
puts "Now I will print out the persistent data"
|
38
|
+
p some_persistent_data # => ["First persisted element", "Second persisted element"]
|
39
|
+
end
|
40
|
+
```
|
41
|
+
|
42
|
+
# Then run the rake file using `rake` from the command line. The
|
43
|
+
# first time you run it the block generating some_persistent_data will
|
44
|
+
# be run, but the second time you run it :some_persistent_data will
|
45
|
+
# just be retrieved from disk. The name "some_persistent_data"
|
46
|
+
# between the pipes in the second block is important because it tells
|
47
|
+
# persist which data to lookup from the marshal files and pass to the
|
48
|
+
# block of code.
|
49
|
+
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'rake/clean'
|
2
|
+
require 'debugger'
|
3
|
+
|
4
|
+
module PersistentBlocks
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def persist(*args, &block)
|
8
|
+
#puts "#{__FILE__}"
|
9
|
+
output_syms, specified_output_files, options = PBsubs.parse_inputs(args)
|
10
|
+
marshal_output_files = PBsubs.sym_ar_to_file_list(output_syms)
|
11
|
+
marshal_dir = PBsubs.ensure_marshal_dir
|
12
|
+
file marshal_output_files[0] => marshal_dir unless marshal_output_files[0].nil?
|
13
|
+
all_output_files = marshal_output_files + specified_output_files
|
14
|
+
target_file = all_output_files[0] # rake tasks only accept one file
|
15
|
+
params = if options[:input_overide]
|
16
|
+
[*options[:input_overide]]
|
17
|
+
else
|
18
|
+
block.parameters.map{|p| p[1]}
|
19
|
+
end
|
20
|
+
prereqs = PBsubs.sym_ar_to_file_list(params)
|
21
|
+
file target_file => prereqs unless prereqs.nil?
|
22
|
+
file target_file do |f|
|
23
|
+
puts "Persistent_blocks: Persisting #{output_syms} and/or #{specified_output_files} from #{[*params]}"
|
24
|
+
raw_inputs = PBsubs.load_inputs(prereqs)
|
25
|
+
inputs = PBsubs.check_for_single_input(raw_inputs, prereqs.count)
|
26
|
+
raw_outputs = block.(inputs)
|
27
|
+
outputs = PBsubs.ensure_array(raw_outputs, output_syms.count)
|
28
|
+
unless output_syms.empty?
|
29
|
+
PBsubs.check_outputs(outputs, output_syms)
|
30
|
+
PBsubs.display_output_info(outputs, output_syms)
|
31
|
+
PBsubs.save_outputs(outputs, marshal_output_files)
|
32
|
+
end
|
33
|
+
unless specified_output_files.empty?
|
34
|
+
PBsubs.check_specified_output_files(specified_output_files)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
options[:task] ||= PBsubs.default_persistent_blocks_task
|
38
|
+
task options[:task].to_sym => target_file
|
39
|
+
all_output_files.each do |out_file|
|
40
|
+
delete_task = "delete_#{out_file}"
|
41
|
+
task delete_task do
|
42
|
+
rm out_file if File.exists? out_file
|
43
|
+
end
|
44
|
+
task "delete_#{options[:task].to_sym}" => delete_task
|
45
|
+
file out_file => target_file unless out_file == target_file
|
46
|
+
end
|
47
|
+
specified_output_files.each do |out_file|
|
48
|
+
CLOBBER.include(out_file)
|
49
|
+
end
|
50
|
+
return target_file # allows file target_file => extra_dependency
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
module PBsubs
|
55
|
+
extend Rake::DSL
|
56
|
+
extend self
|
57
|
+
|
58
|
+
attr_accessor :marshal_dir, :default_persistent_blocks_task
|
59
|
+
@marshal_dir ||= 'marshal_dir'
|
60
|
+
@default_persistent_blocks_task ||= :default
|
61
|
+
|
62
|
+
def ensure_array(raw, n_expected)
|
63
|
+
(n_expected == 1) ? [raw] : raw
|
64
|
+
end
|
65
|
+
|
66
|
+
def check_for_single_input(raw, n_expected)
|
67
|
+
(n_expected == 1) ? raw[0] : raw
|
68
|
+
end
|
69
|
+
|
70
|
+
def sym_to_filename(sym)
|
71
|
+
File.join(@marshal_dir, sym.to_s)
|
72
|
+
end
|
73
|
+
|
74
|
+
def sym_ar_to_file_list(sym_ar)
|
75
|
+
sym_ar.map{|sym| sym_to_filename(sym)}
|
76
|
+
end
|
77
|
+
|
78
|
+
def marshal_save(object, filename)
|
79
|
+
File.open(filename, 'w') {|io| Marshal.dump(object, io)}
|
80
|
+
end
|
81
|
+
|
82
|
+
def marshal_load(filename)
|
83
|
+
File.open(filename, 'r') {|io| Marshal.load(io)}
|
84
|
+
end
|
85
|
+
|
86
|
+
def load_inputs(filenames)
|
87
|
+
return nil if filenames.nil? || filenames.empty?
|
88
|
+
filenames.map{|f| marshal_load(f)}
|
89
|
+
end
|
90
|
+
|
91
|
+
def save_outputs(objects_to_save, filenames)
|
92
|
+
return if objects_to_save.nil?
|
93
|
+
return if filenames.nil? || filenames.empty?
|
94
|
+
objects_to_save.zip(filenames).each {|o,f| marshal_save(o, f)}
|
95
|
+
end
|
96
|
+
|
97
|
+
def check_outputs(outputs, output_syms)
|
98
|
+
n_expect = output_syms.count
|
99
|
+
case n_expect
|
100
|
+
when 0 # NP, the block might return an argument, but we don't need to save it
|
101
|
+
when 1
|
102
|
+
raise "Error: expecting an output (#{outputs}) from the block but got none" if outputs.nil?
|
103
|
+
# if we get an array of outputs they will just be mapped to a single marshal file
|
104
|
+
else
|
105
|
+
raise "Error: expecting #{n_expect} outputs (#{output_sym}) from block but got none" if outputs.nil?
|
106
|
+
n_received = [*outputs].count
|
107
|
+
raise "Error: expecting #{n_expect} outputs (#{output_syms}) from block but got #{n_received} instead" if n_expect != n_received
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def display_output_info(outputs, output_syms)
|
112
|
+
output_ar = output_syms.zip(outputs).map do |sym, output|
|
113
|
+
how_long = (output.respond_to?:length) ? output.length : nil
|
114
|
+
"#{sym} (length = #{how_long})"
|
115
|
+
end
|
116
|
+
puts "Block Outputs:"
|
117
|
+
puts output_ar
|
118
|
+
end
|
119
|
+
|
120
|
+
def ensure_marshal_dir
|
121
|
+
CLOBBER.include(@marshal_dir) unless CLOBBER.include?(@marshal_dir)
|
122
|
+
directory @marshal_dir
|
123
|
+
@marshal_dir
|
124
|
+
end
|
125
|
+
|
126
|
+
def parse_inputs(input_args)
|
127
|
+
args = input_args.dup
|
128
|
+
options = (args[-1].is_a? Hash) ? args.pop : {}
|
129
|
+
syms = args.select{|out| out.is_a? Symbol}
|
130
|
+
files = args.select{|out| out.is_a? String}
|
131
|
+
return syms, files, options
|
132
|
+
end
|
133
|
+
|
134
|
+
def check_specified_output_files(specified_files)
|
135
|
+
specified_files.each do |file|
|
136
|
+
unless File.exists? file
|
137
|
+
raise "Error: #{file} was not created"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
Gem::Specification.new do |gem|
|
2
|
+
gem.name = "persistent_blocks"
|
3
|
+
gem.version = "0.1.0"
|
4
|
+
gem.author = "Morris Feldman"
|
5
|
+
gem.email = "morrifeldman@gmail.com"
|
6
|
+
gem.summary = "Persists the output of Ruby blocks"
|
7
|
+
gem.homepage = "http://github.com/morrifeldman/persistent_blocks"
|
8
|
+
|
9
|
+
gem.files = `git ls-files`.split($/)
|
10
|
+
|
11
|
+
gem.add_runtime_dependency 'rake'
|
12
|
+
|
13
|
+
gem.description = <<EODESC
|
14
|
+
This gem provides a rake extension to wrap blocks of ruby code so
|
15
|
+
that the output of the block is persisted using marshal. Blocks
|
16
|
+
with persisted data will not be rerun and their data is available to
|
17
|
+
subsequent blocks which can themselves generate persistent data.
|
18
|
+
This allow a very simple and robust pipeline to be constructed in
|
19
|
+
within a regular rakefile.
|
20
|
+
EODESC
|
21
|
+
|
22
|
+
end
|
data/rakefile.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'persistent_blocks'
|
4
|
+
|
5
|
+
Test_rakefile = 'test_rakefile.rb'
|
6
|
+
Test_dir = 'test'
|
7
|
+
def run_rakefile(arg = '')
|
8
|
+
`rake -I../lib -f #{Test_rakefile} #{arg}` #../lib require to locate
|
9
|
+
#persistent_blocks because we are shelling out
|
10
|
+
end
|
11
|
+
|
12
|
+
describe 'persistent_blocks' do
|
13
|
+
before do
|
14
|
+
Dir.chdir(Test_dir) do
|
15
|
+
puts run_rakefile('clobber')
|
16
|
+
|
17
|
+
puts "\nRunning tests for the first time:\n"
|
18
|
+
puts run_rakefile
|
19
|
+
|
20
|
+
puts "\nRunning tests for the second time:\n"
|
21
|
+
puts @second_run = run_rakefile
|
22
|
+
|
23
|
+
puts run_rakefile('delete_marshal_dir/test3')
|
24
|
+
puts run_rakefile('delete_marshal_dir/test4')
|
25
|
+
# this last line should not raise an error
|
26
|
+
puts run_rakefile('delete_marshal_dir/test4')
|
27
|
+
# asking for another delete shouldn't cause a problem either
|
28
|
+
|
29
|
+
# clean up
|
30
|
+
puts run_rakefile('clobber')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
it 'should persist data' do
|
34
|
+
@second_run.must_equal ''
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'specify task when calling perist' do
|
39
|
+
before do
|
40
|
+
Dir.chdir(Test_dir) do
|
41
|
+
puts run_rakefile('clobber')
|
42
|
+
run_rakefile('with_set_task')
|
43
|
+
@test_task_persisted = PBsubs.marshal_load(PBsubs.sym_to_filename(:set_task))
|
44
|
+
run_rakefile('delete_with_set_task')
|
45
|
+
@set_task_exists = File.exists?(File.join(Test_dir, 'marshal_dir','set_task'))
|
46
|
+
puts run_rakefile('clobber')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
it 'should run the test_task' do
|
50
|
+
@test_task_persisted.must_equal 'My task was set with the :task option'
|
51
|
+
end
|
52
|
+
it 'the delete_task should delete the specific task' do
|
53
|
+
@set_task_exists.must_equal false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe 'setting default_peristent_blocks_task' do
|
58
|
+
before do
|
59
|
+
Dir.chdir(Test_dir) do
|
60
|
+
puts run_rakefile('clobber')
|
61
|
+
run_rakefile('non_default_task')
|
62
|
+
@non_default_peristed = PBsubs.marshal_load(PBsubs.sym_to_filename(:not_a_default_task))
|
63
|
+
run_rakefile('delete_non_default_task')
|
64
|
+
@non_default_task_exists = File.exists?(File.join(Test_dir, 'marshal_dir', 'not_a_default_task'))
|
65
|
+
run_rakefile('clobber')
|
66
|
+
end
|
67
|
+
end
|
68
|
+
it 'should run the non-default task' do
|
69
|
+
@non_default_peristed.must_equal 'I am not a default task, I am a non_default_task'
|
70
|
+
end
|
71
|
+
it 'should run the delete_task' do
|
72
|
+
@non_default_task_exists.must_equal false
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require "persistent_blocks"
|
2
|
+
require 'debugger'
|
3
|
+
|
4
|
+
extend PersistentBlocks
|
5
|
+
|
6
|
+
persist :test do
|
7
|
+
puts "running the first task"
|
8
|
+
puts a = [1,2,3]
|
9
|
+
a
|
10
|
+
end
|
11
|
+
|
12
|
+
persist :test2 do |test|
|
13
|
+
puts "running the second task"
|
14
|
+
puts "input = #{test}"
|
15
|
+
'I do not want to be packed into an array'
|
16
|
+
end
|
17
|
+
|
18
|
+
persist :test3, :test4 do |test2|
|
19
|
+
puts "About to simulate a 2 sec calc"
|
20
|
+
sleep(2)
|
21
|
+
puts "test2 = #{test2}"
|
22
|
+
puts "test2 = '#{test2}', it should not be an array"
|
23
|
+
[test2*1, test2.upcase]
|
24
|
+
end
|
25
|
+
|
26
|
+
persist :task4 do |test3, test4|
|
27
|
+
puts "running the 4th task"
|
28
|
+
puts "test3 = #{test3}"
|
29
|
+
puts "test4 = #{test4}"
|
30
|
+
['String Output\nSecond Line', {test: 1, :test => [1, 2, 3]}]
|
31
|
+
end
|
32
|
+
|
33
|
+
persist :no_paren do
|
34
|
+
'no parentheses'
|
35
|
+
end
|
36
|
+
|
37
|
+
persist :overide_test, input_overide: [:test3, :test4] do |x,y|
|
38
|
+
puts "test3 (#{x}) was mapped to x"
|
39
|
+
puts "test4 (#{y})) was mapped to y"
|
40
|
+
1
|
41
|
+
end
|
42
|
+
|
43
|
+
persist :overide_test_no_bracket, :input_overide => :test3 do |x|
|
44
|
+
puts "input overide also works without brackets"
|
45
|
+
end
|
46
|
+
|
47
|
+
persist :set_task, :task => :with_set_task do
|
48
|
+
'My task was set with the :task option'
|
49
|
+
end
|
50
|
+
|
51
|
+
PBsubs.default_persistent_blocks_task = :non_default_task
|
52
|
+
persist :not_a_default_task do
|
53
|
+
'I am not a default task, I am a non_default_task'
|
54
|
+
end
|
55
|
+
|
56
|
+
#persist(:should_fail1, :should_fail_2) do
|
57
|
+
# 'one_output, but expecting two'
|
58
|
+
#end
|
59
|
+
|
60
|
+
#persist (:should_fail) do
|
61
|
+
# puts "This task should fail because it doesn't return anything"
|
62
|
+
#end
|
63
|
+
|
64
|
+
|
65
|
+
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: persistent_blocks
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Morris Feldman
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-09-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ! '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: ! " This gem provides a rake extension to wrap blocks of ruby code so\n
|
28
|
+
\ that the output of the block is persisted using marshal. Blocks\n with persisted
|
29
|
+
data will not be rerun and their data is available to\n subsequent blocks which
|
30
|
+
can themselves generate persistent data.\n This allow a very simple and robust
|
31
|
+
pipeline to be constructed in\n within a regular rakefile. \n"
|
32
|
+
email: morrifeldman@gmail.com
|
33
|
+
executables: []
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- .gitignore
|
38
|
+
- LICENSE.txt
|
39
|
+
- README.md
|
40
|
+
- lib/persistent_blocks.rb
|
41
|
+
- persistent_blocks.gemspec
|
42
|
+
- rakefile.rb
|
43
|
+
- test/persistent_blocks_test.rb
|
44
|
+
- test/test_rakefile.rb
|
45
|
+
homepage: http://github.com/morrifeldman/persistent_blocks
|
46
|
+
licenses: []
|
47
|
+
metadata: {}
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ! '>='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
requirements: []
|
63
|
+
rubyforge_project:
|
64
|
+
rubygems_version: 2.1.3
|
65
|
+
signing_key:
|
66
|
+
specification_version: 4
|
67
|
+
summary: Persists the output of Ruby blocks
|
68
|
+
test_files: []
|