first commit

This commit is contained in:
ahoward 2012-07-23 00:29:15 -06:00
commit 1325a3632c
15 changed files with 1314 additions and 0 deletions

BIN
.README.md.swp Normal file

Binary file not shown.

BIN
.Rakefile.swp Normal file

Binary file not shown.

BIN
.a.rb.swp Normal file

Binary file not shown.

52
README.md Normal file
View File

@ -0,0 +1,52 @@
NAME
----
mongoid_grid_fs
SYNOPSIS
--------
````ruby
require 'mongoid-grid_fs'
g = GridFs.put anthing_that_respons_to_read
GridFS.get id
GridFS.delete id
````
DESCRIPTION
-----------
mongoid_grid_fs is pure mongoid 3 / moped implementation of the mongodb
grid_fs specification
ref: http://www.mongodb.org/display/DOCS/GridFS+Specification
it has the following features:
- implementation is on top of mongoid for portability. moped (the drive)
is barely used
- simple, REST-like api
- support for custom namespaces (fs.files vs. image.files)
- pathnames and io-like objects can be written to the grid
- auto-unique pathnames are generated (by default) to avoid collisions using #put
'path/info/a.rb' -> '$object_id/a.rb'
- #[] and #[]= methods which allow the grid to be used like a giant file
hash in the sky
- supprt for data_uris
````eruby
<%= image_tag :src => file.data_url %>
````

446
Rakefile Normal file
View File

@ -0,0 +1,446 @@
This.name =
"GridFs"
This.synopsis =
"a mongoid 3/moped compatible implementation of the grid_fs specification"
This.rubyforge_project = 'codeforpeople'
This.author = "Ara T. Howard"
This.email = "ara.t.howard@gmail.com"
This.homepage = "https://github.com/ahoward/#{ This.lib }"
This.setup!
task :default do
puts((Rake::Task.tasks.map{|task| task.name.gsub(/::/,':')} - ['default']).sort)
end
task :test do
This.run_tests!
end
namespace :test do
task(:unit){ This.run_tests!(:unit) }
task(:functional){ This.run_tests!(:functional) }
task(:integration){ This.run_tests!(:integration) }
end
def This.run_tests!(which = nil)
which ||= '**'
test_dir = File.join(This.dir, "test")
test_glob ||= File.join(test_dir, "#{ which }/**_test.rb")
test_rbs = Dir.glob(test_glob).sort
div = ('=' * 119)
line = ('-' * 119)
test_rbs.each_with_index do |test_rb, index|
testno = index + 1
command = "#{ File.basename(This.ruby) } -I ./lib -I ./test/lib #{ test_rb }"
puts
This.say(div, :color => :cyan, :bold => true)
This.say("@#{ testno } => ", :bold => true, :method => :print)
This.say(command, :color => :cyan, :bold => true)
This.say(line, :color => :cyan, :bold => true)
system(command)
This.say(line, :color => :cyan, :bold => true)
status = $?.exitstatus
if status.zero?
This.say("@#{ testno } <= ", :bold => true, :color => :white, :method => :print)
This.say("SUCCESS", :color => :green, :bold => true)
else
This.say("@#{ testno } <= ", :bold => true, :color => :white, :method => :print)
This.say("FAILURE", :color => :red, :bold => true)
end
This.say(line, :color => :cyan, :bold => true)
exit(status) unless status.zero?
end
end
task :gemspec do
ignore_extensions = ['git', 'svn', 'tmp', /sw./, 'bak', 'gem']
ignore_directories = ['pkg', 'db']
ignore_files = ['test/log', 'test/db.yml', 'a.rb', 'b.rb'] + Dir['db/*'] + %w'db'
shiteless =
lambda do |list|
list.delete_if do |entry|
next unless test(?e, entry)
extension = File.basename(entry).split(%r/[.]/).last
ignore_extensions.any?{|ext| ext === extension}
end
list.delete_if do |entry|
next unless test(?d, entry)
dirname = File.expand_path(entry)
ignore_directories.any?{|dir| File.expand_path(dir) == dirname}
end
list.delete_if do |entry|
next unless test(?f, entry)
filename = File.expand_path(entry)
ignore_files.any?{|file| File.expand_path(file) == filename}
end
end
lib = This.lib
object = This.object
version = This.version
files = shiteless[Dir::glob("**/**")]
executables = shiteless[Dir::glob("bin/*")].map{|exe| File.basename(exe)}
#has_rdoc = true #File.exist?('doc')
test_files = test(?e, "test/#{ lib }.rb") ? "test/#{ lib }.rb" : nil
summary = This.summary || This.synopsis || "#{ lib } kicks the ass"
description = This.description || summary
if This.extensions.nil?
This.extensions = []
extensions = This.extensions
%w( Makefile configure extconf.rb ).each do |ext|
extensions << ext if File.exists?(ext)
end
end
extensions = [extensions].flatten.compact
# TODO
if This.dependencies.nil?
dependencies = []
else
case This.dependencies
when Hash
dependencies = This.dependencies.values
when Array
dependencies = This.dependencies
end
end
template =
if test(?e, 'gemspec.erb')
This.template_for{ IO.read('gemspec.erb') }
else
This.template_for {
<<-__
## <%= lib %>.gemspec
#
Gem::Specification::new do |spec|
spec.name = <%= lib.inspect %>
spec.version = <%= version.inspect %>
spec.platform = Gem::Platform::RUBY
spec.summary = <%= lib.inspect %>
spec.description = <%= description.inspect %>
spec.files =\n<%= files.sort.pretty_inspect %>
spec.executables = <%= executables.inspect %>
spec.require_path = "lib"
spec.test_files = <%= test_files.inspect %>
<% dependencies.each do |lib_version| %>
spec.add_dependency(*<%= Array(lib_version).flatten.inspect %>)
<% end %>
spec.extensions.push(*<%= extensions.inspect %>)
spec.rubyforge_project = <%= This.rubyforge_project.inspect %>
spec.author = <%= This.author.inspect %>
spec.email = <%= This.email.inspect %>
spec.homepage = <%= This.homepage.inspect %>
end
__
}
end
FileUtils.mkdir_p(This.pkgdir)
gemspec = "#{ lib }.gemspec"
open(gemspec, "w"){|fd| fd.puts(template)}
This.gemspec = gemspec
end
task :gem => [:clean, :gemspec] do
FileUtils.mkdir_p(This.pkgdir)
before = Dir['*.gem']
cmd = "gem build #{ This.gemspec }"
`#{ cmd }`
after = Dir['*.gem']
gem = ((after - before).first || after.first) or abort('no gem!')
FileUtils.mv(gem, This.pkgdir)
This.gem = File.join(This.pkgdir, File.basename(gem))
end
task :readme do
samples = ''
prompt = '~ > '
lib = This.lib
version = This.version
Dir['sample*/*'].sort.each do |sample|
samples << "\n" << " <========< #{ sample } >========>" << "\n\n"
cmd = "cat #{ sample }"
samples << This.util.indent(prompt + cmd, 2) << "\n\n"
samples << This.util.indent(`#{ cmd }`, 4) << "\n"
cmd = "ruby #{ sample }"
samples << This.util.indent(prompt + cmd, 2) << "\n\n"
cmd = "ruby -e'STDOUT.sync=true; exec %(ruby -I ./lib #{ sample })'"
samples << This.util.indent(`#{ cmd } 2>&1`, 4) << "\n"
end
template =
if test(?e, 'readme.erb')
This.template_for{ IO.read('readme.erb') }
else
This.template_for {
<<-__
NAME
#{ lib }
DESCRIPTION
INSTALL
gem install #{ lib }
SAMPLES
#{ samples }
__
}
end
open("README", "w"){|fd| fd.puts template}
end
task :clean do
Dir[File.join(This.pkgdir, '**/**')].each{|entry| FileUtils.rm_rf(entry)}
end
task :release => [:clean, :gemspec, :gem] do
gems = Dir[File.join(This.pkgdir, '*.gem')].flatten
raise "which one? : #{ gems.inspect }" if gems.size > 1
raise "no gems?" if gems.size < 1
cmd = "gem push #{ This.gem }"
puts cmd
puts
system(cmd)
abort("cmd(#{ cmd }) failed with (#{ $?.inspect })") unless $?.exitstatus.zero?
#cmd = "rubyforge login && rubyforge add_release #{ This.rubyforge_project } #{ This.lib } #{ This.version } #{ This.gem }"
#puts cmd
#puts
#system(cmd)
#abort("cmd(#{ cmd }) failed with (#{ $?.inspect })") unless $?.exitstatus.zero?
end
BEGIN {
# support for this rakefile
#
$VERBOSE = nil
require 'erb'
require 'fileutils'
require 'rbconfig'
require 'pp'
# cache a bunch of stuff about this rakefile/environment
#
This =
Class.new(Hash) do
def method_missing(method, *args, &block)
if method.to_s =~ /=/
key = method.to_s.chomp('=')
value = block ? block : args.shift
self[key] = value
else
key = method.to_s
if block
value = block
self[key] = value
else
value = self[key]
if value.respond_to?(:call)
self[key] = value.call()
else
value
end
end
end
end
def inspect
expand!
PP.pp(self, '')
end
def expand!
keys.each do |key|
value = self[key]
if value.respond_to?(:call)
self[key] = value.call()
end
end
end
end.new()
This.file = File.expand_path(__FILE__)
This.dir = File.dirname(This.file)
This.pkgdir = File.join(This.dir, 'pkg')
# defaults
#
This.lib do
File.basename(Dir.pwd)
end
def This.setup!
begin
require "./lib/#{ This.lib }"
rescue LoadError
abort("could not load #{ This.lib }")
end
end
This.name do
This.name = This.lib.capitalize
end
This.object do
begin
This.object = eval(This.name)
rescue Object
abort("could not determine object from #{ This.name }")
end
end
This.version do
This.object.send(:version)
end
This.dependencies do
if This.object.respond_to?(:dependencies)
This.object.dependencies
end
end
This.ruby do
c = Config::CONFIG
bindir = c["bindir"] || c['BINDIR']
ruby_install_name = c['ruby_install_name'] || c['RUBY_INSTALL_NAME'] || 'ruby'
ruby_ext = c['EXEEXT'] || ''
File.join(bindir, (ruby_install_name + ruby_ext))
end
# some utils
#
This.util = Module.new do
def indent(s, n = 2)
s = unindent(s)
ws = ' ' * n
s.gsub(%r/^/, ws)
end
def unindent(s)
indent = nil
s.each_line do |line|
next if line =~ %r/^\s*$/
indent = line[%r/^\s*/] and break
end
indent ? s.gsub(%r/^#{ indent }/, "") : s
end
extend self
end
# template support
#
This.template = Class.new do
def initialize(&block)
@block = block
@template = block.call.to_s
end
def expand(b=nil)
ERB.new(This.util.unindent(@template)).result((b||@block).binding)
end
alias_method 'to_s', 'expand'
end
def This.template_for(*args, &block)
This.template.new(*args, &block)
end
# colored console output support
#
This.ansi = {
:clear => "\e[0m",
:reset => "\e[0m",
:erase_line => "\e[K",
:erase_char => "\e[P",
:bold => "\e[1m",
:dark => "\e[2m",
:underline => "\e[4m",
:underscore => "\e[4m",
:blink => "\e[5m",
:reverse => "\e[7m",
:concealed => "\e[8m",
:black => "\e[30m",
:red => "\e[31m",
:green => "\e[32m",
:yellow => "\e[33m",
:blue => "\e[34m",
:magenta => "\e[35m",
:cyan => "\e[36m",
:white => "\e[37m",
:on_black => "\e[40m",
:on_red => "\e[41m",
:on_green => "\e[42m",
:on_yellow => "\e[43m",
:on_blue => "\e[44m",
:on_magenta => "\e[45m",
:on_cyan => "\e[46m",
:on_white => "\e[47m"
}
def This.say(something, *args)
options = args.last.is_a?(Hash) ? args.pop : {}
options[:color] = args.shift.to_s.to_sym unless args.empty?
keys = options.keys
keys.each{|key| options[key.to_s.to_sym] = options.delete(key)}
color = options[:color]
bold = options.has_key?(:bold)
parts = [something]
parts.unshift(This.ansi[color]) if color
parts.unshift(This.ansi[:bold]) if bold
parts.push(This.ansi[:clear]) if parts.size > 1
method = options[:method] || :puts
Kernel.send(method, parts.join)
end
# always run out of the project dir
#
Dir.chdir(This.dir)
}

BIN
lib/.mongoid-grid_fs.rb.swp Normal file

Binary file not shown.

419
lib/mongoid-grid_fs.rb Normal file
View File

@ -0,0 +1,419 @@
require "mongoid"
require "mime/types"
require "digest/md5"
require "cgi"
class GridFS
##
#
class << GridFS
def version
"1.0.0"
end
attr_accessor :namespace
attr_accessor :file_model
attr_accessor :chunk_model
def init!
GridFS.build_namespace_for(:Fs)
GridFS.namespace = Fs
GridFS.file_model = Fs.file_model
GridFS.chunk_model = Fs.chunk_model
const_set(:File, Fs.file_model)
const_set(:Chunk, Fs.chunk_model)
to_delegate = %w(
put
get
delete
find
[]
[]=
)
to_delegate.each do |method|
class_eval <<-__
def GridFS.#{ method }(*args, &block)
::GridFS::Fs::#{ method }(*args, &block)
end
__
end
end
end
##
#
def GridFS.namespace_for(prefix)
prefix = prefix.to_s.downcase
const = "::GridFS::#{ prefix.to_s.camelize }"
namespace = const.split(/::/).last
const_defined?(namespace) ? const_get(namespace) : build_namespace_for(namespace)
end
##
#
def GridFS.build_namespace_for(prefix)
prefix = prefix.to_s.downcase
const = prefix.camelize
namespace =
Module.new do
module_eval(&NamespaceMixin)
self
end
const_set(const, namespace)
file_model = build_file_model_for(namespace)
chunk_model = build_chunk_model_for(namespace)
file_model.namespace = namespace
chunk_model.namespace = namespace
file_model.chunk_model = chunk_model
chunk_model.file_model = file_model
namespace.prefix = prefix
namespace.file_model = file_model
namespace.chunk_model = chunk_model
namespace.send(:const_set, :File, file_model)
namespace.send(:const_set, :Chunk, chunk_model)
#at_exit{ file_model.create_indexes rescue nil }
#at_exit{ chunk_model.create_indexes rescue nil }
const_get(const)
end
NamespaceMixin = proc do
class << self
attr_accessor :prefix
attr_accessor :file_model
attr_accessor :chunk_model
def to_s
prefix
end
def namespace
prefix
end
def put(readable, attributes = {})
chunks = []
file = file_model.new
attributes.to_options!
if attributes.has_key?(:id)
file.id = attributes.delete(:id)
end
if attributes.has_key?(:_id)
file.id = attributes.delete(:_id)
end
if attributes.has_key?(:content_type)
attributes[:contentType] = attributes.delete(:content_type)
end
if attributes.has_key?(:upload_date)
attributes[:uploadDate] = attributes.delete(:upload_date)
end
md5 = Digest::MD5.new
length = 0
chunkSize = file.chunkSize
n = 0
GridFS.reading(readable) do |io|
filename =
attributes[:filename] ||=
[file.id.to_s, GridFS.extract_basename(io)].join('/').squeeze('/')
content_type =
attributes[:contentType] ||=
GridFS.extract_content_type(filename) || file.contentType
while((buf = io.read(chunkSize)))
md5 << buf
length += buf.size
chunk = file.chunks.build
chunk.data = binary_for(buf)
chunk.n = n
n += 1
chunk.save!
chunks.push(chunk)
end
end
attributes[:length] ||= length
attributes[:uploadDate] ||= Time.now.utc
attributes[:md5] ||= md5.hexdigest
file.update_attributes(attributes)
file.save!
file
ensure
chunks.each{|chunk| chunk.destroy rescue nil} if $!
end
if defined?(Moped)
def binary_for(*buf)
Moped::BSON::Binary.new(:generic, buf.join)
end
else
def binary_for(buf)
BSON::Binary.new(buf.bytes.to_a)
end
end
def get(id)
file_model.find(id)
end
def delete(id)
file_model.find(id).destroy
rescue
nil
end
def where(conditions = {})
case conditions
when String
file_model.where(:filename => conditions)
else
file_model.where(conditions)
end
end
def find(*args)
where(*args).first
end
def [](filename)
file_model.where(:filename => filename.to_s).first
end
def []=(filename, readable)
file = self[filename]
file.destroy if file
put(readable, :filename => filename.to_s)
end
# TODO - opening with a mode = 'w' should return a GridIO::IOProxy
# implementing a StringIO-like interface
#
def open(filename, mode = 'r', &block)
raise NotImplementedError
end
end
end
##
#
def GridFS.build_file_model_for(namespace)
prefix = namespace.name.split(/::/).last.downcase
file_model_name = "#{ namespace.name }::File"
chunk_model_name = "#{ namespace.name }::Chunk"
Class.new do
include Mongoid::Document
singleton_class = class << self; self; end
singleton_class.instance_eval do
define_method(:name){ file_model_name }
attr_accessor :chunk_model
attr_accessor :namespace
end
self.default_collection_name = "#{ prefix }.files"
field(:filename, :type => String)
field(:contentType, :type => String, :default => 'application/octet-stream')
field(:length, :type => Integer, :default => 0)
field(:chunkSize, :type => Integer, :default => (256 * (2 ** 20)))
field(:uploadDate, :type => Date, :default => Time.now.utc)
field(:md5, :type => String, :default => Digest::MD5.hexdigest(''))
%w( filename contentType length chunkSize uploadDate md5 ).each do |f|
validates_presence_of(f)
end
validates_uniqueness_of(:filename)
has_many(:chunks, :class_name => chunk_model_name, :inverse_of => :files, :dependent => :destroy, :order => [:n, :asc])
index({:filename => 1}, :unique => true)
def path
filename
end
def basename
::File.basename(filename)
end
def prefix
self.class.namespace.prefix
end
def each(&block)
chunks.all.order_by([:n, :asc]).each do |chunk|
block.call(chunk.to_s)
end
end
def data
data = ''
each{|chunk| data << chunk}
data
end
def base64
Array(to_s).pack('m')
end
def data_uri(options = {})
data = base64.chomp
"data:#{ content_type };base64,".concat(data)
end
def bytes(&block)
if block
each{|data| block.call(data)}
length
else
bytes = []
each{|data| bytes.push(*data)}
bytes
end
end
def close
self
end
def content_type
contentType
end
def update_date
updateDate
end
def created_at
updateDate
end
def namespace
self.class.namespace
end
end
end
##
#
def GridFS.build_chunk_model_for(namespace)
prefix = namespace.name.split(/::/).last.downcase
file_model_name = "#{ namespace.name }::File"
chunk_model_name = "#{ namespace.name }::Chunk"
Class.new do
include Mongoid::Document
singleton_class = class << self; self; end
singleton_class.instance_eval do
define_method(:name){ chunk_model_name }
attr_accessor :file_model
attr_accessor :namespace
end
self.default_collection_name = "#{ prefix }.chunks"
field(:n, :type => Integer, :default => 0)
field(:data, :type => Moped::BSON::Binary)
belongs_to(:file, :foreign_key => :files_id, :class_name => file_model_name)
index({:files_id => 1, :n => -1}, :unique => true)
def namespace
self.class.namespace
end
def to_s
data.data
end
alias_method 'to_str', 'to_s'
end
end
##
#
def GridFS.reading(arg, &block)
if arg.respond_to?(:read)
rewind(arg) do |io|
block.call(io)
end
else
open(arg.to_s) do |io|
block.call(io)
end
end
end
def GridFS.rewind(io, &block)
begin
pos = io.pos
io.flush
io.rewind
rescue
nil
end
begin
block.call(io)
ensure
begin
io.pos = pos
rescue
nil
end
end
end
def GridFS.extract_basename(object)
filename = nil
[:original_path, :original_filename, :path, :filename, :pathname].each do |msg|
if object.respond_to?(msg)
filename = object.send(msg)
break
end
end
filename ? cleanname(filename) : nil
end
def GridFS.extract_content_type(filename)
content_type = MIME::Types.type_for(::File.basename(filename.to_s)).first
content_type.to_s if content_type
end
def GridFS.cleanname(pathname)
basename = ::File.basename(pathname.to_s)
CGI.unescape(basename).gsub(%r/[^0-9a-zA-Z_@)(~.-]/, '_').gsub(%r/_+/,'_')
end
end
GridFs = GridFS
GridFS.init!

36
mongoid-grid_fs.gemspec Normal file
View File

@ -0,0 +1,36 @@
## mongoid-grid_fs.gemspec
#
Gem::Specification::new do |spec|
spec.name = "mongoid-grid_fs"
spec.version = "1.0.0"
spec.platform = Gem::Platform::RUBY
spec.summary = "mongoid-grid_fs"
spec.description = "a mongoid 3/moped compatible implementation of the grid_fs specification"
spec.files =
["README.md",
"Rakefile",
"lib",
"lib/mongoid-grid_fs.rb",
"mongoid-grid_fs.gemspec",
"test",
"test/helper.rb",
"test/mongoid-grid_fs_test.rb",
"test/testing.rb"]
spec.executables = []
spec.require_path = "lib"
spec.test_files = nil
spec.extensions.push(*[])
spec.rubyforge_project = "codeforpeople"
spec.author = "Ara T. Howard"
spec.email = "ara.t.howard@gmail.com"
spec.homepage = "https://github.com/ahoward/mongoid-grid_fs"
end

Binary file not shown.

BIN
test/.helper.rb.swp Normal file

Binary file not shown.

Binary file not shown.

BIN
test/.testing.rb.swp Normal file

Binary file not shown.

18
test/helper.rb Normal file
View File

@ -0,0 +1,18 @@
# -*- encoding : utf-8 -*-
require_relative 'testing'
require_relative '../lib/mongoid-grid_fs.rb'
Mongoid.configure do |config|
config.connect_to('mongoid-grid_fs_test')
end
require 'stringio'
class SIO < StringIO
attr_accessor :filename
def initialize(filename, *args, &block)
@filename = filename
super(*args, &block)
end
end

View File

@ -0,0 +1,147 @@
require_relative 'helper'
Testing GridFs do
##
#
prepare do
GridFS::File.destroy_all
GridFS::Chunk.destroy_all
end
##
#
context '#put' do
test 'default' do
filename = __FILE__
basename = File.basename(filename)
g = assert{ GridFS.put(filename) }
assert{ g.filename =~ %r| #{ object_id_re } / #{ basename } \Z|imox }
assert{ g.content_type == "application/x-ruby" }
assert{ g.data == IO.read(filename) }
end
test 'with a :filename' do
filename = 'path/info/a.rb'
g = assert{ GridFS.put(__FILE__, :filename => filename) }
assert{ g.filename == filename }
end
end
##
#
context '#get' do
test 'default' do
id = assert{ GridFS::File.last.id }
g = assert{ GridFs.get(id) }
end
end
##
#
context '#delete' do
test 'default' do
id = assert{ GridFS::File.last.id }
g = assert{ GridFs.get(id) }
assert{ GridFs.delete(id) }
assert_raises( Mongoid::Errors::DocumentNotFound){ GridFs.get(id) }
end
end
##
#
context '[] and []=' do
test 'default' do
path = 'a.rb'
data = IO.read(__FILE__)
sio = SIO.new(path, data)
g = assert{ GridFs[path] = sio and GridFs[path] }
assert{ g.data == data }
assert{ g.content_type == "application/x-ruby" }
before = GridFs::File.count
assert{ GridFs[path] = SIO.new(path, 'foobar') }
assert{ GridFs[path].data == 'foobar' }
after = GridFs::File.count
created = after - before
assert{ created.zero? }
end
##
#
context 'data uris' do
test 'default' do
id = assert{ GridFS::File.last.id }
g = assert{ GridFs.get(id) }
content_type = g.content_type
base64 = [g.to_s].pack('m').chomp
data_uri = "data:#{ content_type };base64,".concat(base64)
assert{ g.data_uri == data_uri }
end
end
##
#
context 'namespaces' do
test 'default' do
assert{ GridFs.namespace.prefix == 'fs' }
assert{ GridFs.file_model.collection_name == 'fs.files' }
assert{ GridFs.chunk_model.collection_name == 'fs.chunks' }
end
test 'new' do
ns = GridFs.namespace_for(:ns)
assert{ ns.prefix == 'ns' }
assert{ ns.file_model < Mongoid::Document }
assert{ ns.file_model.collection_name == 'ns.files' }
assert{ ns.chunk_model < Mongoid::Document }
assert{ ns.chunk_model.collection_name == 'ns.chunks' }
assert{ ns.file_model.destroy_all }
count = GridFs::File.count
assert{ ns.file_model.count == 0}
assert{ ns.put __FILE__ }
assert{ ns.file_model.count == 1}
assert{ count == GridFs::File.count }
end
end
end
##
#
protected
def object_id_re
%r| \w{#{ BSON::ObjectId.new.to_s.size }} |iomx
end
end

196
test/testing.rb Normal file
View File

@ -0,0 +1,196 @@
# -*- encoding : utf-8 -*-
require 'test/unit'
testdir = File.expand_path(File.dirname(__FILE__))
rootdir = File.dirname(testdir)
libdir = File.join(rootdir, 'lib')
STDOUT.sync = true
$:.unshift(testdir) unless $:.include?(testdir)
$:.unshift(libdir) unless $:.include?(libdir)
$:.unshift(rootdir) unless $:.include?(rootdir)
class Testing
class Slug < ::String
def Slug.for(*args)
string = args.flatten.compact.join('-')
words = string.to_s.scan(%r/\w+/)
words.map!{|word| word.gsub %r/[^0-9a-zA-Z_-]/, ''}
words.delete_if{|word| word.nil? or word.strip.empty?}
new(words.join('-').downcase)
end
end
class Context
attr_accessor :name
def initialize(name, *args)
@name = name
end
def to_s
Slug.for(name)
end
end
end
def Testing(*args, &block)
Class.new(::Test::Unit::TestCase) do
## class methods
#
class << self
def contexts
@contexts ||= []
end
def context(*args, &block)
return contexts.last if(args.empty? and block.nil?)
context = Testing::Context.new(*args)
contexts.push(context)
begin
block.call(context)
ensure
contexts.pop
end
end
def slug_for(*args)
string = [context, args].flatten.compact.join('-')
words = string.to_s.scan(%r/\w+/)
words.map!{|word| word.gsub %r/[^0-9a-zA-Z_-]/, ''}
words.delete_if{|word| word.nil? or word.strip.empty?}
words.join('-').downcase.sub(/_$/, '')
end
def name() const_get(:Name) end
def testno()
'%05d' % (@testno ||= 0)
ensure
@testno += 1
end
def testing(*args, &block)
method = ["test", testno, slug_for(*args)].delete_if{|part| part.empty?}.join('_')
define_method(method, &block)
end
def test(*args, &block)
testing(*args, &block)
end
def setup(&block)
define_method(:setup, &block) if block
end
def teardown(&block)
define_method(:teardown, &block) if block
end
def prepare(&block)
@prepare ||= []
@prepare.push(block) if block
@prepare
end
def cleanup(&block)
@cleanup ||= []
@cleanup.push(block) if block
@cleanup
end
end
## configure the subclass!
#
const_set(:Testno, '0')
slug = slug_for(*args).gsub(%r/-/,'_')
name = ['TESTING', '%03d' % const_get(:Testno), slug].delete_if{|part| part.empty?}.join('_')
name = name.upcase!
const_set(:Name, name)
const_set(:Missing, Object.new.freeze)
## instance methods
#
alias_method('__assert__', 'assert')
def assert(*args, &block)
if args.size == 1 and args.first.is_a?(Hash)
options = args.first
expected = getopt(:expected, options){ missing }
actual = getopt(:actual, options){ missing }
if expected == missing and actual == missing
actual, expected, *ignored = options.to_a.flatten
end
expected = expected.call() if expected.respond_to?(:call)
actual = actual.call() if actual.respond_to?(:call)
assert_equal(expected, actual)
end
if block
label = "assert(#{ args.join(' ') })"
result = nil
assert_nothing_raised{ result = block.call }
__assert__(result, label)
result
else
result = args.shift
label = "assert(#{ args.join(' ') })"
__assert__(result, label)
result
end
end
def missing
self.class.const_get(:Missing)
end
def getopt(opt, hash, options = nil, &block)
[opt.to_s, opt.to_s.to_sym].each do |key|
return hash[key] if hash.has_key?(key)
end
default =
if block
block.call
else
options.is_a?(Hash) ? options[:default] : nil
end
return default
end
def subclass_of exception
class << exception
def ==(other) super or self > other end
end
exception
end
##
#
module_eval(&block)
self.setup()
self.prepare.each{|b| b.call()}
at_exit{
self.teardown()
self.cleanup.each{|b| b.call()}
}
self
end
end
if $0 == __FILE__
Testing 'Testing' do
testing('foo'){ assert true }
test{ assert true }
p instance_methods.grep(/test/)
end
end