Michael Granger avatar Michael Granger committed 31309a4

Started update of specs and build system

Comments (0)

Files changed (12)

Linguistics.tmproj

 <?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
 <dict>
 	<key>currentDocument</key>
-	<string>lib/linguistics.rb</string>
+	<string>tests/en/inflect.tests.rb</string>
 	<key>documents</key>
 	<array>
 		<dict>
 			<key>name</key>
 			<string>Linguistics</string>
 			<key>regexFolderFilter</key>
-			<string>!.*/(\.[^/]*|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$</string>
+			<string>!.*/(\.(svn|DS_Store)|CVS|_darcs|_MTN|\{arch\}|blib|.*~\.nib|.*\.(framework|app|pbproj|pbxproj|xcode(proj)?|bundle))$</string>
 			<key>sourceDirectory</key>
 			<string></string>
 		</dict>
 	<integer>317</integer>
 	<key>metaData</key>
 	<dict>
-		<key>.irbrc</key>
-		<dict>
-			<key>caret</key>
-			<dict>
-				<key>column</key>
-				<integer>24</integer>
-				<key>line</key>
-				<integer>30</integer>
-			</dict>
-			<key>columnSelection</key>
-			<false/>
-			<key>firstVisibleColumn</key>
-			<integer>0</integer>
-			<key>firstVisibleLine</key>
-			<integer>0</integer>
-			<key>selectFrom</key>
-			<dict>
-				<key>column</key>
-				<integer>45</integer>
-				<key>line</key>
-				<integer>30</integer>
-			</dict>
-			<key>selectTo</key>
-			<dict>
-				<key>column</key>
-				<integer>24</integer>
-				<key>line</key>
-				<integer>30</integer>
-			</dict>
-		</dict>
 		<key>README</key>
 		<dict>
 			<key>caret</key>
 			<key>firstVisibleLine</key>
 			<integer>0</integer>
 		</dict>
-		<key>lib/linguistics.rb</key>
-		<dict>
-			<key>caret</key>
-			<dict>
-				<key>column</key>
-				<integer>0</integer>
-				<key>line</key>
-				<integer>272</integer>
-			</dict>
-			<key>firstVisibleColumn</key>
-			<integer>0</integer>
-			<key>firstVisibleLine</key>
-			<integer>246</integer>
-		</dict>
-		<key>lib/linguistics/en.rb</key>
-		<dict>
-			<key>caret</key>
-			<dict>
-				<key>column</key>
-				<integer>0</integer>
-				<key>line</key>
-				<integer>0</integer>
-			</dict>
-			<key>firstVisibleColumn</key>
-			<integer>0</integer>
-			<key>firstVisibleLine</key>
-			<integer>23</integer>
-		</dict>
 		<key>lib/linguistics/en/linkparser.rb</key>
 		<dict>
 			<key>caret</key>
 			<key>firstVisibleLine</key>
 			<integer>0</integer>
 		</dict>
+		<key>tests/en/inflect.tests.rb</key>
+		<dict>
+			<key>caret</key>
+			<dict>
+				<key>column</key>
+				<integer>20</integer>
+				<key>line</key>
+				<integer>372</integer>
+			</dict>
+			<key>firstVisibleColumn</key>
+			<integer>0</integer>
+			<key>firstVisibleLine</key>
+			<integer>276</integer>
+		</dict>
 		<key>tests/en/linkparser.tests.rb</key>
 		<dict>
 			<key>caret</key>
 	</dict>
 	<key>openDocuments</key>
 	<array>
-		<string>tests/en/lprintf.tests.rb</string>
-		<string>lib/linguistics/en.rb</string>
-		<string>README</string>
-		<string>experiments/farmobjs.rb</string>
-		<string>.irbrc</string>
-		<string>tests/en/linkparser.tests.rb</string>
-		<string>examples/generalize_sentence.rb</string>
-		<string>lib/linguistics.rb</string>
-		<string>lib/linguistics/en/linkparser.rb</string>
+		<string>tests/en/inflect.tests.rb</string>
 	</array>
 	<key>showFileHierarchyDrawer</key>
 	<true/>
+	<key>treeState</key>
+	<dict>
+		<key>Linguistics</key>
+		<dict>
+			<key>isExpanded</key>
+			<true/>
+			<key>subItems</key>
+			<dict>
+				<key>lib</key>
+				<dict>
+					<key>isExpanded</key>
+					<true/>
+					<key>subItems</key>
+					<dict>
+						<key>linguistics</key>
+						<dict>
+							<key>isExpanded</key>
+							<true/>
+							<key>subItems</key>
+							<dict>
+								<key>en</key>
+								<dict>
+									<key>isExpanded</key>
+									<true/>
+									<key>subItems</key>
+									<dict/>
+								</dict>
+							</dict>
+						</dict>
+					</dict>
+				</dict>
+				<key>spec</key>
+				<dict>
+					<key>isExpanded</key>
+					<true/>
+					<key>subItems</key>
+					<dict>
+						<key>linguistics</key>
+						<dict>
+							<key>isExpanded</key>
+							<true/>
+							<key>subItems</key>
+							<dict/>
+						</dict>
+					</dict>
+				</dict>
+				<key>tests</key>
+				<dict>
+					<key>isExpanded</key>
+					<true/>
+					<key>subItems</key>
+					<dict>
+						<key>en</key>
+						<dict>
+							<key>isExpanded</key>
+							<true/>
+							<key>subItems</key>
+							<dict/>
+						</dict>
+					</dict>
+				</dict>
+			</dict>
+		</dict>
+	</dict>
 	<key>windowFrame</key>
-	<string>{{302, 28}, {837, 1000}}</string>
+	<string>{{302, 63}, {837, 965}}</string>
 </dict>
 </plist>
+#!rake
+#
+# Ruby Linguistics library rakefile
+#
+# Copyright (c) 2008, The FaerieMUD Consortium
+#
+# Authors:
+# * Michael Granger <mgranger@laika.com>
+#
+
+BEGIN {
+	require 'pathname'
+	basedir = Pathname.new( __FILE__ ).dirname
+	libdir = basedir + 'lib'
+	docsdir = basedir + 'docs'
+
+	$LOAD_PATH.unshift( libdir.to_s ) unless $LOAD_PATH.include?( libdir.to_s )
+	$LOAD_PATH.unshift( docsdir.to_s ) unless $LOAD_PATH.include?( docsdir.to_s )
+}
+
+
+require 'rbconfig'
+require 'rubygems'
+require 'rake'
+require 'rake/rdoctask'
+require 'rake/packagetask'
+require 'rake/gempackagetask'
+require 'pathname'
+
+include Config
+$dryrun = false
+
+# Pathname constants
+BASEDIR       = Pathname.new( __FILE__ ).dirname.expand_path
+LIBDIR        = BASEDIR + 'lib'
+DOCSDIR       = BASEDIR + 'docs'
+MISCDIR       = BASEDIR + 'misc'
+RDOCDIR       = DOCSDIR + 'rdoc'
+PKGDIR        = BASEDIR + 'pkg'
+ARTIFACTS_DIR = Pathname.new( ENV['CC_BUILD_ARTIFACTS'] || '' )
+
+TEXT_FILES    = %w( Rakefile README README.english Artistic ).
+	collect {|filename| BASEDIR + filename }
+
+SPECDIR       = BASEDIR + 'spec'
+SPEC_FILES    = Pathname.glob( SPECDIR + '**/*_spec.rb' ).
+	delete_if {|item| item =~ /\.svn/ }
+SPEC_EXCLUDES = 'spec,monkeypatches,/Library/Ruby'
+
+LIB_FILES     = Pathname.glob( LIBDIR + '**/*.rb').
+	delete_if {|item| item =~ /\.svn/ }
+
+RELEASE_FILES = TEXT_FILES + LIB_FILES + SPEC_FILES
+
+
+### Rake helper functions
+require MISCDIR + 'rake/helpers'
+
+### Package constants
+PKG_NAME      = 'linguistics'
+PKG_VERSION   = find_pattern_in_file( /VERSION = '(\d+\.\d+\.\d+)'/, LIBDIR + 'linguistics.rb' ).first
+PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
+
+RELEASE_NAME  = "REL #{PKG_VERSION}"
+
+# Load task plugins
+RAKE_TASKDIR = MISCDIR + 'rake'
+Pathname.glob( RAKE_TASKDIR + '*.rb' ).each do |tasklib|
+	next if tasklib =~ %r{/helpers.rb$}
+	require tasklib
+end
+
+if Rake.application.options.trace
+	$trace = true
+	log "$trace is enabled"
+end
+
+if Rake.application.options.dryrun
+	$dryrun = true
+	log "$dryrun is enabled"
+	Rake.application.options.dryrun = false
+end
+
+### Default task
+task :default  => [:clean, :spec, :verify, :package]
+
+
+### Task: clean
+desc "Clean pkg, coverage, and rdoc; remove .bak files"
+task :clean => [ :clobber_rdoc, :clobber_package, :clobber_coverage ] do
+	files = FileList['**/*.bak']
+	files.clear_exclude
+	File.rm( files ) unless files.empty?
+	FileUtils.rm_rf( 'artifacts' )
+end
+
+
+### Task: docs -- Convenience task for rebuilding dynamic docs
+task :docs => [ :coverage, :rdoc ]
+
+
+### Task: rdoc
+Rake::RDocTask.new do |rdoc|
+	rdoc.rdoc_dir = 'docs/api'
+	rdoc.title    = "Linguistics - a natural language framework for Ruby"
+
+	rdoc.options += [
+		'-w', '4',
+		'-SHN',
+		'-i', 'docs',
+		'-f', 'darkfish',
+		'-m', 'README',
+		'-W', 'http://deveiate.org/projects/linguistics/trunk/'
+	  ]
+	
+	rdoc.rdoc_files.include TEXT_FILES.collect {|f| f.relative_path_from(BASEDIR).to_s }
+	rdoc.rdoc_files.include LIB_FILES.collect {|f| f.relative_path_from(BASEDIR).to_s }
+end
+
+
+### Task: gem
+gemspec = Gem::Specification.new do |gem|
+	pkg_build = get_svn_rev( BASEDIR ) || 0
+	
+	gem.name    	= PKG_NAME
+	gem.version 	= "%s.%s" % [ PKG_VERSION, pkg_build ]
+
+	gem.summary     = "A generic, language-neutral framework for extending Ruby " +
+	        "objects with linguistic methods."
+	gem.description = <<-EOD
+	:TODO: Finish writing this description.
+	ThingFish is a highly-accessable network datastore. And it needs more description.
+	EOD
+
+	gem.authors  	= "Michael Granger, Martin Chase"
+	gem.email       = "ged@FaerieMUD.org, stillflame@FaerieMUD.org"
+	gem.homepage 	= "http://deveiate.org/projects/Linguistics/"
+
+	gem.has_rdoc 	= true
+
+	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 }
+end
+Rake::GemPackageTask.new( gemspec ) do |task|
+	task.gem_spec = gemspec
+	task.need_tar = false
+	task.need_tar_gz = true
+	task.need_tar_bz2 = true
+	task.need_zip = true
+end
+
+
+### Task: install
+desc "Install Linguistics as a conventional library"
+task :install do
+	log "Installing Linguistics as a convention library"
+	sitelib = Pathname.new( CONFIG['sitelibdir'] )
+	Dir.chdir( LIBDIR ) do
+		LIB_FILES.each do |libfile|
+			relpath = libfile.relative_path_from( LIBDIR )
+			target = sitelib + relpath
+			FileUtils.mkpath target.dirname,
+				:mode => 0755, :verbose => true, :noop => $dryrun unless target.dirname.directory?
+			FileUtils.install relpath, target,
+				:mode => 0644, :verbose => true, :noop => $dryrun
+		end
+	end
+end
+
+### Task: install_gem
+desc "Install Linguistics as a gem"
+task :install_gem => [:package] do
+	installer = Gem::Installer.new( %{pkg/#{PKG_FILE_NAME}.gem} )
+	installer.install
+end
+
+desc "Uninstall Linguistics if it's been installed as a conventional library"
+task :uninstall do
+	log "Uninstalling conventionally-installed Linguistics library files"
+	sitelib = Pathname.new( CONFIG['sitelibdir'] )
+	dir = sitelib + 'linguistics'
+	FileUtils.rm_rf( dir, :verbose => true, :noop => $dryrun )
+	lib = sitelib + 'linguistics.rb'
+	FileUtils.rm( lib, :verbose => true, :noop => $dryrun )
+end
+
+### Task: uninstall_gem
+task :uninstall_gem => [:clean] do
+	uninstaller = Gem::Uninstaller.new( PKG_FILE_NAME )
+	uninstaller.uninstall
+end
+
+
+
+### Cruisecontrol task
+desc "Cruisecontrol build"
+task :cruise => [:clean, :coverage, :package] do |task|
+	raise "Artifacts dir not set." if ARTIFACTS_DIR.to_s.empty?
+	artifact_dir = ARTIFACTS_DIR.cleanpath
+	artifact_dir.mkpath
+	
+	$stderr.puts "Copying coverage stats..."
+	FileUtils.cp_r( 'coverage', artifact_dir )
+	
+	$stderr.puts "Copying packages..."
+	FileUtils.cp_r( FileList['pkg/*'].to_a, artifact_dir )
+end
+
+
+### RSpec tasks
+begin
+	gem 'rspec', '>= 1.1.1'
+	require 'spec/rake/spectask'
+
+	COMMON_SPEC_OPTS = ['-c', '-f', 's']
+
+	### Task: spec
+	Spec::Rake::SpecTask.new( :spec ) do |task|
+		task.spec_files = SPEC_FILES
+		task.libs += [LIBDIR]
+		task.spec_opts = COMMON_SPEC_OPTS
+	end
+	task :test => [:spec]
+
+
+	namespace :spec do
+		desc "Run rspec every time there's a change to one of the files"
+        task :autotest do
+            require 'autotest/rspec'
+            autotester = Autotest::Rspec.new
+
+			autotester.exceptions = %r{\.svn|\.skel}
+            autotester.run
+        end
+
+	
+		desc "Generate HTML output for a spec run"
+		Spec::Rake::SpecTask.new( :html ) do |task|
+			task.spec_files = SPEC_FILES
+			task.spec_opts = ['-f','h', '-D']
+		end
+
+		desc "Generate plain-text output for a CruiseControl.rb build"
+		Spec::Rake::SpecTask.new( :text ) do |task|
+			task.spec_files = SPEC_FILES
+			task.spec_opts = ['-f','p']
+		end
+	end
+rescue LoadError => err
+	task :no_rspec do
+		$stderr.puts "Testing tasks not defined: RSpec rake tasklib not available: %s" %
+			[ err.message ]
+	end
+	
+	task :spec => :no_rspec
+	namespace :spec do
+		task :autotest => :no_rspec
+		task :html => :no_rspec
+		task :text => :no_rspec
+	end
+end
+
+
+### RCov (via RSpec) tasks
+begin
+	gem 'rcov'
+	gem 'rspec', '>= 1.1.1'
+
+	COVERAGE_TARGETDIR = BASEDIR + 'coverage'
+
+	RCOV_OPTS = ['--exclude', SPEC_EXCLUDES, '--xrefs', '--save']
+
+	### Task: coverage (via RCov)
+	### Task: spec
+	desc "Build test coverage reports"
+	Spec::Rake::SpecTask.new( :coverage ) do |task|
+		task.spec_files = SPEC_FILES
+		task.libs += LIB_FILES
+		task.spec_opts = ['-f', 'p', '-b']
+		task.rcov_opts = RCOV_OPTS
+		task.rcov = true
+	end
+	task :coverage do
+		rmtree( COVERAGE_TARGETDIR )
+		cp_r( 'coverage', COVERAGE_TARGETDIR, :verbose => true )
+	end
+	
+	task :rcov => [:coverage] do; end
+	
+	### Other coverage tasks
+	namespace :coverage do
+		desc "Generate a detailed text coverage report"
+		Spec::Rake::SpecTask.new( :text ) do |task|
+			task.spec_files = SPEC_FILES
+			task.libs += FileList['plugins/**/lib']
+			task.rcov_opts = RCOV_OPTS + ['--text-report']
+			task.rcov = true
+		end
+
+		desc "Show differences in coverage from last run"
+		Spec::Rake::SpecTask.new( :diff ) do |task|
+			task.spec_files = SPEC_FILES
+			task.libs += FileList['plugins/**/lib']
+			task.rcov_opts = ['--text-coverage-diff']
+			task.rcov = true
+		end
+
+		### Task: verify coverage
+		desc "Build coverage statistics"
+		VerifyTask.new( :verify => :rcov ) do |task|
+			task.threshold = 85.0
+		end
+		
+		desc "Run RCov in 'spec-only' mode to check coverage from specs"
+		Spec::Rake::SpecTask.new( :speconly ) do |task|
+			task.spec_files = SPEC_FILES
+			task.libs += FileList['plugins/**/lib']
+			task.rcov_opts = ['--exclude', SPEC_EXCLUDES, '--text-report', '--save']
+			task.rcov = true
+		end
+	end
+
+	task :clobber_coverage do
+		rmtree( COVERAGE_TARGETDIR )
+	end
+
+rescue LoadError => err
+	task :no_rcov do
+		$stderr.puts "Coverage tasks not defined: RSpec+RCov tasklib not available: %s" %
+			[ err.message ]
+	end
+
+	task :coverage => :no_rcov
+	task :clobber_coverage
+	task :rcov => :no_rcov
+	namespace :coverage do
+		task :text => :no_rcov
+		task :diff => :no_rcov
+	end
+	task :verify => :no_rcov
+end
+
+
+
+### Coding style checks and fixes
+namespace :style do
+	
+	BLANK_LINE = /^\s*$/
+	GOOD_INDENT = /^(\t\s*)?\S/
+
+	# A list of the files that have legitimate leading whitespace, etc.
+	PROBLEM_FILES = [ SPECDIR + 'config_spec.rb' ]
+	
+	desc "Check source files for inconsistent indent and fix them"
+	task :fix_indent do
+		files = LIB_FILES + SPEC_FILES
+
+		badfiles = Hash.new {|h,k| h[k] = [] }
+		
+		trace "Checking files for indentation"
+		files.each do |file|
+			if PROBLEM_FILES.include?( file )
+				trace "  skipping problem file #{file}..."
+				next
+			end
+			
+			trace "  #{file}"
+			linecount = 0
+			file.each_line do |line|
+				linecount += 1
+				
+				# Skip blank lines
+				next if line =~ BLANK_LINE
+				
+				# If there's a line with incorrect indent, note it and skip to the 
+				# next file
+				if line !~ GOOD_INDENT
+					trace "    Bad line %d: %p" % [ linecount, line ]
+					badfiles[file] << [ linecount, line ]
+				end
+			end
+		end
+
+		if badfiles.empty?
+			log "No indentation problems found."
+		else
+			log "Found incorrect indent in #{badfiles.length} files:\n  "
+			badfiles.each do |file, badlines|
+				log "  #{file}:\n" +
+					"    " + badlines.collect {|badline| "%5d: %p" % badline }.join( "\n    " )
+			end
+		end
+	end
+
+end
+
+

lib/linguistics.rb

 	# Subversion ID
 	SVNid = %q$Id$
 
+	# Release version
+	VERSION = '1.0.6'
+
 	# Language module implementors should do something like:
 	#   Linguistics::DefaultLanguages.push( :ja ) # or whatever
 	# so that direct requiring of a language module sets the default.

lib/linguistics/iso639.rb

 
 	# Read through the source for this file, capturing everything
 	# between __END__ and __END_DATA__ tokens.
-	inDataSection = false
+	in_data_section = false
 	File::readlines( __FILE__ ).each {|line|
 		case line
 		when /^__END_DATA__$/
-			inDataSection = false
+			in_data_section = false
 			false
 			
 		when /^__END__$/
-			inDataSection = true
+			in_data_section = true
 			false
 			
 		else
-			if inDataSection
+			if in_data_section
 				codes, desc = line[0,15].split(%r{/|\s+}), line[15...-1]
 				codes.delete_if {|code| code.empty?}
 				entry = {

misc/rake/helpers.rb

+#####################################################################
+###	G L O B A L   H E L P E R   F U N C T I O N S
+#####################################################################
+
+require 'pathname'
+require 'readline'
+
+# Set some ANSI escape code constants (Shamelessly stolen from Perl's
+# Term::ANSIColor by Russ Allbery <rra@stanford.edu> and Zenin <zenin@best.com>
+ANSI_ATTRIBUTES = {
+	'clear'      => 0,
+	'reset'      => 0,
+	'bold'       => 1,
+	'dark'       => 2,
+	'underline'  => 4,
+	'underscore' => 4,
+	'blink'      => 5,
+	'reverse'    => 7,
+	'concealed'  => 8,
+
+	'black'      => 30,   'on_black'   => 40, 
+	'red'        => 31,   'on_red'     => 41, 
+	'green'      => 32,   'on_green'   => 42, 
+	'yellow'     => 33,   'on_yellow'  => 43, 
+	'blue'       => 34,   'on_blue'    => 44, 
+	'magenta'    => 35,   'on_magenta' => 45, 
+	'cyan'       => 36,   'on_cyan'    => 46, 
+	'white'      => 37,   'on_white'   => 47
+}
+
+
+### Output a logging message
+def log( *msg )
+	output = colorize( msg.flatten.join(' '), 'cyan' )
+	$deferr.puts( output )
+end
+
+
+### Output a logging message
+def trace( *msg )
+	return unless $trace
+	output = colorize( msg.flatten.join(' '), 'yellow' )
+	$deferr.puts( output )
+end
+
+
+### Run the specified command +cmd+ with system(), failing if the execution
+### fails.
+def run( *cmd )
+	log( cmd.collect {|part| part =~ /\s/ ? part.inspect : part} ) 
+	if $dryrun
+		$deferr.puts "(dry run mode)"
+	else
+		system( *cmd )
+		unless $?.success?
+			fail "Command failed: [%s]" % [cmd.join(' ')]
+		end
+	end
+end
+
+
+### Download the file at +sourceuri+ via HTTP and write it to +targetfile+.
+def download( sourceuri, targetfile )
+	oldsync = $defout.sync
+	$defout.sync = true
+	require 'net/http'
+	require 'uri'
+
+	targetpath = Pathname.new( targetfile )
+
+	log "Downloading %s to %s" % [sourceuri, targetfile]
+	targetpath.open( File::WRONLY|File::TRUNC|File::CREAT, 0644 ) do |ofh|
+	
+		url = URI.parse( sourceuri )
+		Net::HTTP.start( url.host, url.port ) do |http|
+			req = Net::HTTP::Get.new( url.path )
+
+			http.request( req ) do |res|
+				if res.is_a?( Net::HTTPSuccess )
+					print "Downloading..."
+					res.read_body do |buf|
+						ofh.print( buf )
+					end
+					puts "done."
+				
+				else
+					res.error!
+				end
+			end
+		end
+		
+	end
+	
+	return targetpath
+ensure
+	$defout.sync = oldsync
+end
+
+
+### Return the fully-qualified path to the specified +program+ in the PATH.
+def which( program )
+	ENV['PATH'].split(/:/).
+		collect {|dir| Pathname.new(dir) + program }.
+		find {|path| path.exist? && path.executable? }
+end
+
+
+### Create a string that contains the ANSI codes specified and return it
+def ansi_code( *attributes )
+	attributes.flatten!
+	attributes.collect! {|at| at.to_s }
+	# $deferr.puts "Returning ansicode for TERM = %p: %p" %
+	# 	[ ENV['TERM'], attributes ]
+	return '' unless /(?:vt10[03]|xterm(?:-color)?|linux|screen)/i =~ ENV['TERM']
+	attributes = ANSI_ATTRIBUTES.values_at( *attributes ).compact.join(';')
+
+	# $deferr.puts "  attr is: %p" % [attributes]
+	if attributes.empty? 
+		return ''
+	else
+		return "\e[%sm" % attributes
+	end
+end
+
+
+### Colorize the given +string+ with the specified +attributes+ and return it, handling line-endings, etc.
+def colorize( string, *attributes )
+	ending = string[/(\s)$/] || ''
+	string = string.rstrip
+	return ansi_code( attributes.flatten ) + string + ansi_code( 'reset' ) + ending
+end
+
+
+### Output the specified <tt>msg</tt> as an ANSI-colored error message
+### (white on red).
+def error_message( msg )
+	$deferr.puts ansi_code( 'bold', 'white', 'on_red' ) +
+		msg.strip + ansi_code( 'reset' ) + "\n\n"
+end
+alias :error :error_message
+
+
+### Output the specified <tt>prompt_string</tt> as a prompt (in green) and
+### return the user's input with leading and trailing spaces removed.  If a
+### test is provided, the prompt will repeat until the test returns true.
+### An optional failure message can also be passed in.
+def prompt( prompt_string, failure_msg="Try again." ) # :yields: response
+	prompt_string.chomp!
+	prompt_string << ":" unless /\W$/.match( prompt_string )
+	response = nil
+
+	begin
+		response = Readline.readline( ansi_code('bold', 'green') +
+			"#{prompt_string} " + ansi_code('reset') ) || ''
+		response.strip!
+		if block_given? && ! yield( response ) 
+			error_message( failure_msg + "\n\n" )
+			response = nil
+		end
+	end until response
+
+	return response
+end
+
+
+### Prompt the user with the given <tt>prompt_string</tt> via #prompt,
+### substituting the given <tt>default</tt> if the user doesn't input
+### anything.  If a test is provided, the prompt will repeat until the test
+### returns true.  An optional failure message can also be passed in.
+def prompt_with_default( prompt_string, default, failure_msg="Try again." )
+	response = nil
+
+	begin
+		response = prompt( "%s [%s]" % [ prompt_string, default ] )
+		response = default if response.empty?
+
+		if block_given? && ! yield( response ) 
+			error_message( failure_msg + "\n\n" )
+			response = nil
+		end
+	end until response
+
+	return response
+end
+
+
+### Display a description of a potentially-dangerous task, and prompt
+### for confirmation. If the user answers with anything that begins
+### with 'y', yield to the block, else raise with an error.
+def ask_for_confirmation( description )
+	puts description
+
+	answer = prompt_with_default( "Continue?", 'n' ) do |input|
+		input =~ /^[yn]/i
+	end
+	case answer
+	when /^y/i
+		yield
+	else
+		error "Aborted."
+		fail
+	end
+end
+
+
+### Search line-by-line in the specified +file+ for the given +regexp+, returning the
+### first match, or nil if no match was found. If the +regexp+ has any capture groups,
+### those will be returned in an Array, else the whole matching line is returned.
+def find_pattern_in_file( regexp, file )
+	rval = nil
+	
+	File.open( file, 'r' ).each do |line|
+		if (( match = regexp.match(line) ))
+			rval = match.captures.empty? ? match[0] : match.captures
+			break
+		end
+	end
+
+	return rval
+end
+
+
+### Get a list of the file or files to run rspec on.
+def rspec_files
+	if ENV['class']
+		classname = ENV['class']
+		spec_file = SPECSDIR + "#{classname}_spec.rb"
+		raise "Can't find the spec file for the class '#{classname}'" unless spec_file.exist?
+		return [ spec_file ]
+	else
+		return SPEC_FILES
+	end
+end
+
+
+#####################################################################
+###	S U B V E R S I O N   T A S K S   A N D   H E L P E R S
+#####################################################################
+
+require 'yaml'
+require 'English'
+
+# Strftime format for tags/releases
+TAG_TIMESTAMP_FORMAT = '%Y%m%d-%H%M%S'
+TAG_TIMESTAMP_PATTERN = /\d{4}\d{2}\d{2}-\d{6}/
+
+RELEASE_VERSION_PATTERN = /\d+\.\d+\.\d+/
+
+DEFAULT_EDITOR = 'vi'
+
+COMMIT_MSG_FILE = 'commit-msg.txt'
+
+###
+### Helpers
+###
+
+### Return a new tag for the given time
+def make_new_tag( time=Time.now )
+	return time.strftime( TAG_TIMESTAMP_FORMAT )
+end
+
+
+### Get the subversion information for the current working directory as
+### a hash.
+def get_svn_info( dir='.' )
+	info = IO.read( '|-' ) or exec 'svn', 'info', dir
+	return YAML.load( info ) # 'svn info' outputs valid YAML! Yay!
+end
+
+
+### Return the URL to the repository root for the specified +dir+.
+def get_svn_repo_root( dir='.' )
+	info = get_svn_info( dir )
+	return info['Repository Root'] + '/thingfish'
+end
+
+
+### Return the Subversion URL to the given +dir+.
+def get_svn_url( dir='.' )
+	info = get_svn_info( dir )
+	return info['URL']
+end
+
+
+### Return the path of the specified +dir+ under the svn root of the 
+### checkout.
+def get_svn_path( dir='.' )
+	root = get_svn_repo_root( dir )
+	url = get_svn_url( dir )
+	
+	return url.sub( root + '/', '' )
+end
+
+
+### Return the latest revision number of the specified +dir+ as an Integer.
+def get_svn_rev( dir='.' )
+	info = get_svn_info( dir )
+	return info['Revision']
+end
+
+
+### Return a list of the entries at the specified Subversion url. If
+### no +url+ is specified, it will default to the list in the URL
+### corresponding to the current working directory.
+def svn_ls( url=nil )
+	url ||= get_svn_url()
+	list = IO.read( '|-' ) or exec 'svn', 'ls', url
+
+	trace 'svn ls of %s: %p' % [url, list] if $trace
+	
+	return [] if list.nil? || list.empty?
+	return list.split( $INPUT_RECORD_SEPARATOR )
+end
+
+
+### Return the URL of the latest timestamp in the tags directory.
+def get_latest_svn_timestamp_tag
+	rooturl = get_svn_repo_root()
+	tagsurl = rooturl + '/tags'
+	
+	tags = svn_ls( tagsurl ).grep( TAG_TIMESTAMP_PATTERN ).sort
+	return nil if tags.nil? || tags.empty?
+	return tagsurl + '/' + tags.last
+end
+
+
+### Return the URL of the latest timestamp in the tags directory.
+def get_latest_release_tag
+	rooturl    = get_svn_repo_root()
+	releaseurl = rooturl + '/releases'
+	
+	tags = svn_ls( releaseurl ).grep( RELEASE_VERSION_PATTERN ).sort_by do |tag|
+		tag.split('.').collect {|i| Integer(i) }
+	end
+	return nil if tags.empty?
+
+	return releaseurl + '/' + tags.last
+end
+
+
+### Extract a diff from the specified subversion working +dir+, rewrite its
+### file lines as Trac links, and return it.
+def make_svn_commit_log( dir='.' )
+	editor_prog = ENV['EDITOR'] || ENV['VISUAL'] || DEFAULT_EDITOR
+	
+	diff = IO.read( '|-' ) or exec 'svn', 'diff'
+	fail "No differences." if diff.empty?
+
+	return diff
+end
+
+
+
+###
+### Tasks
+###
+
+desc "Subversion tasks"
+namespace :svn do
+
+	desc "Copy the HEAD revision of the current trunk/ to tags/ with a " +
+		 "current timestamp."
+	task :tag do
+		svninfo   = get_svn_info()
+		tag       = make_new_tag()
+		svntrunk  = svninfo['Repository Root'] + '/thingfish/trunk'
+		svntagdir = svninfo['Repository Root'] + '/thingfish/tags'
+		svntag    = svntagdir + '/' + tag
+
+		desc = "Tagging trunk as #{svntag}"
+		ask_for_confirmation( desc ) do
+			msg = prompt_with_default( "Commit log: ", "Tagging for code push" )
+			run 'svn', 'cp', '-m', msg, svntrunk, svntag
+		end
+	end
+
+
+	desc "Copy the most recent tag to releases/#{PKG_VERSION}"
+	task :release do
+		last_tag    = get_latest_svn_timestamp_tag()
+		svninfo     = get_svn_info()
+		release     = PKG_VERSION
+		svnrel      = svninfo['Repository Root'] + '/thingfish/releases'
+		svnrelease  = svnrel + '/' + release
+
+		if last_tag.nil?
+			error "There are no tags in the repository"
+			fail
+		end
+
+		releases = svn_ls( svnrel )
+		trace "Releases: %p" % [releases]
+		if releases.include?( release )
+			error "Version #{release} already has a branch (#{svnrelease}). Did you mean" +
+				"to increment the version in thingfish.rb?"
+			fail
+		else
+			trace "No #{svnrel} version currently exists"
+		end
+		
+		desc = "Release tag\n  #{last_tag}\nto\n  #{svnrelease}"
+		ask_for_confirmation( desc ) do
+			msg = prompt_with_default( "Commit log: ", "Branching for release" )
+			run 'svn', 'cp', '-m', msg, last_tag, svnrelease
+		end
+	end
+
+
+	desc "Generate a commit log"
+	task :commitlog => [COMMIT_MSG_FILE]
+	
+	desc "Show the (pre-edited) commit log for the current directory"
+	task :show_commitlog do
+		puts make_svn_commit_log()
+	end
+	
+
+	file COMMIT_MSG_FILE do
+		diff = make_svn_commit_log()
+		
+		File.open( COMMIT_MSG_FILE, File::WRONLY|File::EXCL|File::CREAT ) do |fh|
+			fh.print( diff )
+		end
+
+		editor = ENV['EDITOR'] || ENV['VISUAL'] || DEFAULT_EDITOR
+		system editor, COMMIT_MSG_FILE
+		unless $?.success?
+			fail "Editor exited uncleanly."
+		end
+	end
+
+
+	desc "Update from Subversion"
+	task :update do
+		run 'svn', 'up'
+	end
+
+
+	desc "Check in all the changes in your current working copy"
+	task :checkin => ['svn:update', 'coverage:verify', COMMIT_MSG_FILE] do
+		$deferr.puts File.read( COMMIT_MSG_FILE )
+		ask_for_confirmation( "Continue with checkin?" ) do
+			run 'svn', 'ci', '-F', COMMIT_MSG_FILE
+			rm_f COMMIT_MSG_FILE
+		end
+	end
+	task :commit => :checkin
+	task :ci => :checkin
+		
+	
+	task :clean do
+		rm_f COMMIT_MSG_FILE
+	end
+
+	
+	task :debug_helpers do
+		methods = [
+			:make_new_tag,
+			:get_svn_info,
+			:get_svn_repo_root,
+			:get_svn_url,
+			:get_svn_path,
+			:svn_ls,
+			:get_latest_svn_timestamp_tag,
+		]
+		maxlen = methods.collect {|sym| sym.to_s.length }.max
+		
+		methods.each do |meth|
+			res = send( meth )
+			puts "%*s => %p" % [ maxlen, colorize(meth.to_s, :cyan), res ]
+		end
+	end
+end
+

misc/rake/verifytask.rb

+#####################################################################
+###	S U B V E R S I O N   T A S K S   A N D   H E L P E R S
+#####################################################################
+
+require 'rake/tasklib'
+
+#
+# Work around the inexplicable behaviour of the original RDoc::VerifyTask, which 
+# errors if your coverage isn't *exactly* the threshold.
+# 
+
+# A task that can verify that the RCov coverage doesn't
+# drop below a certain threshold. It should be run after
+# running Spec::Rake::SpecTask.
+class VerifyTask < Rake::TaskLib
+	
+	COVERAGE_PERCENTAGE_PATTERN = 
+		%r{<tt class='coverage_code'>(\d+\.\d+)%</tt>}
+	
+	# Name of the task. Defaults to :verify_rcov
+	attr_accessor :name
+
+	# Path to the index.html file generated by RCov, which
+	# is the file containing the total coverage.
+	# Defaults to 'coverage/index.html'
+	attr_accessor :index_html
+
+	# Whether or not to output details. Defaults to true.
+	attr_accessor :verbose
+
+	# The threshold value (in percent) for coverage. If the 
+	# actual coverage is not equal to this value, the task will raise an 
+	# exception. 
+	attr_accessor :threshold
+
+	def initialize( name=:verify )
+		@name = name
+		@index_html = 'coverage/index.html'
+		@verbose = true
+		yield self if block_given?
+		raise "Threshold must be set" if @threshold.nil?
+		define
+	end
+
+	def define
+		desc "Verify that rcov coverage is at least #{threshold}%"
+
+		task @name do
+			total_coverage = nil
+			if match = File.read( index_html ).match( COVERAGE_PERCENTAGE_PATTERN )
+				total_coverage = Float( match[1] )
+			else
+				raise "Couldn't find the coverage percentage in #{index_html}"
+			end
+
+			puts "Coverage: #{total_coverage}% (threshold: #{threshold}%)" if verbose
+			if total_coverage < threshold
+				raise "Coverage must be at least #{threshold}% but was #{total_coverage}%"
+			end
+		end
+	end
+end
+
+# vim: set nosta noet ts=4 sw=4:

spec/linguistics/en_spec.rb

+#!/usr/bin/env spec -cfs
+
+BEGIN {
+	require 'pathname'
+	basedir = Pathname.new( __FILE__ ).dirname.parent.parent
+
+	libdir = basedir + "lib"
+
+	$LOAD_PATH.unshift( libdir ) unless $LOAD_PATH.include?( libdir )
+}
+
+begin
+	require 'spec/runner'
+	require 'linguistics'
+	require 'linguistics/en'
+rescue LoadError
+	unless Object.const_defined?( :Gem )
+		require 'rubygems'
+		retry
+	end
+	raise
+end
+
+
+describe Linguistics::EN do
+
+	before( :all ) do
+		Linguistics::use( :en )
+		include Linguistics::EN
+	end
+
+
+	describe "conjunctions with an Array of a single element" do
+
+		before( :each ) do
+			@array = ['cat']
+		end
+
+		it "results in a phrase with indefinite article" do
+			@array.en.conjunction.should == "a cat"
+		end
+		
+	end
+
+
+	describe "conjunction with an Array of two different words" do
+
+		before( :each ) do
+			@array = ['cat', 'dog']
+		end
+
+		it "results in a phrase joined with 'and' with default options" do
+			@array.en.conjunction.should == "a cat and a dog"
+		end
+		
+		it "results in a phrase joined with 'plus' if 'plus' is set as the conjunctive" do
+			@array.en.conjunction(:conjunctive => 'plus').should == "a cat plus a dog"
+		end
+		
+		it "results in a phrase joined with a space if an empty string is set as the conjunctive" do
+			@array.en.conjunction(:conjunctive => '').should == "a cat a dog"
+		end
+		
+	end
+
+
+	describe "conjunction with an Array of two words that differ only in case" do
+
+		before( :each ) do
+			@array = ['cat', 'Cat']
+		end
+
+		it "combines them into their downcased equivalents with default options" do
+			@array.en.conjunction.should == "two cats"
+		end
+		
+		it "lists them separately if :combine is set to false" do
+			@array.en.conjunction(:combine => false).should == "a cat and a Cat"
+		end
+		
+		it "doesn't combine them if :casefold is turned off" do
+			@array.en.conjunction(:casefold => false).should == "a cat and a Cat"
+		end
+		
+		it "combines and lists them with a non-specific count if :generalize is set" do
+			@array.en.conjunction(:generalize => true).should == "several cats"
+		end
+		
+	end
+
+
+	describe "conjunction with an Array of many (more than two) words of varying cases" do
+
+		before( :each ) do
+			@array = %w{cat dog fox dog chicken chicken Fox chicken goose Dog goose}
+		end
+
+		it "combines them into their downcased equivalents and lists them in order of amount " +
+		   "with default options" do
+			@array.en.conjunction.should == 
+				'three dogs, three chickens, two foxes, two geese, and a cat'
+		end
+		
+		it "lists them separately if :combine is set to false" do
+			@array.en.conjunction(:combine => false).should == 
+				'a cat, a dog, a fox, a dog, a chicken, a chicken, a Fox, a '\
+				'chicken, a goose, a Dog, and a goose'
+		end
+		
+		it "doesn't combine the differently-cased ones if :casefold is turned off" do
+			@array.en.conjunction(:casefold => false).should == 
+				'three chickens, two dogs, two geese, a cat, a fox, a Fox, '\
+				'and a Dog'
+		end
+		
+		it "combines and lists them with a non-specific count if :generalize is set" do
+			@array.en.conjunction(:generalize => true).should == 
+				'several dogs, several chickens, several foxes, several '\
+				'geese, and a cat'
+		end
+		
+	end
+	
+
+	describe "conjunction with an object-transform block" do
+
+		it "doesn't still have #6: #conjunction doesn't invoke supplied block under some conditions"
+		before( :each ) do
+			# Create a new class, as we need to guarantee that this will be the
+			# first #conjunction call to it.
+			@collection = Class::new {
+				include Enumerable, Linguistics
+				def initialize( *ary )
+					@ary = ary.flatten
+				end
+
+				# Delegate #each to the contained Array
+				def each( &block )
+					@ary.each( &block )
+				end
+			}
+
+			@obj = @collection.new( 'foo', 'bar', 'baz', 'tree', 'node', 'sonogram' )
+		end
+
+		it "uses supplied block for object transform on first invocation" do
+			@obj.en.conjunction {|word| "%s-letter word" % word.length.en.numwords }.should ==
+				"three three-letter words, two four-letter words, and an eight-letter word"
+		end
+	end
+
+
+
+	def test_conjunction_should_use_supplied_block_for_object_transform
+		rval = nil
+
+		assert_nothing_raised do
+			rval = Items.en.conjunction {|word| "%s-word" % word[0,1]}
+		end
+
+		assert_equal "three c-words and a b-word", rval
+	end
+
+
+	def test_conjunction_should_use_supplied_block_for_object_transform_through_autoproxy
+		rval = nil
+
+		assert_nothing_raised do
+			rval = Items.conjunction {|word| "%s-word" % word[0,1]}
+		end
+
+		assert_equal "three c-words and a b-word", rval
+	end
+
+	def test_conjunction_with_penultimate_separator_turned_off_should_not_use_one
+		rval = nil
+		
+		assert_nothing_raised do
+			rval = Items.en.conjunction( :penultimate => false )
+		end
+		
+		assert_equal "a cow, a chicken, a blancmange and a cyclist", rval
+	end
+
+	def test_three_item_conjunction_should_honor_penultimate_setting
+		rval = nil
+		
+		assert_nothing_raised do
+			rval = %w{duck cow dog}.en.conjunction( :penultimate => false )
+		end
+		
+		assert_equal "a duck, a cow and a dog", rval
+	end
+
+	def test_conjunction_uses_alt_separator_if_phrases_include_the_primary_one
+		rval = nil
+		scene_items = [
+			"desk with stamps, paper, and envelopes on it",
+			"basket containing milk, eggs, and broccoli",
+			"chair",
+			"wooden chest",
+			"hat rack",
+		]
+		
+		assert_nothing_raised do
+			rval = scene_items.conjunction
+		end
+		
+		assert_equal "a desk with stamps, paper, and envelopes on it; " +
+			"a basket containing milk, eggs, and broccoli; " +
+			"a chair; a wooden chest; and a hat rack", rval
+	end
+
+end
+

spec/linguistics/iso639_spec.rb

+#!/usr/bin/env spec -cfs
+
+BEGIN {
+	require 'pathname'
+	basedir = Pathname.new( __FILE__ ).dirname.parent.parent
+
+	libdir = basedir + "lib"
+
+	$LOAD_PATH.unshift( libdir ) unless $LOAD_PATH.include?( libdir )
+}
+
+begin
+	require 'spec/runner'
+	require 'linguistics/iso639'
+rescue LoadError
+	unless Object.const_defined?( :Gem )
+		require 'rubygems'
+		retry
+	end
+	raise
+end
+
+
+describe Linguistics, " language codes" do
+
+	it "loads simple language codes from its __DATA__ section" do
+		Linguistics::LanguageCodes.should have_key( "en" )
+		Linguistics::LanguageCodes[ "en" ].should have(2).members
+
+		Linguistics::LanguageCodes[ "en" ].should have_key( :codes )
+		Linguistics::LanguageCodes[ "en" ][:codes].should have(2).members
+		Linguistics::LanguageCodes[ "en" ][:codes].should include("en")
+		Linguistics::LanguageCodes[ "en" ][:codes].should include("eng")
+		
+		Linguistics::LanguageCodes[ "en" ].should have_key( :desc )
+		Linguistics::LanguageCodes[ "en" ][:desc].should == 'English'
+	end
+	
+	it "loads language codes with variants from its __DATA__ section" do
+
+		# ces/cze  cs    Czech
+		Linguistics::LanguageCodes.should have_key( "cs" )
+		Linguistics::LanguageCodes[ "cs" ].should have(2).members
+
+		Linguistics::LanguageCodes[ "cs" ].should have_key( :codes )
+		Linguistics::LanguageCodes[ "cs" ][:codes].should have(3).members
+		Linguistics::LanguageCodes[ "cs" ][:codes].should include("cs")
+		Linguistics::LanguageCodes[ "cs" ][:codes].should include("ces")
+		Linguistics::LanguageCodes[ "cs" ][:codes].should include("cze")
+		
+		Linguistics::LanguageCodes[ "cs" ].should have_key( :desc )
+		Linguistics::LanguageCodes[ "cs" ][:desc].should == 'Czech'
+		
+		# jav/jaw  jv/jw Javanese
+		Linguistics::LanguageCodes.should have_key( "jv" )
+		Linguistics::LanguageCodes.should have_key( "jw" )
+		Linguistics::LanguageCodes[ "jv" ].should == Linguistics::LanguageCodes[ "jw" ]
+		Linguistics::LanguageCodes[ "jv" ].should have(2).members
+
+		Linguistics::LanguageCodes[ "jv" ].should have_key( :codes )
+		Linguistics::LanguageCodes[ "jv" ][:codes].should have(4).members
+		Linguistics::LanguageCodes[ "jv" ][:codes].should include("jv")
+		Linguistics::LanguageCodes[ "jv" ][:codes].should include("jw")
+		Linguistics::LanguageCodes[ "jv" ][:codes].should include("jav")
+		Linguistics::LanguageCodes[ "jv" ][:codes].should include("jaw")
+
+		Linguistics::LanguageCodes[ "jv" ].should have_key( :desc )
+		Linguistics::LanguageCodes[ "jv" ][:desc].should == 'Javanese'
+		
+	end
+	
+end

spec/linguistics_spec.rb

+#!/usr/bin/env spec -cfs
+
+BEGIN {
+	require 'pathname'
+	basedir = Pathname.new( __FILE__ ).dirname.parent
+
+	libdir = basedir + "lib"
+
+	$LOAD_PATH.unshift( libdir ) unless $LOAD_PATH.include?( libdir )
+}
+
+begin
+	require 'spec/runner'
+	require 'linguistics'
+rescue LoadError
+	unless Object.const_defined?( :Gem )
+		require 'rubygems'
+		retry
+	end
+	raise
+end
+
+
+describe Linguistics do
+
+	TestArray = %w{stone stick hammer stone lantern}
+	TestString = "banner"
+	TestNumber = 5
+
+	before( :all ) do
+		Linguistics.use( :en )
+	end
+
+
+	it "loads a language's linguistic functions via variants of its ISO639 code" do
+
+		[:en, :EN, 'en', 'EN', 'En', 'eN'].each do |code|
+			res = Linguistics.use( code )
+			res.should have(3).members
+			res.should include(Array)
+			res.should include(String)
+			res.should include(Numeric)
+		end
+	end
+
+
+	it "raises an error when a language that doesn't exist is requested" do
+		[ :zz, :ry, :qi ].each do |code|
+			lambda { 
+				Linguistics.use( code )
+			  }.should raise_error( RuntimeError, /unknown language code/i )
+		end
+	end
+	
+	
+	it "raises an error for valid languages that don't have any linguistic functions to load" do
+		[ :ja, :fr, :es ].each do |code|
+			lambda {
+				Linguistics.use( code )
+			  }.should raise_error( LoadError, /failed to load language extension/i )
+		end
+
+	end
+
+
+	it "adds a method with the same name as the language code to Array that returns an inflector" +
+	   " proxy for that language" do
+		TestArray.should respond_to( :en )
+		TestArray.en.should be_a_kind_of( Linguistics::LanguageProxyClass )
+	end
+	
+	
+	it "adds a method with the same name as the language code to String that returns an inflector" +
+	   " proxy for that language" do
+		TestString.should respond_to( :en )
+		TestString.en.should be_a_kind_of( Linguistics::LanguageProxyClass )
+	end
+	
+	
+	it "adds a method with the same name as the language code to Numeric that returns an inflector" +
+	   " proxy for that language" do
+		TestNumber.should respond_to( :en )
+		TestNumber.en.should be_a_kind_of( Linguistics::LanguageProxyClass )
+	end
+	
+	
+	it "allows one to extend an additional class by passing it in the ':classes' argument to ::use" do
+		Linguistics.use( :en, :classes => Symbol )
+		:foo.should respond_to( :en )
+		:foo.en.should be_a_kind_of( Linguistics::LanguageProxyClass )
+	end
+	
+
+	it "allows one to extend multiple additional classes by passing them in an Array in the "+
+	   " ':classes' argument to ::use" do
+		Linguistics.use( :en, :classes => [IO, Class, Range] )
+		$stderr.should respond_to( :en )
+		$stderr.en.should be_a_kind_of( Linguistics::LanguageProxyClass )
+
+		Object.should respond_to( :en )
+		Object.en.should be_a_kind_of( Linguistics::LanguageProxyClass )
+
+		(0..155).should respond_to( :en )
+		(0..155).en.should be_a_kind_of( Linguistics::LanguageProxyClass )
+	end
+	
+end

tests/en/conjunction.tests.rb

-#!/usr/bin/ruby -w
-#
-# Unit test for English conjunctions 
-# $Id: conjunction.tests.rb,v 1.2 2003/09/11 05:03:12 deveiant Exp $
-#
-# Copyright (c) 2003, 2005 The FaerieMUD Consortium.
-# 
-
-unless defined? Linguistics::TestCase
-	testsdir = File::dirname( File::dirname(File::expand_path( __FILE__ )) )
-	$LOAD_PATH.unshift testsdir unless $LOAD_PATH.include?( testsdir )
-
-	require 'lingtestcase'
-end
-
-
-### This test suite tests the stage1 (static) parser and the metagrammar it
-### parses in which the actual parser-generator's behaviour is defined.
-class EnglishConjunctionsTestCase < Linguistics::TestCase
-
-	Linguistics::use( :en, :installProxy => true )
-	include Linguistics::EN
-	
-	Tests = {
-		# Test name		=> {
-		#	target => {
-		#		{<options>} => <expected output>
-		#	},
-		:singleWord => {
-			['cat'] => {
-				{} => %{a cat}
-			},
-		},
-
-		:twoWord => {
-			%w{cat dog} => {
-				{} => 'a cat and a dog',
-				{:conjunctive => 'plus'} => 'a cat plus a dog',
-				{:conjunctive => ''} => 'a cat a dog',
-			},
-			%w{cat Cat} => {
-				{} => %{two cats},
-				{:combine => false} => 'a cat and a Cat',
-				{:casefold => false} => 'a cat and a Cat',
-				{:generalize => true} => 'several cats',
-			},
-		},
-
-		:manyWord => {
-			%w{cat dog fox dog chicken chicken Fox chicken goose Dog goose} => {
-				{} => 'three dogs, three chickens, two foxes, two geese, and a cat',
-				{:combine => false} => 
-					'a cat, a dog, a fox, a dog, a chicken, a chicken, a Fox, a '\
-					'chicken, a goose, a Dog, and a goose',
-				{:casefold => false} => 
-					'three chickens, two dogs, two geese, a cat, a fox, a Fox, '\
-					'and a Dog',
-				{:generalize => true} => 
-					'several dogs, several chickens, several foxes, several '\
-					'geese, and a cat',
-			}
-		},
-	}
-
-	# Auto-generate tests for the dataset
-	Tests.each_with_index {|test,i|
-		name = test[0]
-		methname = "test_%03d_%s" %
-			[ i + 50, name ]
-		define_method( methname ) {
-			printTestHeader "Conjunction: #{name}"
-			Tests[name].each {|target, tests|
-				rval = nil
-				tests.each {|opts, output|
-					op = "conjunction of %s with options=%s" %
-						[ target.inspect, opts.inspect ]
-
-					# Method interface
-					assert_nothing_raised( op ) {
-						rval = target.en.conjunction( opts )
-					}
-					assert_equal output, rval, op
-
-					# Function interface
-					assert_nothing_raised( op ) {
-						rval = conjunction( target, opts )
-					}
-					assert_equal output, rval, op
-				}
-			}
-		}
-	}
-	
-	### Overridden initializer: auto-generated test methods have an arity of 1
-	### even though they don't require an argument (as of the current Ruby CVS),
-	### and the default initializer throws an :invalid_test for methods with
-	### arity != 0.
-	def initialize( test_method_name )
-        if !respond_to?( test_method_name )
-			throw :invalid_test
-        end
-        @method_name = test_method_name
-        @test_passed = true
-	end
-
-
-	#################################################################
-	###	T E S T S
-	#################################################################
-
-	Items = %w{cow chicken blancmange cyclist}
-
-	# Test for defect #6
-	def test_conjunction_should_use_supplied_block_for_object_transform_on_first_invocation
-		rval = nil
-
-		# Create a new class, as we need to guarantee that this will be the
-		# first #conjunction call to it.
-		collection = Class::new {
-			include Enumerable, Linguistics
-			def initialize( *ary )
-				@ary = ary.flatten
-			end
-
-			# Delegate #each to the contained Array
-			def each( &block )
-				@ary.each( &block )
-			end
-		}
-
-		obj = collection.new( 'foo', 'bar', 'baz' )
-
-		assert_nothing_raised do
-			rval = obj.en.conjunction {|word| "%d-letter word" % word.length }
-		end
-	end
-
-
-	def test_conjunction_should_use_supplied_block_for_object_transform
-		rval = nil
-
-		assert_nothing_raised do
-			rval = Items.en.conjunction {|word| "%s-word" % word[0,1]}
-		end
-
-		assert_equal "three c-words and a b-word", rval
-	end
-
-
-	def test_conjunction_should_use_supplied_block_for_object_transform_through_autoproxy
-		rval = nil
-
-		assert_nothing_raised do
-			rval = Items.conjunction {|word| "%s-word" % word[0,1]}
-		end
-
-		assert_equal "three c-words and a b-word", rval
-	end
-
-	def test_conjunction_with_penultimate_separator_turned_off_should_not_use_one
-		rval = nil
-		
-		assert_nothing_raised do
-			rval = Items.en.conjunction( :penultimate => false )
-		end
-		
-		assert_equal "a cow, a chicken, a blancmange and a cyclist", rval
-	end
-
-	def test_three_item_conjunction_should_honor_penultimate_setting
-		rval = nil
-		
-		assert_nothing_raised do
-			rval = %w{duck cow dog}.en.conjunction( :penultimate => false )
-		end
-		
-		assert_equal "a duck, a cow and a dog", rval
-	end
-
-	def test_conjunction_uses_alt_separator_if_phrases_include_the_primary_one
-		rval = nil
-		scene_items = [
-			"desk with stamps, paper, and envelopes on it",
-			"basket containing milk, eggs, and broccoli",
-			"chair",
-			"wooden chest",
-			"hat rack",
-		]
-		
-		assert_nothing_raised do
-			rval = scene_items.conjunction
-		end
-		
-		assert_equal "a desk with stamps, paper, and envelopes on it; " +
-			"a basket containing milk, eggs, and broccoli; " +
-			"a chair; a wooden chest; and a hat rack", rval
-	end
-
-end
-

tests/use.tests.rb

-#!/usr/bin/ruby -w
-#
-# Unit test for the 'use' function of the Linguistics module.
-# $Id$
-#
-# Copyright (c) 2003 The FaerieMUD Consortium.
-# 
-
-unless defined? Linguistics::TestCase
-	testsdir = File::dirname( File::expand_path( __FILE__ ) )
-	$LOAD_PATH.unshift testsdir unless $LOAD_PATH.include?( testsdir )
-
-	require 'lingtestcase'
-end
-
-
-### This test suite tests the language-installation function of the Linguistics
-### module.
-module Linguistics
-class UseTestCase < Linguistics::TestCase
-
-	LanguageCodes = [ :en, :EN, 'en', 'EN', 'En', 'eN' ]
-	BogusLanguageCodes = [ :zz, :ry, :qi ]
-	MissingLanguageCodes = [ :ja, :fr, :es ]
-
-	TestArray = %w{stone stick hammer stone lantern}
-	TestString = "banner"
-	TestNumber = 5
-
-	def test_00_UseEnglish
-		printTestHeader "Linguistics: Use <language>"
-
-		# Test the only (currently) valid codes
-		LanguageCodes.each do |code|
-			assert_nothing_raised {
-				Linguistics::use( code )
-			}
-		end
-
-		# Test bogus codes
-		BogusLanguageCodes.each do |code|
-			assert_raises( RuntimeError ) {
-				Linguistics::use( code )
-			}
-		end
-
-		# Test valid, but missing languages (might fail for implementors of new
-		# languages).
-		MissingLanguageCodes.each do |code|
-			assert_raises( LoadError ) {
-				Linguistics::use( code )
-			}
-		end
-
-	end
-
-
-	def test_10_InflectorMethod
-		printTestHeader "Linguistics: Inflector method (core classes)"
-		rval = nil
-
-		# This shouldn't be necessary, but it's here for completeness
-		Linguistics::use( :en )
-
-		[ TestArray, TestString, TestNumber ].each do |obj|
-			debug_msg "obj.class.instance_variables = %s" %
-				obj.class.instance_variables.inspect
-
-			assert_respond_to obj, :en
-			assert_nothing_raised {
-				rval = obj.en
-			}
-			assert_kind_of Linguistics::LanguageProxyClass, rval
-		end
-	end
-
-
-	def test_20_SpecifyClasses
-		printTestHeader "Linguistics: Extend specific classes"
-		
-		assert_nothing_raised( "One class, not in an array" ) {
-			Linguistics::use( :en, :classes => Symbol )
-		}
-		assert_respond_to :foo, :en
-
-		assert_nothing_raised( "One class, in an array" ) {
-			Linguistics::use( :en, :classes => [IO] )
-		}
-		assert_respond_to $stderr, :en
-
-		assert_nothing_raised( "Two classes, in an Array" ) {
-			Linguistics::use( :en, :classes => [Class, Range] )
-		}
-		assert_respond_to Array, :en
-		assert_respond_to( (1..5), :en )
-	end
-
-end
-end
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.