Directory structure reorganization.
--- /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