Directory structure reorganization. ruby-modules tip
authorMahlon E. Smith <mahlon@martini.nu>
Wed, 06 Jan 2016 14:36:04 -0800
branchruby-modules
changeset 9 bab54dae339a
parent 8 fe38422c10a4
Directory structure reorganization.
.rspec
LICENSE
README
Rakefile
chunker/.rspec
chunker/LICENSE
chunker/README
chunker/Rakefile
chunker/lib/chunker.rb
chunker/spec/chunker_spec.rb
lib/chunker.rb
spec/chunker_spec.rb
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.rspec	Wed Jan 06 14:36:04 2016 -0800
@@ -0,0 +1,2 @@
+-r ~/.vim/bundle/specky/ruby/specky_formatter
+-f SpeckyFormatter
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LICENSE	Wed Jan 06 14:36:04 2016 -0800
@@ -0,0 +1,29 @@
+Copyright (c) 2008-2011, Mahlon E. Smith <mahlon@martini.nu>
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are
+permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice, this
+      list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above copyright notice, this
+      list of conditions and the following disclaimer in the documentation and/or
+      other materials provided with the distribution.
+
+    * Neither the name of the author, nor the names of contributors may be used to
+      endorse or promote products derived from this software without specific prior
+      written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README	Wed Jan 06 14:36:04 2016 -0800
@@ -0,0 +1,59 @@
+
+Preface:
+
+	Ruby provides an automatic constant called DATA, which is an IO object
+	that references all text in the current file under an __END__ token.
+
+	I find it convenient to use the __END__ area to store all sorts of
+	stuff, rather than have to worry about distributing separate files.
+
+
+The problem:
+
+	The DATA constant is determined from whatever ruby believes $0 to be.
+	It doesn't work inside of other required libraries, so you'll see stuff
+	like this all the time:
+
+	END = File.open( __FILE__ ).read.split( /^__END__/, 2 ).last
+
+	It works, but it's more work than I want to do.
+
+
+A workaround:
+
+	Chunker solves this by parsing __END__ tokens for you, and making it
+	available in the form of a 'DATA_END' constant.  It installs this
+	constant into the class that includes Chunker, so you can use it again
+	and again, assuming you use a different file for each class.
+
+	It also automatically parses out other things that look like tokens, so
+	you can easily have multiple, distinct documents all embedded into the
+	__END__ block.
+
+
+Usage:
+
+	There is no direct interface to Chunker.  Just include it from a
+	class to have that file's __END__ data blocks magically become DATA_*
+	IO constants within that class.
+
+
+Example:
+
+	This produces the string "Yep.\n".
+
+
+		require 'chunker'
+		class Foom
+			include Chunker
+		end
+
+		puts Foom.new.class.const_get( :DATA_WICKED ).read
+
+		__END__
+		Stuff in the END block!
+		__WOW__
+		Ultimate success!
+		__WICKED__
+		Yep.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Rakefile	Wed Jan 06 14:36:04 2016 -0800
@@ -0,0 +1,141 @@
+#!/usr/bin/env rake
+#
+
+require 'rubygems'
+require 'pathname'
+
+require 'rake'
+require 'rspec'
+require 'rspec/core/rake_task'
+require 'rake/packagetask'
+require 'rake/gempackagetask'
+require 'rubygems/installer'
+require 'rubygems/uninstaller'
+
+
+######################################################################
+### P A T H S  A N D  F I L E S
+######################################################################
+
+BASEDIR = Pathname.new( __FILE__ ).expand_path.dirname.relative_path_from( Pathname.getwd )
+
+TEXT_FILES = %w{ Rakefile README LICENSE }.collect {|f| BASEDIR + f }
+
+SPECDIR    = BASEDIR + 'spec'
+SPEC_FILES = Pathname.glob( SPECDIR + '**/*_spec.rb' )
+
+LIBDIR    = BASEDIR + 'lib'
+LIB_FILES = Pathname.glob( LIBDIR + '**/*.rb')
+
+RELEASE_FILES = TEXT_FILES + LIB_FILES + SPEC_FILES
+
+
+######################################################################
+### H E L P E R S
+######################################################################
+
+### Given a +file+ path, find the first captured match of +pattern+,
+### or the string 'UNKNOWN' if not found. (easy to notice something is wrong.)
+###
+def find_pattern( file, pattern )
+	ver = nil
+	File.open( file ) do |f|
+		ver = f.each do |line|
+			break $1 if line =~ pattern
+		end
+	end
+	return ver.is_a?( String ) ? ver : 'UNKNOWN'
+end
+
+
+######################################################################
+### P A C K A G E   C O N S T A N T S
+######################################################################
+
+PKG_NAME      = 'chunker'
+PKG_VERSION   = find_pattern( LIBDIR + 'chunker.rb', /VERSION = ['"](.+)['"]/ )
+PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
+
+
+######################################################################
+### T A S K S
+######################################################################
+
+task :test    => 'test:spec'
+task :default => :test
+# task :default => [ :test, :package ]
+
+
+### Tasks: testing via rspec
+###
+namespace :test do
+	desc 'Generate verbose and pretty output'
+	RSpec::Core::RakeTask.new( :spec ) do |task|
+		task.pattern = SPEC_FILES
+		task.rspec_opts  = ['-b', '-fd', '-c']
+	end
+
+	desc 'Generate quiet non-colored plain-text output'
+	RSpec::Core::RakeTask.new( :quiet ) do |task|
+		task.pattern = SPEC_FILES
+		task.rspec_opts  = ['-f', 'p']
+	end
+end
+
+
+### Task: generate ctags
+### This assumes exuberant ctags, since ctags 'native' doesn't support ruby anyway.
+###
+desc "Generate a ctags 'tags' file from Chunker source"
+task :ctags do
+	sh "ctags -R #{LIBDIR}"
+end
+
+
+### Task: Create gem from source
+###
+gem = Gem::Specification.new do |gem|
+	gem.summary           = "A convenience library for parsing __END__ tokens consistently."
+	gem.name              = PKG_NAME
+	gem.version           = PKG_VERSION
+	gem.author            = 'Mahlon E. Smith'
+	gem.email             = 'mahlon@martini.nu'
+	gem.homepage          = 'http://projects.martini.nu/ruby-modules/wiki/Chunker'
+	gem.has_rdoc          = true
+	gem.extra_rdoc_files  = ['README']
+	gem.rdoc_options      << '--main' << 'README'
+
+
+	gem.files = RELEASE_FILES.
+		collect {|f| f.relative_path_from(BASEDIR).to_s }
+	gem.test_files	= SPEC_FILES.
+		collect {|f| f.relative_path_from(BASEDIR).to_s }
+
+	gem.description = "Embed arbitrary data and multiple, distinct documents within ruby files."
+end
+
+Rake::GemPackageTask.new( gem ) do |pkg|
+	pkg.need_zip     = true
+	pkg.need_tar     = true
+	pkg.need_tar_bz2 = true
+end
+
+
+### Task: install
+###
+task :install_gem => [ :package ] do
+	$stderr.puts
+	installer = Gem::Installer.new( "pkg/#{PKG_FILE_NAME}.gem" )
+	installer.install
+end
+task :install => [ :install_gem ]
+
+
+### Task: uninstall
+###
+task :uninstall_gem do
+	uninstaller = Gem::Uninstaller.new( PKG_NAME )
+	uninstaller.uninstall
+end
+task :uninstall => [ :uninstall_gem ]
+
--- a/chunker/.rspec	Sat Jan 22 01:55:55 2011 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
--r ~/.vim/bundle/specky/ruby/specky_formatter
--f SpeckyFormatter
--- a/chunker/LICENSE	Sat Jan 22 01:55:55 2011 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,29 +0,0 @@
-Copyright (c) 2008-2011, Mahlon E. Smith <mahlon@martini.nu>
-
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification, are
-permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright notice, this
-      list of conditions and the following disclaimer.
-
-    * Redistributions in binary form must reproduce the above copyright notice, this
-      list of conditions and the following disclaimer in the documentation and/or
-      other materials provided with the distribution.
-
-    * Neither the name of the author, nor the names of contributors may be used to
-      endorse or promote products derived from this software without specific prior
-      written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
-CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
-PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--- a/chunker/README	Sat Jan 22 01:55:55 2011 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-
-Preface:
-
-	Ruby provides an automatic constant called DATA, which is an IO object
-	that references all text in the current file under an __END__ token.
-
-	I find it convenient to use the __END__ area to store all sorts of
-	stuff, rather than have to worry about distributing separate files.
-
-
-The problem:
-
-	The DATA constant is determined from whatever ruby believes $0 to be.
-	It doesn't work inside of other required libraries, so you'll see stuff
-	like this all the time:
-
-	END = File.open( __FILE__ ).read.split( /^__END__/, 2 ).last
-
-	It works, but it's more work than I want to do.
-
-
-A workaround:
-
-	Chunker solves this by parsing __END__ tokens for you, and making it
-	available in the form of a 'DATA_END' constant.  It installs this
-	constant into the class that includes Chunker, so you can use it again
-	and again, assuming you use a different file for each class.
-
-	It also automatically parses out other things that look like tokens, so
-	you can easily have multiple, distinct documents all embedded into the
-	__END__ block.
-
-
-Usage:
-
-	There is no direct interface to Chunker.  Just include it from a
-	class to have that file's __END__ data blocks magically become DATA_*
-	IO constants within that class.
-
-
-Example:
-
-	This produces the string "Yep.\n".
-
-
-		require 'chunker'
-		class Foom
-			include Chunker
-		end
-
-		puts Foom.new.class.const_get( :DATA_WICKED ).read
-
-		__END__
-		Stuff in the END block!
-		__WOW__
-		Ultimate success!
-		__WICKED__
-		Yep.
-
--- a/chunker/Rakefile	Sat Jan 22 01:55:55 2011 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,141 +0,0 @@
-#!/usr/bin/env rake
-#
-
-require 'rubygems'
-require 'pathname'
-
-require 'rake'
-require 'rspec'
-require 'rspec/core/rake_task'
-require 'rake/packagetask'
-require 'rake/gempackagetask'
-require 'rubygems/installer'
-require 'rubygems/uninstaller'
-
-
-######################################################################
-### P A T H S  A N D  F I L E S
-######################################################################
-
-BASEDIR = Pathname.new( __FILE__ ).expand_path.dirname.relative_path_from( Pathname.getwd )
-
-TEXT_FILES = %w{ Rakefile README LICENSE }.collect {|f| BASEDIR + f }
-
-SPECDIR    = BASEDIR + 'spec'
-SPEC_FILES = Pathname.glob( SPECDIR + '**/*_spec.rb' )
-
-LIBDIR    = BASEDIR + 'lib'
-LIB_FILES = Pathname.glob( LIBDIR + '**/*.rb')
-
-RELEASE_FILES = TEXT_FILES + LIB_FILES + SPEC_FILES
-
-
-######################################################################
-### H E L P E R S
-######################################################################
-
-### Given a +file+ path, find the first captured match of +pattern+,
-### or the string 'UNKNOWN' if not found. (easy to notice something is wrong.)
-###
-def find_pattern( file, pattern )
-	ver = nil
-	File.open( file ) do |f|
-		ver = f.each do |line|
-			break $1 if line =~ pattern
-		end
-	end
-	return ver.is_a?( String ) ? ver : 'UNKNOWN'
-end
-
-
-######################################################################
-### P A C K A G E   C O N S T A N T S
-######################################################################
-
-PKG_NAME      = 'chunker'
-PKG_VERSION   = find_pattern( LIBDIR + 'chunker.rb', /VERSION = ['"](.+)['"]/ )
-PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
-
-
-######################################################################
-### T A S K S
-######################################################################
-
-task :test    => 'test:spec'
-task :default => :test
-# task :default => [ :test, :package ]
-
-
-### Tasks: testing via rspec
-###
-namespace :test do
-	desc 'Generate verbose and pretty output'
-	RSpec::Core::RakeTask.new( :spec ) do |task|
-		task.pattern = SPEC_FILES
-		task.rspec_opts  = ['-b', '-fd', '-c']
-	end
-
-	desc 'Generate quiet non-colored plain-text output'
-	RSpec::Core::RakeTask.new( :quiet ) do |task|
-		task.pattern = SPEC_FILES
-		task.rspec_opts  = ['-f', 'p']
-	end
-end
-
-
-### Task: generate ctags
-### This assumes exuberant ctags, since ctags 'native' doesn't support ruby anyway.
-###
-desc "Generate a ctags 'tags' file from Chunker source"
-task :ctags do
-	sh "ctags -R #{LIBDIR}"
-end
-
-
-### Task: Create gem from source
-###
-gem = Gem::Specification.new do |gem|
-	gem.summary           = "A convenience library for parsing __END__ tokens consistently."
-	gem.name              = PKG_NAME
-	gem.version           = PKG_VERSION
-	gem.author            = 'Mahlon E. Smith'
-	gem.email             = 'mahlon@martini.nu'
-	gem.homepage          = 'http://projects.martini.nu/ruby-modules/wiki/Chunker'
-	gem.has_rdoc          = true
-	gem.extra_rdoc_files  = ['README']
-	gem.rdoc_options      << '--main' << 'README'
-
-
-	gem.files = RELEASE_FILES.
-		collect {|f| f.relative_path_from(BASEDIR).to_s }
-	gem.test_files	= SPEC_FILES.
-		collect {|f| f.relative_path_from(BASEDIR).to_s }
-
-	gem.description = "Embed arbitrary data and multiple, distinct documents within ruby files."
-end
-
-Rake::GemPackageTask.new( gem ) do |pkg|
-	pkg.need_zip     = true
-	pkg.need_tar     = true
-	pkg.need_tar_bz2 = true
-end
-
-
-### Task: install
-###
-task :install_gem => [ :package ] do
-	$stderr.puts
-	installer = Gem::Installer.new( "pkg/#{PKG_FILE_NAME}.gem" )
-	installer.install
-end
-task :install => [ :install_gem ]
-
-
-### Task: uninstall
-###
-task :uninstall_gem do
-	uninstaller = Gem::Uninstaller.new( PKG_NAME )
-	uninstaller.uninstall
-end
-task :uninstall => [ :uninstall_gem ]
-
--- a/chunker/lib/chunker.rb	Sat Jan 22 01:55:55 2011 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,133 +0,0 @@
-# vim: set nosta noet ts=4 sw=4:
-
-require 'strscan'
-require 'stringio'
-
-#
-# Chunker: A convenience library for parsing __END__ tokens consistently.
-#
-# == Version
-#
-#	$Id$
-#
-# == Author
-#
-# * Mahlon E. Smith <mahlon@martini.nu>
-#
-# :include: LICENSE
-#
-
-### Namespace for the datablock parser.
-###
-module Chunker
-
-	# VCS Revision
-	VCSRev = %q$Rev$
-
-	# VCS Id
-	VCSId = %q$Id$
-
-	# Package version
-	VERSION = '1.0.0'
-
-
-	### Parser class for __END__ data blocks.
-	### Find each __TOKEN__ within the __END__, and put each into a
-	### DATA_TOKEN constant within the namespace that included us.
-	###
-	class DataParser
-
-		# The mark for a DATA block.
-		END_TOKEN = /^__END__\r?\n/
-
-		# The mark for a 'sub' block.
-		CHUNK_TOKEN = /^__([A-Z\_0-9]+)__\r?\n/
-
-
-		### Constructor: Given a +klass+ and an +io+ to the class file,
-		### extract the data blocks and install constants.
-		###
-		def initialize( klass, io )
-			io.open if io.closed?
-			end_string = io.read.split( END_TOKEN, 2 ).last
-
-			@klass   = klass
-			@scanner = StringScanner.new( end_string )
-			io.close
-
-			# put each chunk into its own constant
-			#
-			if @scanner.check_until( CHUNK_TOKEN )
-				self.extract_blocks
-
-			# no sub blocks, put the whole mess into DATA_END
-			#
-			else
-				@klass.const_set( :DATA_END, StringIO.new( end_string ) )
-			end
-		end
-
-
-		#########
-		protected
-		#########
-
-		### Parse the current +io+ for data blocks, set contents to
-		### IO constants in the including class.
-		###
-		def extract_blocks
-			label = nil
-
-			while @scanner.scan_until( CHUNK_TOKEN ) and ! @scanner.eos?
-				data = ''
-
-				# First pass, __END__ contents (until next token, instead
-				# of entire data block.)
-				#
-				if label.nil?
-					label = 'END'
-					data  = @scanner.pre_match
-
-					@scanner.pos = self.next_position
-				else
-					label = @scanner[1]
-
-					# Pull the next token text out of the data, set up the next pass
-					#
-					if data = @scanner.scan_until( CHUNK_TOKEN )
-						data = data[ 0, data.length - @scanner[0].length ]
-						@scanner.pos = self.next_position
-
-					# No additional blocks
-					#
-					else
-						data = @scanner.rest
-					end
-				end
-
-				# Add the IO constant to the class that included me.
-				@klass.const_set( "DATA_#{label}".to_sym, StringIO.new( data ) )
-			end
-		end
-
-
-		### Return the next scanner position for searching.
-		###
-		def next_position
-			return @scanner.pos - @scanner[0].length
-		end
-	end
-
-
-	### Hook included: Find the file path for how we arrived here, and open
-	### it as an IO object.  Parse the IO for data block tokens.
-	###
-    def self.included( klass )
-		# klass.instance_eval{ __FILE__ }   awww, nope.
-		# __FILE__ won't work here, so we find the filename via caller().
-		io = File.open( caller(1).last.sub(/:.*?$/, ''), 'r' )
-
-		DataParser.new( klass, io )
-    end
-end
-
--- a/chunker/spec/chunker_spec.rb	Sat Jan 22 01:55:55 2011 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,118 +0,0 @@
-# vim: set nosta noet ts=4 sw=4 ft=rspec:
-
-BEGIN {
-	require 'pathname'
-	basedir = Pathname.new( __FILE__ ).dirname.parent
-	libdir = basedir + "lib"
-
-	$LOAD_PATH.unshift( libdir ) unless $LOAD_PATH.include?( libdir )
-}
-
-require 'rspec'
-require 'chunker'
-
-ENDSTUFF = <<ENDSTUFF
-Stuff within the end block.
-
-Content of the END block
-Content of the END block
-Content of the END block
-Content of the END block
-ENDSTUFF
-
-HURGADURGA = <<HURGADURGA
-
-Content of the HURGADURGA block
-Content of the HURGADURGA block
-Content of the HURGADURGA block
-Content of the HURGADURGA block
-
-HURGADURGA
-
-HURRRRG = <<HURRRRG
-	123123123 123123123 123123123
-	123123123 123123123 123123123
-	123123123 123123123 123123123
-HURRRRG
-
-POOP = <<POOP
-Content of the POOP block
-POOP
-
-FILE_TEXT = <<EO_FILE_TEXT
-
-This is stuff we shouldn't see or care about.
-You know, stuff like code, presumably.
-
-__END__
-#{ENDSTUFF}
-EO_FILE_TEXT
-
-FILE_TEXT_MULTIPLE = <<EO_FILE_TEXT
-
-This is stuff we shouldn't see or care about.
-You know, stuff like code, presumably.
-
-__END__
-#{ENDSTUFF}
-__POOP__
-#{POOP}
-__HURRRRG__
-#{HURRRRG}
-__HURGADURGA__
-#{HURGADURGA}
-EO_FILE_TEXT
-
-
-describe Chunker do
-
-	describe Chunker::DataParser do
-
-		it "doesn't include content above the __END__ token" do
-			klass = Class.new
-			dp = Chunker::DataParser.new( klass, StringIO.new( FILE_TEXT_MULTIPLE ))
-			dp.instance_variable_get( :@scanner ).string.
-				should_not =~ /This is stuff we shouldn't see/
-		end
-
-		it "doesn't contain the __END__ token itself" do
-			klass = Class.new
-			dp = Chunker::DataParser.new( klass, StringIO.new( FILE_TEXT ))
-			dp.instance_variable_get( :@scanner ).string.should_not =~ /^__END__/
-		end
-	end
-
-
-	context 'when included from another class' do
-
-		it "has all content in DATA_END if there are no sub blocks" do
-			File.stub!( :open ).and_return( StringIO.new( FILE_TEXT ))
-			klass = Class.new { include Chunker }
-
-			klass.constants.should_not include( 'DATA_POOP' )
-			klass.constants.should_not include( 'DATA_HURRRRG' )
-			klass.constants.should_not include( 'DATA_HURGADURGA' )
-			klass.constants.should include( 'DATA_END' )
-		end
-
-		it "separates data sub blocks into individual constants" do
-			File.stub!( :open ).and_return( StringIO.new( FILE_TEXT_MULTIPLE ))
-			klass = Class.new { include Chunker }
-
-			klass.constants.should include( 'DATA_END' )
-			klass.constants.should include( 'DATA_POOP' )
-			klass.constants.should include( 'DATA_HURRRRG' )
-			klass.constants.should include( 'DATA_HURGADURGA' )
-		end
-
-		it "has IO constants that contain the data block contents" do
-			File.stub!( :open ).and_return( StringIO.new( FILE_TEXT_MULTIPLE ))
-			klass = Class.new { include Chunker }
-
-			klass.const_get( :DATA_END ).read.chomp.should        == ENDSTUFF
-			klass.const_get( :DATA_POOP ).read.chomp.should       == POOP
-			klass.const_get( :DATA_HURRRRG ).read.chomp.should    == HURRRRG
-			klass.const_get( :DATA_HURGADURGA ).read.chomp.should == HURGADURGA
-		end
-	end
-end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/chunker.rb	Wed Jan 06 14:36:04 2016 -0800
@@ -0,0 +1,133 @@
+# vim: set nosta noet ts=4 sw=4:
+
+require 'strscan'
+require 'stringio'
+
+#
+# Chunker: A convenience library for parsing __END__ tokens consistently.
+#
+# == Version
+#
+#	$Id$
+#
+# == Author
+#
+# * Mahlon E. Smith <mahlon@martini.nu>
+#
+# :include: LICENSE
+#
+
+### Namespace for the datablock parser.
+###
+module Chunker
+
+	# VCS Revision
+	VCSRev = %q$Rev$
+
+	# VCS Id
+	VCSId = %q$Id$
+
+	# Package version
+	VERSION = '1.0.0'
+
+
+	### Parser class for __END__ data blocks.
+	### Find each __TOKEN__ within the __END__, and put each into a
+	### DATA_TOKEN constant within the namespace that included us.
+	###
+	class DataParser
+
+		# The mark for a DATA block.
+		END_TOKEN = /^__END__\r?\n/
+
+		# The mark for a 'sub' block.
+		CHUNK_TOKEN = /^__([A-Z\_0-9]+)__\r?\n/
+
+
+		### Constructor: Given a +klass+ and an +io+ to the class file,
+		### extract the data blocks and install constants.
+		###
+		def initialize( klass, io )
+			io.open if io.closed?
+			end_string = io.read.split( END_TOKEN, 2 ).last
+
+			@klass   = klass
+			@scanner = StringScanner.new( end_string )
+			io.close
+
+			# put each chunk into its own constant
+			#
+			if @scanner.check_until( CHUNK_TOKEN )
+				self.extract_blocks
+
+			# no sub blocks, put the whole mess into DATA_END
+			#
+			else
+				@klass.const_set( :DATA_END, StringIO.new( end_string ) )
+			end
+		end
+
+
+		#########
+		protected
+		#########
+
+		### Parse the current +io+ for data blocks, set contents to
+		### IO constants in the including class.
+		###
+		def extract_blocks
+			label = nil
+
+			while @scanner.scan_until( CHUNK_TOKEN ) and ! @scanner.eos?
+				data = ''
+
+				# First pass, __END__ contents (until next token, instead
+				# of entire data block.)
+				#
+				if label.nil?
+					label = 'END'
+					data  = @scanner.pre_match
+
+					@scanner.pos = self.next_position
+				else
+					label = @scanner[1]
+
+					# Pull the next token text out of the data, set up the next pass
+					#
+					if data = @scanner.scan_until( CHUNK_TOKEN )
+						data = data[ 0, data.length - @scanner[0].length ]
+						@scanner.pos = self.next_position
+
+					# No additional blocks
+					#
+					else
+						data = @scanner.rest
+					end
+				end
+
+				# Add the IO constant to the class that included me.
+				@klass.const_set( "DATA_#{label}".to_sym, StringIO.new( data ) )
+			end
+		end
+
+
+		### Return the next scanner position for searching.
+		###
+		def next_position
+			return @scanner.pos - @scanner[0].length
+		end
+	end
+
+
+	### Hook included: Find the file path for how we arrived here, and open
+	### it as an IO object.  Parse the IO for data block tokens.
+	###
+    def self.included( klass )
+		# klass.instance_eval{ __FILE__ }   awww, nope.
+		# __FILE__ won't work here, so we find the filename via caller().
+		io = File.open( caller(1).last.sub(/:.*?$/, ''), 'r' )
+
+		DataParser.new( klass, io )
+    end
+end
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/spec/chunker_spec.rb	Wed Jan 06 14:36:04 2016 -0800
@@ -0,0 +1,118 @@
+# vim: set nosta noet ts=4 sw=4 ft=rspec:
+
+BEGIN {
+	require 'pathname'
+	basedir = Pathname.new( __FILE__ ).dirname.parent
+	libdir = basedir + "lib"
+
+	$LOAD_PATH.unshift( libdir ) unless $LOAD_PATH.include?( libdir )
+}
+
+require 'rspec'
+require 'chunker'
+
+ENDSTUFF = <<ENDSTUFF
+Stuff within the end block.
+
+Content of the END block
+Content of the END block
+Content of the END block
+Content of the END block
+ENDSTUFF
+
+HURGADURGA = <<HURGADURGA
+
+Content of the HURGADURGA block
+Content of the HURGADURGA block
+Content of the HURGADURGA block
+Content of the HURGADURGA block
+
+HURGADURGA
+
+HURRRRG = <<HURRRRG
+	123123123 123123123 123123123
+	123123123 123123123 123123123
+	123123123 123123123 123123123
+HURRRRG
+
+POOP = <<POOP
+Content of the POOP block
+POOP
+
+FILE_TEXT = <<EO_FILE_TEXT
+
+This is stuff we shouldn't see or care about.
+You know, stuff like code, presumably.
+
+__END__
+#{ENDSTUFF}
+EO_FILE_TEXT
+
+FILE_TEXT_MULTIPLE = <<EO_FILE_TEXT
+
+This is stuff we shouldn't see or care about.
+You know, stuff like code, presumably.
+
+__END__
+#{ENDSTUFF}
+__POOP__
+#{POOP}
+__HURRRRG__
+#{HURRRRG}
+__HURGADURGA__
+#{HURGADURGA}
+EO_FILE_TEXT
+
+
+describe Chunker do
+
+	describe Chunker::DataParser do
+
+		it "doesn't include content above the __END__ token" do
+			klass = Class.new
+			dp = Chunker::DataParser.new( klass, StringIO.new( FILE_TEXT_MULTIPLE ))
+			dp.instance_variable_get( :@scanner ).string.
+				should_not =~ /This is stuff we shouldn't see/
+		end
+
+		it "doesn't contain the __END__ token itself" do
+			klass = Class.new
+			dp = Chunker::DataParser.new( klass, StringIO.new( FILE_TEXT ))
+			dp.instance_variable_get( :@scanner ).string.should_not =~ /^__END__/
+		end
+	end
+
+
+	context 'when included from another class' do
+
+		it "has all content in DATA_END if there are no sub blocks" do
+			File.stub!( :open ).and_return( StringIO.new( FILE_TEXT ))
+			klass = Class.new { include Chunker }
+
+			klass.constants.should_not include( 'DATA_POOP' )
+			klass.constants.should_not include( 'DATA_HURRRRG' )
+			klass.constants.should_not include( 'DATA_HURGADURGA' )
+			klass.constants.should include( 'DATA_END' )
+		end
+
+		it "separates data sub blocks into individual constants" do
+			File.stub!( :open ).and_return( StringIO.new( FILE_TEXT_MULTIPLE ))
+			klass = Class.new { include Chunker }
+
+			klass.constants.should include( 'DATA_END' )
+			klass.constants.should include( 'DATA_POOP' )
+			klass.constants.should include( 'DATA_HURRRRG' )
+			klass.constants.should include( 'DATA_HURGADURGA' )
+		end
+
+		it "has IO constants that contain the data block contents" do
+			File.stub!( :open ).and_return( StringIO.new( FILE_TEXT_MULTIPLE ))
+			klass = Class.new { include Chunker }
+
+			klass.const_get( :DATA_END ).read.chomp.should        == ENDSTUFF
+			klass.const_get( :DATA_POOP ).read.chomp.should       == POOP
+			klass.const_get( :DATA_HURRRRG ).read.chomp.should    == HURRRRG
+			klass.const_get( :DATA_HURGADURGA ).read.chomp.should == HURGADURGA
+		end
+	end
+end