jashmenn-basket 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+