jashmenn-basket 0.0.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.
@@ -0,0 +1,37 @@
1
+ = Basket
2
+
3
+ Easily process and sort a directory of files.
4
+
5
+ = Example:
6
+
7
+ Basket.process("orders") do |file|
8
+ puts "we are processing #{file}"
9
+ end
10
+
11
+ Assuming there are number of files in <tt>orders/inbox</tt>
12
+ * each file is +mv+'d to <tt>orders/pending</tt>
13
+ * the block is called on each file
14
+ * the file is +mv+'d to <tt>orders/archive</tt>
15
+
16
+ See Basket#process for a list of all the options.
17
+
18
+ = Install:
19
+
20
+ gem install jashmenn-basket --source http://gems.github.com
21
+
22
+ = More examples:
23
+
24
+ The output folder can be conditional based on the output of the block, as in
25
+ the following example. In this case the default names of the folders are
26
+ +success+ and +fail+ based on the return value of the block being +true+ or
27
+ +false+.
28
+
29
+ :include:examples/02_conditional.rb
30
+
31
+ You can create arbitrary baskets for the output. If you specify <tt>:other</tt> then the files are *not* +mv+'d automatically. You must call the appropriate bang method on the file. For example:
32
+
33
+ :include:examples/03_other_baskets.rb
34
+
35
+ Baskets has (experimental) built-in support for doing parallel processing using <tt>forkoff</tt>. Example:
36
+
37
+ :include:examples/04_parallel.rb
@@ -0,0 +1,72 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rubygems/specification'
4
+ require 'date'
5
+ require 'spec/rake/spectask'
6
+ require 'rake/rdoctask'
7
+ require 'lib/ext/core'
8
+
9
+ GEM = "basket"
10
+ GEM_VERSION = "0.0.2"
11
+ AUTHOR = "Nate Murray"
12
+ EMAIL = "nate@natemurray.com"
13
+ HOMEPAGE = "http://www.xcombinator.com"
14
+ SUMMARY = "Easily process and sort a directory of files."
15
+
16
+ spec = Gem::Specification.new do |s|
17
+ s.name = GEM
18
+ s.version = GEM_VERSION
19
+ s.platform = Gem::Platform::RUBY
20
+ s.has_rdoc = true
21
+ s.extra_rdoc_files = ["README.rdoc"]
22
+ s.summary = SUMMARY
23
+ s.description = s.summary
24
+ s.author = AUTHOR
25
+ s.email = EMAIL
26
+ s.homepage = HOMEPAGE
27
+ s.add_dependency('forkoff', '>= 0.0.1')
28
+
29
+ s.require_path = 'lib'
30
+ s.autorequire = GEM
31
+ s.files = %w(README.rdoc Rakefile) + Dir.glob("{lib,spec,examples}/**/*").reject{|f| f =~ /(spec\/fixtures)/}
32
+ end
33
+
34
+ task :default => :spec
35
+
36
+ desc "Run specs"
37
+ Spec::Rake::SpecTask.new do |t|
38
+ t.spec_files = FileList['spec/**/*_spec.rb']
39
+ t.spec_opts = %w(-fs --color)
40
+ end
41
+
42
+ Rake::GemPackageTask.new(spec) do |pkg|
43
+ pkg.gem_spec = spec
44
+ end
45
+
46
+ desc "install the gem locally"
47
+ task :install => [:package] do
48
+ sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
49
+ end
50
+
51
+ desc "create a gemspec file"
52
+ task :make_spec do
53
+ File.open("#{GEM}.gemspec", "w") do |file|
54
+ file.puts spec.to_ruby
55
+ end
56
+ end
57
+
58
+ namespace :rdoc do
59
+ Rake::RDocTask.new :html do |rd|
60
+ rd.main = "README.rdoc"
61
+ rd.rdoc_dir = 'rdoc'
62
+ rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
63
+ end
64
+ task :open do
65
+ system 'open ' + (:rdoc / 'index.html') if PLATFORM['darwin']
66
+ end
67
+ end
68
+
69
+ desc "run rstakeout"
70
+ task :rstakeout do
71
+ exec "AUTOTEST=true rstakeout -t 1 -v \"spec spec --format=specdoc --color\" '*/**/*.rb'"
72
+ end
@@ -0,0 +1,8 @@
1
+ $:.unshift File.dirname(__FILE__) + "/../lib"
2
+ require 'basket'
3
+ def log(*args); p args; end
4
+
5
+ # Default baskets: inbox, pending, archive
6
+ Basket.process("orders") do |file|
7
+ log :processing, file
8
+ end
@@ -0,0 +1,13 @@
1
+ $:.unshift File.dirname(__FILE__) + "/../lib"
2
+ require 'basket'
3
+ def log(*args); p args; end
4
+
5
+ Basket.process("orders", :conditional => true, :logdev => "/dev/null") do |file, i|
6
+ if i % 2 == 0
7
+ log :success, file
8
+ true # returning true mv's file to +success+
9
+ else
10
+ log :fail, file
11
+ false # returning false mv's file to +faile+
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ $:.unshift File.dirname(__FILE__) + "/../lib"
2
+ require 'basket'
3
+ def log(*args); p args; end
4
+
5
+ Basket.process("orders", :inbox => "new", :pending => "work", :other => %w{good bad unknown}) do |file, i|
6
+ case i % 3
7
+ when 0
8
+ log :good, file
9
+ file.good! # mv's file to "orders/good"
10
+ when 1
11
+ log :bad, file
12
+ file.bad!
13
+ when 2
14
+ log :unknown, file
15
+ file.unknown!
16
+ end
17
+ end
@@ -0,0 +1,10 @@
1
+ $:.unshift File.dirname(__FILE__) + "/../lib"
2
+ require 'basket'
3
+ def log(*args); p args; end
4
+
5
+
6
+ # want to be able to process in parallel
7
+ Basket.process("orders", :workers => 4) do |file|
8
+ log $$, :processing, file
9
+ end
10
+
@@ -0,0 +1,133 @@
1
+ module Basket
2
+ VERSION = '0.0.1'
3
+
4
+ DIR = File.expand_path(File.dirname(File.expand_path(__FILE__)))
5
+ $:.unshift DIR
6
+
7
+ require 'fileutils'
8
+ require 'pp'
9
+ require 'ext/core'
10
+ require 'ext/metaid'
11
+ require 'has_logger'
12
+ require 'forkoff'
13
+
14
+ class << self
15
+ @logging = false
16
+ attr_accessor :logging
17
+ end
18
+
19
+ # == Basket
20
+ # Process a directory of files, one at a time.
21
+ #
22
+ # == Overview
23
+ # Take +root+ folder and look for the directory +inbox+. Each file in
24
+ # +inbox+ is first +mv+'d to +pending+ and then yielded to +block+. Upon
25
+ # completion of the block the file is +mv+'d to a directory, as specifed in
26
+ # the options.
27
+ #
28
+ # == Options
29
+ # * <tt>:inbox</tt>: the name of the inbox folder (default +inbox+)
30
+ # * <tt>:pending</tt>: the name of the pending folder (default +pending+)
31
+ # * <tt>:archive</tt>: the name of the archive folder (default +archive+)
32
+ # * <tt>:other</tt>: if +other+ is specified then the files are *not* moved to the archive or any other directory automatically. You *must* specify where the file will go or it will remain in +inbox+. incompatable with +conditional+.
33
+ # * <tt>:logdev</tt>: device to log to. For example: <tt>STDOUT</tt> or <tt>"/path/to/log.log"</tt> (default: <tt>/dev/null</tt>)
34
+ # * <tt>:conditional</tt>: if +conditional+ is specified then then the result of the #process block is interpreted as boolean. if the result is +true+ then the file is mv'd to +success+ otherwise it is mv'd to +fail+
35
+ #
36
+ # == Block Arity
37
+ # If the block takes a single argument, then a string containing the path to
38
+ # the +pending+ file is yielded. If the block accepts two arugments then the
39
+ # file index is also yielded. Does not work for parallel processing with forkoff.
40
+ def self.process(input, opts={}, &block)
41
+ b = Base.new(input, opts)
42
+ b.process(&block)
43
+ end
44
+
45
+ class Base
46
+
47
+ INBOX = "inbox"
48
+ PENDING = "pending"
49
+ ARCHIVE = "archive"
50
+
51
+ attr_accessor :root
52
+ attr_accessor :inbox
53
+ attr_accessor :pending
54
+ attr_accessor :other
55
+
56
+ include HasLogger
57
+ include FileUtils
58
+
59
+ def initialize(root, opts={})
60
+ @root = root
61
+ @inbox = opts.delete(:inbox) || INBOX
62
+ @pending = opts.delete(:pending) || PENDING
63
+ @archive = opts.delete(:archive) || ARCHIVE
64
+ @other = opts.delete(:other) || []
65
+ @logdev = opts.delete(:logdev) || "/dev/null"
66
+ @opts = opts
67
+ end
68
+
69
+ def process(&block)
70
+ create_directories
71
+ files = Dir[@root/@inbox/"*"]
72
+ logger.debug(["#{files.size} files in", @root/@inbox/"*"])
73
+ send_args = @opts[:workers] ? [:forkoff!, {:processes => @opts[:workers]}] : [:each]
74
+ i = 0
75
+ files.send(*send_args) do |file|
76
+ pending_file = @root/@pending/File.basename(file)
77
+ logger.info [:mv, file, pending_file]
78
+ mv file, pending_file
79
+
80
+ to_mix = mixin
81
+ pending_file.meta_eval { include to_mix }
82
+ result = block.arity > 1 ? block.call(pending_file, i) : block.call(pending_file)
83
+
84
+ if @opts[:conditional]
85
+ destination = result ? @root/"success" : @root/"fail"
86
+ logger.info [:mv, pending_file, destination]
87
+ mv pending_file, destination
88
+ elsif @other.size > 0
89
+ # don't mv anything
90
+ else
91
+ logger.info [:mv, pending_file, @root/@archive]
92
+ mv pending_file, @root/@archive
93
+ end
94
+ i += 1
95
+ end
96
+ end
97
+
98
+ protected
99
+ def create_directories
100
+ baskets.each do |dir|
101
+ logger.debug([:creating, @root/dir])
102
+ mkdir_p(@root/dir)
103
+ end
104
+ end
105
+
106
+ # create the Module that will be mixed in to the String representing the
107
+ # files this allows us to define exclamation methods that will move the
108
+ # files to a particular directory
109
+ def mixin # :nodoc:
110
+ our_baskets, our_root, our_logger = baskets, @root, logger # create local definitions for closures
111
+ @mixin ||= begin
112
+ movingMethods = Module.new
113
+ movingMethods.module_eval do
114
+ our_baskets.each do |basket|
115
+ define_method "#{basket}!" do
116
+ our_logger.info [:mv, self, our_root/basket]
117
+ FileUtils.mv self, our_root/basket
118
+ end
119
+ end
120
+ end
121
+ movingMethods
122
+ end
123
+ end
124
+
125
+ def baskets
126
+ baskets = [@inbox, @pending, @archive, @other]
127
+ baskets << ["success", "fail"] if @opts[:conditional]
128
+ baskets.flatten.compact
129
+ end
130
+
131
+ end
132
+ end
133
+
@@ -0,0 +1,11 @@
1
+ class String
2
+ def /(o)
3
+ File.join(self, o.to_s)
4
+ end
5
+ end
6
+
7
+ class Symbol
8
+ def /(o)
9
+ File.join(self.to_s, o.to_s)
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ class Object
2
+ # The hidden singleton lurks behind everyone
3
+ def metaclass; class << self; self; end; end
4
+ def meta_eval &blk; metaclass.instance_eval &blk; end
5
+
6
+ # Adds methods to a metaclass
7
+ def meta_def name, &blk
8
+ meta_eval { define_method name, &blk }
9
+ end
10
+
11
+ # Defines an instance method within a class
12
+ def class_def name, &blk
13
+ class_eval { define_method name, &blk }
14
+ end
15
+ end
@@ -0,0 +1,34 @@
1
+ # Module for easy logging
2
+ #
3
+ # Example:
4
+ #
5
+ # class MyClass
6
+ # include HasLogger
7
+ #
8
+ # # ...
9
+ # def do_something
10
+ # logger.info("I just did something!")
11
+ # end
12
+ # end
13
+ require 'logger'
14
+
15
+ module HasLogger
16
+ def self.included(mod)
17
+ mod.extend(ClassMethods)
18
+ mod.send :include, InstanceMethods
19
+ end
20
+
21
+ module ClassMethods
22
+ end
23
+
24
+ module InstanceMethods
25
+ def logger
26
+ @logger ||= begin
27
+ logger = Logger.new(@logdev || STDOUT)
28
+ logger.formatter = Logger::Formatter.new
29
+ logger.datetime_format = "%Y-%m-%d %H:%M:%S"
30
+ logger
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,122 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ def log *args
4
+ return false # comment to log these tests to stdout
5
+ p args
6
+ end
7
+
8
+ describe "Basket" do
9
+ include BasketFixtures
10
+
11
+ before(:each) do
12
+ create_fixture_files
13
+ @root = FIXTURES_DIR/"orders"
14
+ end
15
+
16
+ describe "Basket::Base" do
17
+ describe "using default options" do
18
+ before(:each) do
19
+ @b = Basket::Base.new(FIXTURES_DIR/"orders", :logdev => "/dev/null")
20
+ end
21
+
22
+ it "should have root" do
23
+ @b.root.should eql(FIXTURES_DIR/"orders")
24
+ end
25
+
26
+ it "should create the needed directories" do
27
+ %w{pending archive}.each { |basket| File.exists?(@b.root/basket).should be_false }
28
+ @b.send(:create_directories)
29
+ %w{pending archive}.each { |basket| File.exists?(@b.root/basket).should be_true }
30
+ end
31
+ end
32
+
33
+ describe "making custom directories" do
34
+ before(:each) do
35
+ @all = %w{new work done foo bar baz}
36
+ @b = Basket::Base.new(FIXTURES_DIR/"orders", :inbox => "new",
37
+ :pending => "work",
38
+ :archive => "done",
39
+ :other => @all,
40
+ :logdev => "/dev/null")
41
+ end
42
+
43
+ it "should create them" do
44
+ @all.each { |basket| File.exists?(@b.root/basket).should be_false }
45
+ @b.send(:create_directories)
46
+ @all.each { |basket| File.exists?(@b.root/basket).should be_true }
47
+ end
48
+ end
49
+ end
50
+
51
+ describe "basic usage" do
52
+ it "should process files" do
53
+ Dir["#{@root}/inbox/*"].size.should == 25 # check and make sure files are in the inbox
54
+
55
+ Basket.process(@root, :logdev => "/dev/null") do |file|
56
+ log :processing, file
57
+ end
58
+
59
+ Dir["#{@root}/inbox/*" ].size.should == 0
60
+ Dir["#{@root}/archive/*"].size.should == 25
61
+ end
62
+ end
63
+
64
+ describe "conditional usage" do
65
+ it "should process files" do
66
+ Dir["#{@root}/inbox/*"].size.should == 25 # check and make sure files are in the inbox
67
+
68
+ Basket.process(@root, :conditional => true, :logdev => "/dev/null") do |file, i|
69
+ if i % 2 == 0
70
+ log :success, file
71
+ true
72
+ else
73
+ log :fail, file
74
+ false
75
+ end
76
+ end
77
+
78
+ Dir["#{@root}/inbox/*" ].size.should == 0
79
+ Dir["#{@root}/success/*" ].size.should == 13
80
+ Dir["#{@root}/fail/*" ].size.should == 12
81
+ end
82
+ end
83
+
84
+ describe "custom baskets" do
85
+ it "should process files" do
86
+ create_fixture_files("new")
87
+ Dir["#{@root}/new/*"].size.should == 25 # check and make sure files are in the inbox
88
+
89
+ Basket.process(@root, :inbox => "new", :pending => "work", :other => %w{good bad unknown}) do |file, i|
90
+ case i % 3
91
+ when 0
92
+ log :good, file
93
+ file.good!
94
+ when 1
95
+ log :bad, file
96
+ file.bad!
97
+ when 2
98
+ log :unknown, file
99
+ file.unknown!
100
+ end
101
+ end
102
+
103
+ Dir["#{@root}/new/*" ].size.should == 0
104
+ Dir["#{@root}/good/*" ].size.should == 9
105
+ Dir["#{@root}/bad/*" ].size.should == 8
106
+ Dir["#{@root}/unknown/*" ].size.should == 8
107
+ end
108
+ end
109
+
110
+ describe "parallel processing with forkoff" do
111
+ it "should process the files" do
112
+ create_fixture_files("inbox", 10)
113
+ Basket.process(@root, :workers => 2) do |file, i|
114
+ log $$, :processing, file, i
115
+ end
116
+ end
117
+ end
118
+
119
+ after(:each) do
120
+ cleanup_fixtures_dir
121
+ end
122
+ end
@@ -0,0 +1,29 @@
1
+ $TESTING=true
2
+ $:.push File.join(File.dirname(__FILE__), '..', 'lib')
3
+ FIXTURES_DIR = File.join(File.dirname(__FILE__), "fixtures")
4
+ require 'basket'
5
+
6
+ module BasketFixtures
7
+ def fixtures_name
8
+ "order"
9
+ end
10
+
11
+ def fixtures_dir
12
+ FIXTURES_DIR/fixtures_name + "s"
13
+ end
14
+
15
+ def create_fixture_files inbox="inbox", howmany=25
16
+ cleanup_fixtures_dir
17
+ inbox_dir = fixtures_dir/inbox
18
+ FileUtils.mkdir_p(inbox_dir)
19
+ 0.upto(howmany - 1) do |i|
20
+ filename = inbox_dir/"#{fixtures_name}_%05d" % i
21
+ FileUtils.touch(filename) unless File.exists?(filename)
22
+ end
23
+ end
24
+
25
+ def cleanup_fixtures_dir
26
+ FileUtils.rm_rf(fixtures_dir) if File.exists?(fixtures_dir)
27
+ end
28
+ end
29
+
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jashmenn-basket
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Nate Murray
8
+ autorequire: basket
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-10 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: forkoff
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.1
24
+ version:
25
+ description: Easily process and sort a directory of files.
26
+ email: nate@natemurray.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - README.rdoc
33
+ files:
34
+ - README.rdoc
35
+ - Rakefile
36
+ - lib/basket.rb
37
+ - lib/ext
38
+ - lib/ext/core.rb
39
+ - lib/ext/metaid.rb
40
+ - lib/has_logger.rb
41
+ - spec/basket_spec.rb
42
+ - spec/spec_helper.rb
43
+ - examples/01_simple.rb
44
+ - examples/02_conditional.rb
45
+ - examples/03_other_baskets.rb
46
+ - examples/04_parallel.rb
47
+ has_rdoc: true
48
+ homepage: http://www.xcombinator.com
49
+ post_install_message:
50
+ rdoc_options: []
51
+
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: "0"
65
+ version:
66
+ requirements: []
67
+
68
+ rubyforge_project:
69
+ rubygems_version: 1.2.0
70
+ signing_key:
71
+ specification_version: 2
72
+ summary: Easily process and sort a directory of files.
73
+ test_files: []
74
+